Skip to content

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 container
  • computed(fn) — lazy, memoized derived signal
  • effect(fn) — side-effect with automatic dependency tracking
  • batch(fn) — coalesce multiple writes into a single notification
  • createScope() / runInScope() / serializeScope() — SSR isolation
  • Dependency graph internals (doubly-linked node lists)
  • Equality checking (Object.is default, custom comparators)
  • Subscribable<T> interface definition

Does Not Own

  • Any paradigm-specific API (no createStore, no atom, no observable)
  • 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 actions
  • set(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)
  • AtomScopeWeakMap-based value container for atom instances
  • Async atom resolution with Promise support
  • Atom<T>, WritableAtom<T>, ReadonlyAtom<T> type definitions

Does Not Own

  • Suspense integration (that's in framework adapters)
  • Framework hooks (useAtom is 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 object
  • snapshot(proxy) — create an immutable, structurally-shared snapshot
  • observe(fn) — auto-tracking side-effect (like MobX autorun)
  • 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 (useSnapshot is in @stateloom/react)
  • proxy-compare library (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)useSyncExternalStore bridge for raw signals
  • useStore(store, selector) — store integration with selector memoization
  • useAtom(atom) / useAtomValue(atom) / useSetAtom(atom) — atom hooks
  • useSnapshot(proxy) — proxy paradigm integration
  • ScopeProvider / ScopeContext — SSR scope context
  • getServerSnapshot implementations for hydration safety

@stateloom/vue

Owns

  • useSignal(signal)shallowRef bridge with onScopeDispose cleanup
  • useStore(store, selector) — store composable
  • Vue plugin for scope injection (app.use(stateloomPlugin))
  • Reactive ref wrappers

@stateloom/solid

Owns

  • useSignal(signal)createSignal bridge
  • useStore(store, selector) — store integration
  • Solid-specific scope management

@stateloom/svelte

Owns

  • Svelte store contract compliance (signals natively satisfy { subscribe })
  • Minimal adapter for $store syntax support
  • Scope management via Svelte context

@stateloom/angular

Owns

  • StateloomService — injectable service
  • toObservable(signal) — RxJS Observable wrapper
  • toSignal(observable) — Angular Signal bridge
  • Module/standalone component integration

Shared Rules for All Adapters

  • Peer-depend on @stateloom/core and 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 persistence
  • partialize, merge, version, migrate configuration
  • Built-in storage adapters: localStorage, sessionStorage, cookieStorage, indexedDB, memory
  • StorageAdapter interface for custom backends

@stateloom/tab-sync

  • broadcast(options) middleware for cross-tab sync
  • BroadcastChannel-based with loop-prevention
  • Field-level filtering and conflict resolution
  • Graceful degradation (no-op when BroadcastChannel unavailable)

@stateloom/history

  • Undo/redo support with two strategies
  • Snapshot-based (simple, Immer structural sharing)
  • Command-based (memory-efficient, explicit commands)
  • canUndo / canRedo as reactive signals

@stateloom/immer

  • Enables mutable update syntax within set() via Immer integration
  • Wraps the set function to use produce()

@stateloom/telemetry

  • Analytics hooks for state change tracking
  • onStateChange, onError callbacks 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-reset
  • mockStore(overrides) — store mocking for component tests
  • TestScopeProvider — 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

ViolationWhy It's WrongWhere It Belongs
Core importing localStorageCore must be runtime-agnostic@stateloom/persist
Store importing React hooksParadigms are framework-agnostic@stateloom/react
React adapter implementing middleware logicAdapters are thin bridges onlyDedicated middleware package
Persist middleware hardcoding RedisPersist provides the interface@stateloom/persist-redis
Any package importing from a higher layerDependencies flow downward onlyRestructure the dependency
Atom importing from StoreParadigms are independent siblingsShare via core primitives
Middleware importing framework hooksMiddleware is framework-agnosticFramework adapter handles integration

Package Selection Guide

Use this decision tree to determine which packages a consumer needs:

Cross-References