Skip to main content

Eidos in the TypeScript Ecosystem

The GoF design patterns are not outdated. The problems they solve are still real. They just need to be adapted to the way we write TypeScript today: composable, functional, without classes or inheritance. Even the Singleton, often criticized as an anti-pattern in OOP, becomes harmless in functional TypeScript where it's just a module-level const.

Pithos is the only TypeScript library that provides all 23 GoF design patterns in a functional style. Some libraries teach them as OOP examples. Others solve one or two patterns in isolation. Eidos is the first to cover all 23 with a unified, composable, framework-agnostic API. This page explains why that matters.


What Exists Today

Design pattern resources in JS/TS fall into three categories. Each solves part of the problem. None solves all of it.

  • Educational repositories.
    Projects like refactoring.guru, torokmark/design_patterns_in_typescript or design-patterns-for-humans cover all 23 patterns, but as examples you cannot npm install. And they all teach patterns through OOP (classes and inheritance), while modern TypeScript stacks increasingly favor composable, functional approaches.

  • Specialized libraries.
    The ecosystem offers excellent single-pattern solutions: RxJS, mitt, or nanoevents for Observer; XState or Robot for State machines; Immer for Command/undo-redo; Ramda for function composition. Some libraries like Zustand combine several patterns (State, Observer, Command) but remain tied to React. Each covers its niche well, but none aims to provide a complete, framework-agnostic set of patterns.

  • FP ecosystems.
    Libraries like fp-ts, Effect, or purify-ts provide functional abstractions that indirectly overlap with some GoF patterns. But none set out to cover all 23. The coverage is incidental.

Where Eidos Fits

Eidos sits in a space none of the three categories above occupy: a production-ready, tree-shakable library that covers every GoF pattern with a consistent functional API.


The Fragmentation Problem

Covering just five patterns with specialized libraries means five npm install, five API styles, five documentation sites, and five mental models for what are, at the core, related structural ideas:

npm install rxjs           # Observer
npm install xstate # State
npm install ramda # Decorator / composition
npm install immer # Memento / Command
npm install iter-tools # Iterator

This is not a criticism of these libraries, they are excellent at what they do. But a project that needs several patterns pays for the fragmentation in cognitive load, bundle weight, maintenance surface, and supply chain risk: each dependency is a potential vulnerability.


What Eidos Does Differently

Framework-Agnostic

Most specialized libraries are tied to a specific ecosystem. RxJS is deeply integrated with Angular. Zustand is designed for React. XState has framework-specific bindings. If you switch framework, or if you work on a backend Node.js project, these libraries either don't apply or bring unnecessary coupling.

Eidos has no opinion on your framework. It produces plain functions and plain data. You can use it in React, Angular, Vue, Svelte, a CLI tool, a serverless function, or a vanilla TypeScript project. The patterns work the same way everywhere because they depend on nothing but TypeScript itself.

One Import Path, One Philosophy

Every pattern follows the same conventions: functions return plain data, state is immutable, configuration uses typed records, composition uses higher-order functions.

import { createStrategies } from "@pithos/core/eidos/strategy/strategy";
import { createMachine } from "@pithos/core/eidos/state/state";
import { createChain } from "@pithos/core/eidos/chain/chain";
import { createObservable } from "@pithos/core/eidos/observer/observer";
import { createBuilder } from "@pithos/core/eidos/builder/builder";

No classes. No inheritance. No abstract methods. Same mental model everywhere.

Honest About What the Language Already Does

Eidos deliberately exports all 23 patterns, including the five that TypeScript makes trivial. These five are marked @deprecated, which serves two purposes: the library remains a complete, homogeneous reference for all GoF patterns, and developers searching for "factory method" or "visitor" in their IDE get immediate guidance toward the idiomatic alternative instead of a class-based implementation.

PatternWhat your IDE shows you
Factory Method"Pass the factory function as a parameter. This is dependency injection."
Bridge"Pass the implementation as a function parameter."
Facade"Write a function that calls other functions."
Visitor"Use a switch on a discriminated union."
Interpreter"Define a discriminated union AST and a recursive eval function."

Shared Infrastructure with Pithos

Four patterns are re-exports from Arkhe, with zero additional bundle weight:

PatternArkhe function
PrototypedeepClone()
Singletononce()
Flyweightmemoize()
Proxymemoize(), throttle(), debounce(), lazy(), guarded()

Every Pattern Has a Live Demo

Each pattern comes with a dedicated page that includes the problem it solves, the Eidos solution, and an interactive demo you can try in the browser, with its source code downloadable.

Discover the demos
State demo

State
Finite state machine with typed transitions and events. A tennis scoreboard for Roland Garros where the Deuce/Advantage loop is encoded in transitions, not conditionals.

createMachine()

Strategy demo

Strategy
Swap algorithms at runtime by key. A cosmetics e-shop pricing calculator with interchangeable discount strategies (Standard, Club, Seasonal, Coffret).

createStrategies(), safeStrategy(), withFallback(), withValidation()

Observer demo

Observer
Typed pub/sub event emitter. A stock trading dashboard where chart, alerts, and portfolio react independently to price changes.

createObservable()

Command demo

Command
Encapsulate actions with undo/redo. A Kanban board where every drag is a reversible command with human-readable history and replay.

