Files
smartstate/readme.hints.md

3.4 KiB

Smartstate Implementation Notes

Current API (as of v2.0.31)

State Part Initialization

  • State parts can be created with different init modes: 'soft' (default), 'mandatory', 'force', 'persistent'
    • 'soft' - returns existing state part if exists, creates new if not
    • 'mandatory' - requires state part to not exist, fails if it does
    • 'force' - always creates new state part, overwriting any existing
    • 'persistent' - like 'soft' but with WebStore persistence (IndexedDB)
  • Persistent mode automatically calls init() internally
  • State merge order fixed: initial state takes precedence over stored state

Actions

  • Actions are created with createAction() method
  • Two ways to dispatch: stateAction.trigger(payload) or statePart.dispatchAction(stateAction, payload)
  • Both return Promise

State Management Methods

  • select(fn?, { signal? }) - returns Observable, memoized by selector fn ref, supports AbortSignal
  • waitUntilPresent(fn?, number | { timeoutMs?, signal? }) - waits for state condition, backward compat with number arg
  • stateSetup() - async state initialization with cumulative defer
  • notifyChangeCumulative() - defers notification to end of call stack
  • getState() - returns current state or undefined
  • setState() - runs middleware, validates, persists, notifies
  • addMiddleware(fn) - intercepts setState, returns removal function

Middleware

  • Type: (newState, oldState) => newState | Promise<newState>
  • Runs sequentially in insertion order before validation/persistence
  • Throw to reject state changes (atomic — state unchanged on error)
  • Does NOT run during initial createStatePart() hydration

Selector Memoization

  • Uses WeakMap<Function, Observable> for fn-keyed cache
  • defaultSelectObservable for no-arg select()
  • Wrapped in shareReplay({ bufferSize: 1, refCount: true })
  • NOT cached when AbortSignal is provided

Batch Updates

  • smartstate.batch(async () => {...}) — defers notifications until batch completes
  • Supports nesting — only flushes at outermost level
  • StatePart has smartstateRef set by createStatePart() for batch awareness
  • State parts created via new StatePart() directly work without batching

Computed State

  • computed(sources, fn) — standalone function using combineLatest + map
  • Also available as smartstate.computed(sources, fn)
  • Lazy — only subscribes when subscribed to

Context Protocol Bridge

  • attachContextProvider(element, { context, statePart, selectorFn? }) — returns cleanup fn
  • Listens for context-request CustomEvent on element
  • Supports one-shot and subscription modes
  • Works with Lit @consume(), FAST, or any Context Protocol consumer

State Hash Detection

  • Uses SHA256 hash to detect actual state changes
  • Hash comparison properly awaits async hash calculation
  • Prevents duplicate notifications for identical state values

State Validation

  • Basic validation ensures state is not null/undefined
  • validateState() can be overridden in subclasses

Key Notes

  • smartstateRef creates circular ref between StatePart and Smartstate
  • Use === not deep equality for StatePart comparison in tests
  • Direct rxjs imports used for: Observable, shareReplay, takeUntil, combineLatest, map

Dependency Versions (v2.0.31)

  • @git.zone/tsbuild: ^4.1.2
  • @git.zone/tsbundle: ^2.9.0
  • @git.zone/tsrun: ^2.0.1
  • @git.zone/tstest: ^3.1.8
  • @push.rocks/smartjson: ^6.0.0
  • @types/node: ^25.3.2