Files
smartstate/readme.hints.md

78 lines
3.4 KiB
Markdown
Raw Permalink Normal View History

# 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<TStatePayload>
### 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