3.4 KiB
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)orstatePart.dispatchAction(stateAction, payload) - Both return Promise
State Management Methods
select(fn?, { signal? })- returns Observable, memoized by selector fn ref, supports AbortSignalwaitUntilPresent(fn?, number | { timeoutMs?, signal? })- waits for state condition, backward compat with number argstateSetup()- async state initialization with cumulative defernotifyChangeCumulative()- defers notification to end of call stackgetState()- returns current state or undefinedsetState()- runs middleware, validates, persists, notifiesaddMiddleware(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
defaultSelectObservablefor 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
smartstateRefset bycreateStatePart()for batch awareness - State parts created via
new StatePart()directly work without batching
Computed State
computed(sources, fn)— standalone function usingcombineLatest+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-requestCustomEvent 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
smartstateRefcreates 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