Skip to content

Architecture Overview

StateLoom is organized as a layered monorepo. Each layer has a clear responsibility boundary, explicit dependency direction, and independent versioning under the @stateloom/* npm scope.

Layer Diagram

Dependency Rules

  1. Downward only: Dependencies flow strictly downward. A lower layer never imports from a higher layer.
  2. Core is the only shared dependency: All packages depend on @stateloom/core as a peer dependency.
  3. Framework adapters peer-depend on their framework: @stateloom/react peer-depends on react, @stateloom/vue on vue, etc.
  4. Paradigm packages are independent: @stateloom/store, @stateloom/atom, and @stateloom/proxy do not depend on each other.
  5. Middleware packages are independent: Each middleware package depends only on core. They do not depend on each other or on paradigm packages.
  6. Platform backends depend on their parent: @stateloom/persist-redis depends on @stateloom/persist.

Package Dependency Graph

Build Order

Turborepo resolves this automatically via "dependsOn": ["^build"]:

PhasePackagesDependencies
1@stateloom/coreNone
2store, atom, proxycore
3devtools, persist, tab-sync, history, immer, telemetry, server, testingcore
4react, vue, solid, svelte, angularcore + paradigms
5persist-redispersist
6examples/*all packages

Phases 2 and 3 execute in parallel. Turborepo's caching means only changed packages and their dependents rebuild.

Package Naming Convention

All packages are published under the @stateloom npm organization:

CategoryPatternExamples
Core@stateloom/core@stateloom/core
Paradigms@stateloom/<paradigm>store, atom, proxy
Framework adapters@stateloom/<framework>react, vue, solid, svelte, angular
Middleware@stateloom/<feature>devtools, persist, tab-sync, history
Platform backends@stateloom/persist-<platform>persist-redis
Utilities@stateloom/<utility>server, testing, immer, telemetry

Monorepo Layout

stateloom/
├── packages/
│   ├── core/                  # Layer 1
│   ├── store/                 # Layer 2
│   ├── atom/                  # Layer 2
│   ├── proxy/                 # Layer 2
│   ├── react/                 # Layer 3
│   ├── vue/                   # Layer 3
│   ├── solid/                 # Layer 3
│   ├── svelte/                # Layer 3
│   ├── angular/               # Layer 3
│   ├── devtools/              # Layer 4
│   ├── persist/               # Layer 4
│   ├── persist-redis/         # Layer 5
│   ├── tab-sync/              # Layer 4
│   ├── history/               # Layer 4
│   ├── immer/                 # Layer 4
│   ├── telemetry/             # Layer 4
│   ├── server/                # Layer 4
│   └── testing/               # Layer 4
├── examples/
├── docs/
├── scripts/
└── [root config files]

Reactive Data Flow

This sequence diagram shows how a signal write propagates through the system from a user action to a framework re-render:

Build Pipeline

Turborepo resolves build order automatically via "dependsOn": ["^build"]. Phases 2 and 3 execute in parallel:

Turborepo's caching means only changed packages and their dependents rebuild.

Key Interfaces

The entire architecture rests on a small number of shared interfaces:

Subscribable<T> — The Universal Contract

typescript
interface Subscribable<T> {
  get(): T;
  subscribe(callback: (value: T) => void): () => void;
}

Every signal, computed, store, and atom implements this. Framework adapters bridge this single interface to framework-specific reactivity.

Middleware<T> — The Extension Point

typescript
interface Middleware<T> {
  name: string;
  init?: (api: MiddlewareAPI<T>) => void;
  onSet?: (api: MiddlewareAPI<T>, next: SetFn<T>, partial: Partial<T>) => void;
  onGet?: (api: MiddlewareAPI<T>, key: keyof T) => void;
  onSubscribe?: (api: MiddlewareAPI<T>, listener: Listener<T>) => Listener<T>;
  onDestroy?: (api: MiddlewareAPI<T>) => void;
}

All cross-cutting concerns (persistence, devtools, tab-sync, history) implement this interface.

Scope — SSR Isolation

typescript
interface Scope {
  fork(): Scope;
  get<T>(subscribable: Subscribable<T>): T;
  set<T>(signal: Signal<T>, value: T): void;
  serialize(): Record<string, unknown>;
}

Scopes provide per-request isolation for SSR environments.

Interface Relationships

This class diagram shows how the key interfaces connect across layers:

When to Use Each Layer

NeedLayerPackage
Raw reactive primitivesCore@stateloom/core
Zustand-like single-object storeParadigm@stateloom/store
Jotai-like bottom-up atomsParadigm@stateloom/atom
Valtio-like mutable proxyParadigm@stateloom/proxy
React hooks for stores/atoms/proxiesFramework@stateloom/react
Vue composablesFramework@stateloom/vue
Persist to localStorage/RedisMiddleware@stateloom/persist + backend
Time-travel debuggingMiddleware@stateloom/devtools
Undo/redoMiddleware@stateloom/history
Cross-tab syncMiddleware@stateloom/tab-sync
SSR per-request isolationCore + Adapter@stateloom/core + framework adapter

Cross-References