Skip to main content

Eidos

εἶδος - "form, pattern"

All 23 Gang of Four design patterns, reimagined for functional TypeScript. No classes, no inheritance, just functions, discriminated unions, and composition.

info

Eidos translates classic OOP patterns into idiomatic functional code. Some patterns become simple functions. Others become type aliases. Some are marked deprecated because TypeScript's type system makes them unnecessary. Each module documents the OOP version, explains the functional equivalent, and provides working functional code.


Philosophy

The GoF patterns were designed for languages without first-class functions, without algebraic types, and without powerful type inference. In functional TypeScript:

  • Strategy is just a Record<string, Function>
  • Visitor is just a switch on a discriminated union
  • Factory Method is just dependency injection
  • Decorator is just function composition

Eidos doesn't force OOP patterns into functional code. It shows the idiomatic way to solve the same problems. When a pattern is absorbed by the language, we document it with a @deprecated function that explains what to do instead, directly accessible from your IDE.


Pattern Categories

Patterns with Real Value

These patterns provide utility functions you can use in your code:

PatternFunctionsDescription
StrategycreateStrategies, safeStrategy, withFallback, withValidationInterchangeable algorithms with typed lookup
ObservercreateObservablePub/sub with typed events
Decoratordecorate, before, after, aroundFunction wrapping and composition
Adapteradapt, createAdapterFunction signature transformation
Commandundoable, createCommandStack, undoableState, createReactiveCommandStackUndo/redo with action history
Chain of ResponsibilitycreateChain, safeChainSequential handler pipelines
StatecreateMachineFinite state machines
IteratorcreateIterable, lazyRange, iterateLazy sequences with operators
Compositeleaf, branch, fold, map, flatten, findTree structures with traversal
Abstract FactorycreateAbstractFactoryFactory families with typed lookup
MediatorcreateMediatorTyped event hub for decoupled communication
MementocreateHistoryState snapshots with undo/redo
BuildercreateBuilder, createValidatedBuilderImmutable fluent builders
Template MethodtemplateWithDefaultsAlgorithm skeletons with overridable steps

Patterns Covered by Arkhe

These patterns are already implemented in Arkhe. Eidos re-exports them for discoverability:

PatternArkhe FunctionsDescription
Proxymemoize, once, throttle, debounce, lazy, guardedFunction interception and caching
PrototypedeepClone, deepCloneFullObject cloning
Singletononce (alias singleton)Single instance creation
FlyweightmemoizeObject pooling via cache

Patterns Absorbed by the Language

These patterns are unnecessary in functional TypeScript. The functions exist only for documentation and are marked @deprecated to guide toward idiomatic code:

PatternWhy Absorbed
BridgeA bridge is just (impl) => abstraction, a function
Factory MethodPass the factory as a parameter (dependency injection)
FacadeA facade is just a function that calls other functions
VisitorUse switch on a discriminated union
InterpreterDiscriminated unions + recursive evaluation

Quick Examples

Strategy: Interchangeable Algorithms

import { createStrategies } from "@pithos/core/eidos/strategy/strategy";

const pricing = createStrategies({
regular: (price: number) => price,
vip: (price: number) => price * 0.8,
premium: (price: number) => price * 0.7,
});

pricing.execute("vip", 100); // 80

State: Finite State Machine

import { createMachine } from "@pithos/core/eidos/state/state";

const light = createMachine({
green: { timer: "yellow" },
yellow: { timer: "red" },
red: { timer: "green" },
}, "green");

light.send("timer"); // "yellow"
light.send("timer"); // "red"

Composite: Tree Structures

import { leaf, branch, fold } from "@pithos/core/eidos/composite/composite";

const tree = branch({ name: "root", size: 0 }, [
leaf({ name: "file1.txt", size: 100 }),
branch({ name: "docs", size: 0 }, [
leaf({ name: "readme.md", size: 50 }),
]),
]);

const totalSize = fold(tree, {
leaf: (data) => data.size,
branch: (_, children) => children.reduce((a, b) => a + b, 0),
}); // 150

Mediator: Decoupled Communication

import { createMediator } from "@pithos/core/eidos/mediator/mediator";

type Events = {
userLoggedIn: { userId: string };
orderPlaced: { orderId: string };
};

const mediator = createMediator<Events>();

mediator.on("userLoggedIn", ({ userId }) => {
console.log(`Welcome ${userId}`);
});

mediator.emit("userLoggedIn", { userId: "alice" });

Builder: Fluent Construction

import { createBuilder } from "@pithos/core/eidos/builder/builder";

const queryBuilder = createBuilder({ table: "", where: [] as string[], limit: 100 })
.step("from", (s, table: string) => ({ ...s, table }))
.step("where", (s, clause: string) => ({ ...s, where: [...s.where, clause] }))
.step("limit", (s, n: number) => ({ ...s, limit: n }))
.done();

const query = queryBuilder()
.from("users")
.where("active = true")
.limit(10)
.build();

Memento vs Command for Undo/Redo

Both patterns support undo/redo, but work differently:

ApproachModuleHow it worksWhen to use
State snapshotsMemento (createHistory)Stores copies of state, restores on undoState is cheap to copy
Action historyCommand (createCommandStack)Stores execute/undo pairsYou have reversible operations
// Memento: store states
const history = createHistory({ count: 0 });
history.push({ count: 1 });
history.undo(); // restores { count: 0 }

// Command: store actions
const stack = createCommandStack();
stack.execute(undoable(() => count++, () => count--));
stack.undo(); // calls the undo function

When to Use

Eidos is useful when:

  • You come from OOP and want to understand the functional equivalents
  • You need a specific pattern (state machine, builder, observer, etc.)
  • You want typed, tested implementations rather than writing them yourself

When NOT to Use

NeedUse Instead
Data transformationArkhe
Error handlingZygos
Schema validationKanon

Related Resources