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.
graph TB
subgraph L1["Layer 1 — Reactive Core"]
Core["@stateloom/core<br/>signal · computed · effect · batch · scope<br/>~1.5 KB gzipped"]
end
subgraph L2["Layer 2 — Paradigm Adapters"]
Store["@stateloom/store<br/>createStore · setState · selectors"]
Atom["@stateloom/atom<br/>atom · derived · async atoms"]
Proxy["@stateloom/proxy<br/>observable · snapshot · observe"]
end
subgraph L3["Layer 3 — Framework Adapters"]
React["@stateloom/react"]
Vue["@stateloom/vue"]
Solid["@stateloom/solid"]
Svelte["@stateloom/svelte"]
Angular["@stateloom/angular"]
end
subgraph L4["Layer 4 — Middleware & Ecosystem"]
Devtools["@stateloom/devtools"]
Persist["@stateloom/persist"]
TabSync["@stateloom/tab-sync"]
History["@stateloom/history"]
Immer["@stateloom/immer"]
Telemetry["@stateloom/telemetry"]
Server["@stateloom/server"]
Testing["@stateloom/testing"]
end
subgraph L5["Layer 5 — Platform Backends"]
PersistRedis["@stateloom/persist-redis"]
end
Core --> Store
Core --> Atom
Core --> Proxy
Store --> React
Store --> Vue
Store --> Solid
Store --> Svelte
Store --> Angular
Atom --> React
Atom --> Vue
Proxy --> React
Proxy --> Vue
Core --> Devtools
Core --> Persist
Core --> TabSync
Core --> History
Core --> Immer
Core --> Telemetry
Core --> Server
Core --> Testing
Persist --> PersistRedis
- Downward only: Dependencies flow strictly downward. A lower layer never imports from a higher layer.
- Core is the only shared dependency: All packages depend on
@stateloom/coreas a peer dependency. - Framework adapters peer-depend on their framework:
@stateloom/reactpeer-depends onreact,@stateloom/vueonvue, etc. - Paradigm packages are independent:
@stateloom/store,@stateloom/atom, and@stateloom/proxydo not depend on each other. - Middleware packages are independent: Each middleware package depends only on core. They do not depend on each other or on paradigm packages.
- Platform backends depend on their parent:
@stateloom/persist-redisdepends on@stateloom/persist.
graph LR
Core["@stateloom/core"]
Core --> Store["@stateloom/store"]
Core --> Atom["@stateloom/atom"]
Core --> Proxy["@stateloom/proxy"]
Core --> Devtools["@stateloom/devtools"]
Core --> Persist["@stateloom/persist"]
Core --> TabSync["@stateloom/tab-sync"]
Core --> History["@stateloom/history"]
Core --> Immer["@stateloom/immer"]
Core --> Telemetry["@stateloom/telemetry"]
Core --> Server["@stateloom/server"]
Core --> Testing["@stateloom/testing"]
Persist --> PersistRedis["@stateloom/persist-redis"]
Store --> ReactA["@stateloom/react"]
Atom --> ReactA
Proxy --> ReactA
Store --> VueA["@stateloom/vue"]
Store --> SolidA["@stateloom/solid"]
Store --> SvelteA["@stateloom/svelte"]
Store --> AngularA["@stateloom/angular"]
Turborepo resolves this automatically via "dependsOn": ["^build"]:
| Phase | Packages | Dependencies |
|---|---|---|
| 1 | @stateloom/core |
None |
| 2 | store, atom, proxy |
core |
| 3 | devtools, persist, tab-sync, history, immer, telemetry, server, testing |
core |
| 4 | react, vue, solid, svelte, angular |
core + paradigms |
| 5 | persist-redis |
persist |
| 6 | examples/* |
all packages |
Phases 2 and 3 execute in parallel. Turborepo's caching means only changed packages and their dependents rebuild.
All packages are published under the @stateloom npm organization:
| Category | Pattern | Examples |
|---|---|---|
| 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 |
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]
This sequence diagram shows how a signal write propagates through the system from a user action to a framework re-render:
sequenceDiagram
participant App as User Action
participant S as signal.set()
participant G as Dependency Graph
participant C as computed (pull)
participant E as effect (flush)
participant FA as Framework Adapter
participant UI as Component Re-render
App->>S: set(newValue)
S->>G: startBatch + propagateChange
G->>G: Mark subscribers DIRTY / MAYBE_DIRTY
S->>S: endBatch
Note over S: Flush notifications + effects
S->>E: effect.run()
E->>C: computed.get() [pull phase]
C->>C: Recompute if stale
C->>E: Return fresh value
E->>FA: useSyncExternalStore / shallowRef
FA->>UI: Schedule re-render
Turborepo resolves build order automatically via "dependsOn": ["^build"]. Phases 2 and 3 execute in parallel:
flowchart LR
subgraph "Phase 1"
Core["@stateloom/core"]
end
subgraph "Phase 2 (parallel)"
Store["store"]
Atom["atom"]
Proxy["proxy"]
end
subgraph "Phase 3 (parallel)"
DT["devtools"]
PS["persist"]
TS["tab-sync"]
HI["history"]
IM["immer"]
TM["telemetry"]
SV["server"]
TST["testing"]
end
subgraph "Phase 4 (parallel)"
React["react"]
Vue["vue"]
Solid["solid"]
Svelte["svelte"]
Angular["angular"]
end
subgraph "Phase 5"
PR["persist-redis"]
end
Core --> Store & Atom & Proxy
Core --> DT & PS & TS & HI & IM & TM & SV & TST
Store & Atom & Proxy --> React & Vue & Solid & Svelte & Angular
PS --> PR
Turborepo's caching means only changed packages and their dependents rebuild.
The entire architecture rests on a small number of shared interfaces:
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.
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.
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.
This class diagram shows how the key interfaces connect across layers:
classDiagram
class Subscribable~T~ {
+get() T
+subscribe(cb) () => void
}
class Signal~T~ {
+get() T
+set(value) void
+update(fn) void
+subscribe(cb) () => void
}
class ReadonlySignal~T~ {
+get() T
+subscribe(cb) () => void
}
class StoreApi~T~ {
+get() T
+getState() T
+setState(partial) void
+subscribe(listener) () => void
+destroy() void
}
class Middleware~T~ {
+name string
+init(api) void
+onSet(api, next, partial) void
+onDestroy(api) void
}
class Scope {
+fork() Scope
+get(sub) T
+set(signal, value) void
+serialize() Record
}
Signal~T~ --|> Subscribable~T~
ReadonlySignal~T~ --|> Subscribable~T~
StoreApi~T~ --|> Subscribable~T~
Middleware~T~ ..> StoreApi~T~ : intercepts
Scope ..> Subscribable~T~ : scopes
| Need | Layer | Package |
|---|---|---|
| Raw reactive primitives | Core | @stateloom/core |
| Zustand-like single-object store | Paradigm | @stateloom/store |
| Jotai-like bottom-up atoms | Paradigm | @stateloom/atom |
| Valtio-like mutable proxy | Paradigm | @stateloom/proxy |
| React hooks for stores/atoms/proxies | Framework | @stateloom/react |
| Vue composables | Framework | @stateloom/vue |
| Persist to localStorage/Redis | Middleware | @stateloom/persist + backend |
| Time-travel debugging | Middleware | @stateloom/devtools |
| Undo/redo | Middleware | @stateloom/history |
| Cross-tab sync | Middleware | @stateloom/tab-sync |
| SSR per-request isolation | Core + Adapter | @stateloom/core + framework adapter |
- Design Philosophy — rationale behind architectural decisions
- Layer Scoping — exact responsibility boundaries per package
- Core Design — reactive kernel internals
- Store Design — store paradigm internals
- Atom Design — atom paradigm internals
- Proxy Design — proxy paradigm internals
- Adapters Overview — framework adapter internals
- Middleware Overview — middleware system internals
- Testing Design — testing infrastructure internals