Layer Scoping
This document defines the exact responsibility boundary for each layer and package in StateLoom. Every package has a single, well-defined scope. If a feature does not fit within a package's scope, it belongs in a different package or a new one.
Layer Boundary Enforcement
Dependencies flow strictly downward. This diagram shows the allowed and prohibited import directions:
TIP
Arrows show dependency direction (who imports whom). Dotted red lines show prohibited imports. Higher layers never import from lower layers.
Layer 1 — Reactive Core (@stateloom/core)
Scope
The reactive kernel. Provides the primitive building blocks that every other package builds on.
Owns
signal(value)— mutable reactive value containercomputed(fn)— lazy, memoized derived signaleffect(fn)— side-effect with automatic dependency trackingbatch(fn)— coalesce multiple writes into a single notificationcreateScope()/runInScope()/serializeScope()— SSR isolation- Dependency graph internals (doubly-linked node lists)
- Equality checking (
Object.isdefault, custom comparators) Subscribable<T>interface definition
Does Not Own
- Any paradigm-specific API (no
createStore, noatom, noobservable) - Any framework-specific code (no React hooks, no Vue composables)
- Any middleware or plugin system
- Any persistence, devtools, or ecosystem features
- Any platform-specific APIs (
localStorage,BroadcastChannel, etc.)
Design Constraints
- Zero platform-specific APIs — works in any JavaScript runtime
- Target: ~1.5 KB gzipped
- Uses only: closures,
WeakMap,Set,Object.is,Promise,queueMicrotask
Layer 2 — Paradigm Adapters
Each paradigm adapter translates a familiar API pattern into operations on the reactive core.
@stateloom/store
Scope
Store-based state management, familiar to Zustand and Redux Toolkit users.
Owns
createStore(creator, options)— create a store with state and actionsset(partial)/set(updater)— state mutation via shallow merge- Selector support for derived reads
- Middleware pipeline composition
StoreApi<T>,StateCreator<T>type definitions
Does Not Own
- The reactive primitives themselves (delegates to core)
- Framework bindings (no
useStore— that's in@stateloom/react) - Specific middleware implementations (those are in dedicated packages)
@stateloom/atom
Scope
Bottom-up atomic state composition, familiar to Jotai users.
Owns
atom(initialValue)— create a base atom config- Derived atoms (read-only, write, async)
AtomScope—WeakMap-based value container for atom instances- Async atom resolution with
Promisesupport Atom<T>,WritableAtom<T>,ReadonlyAtom<T>type definitions
Does Not Own
- Suspense integration (that's in framework adapters)
- Framework hooks (
useAtomis in@stateloom/react) - Store-based features
@stateloom/proxy
Scope
Proxy-based mutable state with transparent tracking, familiar to Valtio and MobX users.
Owns
observable(obj)— create a deeply-proxied mutable state objectsnapshot(proxy)— create an immutable, structurally-shared snapshotobserve(fn)— auto-tracking side-effect (like MobXautorun)ref(value)— opt a value out of proxying (DOM elements, class instances)- Two-layer proxy architecture (write-tracking source + read-tracking snapshot)
Does Not Own
- Framework bindings (
useSnapshotis in@stateloom/react) proxy-comparelibrary (it's a dependency, not owned code)- Store or atom features
Layer 3 — Framework Adapters
Each adapter is a thin bridge (50–200 lines) connecting the Subscribable<T> contract to framework-specific reactivity.
@stateloom/react
Owns
useSignal(signal)—useSyncExternalStorebridge for raw signalsuseStore(store, selector)— store integration with selector memoizationuseAtom(atom)/useAtomValue(atom)/useSetAtom(atom)— atom hooksuseSnapshot(proxy)— proxy paradigm integrationScopeProvider/ScopeContext— SSR scope contextgetServerSnapshotimplementations for hydration safety
@stateloom/vue
Owns
useSignal(signal)—shallowRefbridge withonScopeDisposecleanupuseStore(store, selector)— store composable- Vue plugin for scope injection (
app.use(stateloomPlugin)) - Reactive ref wrappers
@stateloom/solid
Owns
useSignal(signal)—createSignalbridgeuseStore(store, selector)— store integration- Solid-specific scope management
@stateloom/svelte
Owns
- Svelte store contract compliance (signals natively satisfy
{ subscribe }) - Minimal adapter for
$storesyntax support - Scope management via Svelte context
@stateloom/angular
Owns
StateloomService— injectable servicetoObservable(signal)— RxJSObservablewrappertoSignal(observable)— Angular Signal bridge- Module/standalone component integration
Shared Rules for All Adapters
- Peer-depend on
@stateloom/coreand the framework - Auto-cleanup subscriptions on component unmount
- Provide SSR-safe snapshot implementations
- No business logic — pure bridging code
Layer 4 — Middleware & Ecosystem
Each middleware package implements the Middleware<T> interface and adds a specific cross-cutting concern.
@stateloom/devtools
- Redux DevTools Extension bridge (standard protocol)
- Custom inspector API for building tools
- Time-travel debugging via action log replay
- Action name inference from function names
@stateloom/persist
persist(options)middleware for storage persistencepartialize,merge,version,migrateconfiguration- Built-in storage adapters:
localStorage,sessionStorage,cookieStorage,indexedDB,memory StorageAdapterinterface for custom backends
@stateloom/tab-sync
broadcast(options)middleware for cross-tab syncBroadcastChannel-based with loop-prevention- Field-level filtering and conflict resolution
- Graceful degradation (no-op when
BroadcastChannelunavailable)
@stateloom/history
- Undo/redo support with two strategies
- Snapshot-based (simple, Immer structural sharing)
- Command-based (memory-efficient, explicit commands)
canUndo/canRedoas reactive signals
@stateloom/immer
- Enables mutable update syntax within
set()via Immer integration - Wraps the
setfunction to useproduce()
@stateloom/telemetry
- Analytics hooks for state change tracking
onStateChange,onErrorcallbacks with metadata- Duration measurement for state transitions
@stateloom/server
createServerScope(options)— TTL-based, LRU-evicting server scope- Per-request scope forking
- Memory-bounded scope management for long-running servers
@stateloom/testing
createTestScope()— isolated scope per test with auto-resetmockStore(overrides)— store mocking for component testsTestScopeProvider— test harness for framework adapter testing
Layer 5 — Platform Backends
Storage backends for specific platforms. Each depends on @stateloom/persist.
@stateloom/persist-redis
- HTTP + TCP Redis adapters
- Edge-compatible via HTTP protocol (Upstash)
- TTL support
Import Direction Rules
This diagram illustrates what each layer is allowed to import, with specific examples:
Boundary Violations to Watch For
| Violation | Why It's Wrong | Where It Belongs |
|---|---|---|
Core importing localStorage | Core must be runtime-agnostic | @stateloom/persist |
| Store importing React hooks | Paradigms are framework-agnostic | @stateloom/react |
| React adapter implementing middleware logic | Adapters are thin bridges only | Dedicated middleware package |
| Persist middleware hardcoding Redis | Persist provides the interface | @stateloom/persist-redis |
| Any package importing from a higher layer | Dependencies flow downward only | Restructure the dependency |
| Atom importing from Store | Paradigms are independent siblings | Share via core primitives |
| Middleware importing framework hooks | Middleware is framework-agnostic | Framework adapter handles integration |
Package Selection Guide
Use this decision tree to determine which packages a consumer needs:
Cross-References
- Architecture Overview — full dependency graph and build order
- Design Philosophy — rationale for the layered architecture
- Core Design — what the core layer owns internally
- Middleware Overview — structural typing pattern that keeps middleware independent