@sladg/apex-state 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @sladg/apex-state
2
+
3
+ Reactive state management for React built on [Valtio](https://github.com/pmndrs/valtio). Declare what your fields need — validation, conditional UI, sync, listeners — and the store handles the rest. Optional Rust/WASM accelerator for complex workloads (up to 367x faster).
4
+
5
+ ```bash
6
+ npm install @sladg/apex-state valtio zod react
7
+ ```
8
+
9
+ ## Example
10
+
11
+ ```tsx
12
+ import { createGenericStore } from '@sladg/apex-state'
13
+ import { z } from 'zod'
14
+
15
+ type OrderState = {
16
+ product: { name: string; quantity: number; price: number }
17
+ shipping: { address: string; express: boolean }
18
+ payment: { method: 'card' | 'cash'; cardNumber: string }
19
+ status: 'draft' | 'submitted'
20
+ }
21
+
22
+ const store = createGenericStore<OrderState>()
23
+
24
+ const OrderForm = () => {
25
+ // Declare side effects
26
+ store.useSideEffects('order', {
27
+ syncPaths: [['product.price', 'shipping.basePrice']],
28
+ flipPaths: [['shipping.express', 'shipping.standard']],
29
+ })
30
+
31
+ // Declare concerns — just data, no logic to test
32
+ store.useConcerns('order', {
33
+ 'product.quantity': {
34
+ validationState: { schema: z.number().min(1).max(100) },
35
+ disabledWhen: { condition: { IS_EQUAL: ['status', 'submitted'] } },
36
+ },
37
+ 'payment.cardNumber': {
38
+ validationState: { schema: z.string().regex(/^\d{16}$/) },
39
+ visibleWhen: { condition: { IS_EQUAL: ['payment.method', 'card'] } },
40
+ },
41
+ })
42
+
43
+ const { value, setValue, validationState, disabledWhen } =
44
+ store.useFieldStore('product.quantity')
45
+
46
+ return (
47
+ <input
48
+ type="number"
49
+ value={value}
50
+ onChange={(e) => setValue(Number(e.target.value))}
51
+ disabled={disabledWhen}
52
+ className={validationState?.isError ? 'error' : ''}
53
+ />
54
+ )
55
+ }
56
+
57
+ const App = () => (
58
+ <store.Provider initialState={{
59
+ product: { name: 'Widget', quantity: 1, price: 29.99 },
60
+ shipping: { address: '', express: false },
61
+ payment: { method: 'card', cardNumber: '' },
62
+ status: 'draft',
63
+ }}>
64
+ <OrderForm />
65
+ </store.Provider>
66
+ )
67
+ ```
68
+
69
+ ## Features
70
+
71
+ | Feature | Description | Details |
72
+ |---|---|---|
73
+ | **Type-safe paths** | `DeepKey<T>` / `DeepValue<T, P>` — compile-time path safety | |
74
+ | **Concerns** | Validation, BoolLogic conditions, dynamic text | [Concerns Guide](docs/guides/CONCERNS_GUIDE.md) |
75
+ | **Side effects** | Sync paths, flip paths, aggregations, listeners | [Side Effects Guide](docs/SIDE_EFFECTS_GUIDE.md) |
76
+ | **WASM mode** | Rust-powered pipeline for bulk operations | [Architecture](docs/WASM_ARCHITECTURE.md) |
77
+ | **Composable hooks** | Buffered, throttled, transformed field wrappers | [Store & Hooks](docs/guides/STORE_HOOKS.md) |
78
+ | **Record/wildcard** | `Record<string, V>` with `[*]` wildcard paths | [Wildcard Guide](docs/WILD_FUNCTION_GUIDE.md) |
79
+
80
+ ## Architecture
81
+
82
+ ```
83
+ setValue("email", "alice@example.com")
84
+
85
+ ├─[Legacy JS]──▶ sync → flip → listeners → applyBatch
86
+
87
+ └─[WASM/Rust]──▶ shadow state + sync + flip + BoolLogic (Rust)
88
+
89
+
90
+ execute listeners + Zod validators (JS)
91
+
92
+
93
+ pipelineFinalize → diff → final changes (Rust)
94
+
95
+
96
+ valtio proxy → React re-render
97
+ ```
98
+
99
+ **Dual-layer design:** JS/React owns reactivity and rendering. Rust/WASM owns heavy computation (graphs, diffing, pipeline orchestration). The boundary is thin: paths cross as strings, values as JSON. WASM decides the execution plan, JS executes user functions.
100
+
101
+ See [docs/WASM_ARCHITECTURE.md](docs/WASM_ARCHITECTURE.md) for the full specification.
102
+
103
+ ## WASM Mode
104
+
105
+ WASM is the default. Pass `{ useLegacyImplementation: true }` for pure JS:
106
+
107
+ ```tsx
108
+ const store = createGenericStore<MyState>() // WASM (default)
109
+ const store = createGenericStore<MyState>({ useLegacyImplementation: true }) // Legacy JS
110
+ ```
111
+
112
+ ### Performance
113
+
114
+ Benchmarked with 60 variants across 3 Record layers, 75 syncs, 40 flips, 100 BoolLogic conditions, 85 listeners:
115
+
116
+ | Operation | Legacy | WASM | Winner |
117
+ |---|---|---|---|
118
+ | Single field edit | **0.5us** | 1.4us | Legacy 2.6x |
119
+ | 7 changes + cascading listeners | 41.8ms | **0.11ms** | WASM 367x |
120
+ | 60 bulk price changes | 596ms | **2.9ms** | WASM 207x |
121
+ | 135 changes (full catalog refresh) | 621ms | **2.99ms** | WASM 208x |
122
+
123
+ Both modes produce **identical state** — verified across all 16 benchmark scenarios. See [docs/BENCHMARK_COMPARISON.md](docs/BENCHMARK_COMPARISON.md) for the full analysis.
124
+
125
+ ### Why WASM is faster
126
+
127
+ - **Pre-computed topic routing** — listener dispatch is O(1) lookup vs O(changes x listeners) string matching
128
+ - **Shadow state diffing** — fast Rust HashMap vs valtio Proxy trap overhead
129
+ - **Single-pass pipeline** — aggregation + sync + flip + BoolLogic in one Rust call
130
+ - **BoolLogic in pipeline** — evaluated in Rust before listeners fire; Legacy defers to async `effect()`
131
+
132
+ ### Why Legacy is faster for small ops
133
+
134
+ Every WASM call pays a fixed cost: JSON serialization, wasm-bindgen marshalling, and two round trips (`processChanges` + `pipelineFinalize`). When the actual work is trivial, this ~1us overhead dominates.
135
+
136
+ ## API Quick Reference
137
+
138
+ ### Store
139
+
140
+ ```tsx
141
+ const {
142
+ Provider, // React context — accepts initialState
143
+ useFieldStore, // { value, setValue, ...concerns } for a path
144
+ useStore, // [value, setValue] tuple for a path
145
+ useJitStore, // { proxyValue, setChanges, getState } for bulk ops
146
+ useSideEffects, // register sync/flip/aggregation/listeners
147
+ useConcerns, // register validation/BoolLogic/custom concerns
148
+ withConcerns, // typed concern selection
149
+ } = createGenericStore<MyState>(config?)
150
+ ```
151
+
152
+ ### Concerns
153
+
154
+ ```tsx
155
+ useConcerns('id', {
156
+ 'user.email': {
157
+ validationState: { schema: z.string().email() },
158
+ disabledWhen: { condition: { IS_EQUAL: ['tosAccepted', false] } },
159
+ visibleWhen: { condition: { AND: [{ EXISTS: 'user.name' }, { IS_EQUAL: ['step', 2] }] } },
160
+ },
161
+ })
162
+ ```
163
+
164
+ Built-in concerns: `validationState`, `disabledWhen`, `readonlyWhen`, `visibleWhen`, `dynamicLabel`, `dynamicTooltip`, `dynamicPlaceholder`.
165
+
166
+ BoolLogic operators: `IS_EQUAL`, `EXISTS`, `IS_EMPTY`, `GT`, `LT`, `GTE`, `LTE`, `IN`, `AND`, `OR`, `NOT`.
167
+
168
+ See [Concerns Guide](docs/guides/CONCERNS_GUIDE.md) for lifecycle, custom concerns, and testing.
169
+
170
+ ### Side Effects
171
+
172
+ ```tsx
173
+ useSideEffects('id', {
174
+ syncPaths: [['source', 'target']],
175
+ flipPaths: [['active', 'inactive']],
176
+ // Aggregation: target reflects the common value when all sources agree, null otherwise.
177
+ // Multiple pairs with the same target form a group.
178
+ // Currently supports consensus (all-equal) mode only — SUM, AVG, COUNT planned (see Roadmap).
179
+ aggregations: [['summary.price', 'legs.0.price'], ['summary.price', 'legs.1.price']],
180
+ listeners: [{ path: 'orders', scope: 'orders', fn: handler }],
181
+ })
182
+ ```
183
+
184
+ See [Side Effects Guide](docs/SIDE_EFFECTS_GUIDE.md) for the full API.
185
+
186
+ ## Development
187
+
188
+ ```bash
189
+ npm install # Install dependencies
190
+ npm run wasm:build # Compile Rust -> WASM
191
+ npm run build # Bundle TypeScript + WASM
192
+ npm run test # Run tests
193
+ npm run code:check # Lint + type check
194
+ npm run wasm:check # Rust lint + check
195
+ ```
196
+
197
+ ### WASM Prerequisites
198
+
199
+ ```bash
200
+ # Rust toolchain
201
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
202
+ rustup target add wasm32-unknown-unknown
203
+ cargo install wasm-pack
204
+ ```
205
+
206
+ ## Documentation
207
+
208
+ | Document | Covers |
209
+ |---|---|
210
+ | [WASM Architecture](docs/WASM_ARCHITECTURE.md) | JS/WASM boundary, data flow, ownership model |
211
+ | [Benchmark Comparison](docs/BENCHMARK_COMPARISON.md) | Legacy vs WASM performance with 16 scenarios |
212
+ | [Concerns Guide](docs/guides/CONCERNS_GUIDE.md) | Concern lifecycle, built-ins, custom concerns |
213
+ | [Side Effects Guide](docs/SIDE_EFFECTS_GUIDE.md) | Sync, flip, aggregation, listener API |
214
+ | [Store & Hooks](docs/guides/STORE_HOOKS.md) | Hook reference and patterns |
215
+ | [Debug Timing](docs/DEBUG_TIMING.md) | Performance debugging utilities |
216
+ | [Wildcard Paths](docs/WILD_FUNCTION_GUIDE.md) | `Wild()` template utility for Record types |
217
+ | [Record Migration](docs/RECORD_MIGRATION.md) | Migration patterns for dynamic Record types |
218
+ | [Full Index](docs/README.md) | Complete documentation index |
219
+
220
+ ## Roadmap
221
+
222
+ - **Aggregation modes** — Aggregations currently use consensus (all-equal) mode. Planned: `SUM`, `AVG`, `COUNT`, `MIN`, `MAX`, and custom reducer functions, declared per-target alongside the source pairs.
223
+ - **Nested sub-stores** — Allow a parent store to contain child stores, enabling component-level state that participates in the parent's pipeline (concerns, listeners, sync).
224
+ - **Technical debt resolution** — See [TECHNICAL_DEBT.md](TECHNICAL_DEBT.md) for tracked items.
225
+
226
+ ## License
227
+
228
+ MIT