undoableState(), createReactiveCommandStack()

Iterator demo

Iterator
Lazy sequences with Option-based next(). A Pokédex browser with three interchangeable traversal strategies over 151 Pokémon.

createIterable(), lazyRange(), iterate()

Builder demo

Builder
Step-by-step construction with a fluent API. A chart builder where toggling steps updates the visualization in real-time.

createBuilder(), createValidatedBuilder()

Decorator demo

Decorator
Stack behaviors without modifying the core function. A DNA analysis pipeline where you toggle quality filter, cache, retry, and timing decorators.

decorate(), before(), after(), around()

Chain demo

Chain of Responsibility
Pipeline of handlers that can pass or short-circuit. An HTTP middleware simulator with toggleable auth, rate-limit, validation, and logging.

createChain(), safeChain()

Mediator demo

Mediator
Centralize communication between components. A DGAC flight dashboard where weather, flights, and runway panels communicate through a typed event hub.

createMediator()

Memento demo

Memento
Capture and restore state snapshots. A photo editor with filters where each change creates a thumbnail snapshot you can jump to.

createHistory()

Template demo

Template Method
Define an algorithm skeleton with overridable steps. A resume builder where Developer, Designer, and Manager profiles override specific steps.

templateWithDefaults()

Abstract Factory demo

Abstract Factory
Create families of related objects. A cross-platform UI kit where switching iOS/Android/Web re-skins the entire form at once.

createAbstractFactory()

Composite demo

Composite
Tree structures with uniform leaf/branch handling. A file explorer where folder sizes are computed recursively via fold().

leaf(), branch(), fold(), map(), flatten(), find()

Adapter demo

Adapter
Make incompatible APIs work together. A map app that normalizes two French open data APIs (EV charging + fuel stations) into one uniform format.

adapt(), createAdapter()

Proxy demo

Proxy
Control access with caching, rate-limiting, and fallback. A simulated LLM API where cache hits save money and rate limits protect the provider.

memoize(), throttle(), lazy(), guarded()

Singleton demo

Singleton
Lazy initialization, same instance on every call. Three services (Database, Cache, Logger) where first request is slow and subsequent ones are instant.

once()

Prototype demo

Prototype
Clone objects without shared references. A config editor where deep clone keeps the original untouched while shallow copy leaks mutations.

deepClone(), deepCloneFull()

Flyweight demo

Flyweight
Share common state to reduce memory. A text editor where style objects are pooled via memoize, showing 96% memory savings.

memoize()

Factory Method demo

Factory Method
Delegate creation to injected functions. A notification sender where the same sendNotification() call produces Email, SMS, Push, or Slack notifications.

Pattern absorbed by the language (dependency injection)

Visitor demo

Visitor
Add operations without modifying objects. An email builder where the same block list produces Preview, HTML, Plain Text, or Accessibility Audit output.

Pattern absorbed by the language (switch on discriminated union)

Bridge demo

Bridge
Decouple two axes that vary independently. A music visualizer with 3 audio sources x 5 renderers = 15 combinations from one function call.

Pattern absorbed by the language (pass functions as parameters)

Facade demo

Facade
Simplified interface to a complex subsystem. An API request demo showing 6 manual steps vs one fetchUser() call.

Pattern absorbed by the language (just a function)

Interpreter demo

Interpreter
Evaluate expressions or DSLs. A full Markdown interpreter with live preview, AST visualization, and synchronized scroll.

Pattern absorbed by the language (discriminated union + recursive eval)


Eidos vs Dedicated Libraries

For some patterns, a dedicated library goes deeper than Eidos. This is by design: Eidos covers the 80% case. When you need the full framework, use the framework.

PatternWhen Eidos is enoughWhen to reach for a dedicated lib
ObserverSimple typed pub/subComplex stream processing (RxJS)
StateFinite state machine with typed transitionsHierarchical statecharts, actors, visualizer (XState)
IteratorLazy sequences, basic operatorsExtensive async iteration operators (IxJS)
CommandUndo/redo with action historyFull state management with devtools (Zustand)
Decoratorbefore/after/around hooksFull FP utility library (Ramda)

Patterns Where No Alternative Exists

Eight patterns have no standalone functional TypeScript library. The only options today are manual implementation or Eidos:

  • Abstract Factory
  • Builder (generic, not domain-specific)
  • Chain of Responsibility (standalone, not framework middleware)
  • Adapter (typed function wrapper)
  • Mediator (typed event hub)
  • Memento (snapshot history)
  • Template Method
  • Composite (typed tree with fold/map)

Eidos Complements, It Doesn't Replace

Eidos is not competing with technology-specific solutions. If your framework provides a reactive primitive (RxJS Observables, Angular Signals, Solid signals), use it. Eidos steps aside. If you need full statecharts with a visual inspector, XState is the right tool. If you need a complete effect system with structured concurrency, Effect is the right tool.

Eidos fills the gaps between these specialized solutions: the patterns that have no dedicated library, the patterns you would otherwise implement by hand, and the patterns that are so simple in functional TypeScript that all you need is a one-line reminder in your IDE.

You can use Eidos alongside any of these libraries. Import one pattern or twenty, each is independently tree-shakable, and patterns that overlap with Arkhe carry zero additional weight.


Further Reading