@sladg/apex-state 3.7.3 → 3.9.0
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/dist/deep-clone-DMRvv0Pr.d.ts +2119 -0
- package/dist/index.d.ts +91 -1771
- package/dist/index.js +157 -31
- package/dist/index.js.map +1 -1
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +21 -15
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,1825 +1,145 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { D as DefaultDepth, R as ResolvableDeepKey, B as BoolLogic, C as CheckAggregationPairs, V as ValidatedAggregationPairs, a as ComputationOp, b as DeepKeyFiltered, c as CheckComputationPairs, d as ValidatedComputationPairs, e as CheckSyncPairs, f as ValidatedFlipPairs, G as GenericMeta, g as CheckListeners, h as ValidatedListeners, i as ValidatedSyncPairs } from './deep-clone-DMRvv0Pr.js';
|
|
2
|
+
export { A as Aggregation, j as AggregationPair, k as ArrayOfChanges, l as BaseConcernProps, m as BufferedField, n as CheckPairValueMatch, o as ClearPathRule, p as ComputationPair, q as ConcernRegistration, r as ConcernRegistrationMap, s as ConcernType, t as DebugConfig, u as DebugTrack, v as DebugTrackEntry, w as DeepKey, x as DeepPartial, y as DeepRequired, z as DeepValue, E as EvaluatedConcerns, F as ExtractEvaluateReturn, H as FieldInput, I as FlipPair, J as GenericStoreApi, K as HASH_KEY, L as KeyboardSelectConfig, M as ListenerRegistration, O as OnStateListener, P as PathsWithSameValueAs, N as ProviderProps, S as SelectOption, Q as SideEffects, T as StoreConfig, U as StoreInstance, W as SyncPair, X as ThrottleConfig, Y as ThrottleFieldInput, Z as TransformConfig, _ as ValidationError, $ as ValidationSchema, a0 as ValidationStateConcern, a1 as ValidationStateInput, a2 as ValidationStateResult, a3 as _, a4 as applyChangesToObject, a5 as createGenericStore, a6 as deepClone, a7 as defaultConcerns, a8 as dot, a9 as evaluateBoolLogic, aa as extractPlaceholders, ab as findConcern, ac as hashKey, ad as interpolateTemplate, ae as is, af as prebuilts, ag as registerFlipPair, ah as registerListenerLegacy, ai as registerSideEffects, aj as registerSyncPairsBatch, ak as useBufferedField, al as useKeyboardSelect, am as useThrottledField, an as useTransformedField } from './deep-clone-DMRvv0Pr.js';
|
|
3
|
+
import 'react/jsx-runtime';
|
|
4
|
+
import 'react';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* Lazy-validated pair helper functions
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
+
* These curried helpers provide O(N) base type + O(K) per-pair validation,
|
|
10
|
+
* avoiding the O(N²) explosion of SyncPair/FlipPair/AggregationPair/ComputationPair
|
|
11
|
+
* on large state types (1500+ paths).
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* type Path = `users.${HASH_KEY}.name` // "users.[*].name"
|
|
13
|
-
* ```
|
|
14
|
-
*/
|
|
15
|
-
type HASH_KEY = '[*]';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* DeepKey utility type
|
|
19
|
-
*
|
|
20
|
-
* Generates a union of all possible dot-notation paths for nested objects.
|
|
21
|
-
* Supports nested objects up to depth 20 to handle deeply nested data structures.
|
|
22
|
-
* Handles arrays and complex object hierarchies.
|
|
23
|
-
*
|
|
24
|
-
* Uses loop unrolling: processes 2 nesting levels per recursion call,
|
|
25
|
-
* halving recursion depth while preserving bottom-up evaluation that
|
|
26
|
-
* TypeScript can cache (no prefix parameter).
|
|
27
|
-
*
|
|
28
|
-
* Examples of supported paths:
|
|
29
|
-
* - Simple: "name", "email"
|
|
30
|
-
* - Nested: "user.address.street"
|
|
31
|
-
* - Deep: "g.p.data.optionsCommon.base.ccyPair.forCurrency.id" (11 levels)
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```typescript
|
|
35
|
-
* type User = {
|
|
36
|
-
* name: string
|
|
37
|
-
* address: {
|
|
38
|
-
* street: string
|
|
39
|
-
* city: string
|
|
40
|
-
* }
|
|
41
|
-
* }
|
|
42
|
-
*
|
|
43
|
-
* // DeepKey<User> = "name" | "address" | "address.street" | "address.city"
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* @example Custom depth — reduce for faster compilation on wide types
|
|
47
|
-
* ```typescript
|
|
48
|
-
* // Only traverse 10 levels deep instead of the default 20
|
|
49
|
-
* type ShallowPaths = DeepKey<User, 10>
|
|
50
|
-
* ```
|
|
51
|
-
*
|
|
52
|
-
* @example Depth limit marker — `??` appears at the cutoff point
|
|
53
|
-
* ```typescript
|
|
54
|
-
* // If a type is nested deeper than Depth, autocomplete shows:
|
|
55
|
-
* // "some.deep.path.??" — signaling you should increase depth or restructure
|
|
56
|
-
* type Paths = DeepKey<VeryDeepType, 6>
|
|
57
|
-
* ```
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
type Primitive = string | number | boolean | bigint | symbol | null | undefined;
|
|
61
|
-
type IsAny$1<T> = 0 extends 1 & T ? true : false;
|
|
62
|
-
/**
|
|
63
|
-
* Default recursion depth for DeepKey and all types that depend on it.
|
|
64
|
-
* Change this single value to adjust the depth limit across the entire type system.
|
|
65
|
-
*
|
|
66
|
-
* Propagates to: SyncPair, FlipPair, AggregationPair, ComputationPair,
|
|
67
|
-
* BoolLogic, SideEffects, DeepKeyFiltered, ClearPathRule.
|
|
68
|
-
*
|
|
69
|
-
* @example Override per-call instead of changing the default
|
|
70
|
-
* ```typescript
|
|
71
|
-
* type Shallow = DeepKey<MyState, 10> // reduced depth
|
|
72
|
-
* type Deeper = DeepKey<MyState, 30> // increased depth
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
type DefaultDepth = 20;
|
|
76
|
-
/**
|
|
77
|
-
* Marker emitted when DeepKey reaches its depth limit on an object type,
|
|
78
|
-
* or when it encounters an `any`-typed property (unknowable structure).
|
|
79
|
-
* Appears as `some.deep.path.??` in autocomplete, signaling the cutoff point.
|
|
80
|
-
* Users seeing this can increase depth: `DeepKey<T, 30>` or restructure their type.
|
|
81
|
-
*/
|
|
82
|
-
type DepthLimitMarker = '??';
|
|
83
|
-
type DeepKey<T, Depth extends number = DefaultDepth> = DeepKeyUnrolled<T, Depth> & string;
|
|
84
|
-
/**
|
|
85
|
-
* DeepKey with `??` marker paths excluded. Use in types that resolve path values
|
|
86
|
-
* (DeepValue, DeepKeyFiltered, PathsWithSameValueAs) where `??` would fail resolution.
|
|
87
|
-
* DeepKey itself keeps `??` for IDE autocomplete visibility.
|
|
88
|
-
*/
|
|
89
|
-
type ResolvableDeepKey<T, Depth extends number = DefaultDepth> = Exclude<DeepKey<T, Depth>, `${string}??` | '??'>;
|
|
90
|
-
/**
|
|
91
|
-
* Loop-unrolled DeepKey: processes 2 nesting levels per recursion call.
|
|
92
|
-
*
|
|
93
|
-
* For each key K of T:
|
|
94
|
-
* - Emit K (level 1 path)
|
|
95
|
-
* - If T[K] is an object, inline level 2:
|
|
96
|
-
* - For each key K2 of T[K]:
|
|
97
|
-
* - Emit `${K}.${K2}` (level 2 path)
|
|
98
|
-
* - If T[K][K2] is an object, recurse with `${K}.${K2}.${DeepKeyUnrolled}`
|
|
99
|
-
*
|
|
100
|
-
* Key insight: DeepKeyUnrolled<T, Depth> has NO prefix parameter,
|
|
101
|
-
* so identical sub-types at the same depth are cached by TypeScript.
|
|
102
|
-
* Template literal distribution (`${prefix}.${union}`) is handled natively
|
|
103
|
-
* by TypeScript without extra type instantiation overhead.
|
|
104
|
-
*/
|
|
105
|
-
type DeepKeyUnrolled<T, Depth extends number> = Depth extends 0 ? T extends Primitive | readonly any[] ? never : DepthLimitMarker : IsAny$1<T> extends true ? never : T extends Primitive ? never : T extends readonly any[] ? never : string extends keyof T ? HASH_KEY | (IsAny$1<T[string]> extends true ? `${HASH_KEY}.${DepthLimitMarker}` : NonNullable<T[string]> extends Primitive | readonly any[] ? never : RecordLevel2<NonNullable<T[string]>, HASH_KEY, Depth>) : {
|
|
106
|
-
[K in keyof T & (string | number)]: (K & string) | (IsAny$1<T[K]> extends true ? `${K & string}.${DepthLimitMarker}` : NonNullable<T[K]> extends Primitive | readonly any[] ? never : ConcreteLevel2<NonNullable<T[K]>, K & string, Depth>);
|
|
107
|
-
}[keyof T & (string | number)];
|
|
108
|
-
/**
|
|
109
|
-
* Level 2 expansion for concrete-key objects.
|
|
110
|
-
* Enumerates V's keys and uses inline template literal distribution for level 3+.
|
|
111
|
-
*/
|
|
112
|
-
type ConcreteLevel2<V, ParentKey extends string, Depth extends number> = IsAny$1<V> extends true ? `${ParentKey}.${DepthLimitMarker}` : V extends Primitive ? never : V extends readonly any[] ? never : string extends keyof V ? `${ParentKey}.${HASH_KEY}` | (IsAny$1<V[string]> extends true ? `${ParentKey}.${HASH_KEY}.${DepthLimitMarker}` : NonNullable<V[string]> extends Primitive | readonly any[] ? never : `${ParentKey}.${HASH_KEY}.${DeepKeyUnrolled<NonNullable<V[string]>, Prev2<Depth>> & string}`) : {
|
|
113
|
-
[K2 in keyof V & (string | number)]: `${ParentKey}.${K2 & string}` | (IsAny$1<V[K2]> extends true ? `${ParentKey}.${K2 & string}.${DepthLimitMarker}` : NonNullable<V[K2]> extends Primitive | readonly any[] ? never : `${ParentKey}.${K2 & string}.${DeepKeyUnrolled<NonNullable<V[K2]>, Prev2<Depth>> & string}`);
|
|
114
|
-
}[keyof V & (string | number)];
|
|
115
|
-
/**
|
|
116
|
-
* Level 2 expansion for Record-value objects.
|
|
117
|
-
* When level 1 was a Record, its value type V is expanded at level 2.
|
|
118
|
-
*/
|
|
119
|
-
type RecordLevel2<V, ParentKey extends string, Depth extends number> = IsAny$1<V> extends true ? `${ParentKey}.${DepthLimitMarker}` : V extends Primitive ? never : V extends readonly any[] ? never : string extends keyof V ? `${ParentKey}.${HASH_KEY}` | (IsAny$1<V[string]> extends true ? `${ParentKey}.${HASH_KEY}.${DepthLimitMarker}` : NonNullable<V[string]> extends Primitive | readonly any[] ? never : `${ParentKey}.${HASH_KEY}.${DeepKeyUnrolled<NonNullable<V[string]>, Prev2<Depth>> & string}`) : {
|
|
120
|
-
[K2 in keyof V & (string | number)]: `${ParentKey}.${K2 & string}` | (IsAny$1<V[K2]> extends true ? `${ParentKey}.${K2 & string}.${DepthLimitMarker}` : NonNullable<V[K2]> extends Primitive | readonly any[] ? never : `${ParentKey}.${K2 & string}.${DeepKeyUnrolled<NonNullable<V[K2]>, Prev2<Depth>> & string}`);
|
|
121
|
-
}[keyof V & (string | number)];
|
|
122
|
-
type Prev2<N extends number> = N extends 20 ? 18 : N extends 19 ? 17 : N extends 18 ? 16 : N extends 17 ? 15 : N extends 16 ? 14 : N extends 15 ? 13 : N extends 14 ? 12 : N extends 13 ? 11 : N extends 12 ? 10 : N extends 11 ? 9 : N extends 10 ? 8 : N extends 9 ? 7 : N extends 8 ? 6 : N extends 7 ? 5 : N extends 6 ? 4 : N extends 5 ? 3 : N extends 4 ? 2 : N extends 3 ? 1 : N extends 2 ? 0 : N extends 1 ? 0 : never;
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* DeepKeyFiltered - Filters paths by their resolved value type
|
|
126
|
-
*
|
|
127
|
-
* Returns only paths from DeepKey<T> that resolve to a specific type U.
|
|
128
|
-
* Useful for type-safe filtering of state paths by their value types.
|
|
129
|
-
*
|
|
130
|
-
* @example
|
|
131
|
-
* ```typescript
|
|
132
|
-
* type Data = {
|
|
133
|
-
* isActive: boolean
|
|
134
|
-
* name: string
|
|
135
|
-
* count: number
|
|
136
|
-
* user: { isAdmin: boolean }
|
|
137
|
-
* }
|
|
138
|
-
*
|
|
139
|
-
* // Only boolean paths
|
|
140
|
-
* type BoolPaths = DeepKeyFiltered<Data, boolean>
|
|
141
|
-
* // Result: "isActive" | "user.isAdmin"
|
|
142
|
-
*
|
|
143
|
-
* // Only string paths
|
|
144
|
-
* type StrPaths = DeepKeyFiltered<Data, string>
|
|
145
|
-
* // Result: "name"
|
|
146
|
-
* ```
|
|
147
|
-
*/
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Filters DeepKey<T> to only include paths that resolve to type U.
|
|
151
|
-
*
|
|
152
|
-
* Uses mapped type with conditional filtering - maps over all possible paths,
|
|
153
|
-
* keeps those matching the target type, and extracts the union.
|
|
154
|
-
*
|
|
155
|
-
* @example Custom depth — propagates to DeepKey
|
|
156
|
-
* ```typescript
|
|
157
|
-
* // Only number paths, limited to 10 levels deep
|
|
158
|
-
* type ShallowNumbers = DeepKeyFiltered<State, number, 10>
|
|
159
|
-
* ```
|
|
160
|
-
*/
|
|
161
|
-
type DeepKeyFiltered<T, U, Depth extends number = DefaultDepth> = {
|
|
162
|
-
[K in ResolvableDeepKey<T, Depth>]: NonNullable<DeepValue<T, K>> extends U ? K : never;
|
|
163
|
-
}[ResolvableDeepKey<T, Depth>];
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* DeepValue utility type
|
|
167
|
-
*
|
|
168
|
-
* Extracts the value type for a given dot-notation path string.
|
|
169
|
-
* Handles nested objects, arrays, and optional properties.
|
|
170
|
-
*
|
|
171
|
-
* @example
|
|
172
|
-
* ```typescript
|
|
173
|
-
* type User = {
|
|
174
|
-
* address: {
|
|
175
|
-
* street: string
|
|
176
|
-
* city: string
|
|
177
|
-
* }
|
|
178
|
-
* }
|
|
13
|
+
* Runtime behavior: identity function (returns input as-is).
|
|
14
|
+
* Type behavior: validates each pair via CheckSyncPairs / CheckAggregationPairs / CheckComputationPairs.
|
|
179
15
|
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
186
|
-
type DeepValue<T, Path extends string> = IsAny<T> extends true ? never : T extends readonly any[] ? T[number] : Path extends `${infer First}.${infer Rest}` ? First extends keyof T ? DeepValue<NonNullable<T[First]>, Rest> : string extends keyof T ? First extends HASH_KEY ? DeepValue<T[string], Rest> : unknown : unknown : Path extends HASH_KEY ? string extends keyof T ? T[string] : unknown : Path extends keyof T ? T[Path] : unknown;
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Boolean logic DSL type definitions
|
|
16
|
+
* **Why these exist**: The direct pair types (`SyncPair<T>`, `FlipPair<T>`, etc.) distribute
|
|
17
|
+
* over all N paths to build an O(N²) union of valid pairs. This hits TS2589 at ~1,500 paths.
|
|
18
|
+
* These helpers defer validation: the function constraint uses `ResolvableDeepKey<T>` (O(N))
|
|
19
|
+
* for autocomplete, then `CheckPairValueMatch` validates only the K pairs you actually write
|
|
20
|
+
* via `DeepValue` comparison (O(1) per pair).
|
|
190
21
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Path-value pair distributed over all valid paths.
|
|
197
|
-
* Each path is paired with its resolved value type, ensuring type safety.
|
|
198
|
-
* The distribution happens inside the tuple, not at the union level —
|
|
199
|
-
* this keeps BoolLogic's top-level union flat so `in` narrowing works.
|
|
200
|
-
*/
|
|
201
|
-
type PathValuePair<STATE, Depth extends number> = DeepKey<STATE, Depth> extends infer P ? P extends string ? [P, DeepValue<STATE, P>] : never : never;
|
|
202
|
-
/**
|
|
203
|
-
* Path-value array pair for IN operator.
|
|
204
|
-
* Same distribution as PathValuePair but with array values.
|
|
205
|
-
*/
|
|
206
|
-
type PathValueArrayPair<STATE, Depth extends number> = DeepKey<STATE, Depth> extends infer P ? P extends string ? [P, DeepValue<STATE, P>[]] : never : never;
|
|
207
|
-
/**
|
|
208
|
-
* Array-elements pair for CONTAINS_ANY / CONTAINS_ALL operators.
|
|
209
|
-
* Same distribution as PathArrayElementPair but the second element is an array
|
|
210
|
-
* of items, enabling multi-value containment checks.
|
|
211
|
-
*/
|
|
212
|
-
type PathArrayElementsPair<STATE, Depth extends number> = DeepKey<STATE, Depth> extends infer P ? P extends string ? NonNullable<DeepValue<STATE, P>> extends readonly (infer Item)[] ? [P, Item[]] : never : never : never;
|
|
213
|
-
/**
|
|
214
|
-
* Paths that resolve to number, plus `.length` on array-valued paths.
|
|
215
|
-
* Allows GT/LT/GTE/LTE to compare against array lengths without
|
|
216
|
-
* polluting DeepKey with virtual paths.
|
|
217
|
-
*/
|
|
218
|
-
type NumericPaths<STATE, Depth extends number> = DeepKeyFiltered<STATE, number, Depth> | `${DeepKeyFiltered<STATE, readonly unknown[], Depth>}.length`;
|
|
219
|
-
/**
|
|
220
|
-
* Boolean logic DSL for conditional expressions
|
|
22
|
+
* **Inference safety**: Parameter types use mapped types only (`{ [I in keyof T]: Check<T[I]> }`)
|
|
23
|
+
* — never `[...T] & MappedType<T>`. This avoids the TS inference competition where `[...T]`
|
|
24
|
+
* and a mapped type both try to infer T, causing tuple widening on small state types.
|
|
221
25
|
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
26
|
+
* **Branded returns**: Each helper returns a `Validated*` branded type so that
|
|
27
|
+
* `useSideEffects({ syncPaths: result })` skips the O(N²) re-validation.
|
|
224
28
|
*
|
|
225
|
-
*
|
|
226
|
-
* -
|
|
227
|
-
* -
|
|
228
|
-
* -
|
|
229
|
-
* -
|
|
230
|
-
*
|
|
231
|
-
* -
|
|
232
|
-
* - CONTAINS_ANY: Check if array at path contains any of the given elements
|
|
233
|
-
* - CONTAINS_ALL: Check if array at path contains all of the given elements
|
|
234
|
-
* - Shorthand: [path, value] tuple as shorthand for IS_EQUAL
|
|
29
|
+
* **Scaling limits** (measured on Apple M4 Pro):
|
|
30
|
+
* - ~82,500 paths (500 flat sectors, depth 4): PASS in 7.1s / 617 MB
|
|
31
|
+
* - ~52,800 paths (binary 6 levels, depth 9): PASS in 4.7s / 574 MB
|
|
32
|
+
* - ~105,600 paths (binary 7 levels, depth 10): TS2589
|
|
33
|
+
* - Practical limit: ~50K–80K paths, bounded by `ResolvableDeepKey` union resolution
|
|
34
|
+
* at the function constraint, not by the validation logic.
|
|
35
|
+
* - Old `SyncPair<T>` hit TS2589 at ~1,500 paths — a ~30–50x improvement.
|
|
235
36
|
*
|
|
236
37
|
* @example
|
|
237
38
|
* ```typescript
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
* const isAdmin2: BoolLogic<State> = ['user.role', 'admin']
|
|
243
|
-
*
|
|
244
|
-
* // Combined conditions with shorthand
|
|
245
|
-
* const canEdit: BoolLogic<State> = {
|
|
246
|
-
* AND: [
|
|
247
|
-
* ['user.role', 'editor'],
|
|
248
|
-
* { EXISTS: 'document.id' },
|
|
249
|
-
* { NOT: ['document.status', 'locked'] }
|
|
250
|
-
* ]
|
|
251
|
-
* }
|
|
252
|
-
*
|
|
253
|
-
* // Numeric comparison
|
|
254
|
-
* const isExpensive: BoolLogic<State> = { GT: ['product.price', 100] }
|
|
255
|
-
*
|
|
256
|
-
* // Array length comparison
|
|
257
|
-
* const hasManyItems: BoolLogic<State> = { GT: ['cart.items.length', 5] }
|
|
39
|
+
* const syncs = syncPairs<State>()([
|
|
40
|
+
* ['user.email', 'profile.email'], // ✓ both string
|
|
41
|
+
* ['user.age', 'profile.name'], // ✗ number vs string → type error
|
|
42
|
+
* ])
|
|
258
43
|
* ```
|
|
259
44
|
*/
|
|
260
|
-
type BoolLogic<STATE, Depth extends number = DefaultDepth> = {
|
|
261
|
-
IS_EQUAL: PathValuePair<STATE, Depth>;
|
|
262
|
-
} | {
|
|
263
|
-
EXISTS: DeepKey<STATE, Depth>;
|
|
264
|
-
} | {
|
|
265
|
-
IS_EMPTY: DeepKey<STATE, Depth>;
|
|
266
|
-
} | {
|
|
267
|
-
AND: BoolLogic<STATE, Depth>[];
|
|
268
|
-
} | {
|
|
269
|
-
OR: BoolLogic<STATE, Depth>[];
|
|
270
|
-
} | {
|
|
271
|
-
NOT: BoolLogic<STATE, Depth>;
|
|
272
|
-
} | {
|
|
273
|
-
GT: [NumericPaths<STATE, Depth>, number];
|
|
274
|
-
} | {
|
|
275
|
-
LT: [NumericPaths<STATE, Depth>, number];
|
|
276
|
-
} | {
|
|
277
|
-
GTE: [NumericPaths<STATE, Depth>, number];
|
|
278
|
-
} | {
|
|
279
|
-
LTE: [NumericPaths<STATE, Depth>, number];
|
|
280
|
-
} | {
|
|
281
|
-
IN: PathValueArrayPair<STATE, Depth>;
|
|
282
|
-
} | {
|
|
283
|
-
CONTAINS_ANY: PathArrayElementsPair<STATE, Depth>;
|
|
284
|
-
} | {
|
|
285
|
-
CONTAINS_ALL: PathArrayElementsPair<STATE, Depth>;
|
|
286
|
-
} | PathValuePair<STATE, Depth>;
|
|
287
45
|
|
|
288
46
|
/**
|
|
289
|
-
*
|
|
47
|
+
* Lazy-validated sync pairs. Curried: `syncPairs<State>()(pairs)`.
|
|
290
48
|
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
49
|
+
* Provides autocomplete for valid paths (O(N)) and validates that both paths
|
|
50
|
+
* in each pair resolve to the same value type (O(K) per pair).
|
|
51
|
+
* Returns branded `ValidatedSyncPairs` — accepted by `useSideEffects` without re-validation.
|
|
293
52
|
*
|
|
294
53
|
* @example
|
|
295
54
|
* ```typescript
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
55
|
+
* const syncs = syncPairs<MyState>()([
|
|
56
|
+
* ['user.email', 'profile.email'],
|
|
57
|
+
* ['user.name', 'profile.name'],
|
|
58
|
+
* ])
|
|
59
|
+
* useSideEffects('my-syncs', { syncPaths: syncs }) // no O(N²) re-check
|
|
300
60
|
* ```
|
|
301
61
|
*/
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Indicates if the change originated from a sync path side-effect.
|
|
305
|
-
* Used to track cascading changes triggered by synchronization logic.
|
|
306
|
-
*/
|
|
307
|
-
isSyncPathChange?: boolean;
|
|
308
|
-
/**
|
|
309
|
-
* Indicates if the change originated from a flip path side-effect.
|
|
310
|
-
* Used to track bidirectional synchronization changes.
|
|
311
|
-
*/
|
|
312
|
-
isFlipPathChange?: boolean;
|
|
313
|
-
/**
|
|
314
|
-
* Indicates if the change was triggered programmatically rather than by user action.
|
|
315
|
-
* Useful for distinguishing between automated and manual state updates.
|
|
316
|
-
*/
|
|
317
|
-
isProgramaticChange?: boolean;
|
|
318
|
-
/**
|
|
319
|
-
* Indicates if the change originated from an aggregation side-effect.
|
|
320
|
-
* Used to track changes triggered by aggregation logic.
|
|
321
|
-
*/
|
|
322
|
-
isAggregationChange?: boolean;
|
|
323
|
-
/**
|
|
324
|
-
* Indicates if the change originated from a listener side-effect.
|
|
325
|
-
* Used to track changes triggered by listener callbacks.
|
|
326
|
-
*/
|
|
327
|
-
isListenerChange?: boolean;
|
|
328
|
-
/**
|
|
329
|
-
* Indicates if the change originated from a clear paths side-effect.
|
|
330
|
-
* Used to track changes triggered by clear paths logic (WASM-only).
|
|
331
|
-
*/
|
|
332
|
-
isClearPathChange?: boolean;
|
|
333
|
-
/**
|
|
334
|
-
* Indicates if the change originated from a computation side-effect (SUM/AVG).
|
|
335
|
-
* Used to track changes triggered by computation logic.
|
|
336
|
-
*/
|
|
337
|
-
isComputationChange?: boolean;
|
|
338
|
-
/**
|
|
339
|
-
* Identifies the originator of the change.
|
|
340
|
-
* Can be a user ID, component name, or any identifier string.
|
|
341
|
-
*/
|
|
342
|
-
sender?: string;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* ArrayOfChanges type
|
|
347
|
-
*
|
|
348
|
-
* Represents an array of changes with paths, values, and metadata.
|
|
349
|
-
* Each change is a tuple of [path, value, metadata].
|
|
350
|
-
*
|
|
351
|
-
* @example
|
|
352
|
-
* ```typescript
|
|
353
|
-
* type User = {
|
|
354
|
-
* name: string
|
|
355
|
-
* age: number
|
|
356
|
-
* }
|
|
357
|
-
*
|
|
358
|
-
* const changes: ArrayOfChanges<User, GenericMeta> = [
|
|
359
|
-
* ["name", "John", { sender: "user-123" }],
|
|
360
|
-
* ["age", 30, { isProgramaticChange: true }]
|
|
361
|
-
* ]
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Represents an array of change tuples.
|
|
367
|
-
* Each tuple contains:
|
|
368
|
-
* - path: A valid deep key path for the data structure
|
|
369
|
-
* - value: The value at that path (properly typed based on the path)
|
|
370
|
-
* - meta: Metadata about the change
|
|
371
|
-
*/
|
|
372
|
-
type ArrayOfChanges<DATA, META extends GenericMeta = GenericMeta> = {
|
|
373
|
-
[K in DeepKey<DATA>]: [K, DeepValue<DATA, K>, META] | [K, DeepValue<DATA, K>];
|
|
374
|
-
}[DeepKey<DATA>][];
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Concern type utilities
|
|
378
|
-
*
|
|
379
|
-
* Generic type helpers for working with concern arrays and extracting
|
|
380
|
-
* their return types for proper type-safe concern registration and reading.
|
|
381
|
-
*/
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Validation schema interface — the minimal contract for validation.
|
|
385
|
-
*
|
|
386
|
-
* Any library whose schema exposes `safeParse()` with this shape works
|
|
387
|
-
* out of the box (Zod, Valibot, ArkType, or a plain object).
|
|
388
|
-
*/
|
|
389
|
-
interface ValidationSchema<T = unknown> {
|
|
390
|
-
safeParse(data: unknown): {
|
|
391
|
-
success: true;
|
|
392
|
-
data: T;
|
|
393
|
-
} | {
|
|
394
|
-
success: false;
|
|
395
|
-
error: {
|
|
396
|
-
errors: {
|
|
397
|
-
path: (string | number)[];
|
|
398
|
-
message: string;
|
|
399
|
-
}[];
|
|
400
|
-
};
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
/**
|
|
404
|
-
* Config type for the validationState concern at a specific path.
|
|
405
|
-
*
|
|
406
|
-
* Defined here (not in concerns/prebuilts/validation-state.ts) to avoid
|
|
407
|
-
* circular imports when used in ConcernRegistrationMap.
|
|
408
|
-
*
|
|
409
|
-
* Can be a schema for the path's own value type, or a scoped schema
|
|
410
|
-
* targeting a different path in the same state.
|
|
411
|
-
*/
|
|
412
|
-
type ValidationStateInput<DATA, PATH extends DeepKey<DATA, Depth>, Depth extends number = DefaultDepth> = {
|
|
413
|
-
schema: ValidationSchema<DeepValue<DATA, PATH>>;
|
|
414
|
-
} | {
|
|
415
|
-
[SCOPE in ResolvableDeepKey<DATA, Depth>]: {
|
|
416
|
-
scope: SCOPE;
|
|
417
|
-
schema: ValidationSchema<DeepValue<DATA, SCOPE>>;
|
|
418
|
-
};
|
|
419
|
-
}[ResolvableDeepKey<DATA, Depth>];
|
|
420
|
-
/**
|
|
421
|
-
* Get the appropriate registration config type for a concern at a given path.
|
|
422
|
-
*
|
|
423
|
-
* - validationState: schema must match DeepValue<DATA, PATH> — path-dependent
|
|
424
|
-
* - BoolLogic-based concerns: boolLogic paths are typed against DATA
|
|
425
|
-
* - Template-based concerns: fixed { template: string }
|
|
426
|
-
* - Custom concerns: fall back to Record<string, unknown>
|
|
427
|
-
*/
|
|
428
|
-
type ConcernConfigFor<C, DATA extends object, PATH extends DeepKey<DATA, Depth>, Depth extends number = DefaultDepth> = C extends {
|
|
429
|
-
name: 'validationState';
|
|
430
|
-
} ? ValidationStateInput<DATA, PATH, Depth> : C extends {
|
|
431
|
-
evaluate: (props: infer P) => any;
|
|
432
|
-
} ? P extends {
|
|
433
|
-
boolLogic: any;
|
|
434
|
-
} ? {
|
|
435
|
-
boolLogic: BoolLogic<DATA, Depth>;
|
|
436
|
-
} : Omit<P, 'state' | 'path' | 'value'> : Record<string, unknown>;
|
|
62
|
+
declare const syncPairs: <DATA extends object, Depth extends number = DefaultDepth>() => <T extends [ResolvableDeepKey<DATA, Depth>, ResolvableDeepKey<DATA, Depth>][]>(pairs: CheckSyncPairs<DATA, T, Depth>) => ValidatedSyncPairs<DATA>;
|
|
437
63
|
/**
|
|
438
|
-
*
|
|
64
|
+
* Lazy-validated flip pairs. Curried: `flipPairs<State>()(pairs)`.
|
|
439
65
|
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
66
|
+
* Identical to syncPairs — validates both paths resolve to the same value type.
|
|
67
|
+
* Returns branded `ValidatedFlipPairs` — accepted by `useSideEffects` without re-validation.
|
|
442
68
|
*
|
|
443
69
|
* @example
|
|
444
70
|
* ```typescript
|
|
445
|
-
*
|
|
446
|
-
*
|
|
71
|
+
* const flips = flipPairs<MyState>()([
|
|
72
|
+
* ['isActive', 'isInactive'],
|
|
73
|
+
* ])
|
|
74
|
+
* useSideEffects('my-flips', { flipPaths: flips }) // no O(N²) re-check
|
|
447
75
|
* ```
|
|
448
76
|
*/
|
|
449
|
-
|
|
450
|
-
evaluate: (...args: any[]) => infer R;
|
|
451
|
-
} ? R : never;
|
|
77
|
+
declare const flipPairs: <DATA extends object, Depth extends number = DefaultDepth>() => <T extends [ResolvableDeepKey<DATA, Depth>, ResolvableDeepKey<DATA, Depth>][]>(pairs: CheckSyncPairs<DATA, T, Depth>) => ValidatedFlipPairs<DATA>;
|
|
452
78
|
/**
|
|
453
|
-
*
|
|
79
|
+
* Lazy-validated aggregation pairs. Curried: `aggregationPairs<State>()(pairs)`.
|
|
454
80
|
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
81
|
+
* Each pair is [target, source] or [target, source, BoolLogic].
|
|
82
|
+
* Validates that target and source resolve to the same value type.
|
|
83
|
+
* Returns branded `ValidatedAggregationPairs` — accepted by `useSideEffects` without re-validation.
|
|
457
84
|
*
|
|
458
85
|
* @example
|
|
459
86
|
* ```typescript
|
|
460
|
-
* const
|
|
461
|
-
* { name: 'validationState', evaluate: () => ({ isError: boolean, errors: [] }) },
|
|
462
|
-
* { name: 'tooltip', evaluate: () => string }
|
|
463
|
-
* ] as const
|
|
464
|
-
*
|
|
465
|
-
* type Evaluated = EvaluatedConcerns<typeof concerns>
|
|
466
|
-
* // { validationState?: { isError: boolean, errors: [] }, tooltip?: string }
|
|
467
|
-
* ```
|
|
468
|
-
*/
|
|
469
|
-
type EvaluatedConcerns<CONCERNS extends readonly any[]> = {
|
|
470
|
-
[K in CONCERNS[number] as K['name']]?: ExtractEvaluateReturn<K>;
|
|
471
|
-
};
|
|
472
|
-
/**
|
|
473
|
-
* Maps field paths to per-concern config objects.
|
|
474
|
-
*
|
|
475
|
-
* When CONCERNS is provided (e.g., from createGenericStore's CONCERNS generic),
|
|
476
|
-
* each concern config is type-checked against:
|
|
477
|
-
* - The concern's own EXTRA_PROPS (e.g., `{ boolLogic: ... }` for disabledWhen)
|
|
478
|
-
* - The path's value type for validationState (schema must match DeepValue<DATA, PATH>)
|
|
479
|
-
*
|
|
480
|
-
* Without CONCERNS (standalone usage), falls back to loose typing for backward compatibility.
|
|
481
|
-
*
|
|
482
|
-
* @example
|
|
483
|
-
* ```typescript
|
|
484
|
-
* // Works with any library implementing ValidationSchema (Zod, Valibot, ArkType, etc.)
|
|
485
|
-
* const registration: ConcernRegistrationMap<MyFormState> = {
|
|
486
|
-
* email: { validationState: { schema: emailSchema } },
|
|
487
|
-
* name: { validationState: { schema: nameSchema } },
|
|
488
|
-
* }
|
|
489
|
-
* ```
|
|
490
|
-
*/
|
|
491
|
-
type ConcernRegistrationMap<DATA extends object, CONCERNS extends readonly any[] = readonly any[], Depth extends number = DefaultDepth> = Partial<{
|
|
492
|
-
[PATH in DeepKey<DATA, Depth>]: Partial<{
|
|
493
|
-
[C in CONCERNS[number] as C extends {
|
|
494
|
-
name: string;
|
|
495
|
-
} ? C['name'] : never]: ConcernConfigFor<C, DATA, PATH, Depth>;
|
|
496
|
-
}>;
|
|
497
|
-
}>;
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* PathsOfSameValue - Type-safe path tuples for side effects
|
|
501
|
-
*
|
|
502
|
-
* Simple tuple-based API for syncPaths, flipPaths, and aggregations.
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* ```typescript
|
|
506
|
-
* type State = {
|
|
507
|
-
* user: { email: string }
|
|
508
|
-
* profile: { email: string }
|
|
509
|
-
* count: number
|
|
510
|
-
* }
|
|
511
|
-
*
|
|
512
|
-
* // ✓ Valid: both paths are string
|
|
513
|
-
* const sync: SyncPair<State> = ['user.email', 'profile.email']
|
|
514
|
-
*
|
|
515
|
-
* // ✗ Error: 'user.email' (string) can't sync with 'count' (number)
|
|
516
|
-
* const invalid: SyncPair<State> = ['user.email', 'count']
|
|
517
|
-
* ```
|
|
518
|
-
*/
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* Get all paths in DATA that have the same value type as the value at PATH
|
|
522
|
-
*/
|
|
523
|
-
type PathsWithSameValueAs<DATA extends object, PATH extends ResolvableDeepKey<DATA, Depth>, Depth extends number = DefaultDepth> = {
|
|
524
|
-
[K in ResolvableDeepKey<DATA, Depth>]: NonNullable<DeepValue<DATA, K>> extends NonNullable<DeepValue<DATA, PATH>> ? NonNullable<DeepValue<DATA, PATH>> extends NonNullable<DeepValue<DATA, K>> ? K : never : never;
|
|
525
|
-
}[ResolvableDeepKey<DATA, Depth>];
|
|
526
|
-
/**
|
|
527
|
-
* A tuple of two paths that must have the same value type.
|
|
528
|
-
* Format: [path1, path2]
|
|
529
|
-
*
|
|
530
|
-
* @example
|
|
531
|
-
* const pair: SyncPair<State> = ['user.email', 'profile.email']
|
|
532
|
-
*
|
|
533
|
-
* @example Custom depth — propagates to DeepKey and PathsWithSameValueAs
|
|
534
|
-
* ```typescript
|
|
535
|
-
* // Limit path traversal to 10 levels
|
|
536
|
-
* const shallow: SyncPair<State, 10> = ['user.email', 'profile.email']
|
|
537
|
-
* ```
|
|
538
|
-
*/
|
|
539
|
-
type SyncPair<DATA extends object, Depth extends number = DefaultDepth> = {
|
|
540
|
-
[P1 in ResolvableDeepKey<DATA, Depth>]: [
|
|
541
|
-
P1,
|
|
542
|
-
PathsWithSameValueAs<DATA, P1, Depth>
|
|
543
|
-
];
|
|
544
|
-
}[ResolvableDeepKey<DATA, Depth>];
|
|
545
|
-
/**
|
|
546
|
-
* A tuple of two paths for flip (alias for SyncPair)
|
|
547
|
-
* Format: [path1, path2]
|
|
548
|
-
*
|
|
549
|
-
* @example
|
|
550
|
-
* const pair: FlipPair<State> = ['isActive', 'isInactive']
|
|
551
|
-
*/
|
|
552
|
-
type FlipPair<DATA extends object, Depth extends number = DefaultDepth> = SyncPair<DATA, Depth>;
|
|
553
|
-
/**
|
|
554
|
-
* A tuple for aggregation: [target, source] or [target, source, excludeWhen]
|
|
555
|
-
* First element (left) is ALWAYS the target (aggregated) path.
|
|
556
|
-
* Second element is a source path.
|
|
557
|
-
* Optional third element is a BoolLogic condition — when true, this source is excluded.
|
|
558
|
-
*
|
|
559
|
-
* Multiple pairs can point to same target for multi-source aggregation.
|
|
560
|
-
*
|
|
561
|
-
* @example
|
|
562
|
-
* // target <- source (target is always first/left)
|
|
563
|
-
* const aggs: AggregationPair<State>[] = [
|
|
87
|
+
* const aggs = aggregationPairs<MyState>()([
|
|
564
88
|
* ['total', 'price1'],
|
|
565
89
|
* ['total', 'price2', { IS_EQUAL: ['price2.disabled', true] }],
|
|
566
|
-
* ]
|
|
567
|
-
|
|
568
|
-
type AggregationPair<DATA extends object, Depth extends number = DefaultDepth> = {
|
|
569
|
-
[P1 in ResolvableDeepKey<DATA, Depth>]: [P1, PathsWithSameValueAs<DATA, P1, Depth>] | [P1, PathsWithSameValueAs<DATA, P1, Depth>, BoolLogic<DATA, Depth>];
|
|
570
|
-
}[ResolvableDeepKey<DATA, Depth>];
|
|
571
|
-
/**
|
|
572
|
-
* Supported computation operations for numeric reduction.
|
|
573
|
-
*/
|
|
574
|
-
type ComputationOp = 'SUM' | 'AVG';
|
|
575
|
-
/**
|
|
576
|
-
* A tuple for computation: [operation, target, source] or [operation, target, source, excludeWhen]
|
|
577
|
-
* First element is the operation (SUM, AVG).
|
|
578
|
-
* Second element is the target path (must be a number path).
|
|
579
|
-
* Third element is a source path (must be a number path).
|
|
580
|
-
* Optional fourth element is a BoolLogic condition — when true, this source is excluded.
|
|
581
|
-
*
|
|
582
|
-
* Multiple pairs can point to same target for multi-source computation.
|
|
583
|
-
*
|
|
584
|
-
* @example
|
|
585
|
-
* const comps: ComputationPair<State>[] = [
|
|
586
|
-
* ['SUM', 'total', 'price1'],
|
|
587
|
-
* ['SUM', 'total', 'price2', { IS_EQUAL: ['price2.disabled', true] }],
|
|
588
|
-
* ['AVG', 'average', 'score1'],
|
|
589
|
-
* ['AVG', 'average', 'score2'],
|
|
590
|
-
* ]
|
|
591
|
-
*/
|
|
592
|
-
type ComputationPair<DATA extends object, Depth extends number = DefaultDepth> = {
|
|
593
|
-
[TARGET in DeepKeyFiltered<DATA, number, Depth>]: [ComputationOp, TARGET, DeepKeyFiltered<DATA, number, Depth>] | [
|
|
594
|
-
ComputationOp,
|
|
595
|
-
TARGET,
|
|
596
|
-
DeepKeyFiltered<DATA, number, Depth>,
|
|
597
|
-
BoolLogic<DATA, Depth>
|
|
598
|
-
];
|
|
599
|
-
}[DeepKeyFiltered<DATA, number, Depth>];
|
|
600
|
-
|
|
601
|
-
/** Recursively makes all properties required, stripping undefined */
|
|
602
|
-
type DeepRequired<T> = {
|
|
603
|
-
[K in keyof T]-?: NonNullable<T[K]> extends object ? DeepRequired<NonNullable<T[K]>> : NonNullable<T[K]>;
|
|
604
|
-
};
|
|
605
|
-
/**
|
|
606
|
-
* Recursively makes all properties optional (allows undefined values).
|
|
607
|
-
*
|
|
608
|
-
* Written as a conditional type so TypeScript distributes it over union members
|
|
609
|
-
* automatically — null/undefined fall through to `: T` and are preserved as-is.
|
|
610
|
-
* Array check comes before object so arrays are not mapped over their keys.
|
|
611
|
-
*/
|
|
612
|
-
type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] : T extends readonly (infer U)[] ? readonly DeepPartial<U>[] : T extends object ? {
|
|
613
|
-
[K in keyof T]?: DeepPartial<T[K]>;
|
|
614
|
-
} : T;
|
|
615
|
-
|
|
616
|
-
interface BaseConcernProps<STATE, PATH extends string> {
|
|
617
|
-
state: STATE;
|
|
618
|
-
path: PATH;
|
|
619
|
-
value: unknown;
|
|
620
|
-
}
|
|
621
|
-
interface ConcernType<NAME extends string = string, EXTRA_PROPS = Record<string, unknown>, RETURN_TYPE = unknown> {
|
|
622
|
-
name: NAME;
|
|
623
|
-
description: string;
|
|
624
|
-
/** Evaluated inside effect() - all state accesses are tracked */
|
|
625
|
-
evaluate: (props: BaseConcernProps<Record<string, unknown>, string> & EXTRA_PROPS) => RETURN_TYPE;
|
|
626
|
-
}
|
|
627
|
-
interface ConcernRegistration {
|
|
628
|
-
id: string;
|
|
629
|
-
path: string;
|
|
630
|
-
concernName: string;
|
|
631
|
-
concern: ConcernType;
|
|
632
|
-
config: Record<string, unknown>;
|
|
633
|
-
dispose: () => void;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
/**
|
|
637
|
-
* Debug Timing Utilities
|
|
638
|
-
*
|
|
639
|
-
* Provides timing measurement for concerns and listeners
|
|
640
|
-
* to detect slow operations during development.
|
|
641
|
-
*/
|
|
642
|
-
type TimingType = 'concerns' | 'listeners' | 'registration';
|
|
643
|
-
interface TimingMeta {
|
|
644
|
-
path: string;
|
|
645
|
-
name: string;
|
|
646
|
-
}
|
|
647
|
-
interface Timing {
|
|
648
|
-
run: <T>(type: TimingType, fn: () => T, meta: TimingMeta) => T;
|
|
649
|
-
reportBatch: (type: TimingType) => void;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/**
|
|
653
|
-
* WASM Bridge — Thin namespace over Rust/WASM exports.
|
|
654
|
-
*
|
|
655
|
-
* Uses serde-wasm-bindgen for hot-path functions (processChanges,
|
|
656
|
-
* shadowInit) — JS objects cross the boundary directly without JSON
|
|
657
|
-
* string intermediary. Registration functions still use JSON strings
|
|
658
|
-
* (cold path, simpler).
|
|
659
|
-
*
|
|
660
|
-
* Loading is handled by `wasm/lifecycle.ts`. After loading, all bridge
|
|
661
|
-
* functions are synchronous.
|
|
662
|
-
*
|
|
663
|
-
* @module wasm/bridge
|
|
664
|
-
*/
|
|
665
|
-
|
|
666
|
-
/** A single state change (input or output). */
|
|
667
|
-
interface Change {
|
|
668
|
-
path: string;
|
|
669
|
-
value: unknown;
|
|
670
|
-
origin?: string;
|
|
671
|
-
}
|
|
672
|
-
/** A single dispatch entry with sequential ID and input change references. */
|
|
673
|
-
interface DispatchEntry {
|
|
674
|
-
dispatch_id: number;
|
|
675
|
-
subscriber_id: number;
|
|
676
|
-
scope_path: string;
|
|
677
|
-
/** Indexes into ProcessResult.changes array. */
|
|
678
|
-
input_change_ids: number[];
|
|
679
|
-
}
|
|
680
|
-
/** A group of dispatches to execute sequentially. */
|
|
681
|
-
interface DispatchGroup {
|
|
682
|
-
dispatches: DispatchEntry[];
|
|
683
|
-
}
|
|
684
|
-
/** A target for propagating produced changes from child to parent dispatch. */
|
|
685
|
-
interface PropagationTarget {
|
|
686
|
-
target_dispatch_id: number;
|
|
687
|
-
/** Prefix to prepend to child's relative paths for the target's scope. */
|
|
688
|
-
remap_prefix: string;
|
|
689
|
-
}
|
|
690
|
-
/** Pre-computed execution plan with propagation map. */
|
|
691
|
-
interface FullExecutionPlan {
|
|
692
|
-
groups: DispatchGroup[];
|
|
693
|
-
/** propagation_map[dispatch_id] = targets to forward produced changes to. */
|
|
694
|
-
propagation_map: PropagationTarget[][];
|
|
695
|
-
}
|
|
696
|
-
/** Validator dispatch info for JS-side execution. */
|
|
697
|
-
interface ValidatorDispatch {
|
|
698
|
-
validator_id: number;
|
|
699
|
-
output_path: string;
|
|
700
|
-
dependency_values: Record<string, string>;
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Extends a base tuple with an optional trailing BoolLogic condition (JSON-serialized).
|
|
704
|
-
* Used by aggregation and computation pairs to share the "optional excludeWhen" pattern.
|
|
705
|
-
*/
|
|
706
|
-
type WasmPairWithCondition<Base extends [...string[]]> = [...Base] | [...Base, condition: string];
|
|
707
|
-
/** WASM-side aggregation pair: [target, source] with optional excludeWhen condition. */
|
|
708
|
-
type WasmAggregationPair = WasmPairWithCondition<[
|
|
709
|
-
target: string,
|
|
710
|
-
source: string
|
|
711
|
-
]>;
|
|
712
|
-
/** WASM-side computation pair: [operation, target, source] with optional excludeWhen condition. */
|
|
713
|
-
type WasmComputationPair = WasmPairWithCondition<[
|
|
714
|
-
op: string,
|
|
715
|
-
target: string,
|
|
716
|
-
source: string
|
|
717
|
-
]>;
|
|
718
|
-
/** WASM-side sync pair: [source, target] — bidirectional sync. */
|
|
719
|
-
type WasmSyncPair = [source: string, target: string];
|
|
720
|
-
/** WASM-side flip pair: [source, target] — inverted boolean sync. */
|
|
721
|
-
type WasmFlipPair = [source: string, target: string];
|
|
722
|
-
/** WASM-side clear path rule: trigger paths → target paths to null. */
|
|
723
|
-
interface WasmClearPathRule {
|
|
724
|
-
triggers: string[];
|
|
725
|
-
targets: string[];
|
|
726
|
-
}
|
|
727
|
-
/** WASM-side listener entry for topic-based dispatch. */
|
|
728
|
-
interface WasmListenerEntry {
|
|
729
|
-
subscriber_id: number;
|
|
730
|
-
topic_path: string;
|
|
731
|
-
scope_path: string;
|
|
732
|
-
}
|
|
733
|
-
/** Consolidated registration input for side effects (sync, flip, aggregation, computation, clear, listeners). */
|
|
734
|
-
interface SideEffectsRegistration {
|
|
735
|
-
registration_id: string;
|
|
736
|
-
sync_pairs?: WasmSyncPair[];
|
|
737
|
-
flip_pairs?: WasmFlipPair[];
|
|
738
|
-
aggregation_pairs?: WasmAggregationPair[];
|
|
739
|
-
computation_pairs?: WasmComputationPair[];
|
|
740
|
-
clear_paths?: WasmClearPathRule[];
|
|
741
|
-
listeners?: WasmListenerEntry[];
|
|
742
|
-
}
|
|
743
|
-
/** Consolidated registration output from side effects registration. */
|
|
744
|
-
interface SideEffectsResult {
|
|
745
|
-
sync_changes: Change[];
|
|
746
|
-
aggregation_changes: Change[];
|
|
747
|
-
computation_changes: Change[];
|
|
748
|
-
registered_listener_ids: number[];
|
|
749
|
-
}
|
|
750
|
-
/** WASM-side BoolLogic entry for declarative boolean evaluation. */
|
|
751
|
-
interface WasmBoolLogicEntry {
|
|
752
|
-
output_path: string;
|
|
753
|
-
tree_json: string;
|
|
754
|
-
}
|
|
755
|
-
/** WASM-side validator entry for validation orchestration. */
|
|
756
|
-
interface WasmValidatorEntry {
|
|
757
|
-
validator_id: number;
|
|
758
|
-
output_path: string;
|
|
759
|
-
dependency_paths: string[];
|
|
760
|
-
scope: string;
|
|
761
|
-
}
|
|
762
|
-
/** WASM-side ValueLogic entry for declarative value computation. */
|
|
763
|
-
interface WasmValueLogicEntry {
|
|
764
|
-
output_path: string;
|
|
765
|
-
tree_json: string;
|
|
766
|
-
}
|
|
767
|
-
/** Consolidated registration input for concerns (BoolLogic, validators, and ValueLogic). */
|
|
768
|
-
interface ConcernsRegistration {
|
|
769
|
-
registration_id: string;
|
|
770
|
-
bool_logics?: WasmBoolLogicEntry[];
|
|
771
|
-
validators?: WasmValidatorEntry[];
|
|
772
|
-
value_logics?: WasmValueLogicEntry[];
|
|
773
|
-
}
|
|
774
|
-
/** Consolidated registration output from concerns registration. */
|
|
775
|
-
interface ConcernsResult {
|
|
776
|
-
bool_logic_changes: Change[];
|
|
777
|
-
registered_logic_ids: number[];
|
|
778
|
-
registered_validator_ids: number[];
|
|
779
|
-
value_logic_changes: Change[];
|
|
780
|
-
registered_value_logic_ids: number[];
|
|
781
|
-
}
|
|
782
|
-
/** Result of processChanges (Phase 1). */
|
|
783
|
-
interface ProcessChangesResult {
|
|
784
|
-
state_changes: Change[];
|
|
785
|
-
changes: Change[];
|
|
786
|
-
validators_to_run: ValidatorDispatch[];
|
|
787
|
-
execution_plan: FullExecutionPlan | null;
|
|
788
|
-
has_work: boolean;
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* An isolated WASM pipeline instance.
|
|
792
|
-
* Each store gets its own pipeline so multiple Providers don't interfere.
|
|
793
|
-
* All methods are pre-bound to the pipeline's ID — consumers never pass IDs.
|
|
794
|
-
*/
|
|
795
|
-
interface WasmPipeline {
|
|
796
|
-
readonly id: number;
|
|
797
|
-
shadowInit: (state: object) => void;
|
|
798
|
-
shadowDump: () => unknown;
|
|
799
|
-
processChanges: (changes: Change[]) => ProcessChangesResult;
|
|
800
|
-
pipelineFinalize: (jsChanges: Change[]) => {
|
|
801
|
-
state_changes: Change[];
|
|
802
|
-
};
|
|
803
|
-
registerSideEffects: (reg: SideEffectsRegistration) => SideEffectsResult;
|
|
804
|
-
unregisterSideEffects: (registrationId: string) => void;
|
|
805
|
-
registerConcerns: (reg: ConcernsRegistration) => ConcernsResult;
|
|
806
|
-
unregisterConcerns: (registrationId: string) => void;
|
|
807
|
-
registerBoolLogic: (outputPath: string, tree: unknown) => number;
|
|
808
|
-
unregisterBoolLogic: (logicId: number) => void;
|
|
809
|
-
pipelineReset: () => void;
|
|
810
|
-
destroy: () => void;
|
|
811
|
-
/** Per-instance storage for validation schemas (can't cross WASM boundary). */
|
|
812
|
-
validatorSchemas: Map<number, ValidationSchema>;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* PathGroups Data Structure
|
|
817
|
-
*
|
|
818
|
-
* A custom data structure that provides O(1) connected component lookups
|
|
819
|
-
* instead of O(V+E) recomputation on every change like graphology's connectedComponents.
|
|
820
|
-
*
|
|
821
|
-
* When wasmGraphType is set ('sync' | 'flip'), addEdge/removeEdge automatically
|
|
822
|
-
* mirror registrations to WASM bridge for pipeline processing.
|
|
823
|
-
*
|
|
824
|
-
* Operations complexity:
|
|
825
|
-
* - getAllGroups(): O(G) where G is number of groups (typically small)
|
|
826
|
-
* - getGroupPaths(path): O(1) lookup
|
|
827
|
-
* - addEdge(p1, p2): O(n) where n is smaller group size (merge)
|
|
828
|
-
* - removeEdge(p1, p2): O(n) for affected component
|
|
829
|
-
* - hasPath(path): O(1)
|
|
830
|
-
* - hasEdge(p1, p2): O(1)
|
|
831
|
-
*/
|
|
832
|
-
/** Graph type for WASM mirroring. When set, addEdge/removeEdge mirror to WASM. */
|
|
833
|
-
type WasmGraphType = 'sync' | 'flip';
|
|
834
|
-
/**
|
|
835
|
-
* PathGroups maintains connected components with O(1) lookups.
|
|
836
|
-
*
|
|
837
|
-
* Internal structure:
|
|
838
|
-
* - pathToGroup: Maps each path to its group ID
|
|
839
|
-
* - groupToPaths: Maps each group ID to the set of paths in that group
|
|
840
|
-
* - edges: Set of edge keys for edge existence checks and proper removal
|
|
841
|
-
* - adjacency: Maps each path to its direct neighbors (for split detection on removal)
|
|
842
|
-
* - nextGroupId: Counter for generating unique group IDs
|
|
843
|
-
*/
|
|
844
|
-
interface PathGroups {
|
|
845
|
-
pathToGroup: Map<string, number>;
|
|
846
|
-
groupToPaths: Map<number, Set<string>>;
|
|
847
|
-
edges: Set<string>;
|
|
848
|
-
adjacency: Map<string, Set<string>>;
|
|
849
|
-
nextGroupId: number;
|
|
850
|
-
wasmGraphType?: WasmGraphType;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
/**
|
|
854
|
-
* Graph Type Definitions
|
|
855
|
-
*
|
|
856
|
-
* Type aliases for the PathGroups data structure used in sync and flip operations.
|
|
857
|
-
* These aliases maintain backward compatibility with the previous graphology-based API.
|
|
858
|
-
*/
|
|
859
|
-
|
|
860
|
-
/**
|
|
861
|
-
* Sync graph: Paths that must have synchronized values
|
|
862
|
-
* Type alias for PathGroups - maintains backward compatibility
|
|
863
|
-
*/
|
|
864
|
-
type SyncGraph = PathGroups;
|
|
865
|
-
/**
|
|
866
|
-
* Flip graph: Paths with inverted boolean values
|
|
867
|
-
* Type alias for PathGroups - maintains backward compatibility
|
|
868
|
-
*/
|
|
869
|
-
type FlipGraph = PathGroups;
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* Core Store Types
|
|
873
|
-
*
|
|
874
|
-
* Foundational type definitions for the store instance.
|
|
875
|
-
* These types are used throughout the library.
|
|
876
|
-
*/
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* Debug configuration for development tooling
|
|
880
|
-
*/
|
|
881
|
-
interface DebugConfig {
|
|
882
|
-
/** Enable timing measurement for concerns and listeners */
|
|
883
|
-
timing?: boolean;
|
|
884
|
-
/** Threshold in milliseconds for slow operation warnings (default: 5ms) */
|
|
885
|
-
timingThreshold?: number;
|
|
886
|
-
/** Enable tracking of processChanges calls and applied changes for testing/debugging */
|
|
887
|
-
track?: boolean;
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* A single recorded processChanges invocation
|
|
891
|
-
*/
|
|
892
|
-
interface DebugTrackEntry {
|
|
893
|
-
/** Input changes passed to processChanges as [path, value, meta] tuples */
|
|
894
|
-
input: [string, unknown, unknown][];
|
|
895
|
-
/** Changes actually applied to state proxy */
|
|
896
|
-
applied: {
|
|
897
|
-
path: string;
|
|
898
|
-
value: unknown;
|
|
899
|
-
}[];
|
|
900
|
-
/** Changes applied to _concerns proxy */
|
|
901
|
-
appliedConcerns: {
|
|
902
|
-
path: string;
|
|
903
|
-
value: unknown;
|
|
904
|
-
}[];
|
|
905
|
-
/** Timestamp of the call */
|
|
906
|
-
timestamp: number;
|
|
907
|
-
}
|
|
908
|
-
/**
|
|
909
|
-
* Debug tracking data exposed on StoreInstance when debug.track is enabled.
|
|
910
|
-
* Provides an append-only log of all processChanges calls and their effects.
|
|
911
|
-
*/
|
|
912
|
-
interface DebugTrack {
|
|
913
|
-
/** All recorded processChanges calls (append-only) */
|
|
914
|
-
calls: DebugTrackEntry[];
|
|
915
|
-
/** Reset all tracking data */
|
|
916
|
-
clear: () => void;
|
|
917
|
-
}
|
|
918
|
-
interface StoreConfig {
|
|
919
|
-
/** Error storage path (default: "_errors") */
|
|
920
|
-
errorStorePath?: string;
|
|
921
|
-
/** Max iterations for change processing (default: 100) */
|
|
922
|
-
maxIterations?: number;
|
|
923
|
-
/** Debug configuration for development tooling */
|
|
924
|
-
debug?: DebugConfig;
|
|
925
|
-
/** Use legacy TypeScript implementation instead of WASM (default: false) */
|
|
926
|
-
useLegacyImplementation?: boolean;
|
|
927
|
-
}
|
|
928
|
-
interface ProviderProps<DATA extends object> {
|
|
929
|
-
initialState: DATA;
|
|
930
|
-
children: ReactNode;
|
|
931
|
-
}
|
|
932
|
-
interface Aggregation {
|
|
933
|
-
id?: string;
|
|
934
|
-
targetPath: string;
|
|
935
|
-
sourcePaths: string[];
|
|
936
|
-
}
|
|
937
|
-
/** Reacts to scoped changes - receives relative paths and scoped state. Only fires for NESTED paths, not the path itself. */
|
|
938
|
-
type OnStateListener<DATA extends object = object, SUB_STATE = DATA, META extends GenericMeta = GenericMeta> = (changes: ArrayOfChanges<SUB_STATE, META>, state: SUB_STATE) => ArrayOfChanges<DATA, META> | undefined;
|
|
939
|
-
/**
|
|
940
|
-
* Listener registration with path (what to watch) and scope (how to present data)
|
|
941
|
-
*
|
|
942
|
-
* @example
|
|
943
|
-
* ```typescript
|
|
944
|
-
* // Watch user.profile.name, get full state
|
|
945
|
-
* {
|
|
946
|
-
* path: 'user.profile.name',
|
|
947
|
-
* scope: null,
|
|
948
|
-
* fn: (changes, state) => {
|
|
949
|
-
* // changes: [['user.profile.name', 'Alice', {}]] - FULL path
|
|
950
|
-
* // state: full DATA object
|
|
951
|
-
* }
|
|
952
|
-
* }
|
|
953
|
-
*
|
|
954
|
-
* // Watch user.profile.*, get scoped state
|
|
955
|
-
* {
|
|
956
|
-
* path: 'user.profile',
|
|
957
|
-
* scope: 'user.profile',
|
|
958
|
-
* fn: (changes, state) => {
|
|
959
|
-
* // changes: [['name', 'Alice', {}]] - RELATIVE to scope
|
|
960
|
-
* // state: user.profile object
|
|
961
|
-
* }
|
|
962
|
-
* }
|
|
963
|
-
*
|
|
964
|
-
* // Watch deep path, get parent scope
|
|
965
|
-
* {
|
|
966
|
-
* path: 'p.123.g.abc.data.strike',
|
|
967
|
-
* scope: 'p.123.g.abc',
|
|
968
|
-
* fn: (changes, state) => {
|
|
969
|
-
* // changes: [['data.strike', value, {}]] - relative to scope
|
|
970
|
-
* // state: p.123.g.abc object
|
|
971
|
-
* }
|
|
972
|
-
* }
|
|
973
|
-
* ```
|
|
974
|
-
*/
|
|
975
|
-
interface ListenerRegistration<DATA extends object = object, META extends GenericMeta = GenericMeta, Depth extends number = DefaultDepth> {
|
|
976
|
-
/**
|
|
977
|
-
* Path to watch - only changes under this path will trigger the listener
|
|
978
|
-
* null = watch all top-level paths
|
|
979
|
-
*/
|
|
980
|
-
path: DeepKey<DATA, Depth> | null;
|
|
981
|
-
/**
|
|
982
|
-
* Scope for state and changes presentation
|
|
983
|
-
* - If null: state is full DATA, changes use FULL paths
|
|
984
|
-
* - If set: state is value at scope, changes use paths RELATIVE to scope
|
|
985
|
-
*
|
|
986
|
-
* Note: Changes are filtered based on `path`, even when scope is null
|
|
987
|
-
*/
|
|
988
|
-
scope: DeepKey<DATA, Depth> | null;
|
|
989
|
-
fn: OnStateListener<DATA, any, META>;
|
|
990
|
-
}
|
|
991
|
-
interface ListenerHandlerRef {
|
|
992
|
-
scope: string | null;
|
|
993
|
-
fn: (...args: unknown[]) => unknown;
|
|
994
|
-
}
|
|
995
|
-
interface SideEffectGraphs<DATA extends object = object, META extends GenericMeta = GenericMeta> {
|
|
996
|
-
sync: SyncGraph;
|
|
997
|
-
flip: FlipGraph;
|
|
998
|
-
listeners: Map<string, ListenerRegistration<DATA, META>[]>;
|
|
999
|
-
sortedListenerPaths: string[];
|
|
1000
|
-
/** O(1) lookup: subscriber_id -> handler ref. Populated by registerListener. */
|
|
1001
|
-
listenerHandlers: Map<number, ListenerHandlerRef>;
|
|
1002
|
-
}
|
|
1003
|
-
interface Registrations {
|
|
1004
|
-
concerns: Map<string, ConcernType[]>;
|
|
1005
|
-
effectCleanups: Set<() => void>;
|
|
1006
|
-
sideEffectCleanups: Map<string, () => void>;
|
|
1007
|
-
aggregations: Map<string, Aggregation[]>;
|
|
1008
|
-
}
|
|
1009
|
-
interface ProcessingState<DATA extends object = object, META extends GenericMeta = GenericMeta> {
|
|
1010
|
-
queue: ArrayOfChanges<DATA, META>;
|
|
1011
|
-
}
|
|
1012
|
-
/** Internal store state (NOT tracked - wrapped in ref()) */
|
|
1013
|
-
interface InternalState<DATA extends object = object, META extends GenericMeta = GenericMeta> {
|
|
1014
|
-
graphs: SideEffectGraphs<DATA, META>;
|
|
1015
|
-
registrations: Registrations;
|
|
1016
|
-
processing: ProcessingState<DATA, META>;
|
|
1017
|
-
timing: Timing;
|
|
1018
|
-
config: DeepRequired<StoreConfig>;
|
|
1019
|
-
/** Per-store WASM pipeline instance (null when using legacy implementation). */
|
|
1020
|
-
pipeline: WasmPipeline | null;
|
|
1021
|
-
/** Pending deferred destroy timer — cancelled on StrictMode re-mount. */
|
|
1022
|
-
_destroyTimer?: ReturnType<typeof setTimeout> | undefined;
|
|
1023
|
-
}
|
|
1024
|
-
type ConcernValues = Record<string, Record<string, unknown>>;
|
|
1025
|
-
/** Two-proxy pattern: state and _concerns are independent to prevent infinite loops */
|
|
1026
|
-
interface StoreInstance<DATA extends object, META extends GenericMeta = GenericMeta> {
|
|
1027
|
-
state: DATA;
|
|
1028
|
-
_concerns: ConcernValues;
|
|
1029
|
-
_internal: InternalState<DATA, META>;
|
|
1030
|
-
/** Debug tracking data, only populated when debug.track is enabled */
|
|
1031
|
-
_debug: DebugTrack | null;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
interface ValidationError {
|
|
1035
|
-
field?: string;
|
|
1036
|
-
message: string;
|
|
1037
|
-
}
|
|
1038
|
-
interface ValidationStateResult {
|
|
1039
|
-
isError: boolean;
|
|
1040
|
-
errors: ValidationError[];
|
|
1041
|
-
}
|
|
1042
|
-
interface ValidationStateConcern {
|
|
1043
|
-
name: 'validationState';
|
|
1044
|
-
description: string;
|
|
1045
|
-
evaluate: <SUB_STATE, PATH extends DeepKey<SUB_STATE>>(props: BaseConcernProps<SUB_STATE, PATH> & ValidationStateInput<SUB_STATE, PATH>) => ValidationStateResult;
|
|
1046
|
-
}
|
|
1047
|
-
declare const validationState: ValidationStateConcern;
|
|
1048
|
-
|
|
1049
|
-
declare const disabledWhen: ConcernType<'disabledWhen', {
|
|
1050
|
-
boolLogic: BoolLogic<any>;
|
|
1051
|
-
}, boolean>;
|
|
1052
|
-
|
|
1053
|
-
/**
|
|
1054
|
-
* Read-only condition concern
|
|
1055
|
-
*
|
|
1056
|
-
* Evaluates a boolean logic expression to determine if a field should be read-only.
|
|
1057
|
-
* Automatically tracks all state paths accessed in the condition.
|
|
1058
|
-
*
|
|
1059
|
-
* Returns true if read-only, false if editable.
|
|
1060
|
-
*
|
|
1061
|
-
* @example
|
|
1062
|
-
* ```typescript
|
|
1063
|
-
* store.useConcerns('my-concerns', {
|
|
1064
|
-
* 'productId': {
|
|
1065
|
-
* readonlyWhen: { boolLogic: { IS_EQUAL: ['order.status', 'completed'] } }
|
|
1066
|
-
* }
|
|
1067
|
-
* })
|
|
1068
|
-
* // Returns: true if order status is 'completed', false otherwise
|
|
1069
|
-
* ```
|
|
1070
|
-
*/
|
|
1071
|
-
|
|
1072
|
-
declare const readonlyWhen: ConcernType<'readonlyWhen', {
|
|
1073
|
-
boolLogic: BoolLogic<any>;
|
|
1074
|
-
}, boolean>;
|
|
1075
|
-
|
|
1076
|
-
/**
|
|
1077
|
-
* Visibility condition concern
|
|
1078
|
-
*
|
|
1079
|
-
* Evaluates a boolean logic expression to determine if a field should be visible.
|
|
1080
|
-
* Automatically tracks all state paths accessed in the condition.
|
|
1081
|
-
*
|
|
1082
|
-
* Returns true if visible, false if hidden.
|
|
1083
|
-
*
|
|
1084
|
-
* @example
|
|
1085
|
-
* ```typescript
|
|
1086
|
-
* store.useConcerns('my-concerns', {
|
|
1087
|
-
* 'advancedOptions': {
|
|
1088
|
-
* visibleWhen: { boolLogic: { IS_EQUAL: ['settings.mode', 'advanced'] } }
|
|
1089
|
-
* }
|
|
1090
|
-
* })
|
|
1091
|
-
* // Returns: true if settings.mode is 'advanced', false otherwise
|
|
90
|
+
* ])
|
|
91
|
+
* useSideEffects('my-aggs', { aggregations: aggs }) // no O(N²) re-check
|
|
1092
92
|
* ```
|
|
1093
93
|
*/
|
|
1094
|
-
|
|
1095
|
-
declare const visibleWhen: ConcernType<'visibleWhen', {
|
|
1096
|
-
boolLogic: BoolLogic<any>;
|
|
1097
|
-
}, boolean>;
|
|
1098
|
-
|
|
94
|
+
declare const aggregationPairs: <DATA extends object, Depth extends number = DefaultDepth>() => <T extends ([ResolvableDeepKey<DATA, Depth>, ResolvableDeepKey<DATA, Depth>] | [ResolvableDeepKey<DATA, Depth>, ResolvableDeepKey<DATA, Depth>, BoolLogic<DATA, Depth>])[]>(pairs: CheckAggregationPairs<DATA, T, Depth>) => ValidatedAggregationPairs<DATA>;
|
|
1099
95
|
/**
|
|
1100
|
-
*
|
|
96
|
+
* Lazy-validated computation pairs. Curried: `computationPairs<State>()(pairs)`.
|
|
1101
97
|
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1104
|
-
*
|
|
1105
|
-
* Returns the interpolated string.
|
|
98
|
+
* Each pair is [op, target, source] or [op, target, source, BoolLogic].
|
|
99
|
+
* Validates that both target and source are number paths.
|
|
100
|
+
* Returns branded `ValidatedComputationPairs` — accepted by `useSideEffects` without re-validation.
|
|
1106
101
|
*
|
|
1107
102
|
* @example
|
|
1108
103
|
* ```typescript
|
|
1109
|
-
*
|
|
1110
|
-
* '
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1113
|
-
* })
|
|
1114
|
-
* // Result: "Price: $99.99"
|
|
1115
|
-
* ```
|
|
1116
|
-
*/
|
|
1117
|
-
|
|
1118
|
-
declare const dynamicLabel: ConcernType<'dynamicLabel', {
|
|
1119
|
-
template: string;
|
|
1120
|
-
}, string>;
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Dynamic placeholder template concern
|
|
1124
|
-
*
|
|
1125
|
-
* Interpolates a template string with values from state.
|
|
1126
|
-
* Automatically tracks all state paths referenced in the template.
|
|
1127
|
-
*
|
|
1128
|
-
* Returns the interpolated string.
|
|
1129
|
-
*
|
|
1130
|
-
* @example
|
|
1131
|
-
* ```typescript
|
|
1132
|
-
* store.useConcerns('my-concerns', {
|
|
1133
|
-
* 'inputField': {
|
|
1134
|
-
* dynamicPlaceholder: { template: "Enter {{field.name}}" }
|
|
1135
|
-
* }
|
|
1136
|
-
* })
|
|
1137
|
-
* // Result: "Enter email address"
|
|
1138
|
-
* ```
|
|
1139
|
-
*/
|
|
1140
|
-
|
|
1141
|
-
declare const dynamicPlaceholder: ConcernType<'dynamicPlaceholder', {
|
|
1142
|
-
template: string;
|
|
1143
|
-
}, string>;
|
|
1144
|
-
|
|
1145
|
-
/**
|
|
1146
|
-
* Dynamic tooltip template concern
|
|
1147
|
-
*
|
|
1148
|
-
* Interpolates a template string with values from state.
|
|
1149
|
-
* Automatically tracks all state paths referenced in the template.
|
|
1150
|
-
*
|
|
1151
|
-
* Returns the interpolated string.
|
|
1152
|
-
*
|
|
1153
|
-
* @example
|
|
1154
|
-
* ```typescript
|
|
1155
|
-
* store.useConcerns('my-concerns', {
|
|
1156
|
-
* 'strikePrice': {
|
|
1157
|
-
* dynamicTooltip: { template: "Strike at {{market.spot}}" }
|
|
1158
|
-
* }
|
|
1159
|
-
* })
|
|
1160
|
-
* // Result: "Strike at 105"
|
|
104
|
+
* const comps = computationPairs<MyState>()([
|
|
105
|
+
* ['SUM', 'total', 'price1'],
|
|
106
|
+
* ['AVG', 'average', 'score1'],
|
|
107
|
+
* ])
|
|
108
|
+
* useSideEffects('my-comps', { computations: comps }) // no O(N²) re-check
|
|
1161
109
|
* ```
|
|
1162
110
|
*/
|
|
1163
|
-
|
|
1164
|
-
declare const dynamicTooltip: ConcernType<'dynamicTooltip', {
|
|
1165
|
-
template: string;
|
|
1166
|
-
}, string>;
|
|
1167
|
-
|
|
1168
|
-
/**
|
|
1169
|
-
* All pre-built concerns as a tuple (for use with findConcern)
|
|
1170
|
-
*/
|
|
1171
|
-
declare const prebuilts: readonly [ValidationStateConcern, ConcernType<"disabledWhen", {
|
|
1172
|
-
boolLogic: BoolLogic<any>;
|
|
1173
|
-
}, boolean>, ConcernType<"readonlyWhen", {
|
|
1174
|
-
boolLogic: BoolLogic<any>;
|
|
1175
|
-
}, boolean>, ConcernType<"visibleWhen", {
|
|
1176
|
-
boolLogic: BoolLogic<any>;
|
|
1177
|
-
}, boolean>, ConcernType<"dynamicTooltip", {
|
|
1178
|
-
template: string;
|
|
1179
|
-
}, string>, ConcernType<"dynamicLabel", {
|
|
1180
|
-
template: string;
|
|
1181
|
-
}, string>, ConcernType<"dynamicPlaceholder", {
|
|
1182
|
-
template: string;
|
|
1183
|
-
}, string>];
|
|
1184
|
-
/**
|
|
1185
|
-
* Namespace style access for pre-builts
|
|
1186
|
-
*/
|
|
1187
|
-
declare const prebuiltsNamespace: {
|
|
1188
|
-
validationState: ValidationStateConcern;
|
|
1189
|
-
disabledWhen: ConcernType<"disabledWhen", {
|
|
1190
|
-
boolLogic: BoolLogic<any>;
|
|
1191
|
-
}, boolean>;
|
|
1192
|
-
readonlyWhen: ConcernType<"readonlyWhen", {
|
|
1193
|
-
boolLogic: BoolLogic<any>;
|
|
1194
|
-
}, boolean>;
|
|
1195
|
-
visibleWhen: ConcernType<"visibleWhen", {
|
|
1196
|
-
boolLogic: BoolLogic<any>;
|
|
1197
|
-
}, boolean>;
|
|
1198
|
-
dynamicTooltip: ConcernType<"dynamicTooltip", {
|
|
1199
|
-
template: string;
|
|
1200
|
-
}, string>;
|
|
1201
|
-
dynamicLabel: ConcernType<"dynamicLabel", {
|
|
1202
|
-
template: string;
|
|
1203
|
-
}, string>;
|
|
1204
|
-
dynamicPlaceholder: ConcernType<"dynamicPlaceholder", {
|
|
1205
|
-
template: string;
|
|
1206
|
-
}, string>;
|
|
1207
|
-
};
|
|
1208
|
-
|
|
1209
|
-
type index_ValidationError = ValidationError;
|
|
1210
|
-
type index_ValidationStateConcern = ValidationStateConcern;
|
|
1211
|
-
type index_ValidationStateInput<DATA, PATH extends DeepKey<DATA, Depth>, Depth extends number = DefaultDepth> = ValidationStateInput<DATA, PATH, Depth>;
|
|
1212
|
-
type index_ValidationStateResult = ValidationStateResult;
|
|
1213
|
-
declare const index_disabledWhen: typeof disabledWhen;
|
|
1214
|
-
declare const index_dynamicLabel: typeof dynamicLabel;
|
|
1215
|
-
declare const index_dynamicPlaceholder: typeof dynamicPlaceholder;
|
|
1216
|
-
declare const index_dynamicTooltip: typeof dynamicTooltip;
|
|
1217
|
-
declare const index_prebuilts: typeof prebuilts;
|
|
1218
|
-
declare const index_prebuiltsNamespace: typeof prebuiltsNamespace;
|
|
1219
|
-
declare const index_readonlyWhen: typeof readonlyWhen;
|
|
1220
|
-
declare const index_validationState: typeof validationState;
|
|
1221
|
-
declare const index_visibleWhen: typeof visibleWhen;
|
|
1222
|
-
declare namespace index {
|
|
1223
|
-
export { type index_ValidationError as ValidationError, type index_ValidationStateConcern as ValidationStateConcern, type index_ValidationStateInput as ValidationStateInput, type index_ValidationStateResult as ValidationStateResult, index_disabledWhen as disabledWhen, index_dynamicLabel as dynamicLabel, index_dynamicPlaceholder as dynamicPlaceholder, index_dynamicTooltip as dynamicTooltip, index_prebuilts as prebuilts, index_prebuiltsNamespace as prebuiltsNamespace, index_readonlyWhen as readonlyWhen, index_validationState as validationState, index_visibleWhen as visibleWhen };
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
/**
|
|
1227
|
-
* Concern lookup by name
|
|
1228
|
-
*
|
|
1229
|
-
* @param name The concern name to look up
|
|
1230
|
-
* @param concerns Optional array of concerns to search (defaults to prebuilts)
|
|
1231
|
-
* @returns The concern definition, or undefined if not found
|
|
1232
|
-
*/
|
|
1233
|
-
declare const findConcern: (name: string, concerns?: readonly any[]) => ConcernType | undefined;
|
|
1234
|
-
/**
|
|
1235
|
-
* Default concerns provided by apex-state
|
|
1236
|
-
*/
|
|
1237
|
-
declare const defaultConcerns: readonly [ValidationStateConcern, ConcernType<"disabledWhen", {
|
|
1238
|
-
boolLogic: BoolLogic<any>;
|
|
1239
|
-
}, boolean>, ConcernType<"readonlyWhen", {
|
|
1240
|
-
boolLogic: BoolLogic<any>;
|
|
1241
|
-
}, boolean>, ConcernType<"visibleWhen", {
|
|
1242
|
-
boolLogic: BoolLogic<any>;
|
|
1243
|
-
}, boolean>, ConcernType<"dynamicTooltip", {
|
|
1244
|
-
template: string;
|
|
1245
|
-
}, string>, ConcernType<"dynamicLabel", {
|
|
1246
|
-
template: string;
|
|
1247
|
-
}, string>, ConcernType<"dynamicPlaceholder", {
|
|
1248
|
-
template: string;
|
|
1249
|
-
}, string>];
|
|
1250
|
-
|
|
111
|
+
declare const computationPairs: <DATA extends object, Depth extends number = DefaultDepth>() => <T extends ([ComputationOp, DeepKeyFiltered<DATA, number, Depth>, DeepKeyFiltered<DATA, number, Depth>] | [ComputationOp, DeepKeyFiltered<DATA, number, Depth>, DeepKeyFiltered<DATA, number, Depth>, BoolLogic<DATA, Depth>])[]>(pairs: CheckComputationPairs<DATA, T, Depth>) => ValidatedComputationPairs<DATA>;
|
|
1251
112
|
/**
|
|
1252
|
-
*
|
|
113
|
+
* Lazy-validated listeners. Curried: `listeners<State>()(items)`.
|
|
1253
114
|
*
|
|
1254
|
-
*
|
|
1255
|
-
*
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
/**
|
|
1259
|
-
* Clear path rule - "when trigger paths change, set target paths to null"
|
|
115
|
+
* Provides autocomplete for valid paths (O(N)) and validates each listener:
|
|
116
|
+
* - path and scope are valid `ResolvableDeepKey<DATA>` or null
|
|
117
|
+
* - When both non-null: scope must be a dot-separated prefix of path
|
|
118
|
+
* - fn receives correctly-typed scoped state based on scope
|
|
1260
119
|
*
|
|
1261
|
-
*
|
|
1262
|
-
* - triggers: paths that activate the rule
|
|
1263
|
-
* - targets: paths to set to null
|
|
1264
|
-
* - expandMatch: if true, [*] in targets expands to ALL keys (not just matched key)
|
|
1265
|
-
*/
|
|
1266
|
-
type ClearPathRule<DATA extends object, Depth extends number = DefaultDepth> = [DeepKey<DATA, Depth>[], DeepKey<DATA, Depth>[], {
|
|
1267
|
-
expandMatch?: boolean;
|
|
1268
|
-
}?];
|
|
1269
|
-
/**
|
|
1270
|
-
* Side effects configuration for useSideEffects hook
|
|
120
|
+
* Returns branded `ValidatedListeners` — accepted by `useSideEffects` without re-validation.
|
|
1271
121
|
*
|
|
1272
122
|
* @example
|
|
1273
123
|
* ```typescript
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1281
|
-
*
|
|
1282
|
-
*
|
|
1283
|
-
* ['total', 'price2'],
|
|
1284
|
-
* ],
|
|
1285
|
-
* listeners: [
|
|
1286
|
-
* {
|
|
1287
|
-
* path: 'user.profile', // Watch user.profile.* changes
|
|
1288
|
-
* scope: 'user.profile', // Receive scoped state
|
|
1289
|
-
* fn: (changes, state) => {
|
|
1290
|
-
* // changes: [['name', 'Alice', {}]] // RELATIVE to scope
|
|
1291
|
-
* // state: user.profile sub-object
|
|
1292
|
-
* return [['status', 'updated', {}]] // Return FULL paths
|
|
1293
|
-
* }
|
|
1294
|
-
* },
|
|
1295
|
-
* {
|
|
1296
|
-
* path: 'user.profile.name', // Watch specific path
|
|
1297
|
-
* scope: null, // Get full state
|
|
1298
|
-
* fn: (changes, state) => {
|
|
1299
|
-
* // changes: [['user.profile.name', 'Alice', {}]] // FULL path
|
|
1300
|
-
* // state: full DATA object
|
|
1301
|
-
* return undefined
|
|
1302
|
-
* }
|
|
1303
|
-
* },
|
|
1304
|
-
* {
|
|
1305
|
-
* path: 'p.123.g.abc.data.strike', // Watch deep path
|
|
1306
|
-
* scope: 'p.123.g.abc', // Get parent scope
|
|
1307
|
-
* fn: (changes, state) => {
|
|
1308
|
-
* // changes: [['data.strike', value, {}]] // RELATIVE to scope
|
|
1309
|
-
* // state: p.123.g.abc object
|
|
1310
|
-
* return [['some.value.elsewhere', computed, {}]] // FULL path
|
|
1311
|
-
* }
|
|
124
|
+
* const myListeners = listeners<MyState>()([
|
|
125
|
+
* {
|
|
126
|
+
* path: 'user.profile.name',
|
|
127
|
+
* scope: 'user.profile',
|
|
128
|
+
* fn: (changes, state) => {
|
|
129
|
+
* // changes: ArrayOfChanges relative to user.profile scope
|
|
130
|
+
* // state: typed as MyState['user']['profile']
|
|
131
|
+
* // return: ArrayOfChanges relative to scope, or undefined
|
|
132
|
+
* return [['name', `updated-${state.name}`, {}]]
|
|
1312
133
|
* }
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1315
|
-
*
|
|
1316
|
-
*/
|
|
1317
|
-
interface SideEffects<DATA extends object, META extends GenericMeta = GenericMeta, Depth extends number = DefaultDepth> {
|
|
1318
|
-
/**
|
|
1319
|
-
* Sync paths - keeps specified paths synchronized
|
|
1320
|
-
* Format: [path1, path2] - both paths stay in sync
|
|
1321
|
-
*/
|
|
1322
|
-
syncPaths?: SyncPair<DATA, Depth>[];
|
|
1323
|
-
/**
|
|
1324
|
-
* Flip paths - keeps specified paths with opposite values
|
|
1325
|
-
* Format: [path1, path2] - paths have inverse boolean values
|
|
1326
|
-
*/
|
|
1327
|
-
flipPaths?: FlipPair<DATA, Depth>[];
|
|
1328
|
-
/**
|
|
1329
|
-
* Aggregations - aggregates sources into target
|
|
1330
|
-
* Format: [target, source] - target is ALWAYS first (left)
|
|
1331
|
-
* Multiple pairs can point to same target for multi-source aggregation
|
|
1332
|
-
*/
|
|
1333
|
-
aggregations?: AggregationPair<DATA, Depth>[];
|
|
1334
|
-
/**
|
|
1335
|
-
* Clear paths - "when X changes, set Y to null"
|
|
1336
|
-
* Format: [triggers[], targets[], { expandMatch?: boolean }?]
|
|
1337
|
-
* - Default: [*] in target correlates with trigger's [*] (same key)
|
|
1338
|
-
* - expandMatch: true → [*] in target expands to ALL keys
|
|
1339
|
-
*/
|
|
1340
|
-
clearPaths?: ClearPathRule<DATA, Depth>[];
|
|
1341
|
-
/**
|
|
1342
|
-
* Computations - numeric reduction operations (SUM, AVG)
|
|
1343
|
-
* Format: [operation, target, source] - target is computed from sources
|
|
1344
|
-
* Multiple pairs can point to same target for multi-source computation
|
|
1345
|
-
* Unidirectional: source → target only (writes to target are no-op)
|
|
1346
|
-
*/
|
|
1347
|
-
computations?: ComputationPair<DATA, Depth>[];
|
|
1348
|
-
/**
|
|
1349
|
-
* Listeners - react to state changes with scoped state
|
|
1350
|
-
*/
|
|
1351
|
-
listeners?: ListenerRegistration<DATA, META>[];
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
declare const createGenericStore: <DATA extends object, META extends GenericMeta = GenericMeta, CONCERNS extends readonly ConcernType<string, any, any>[] = typeof defaultConcerns>(config?: StoreConfig) => {
|
|
1355
|
-
Provider: {
|
|
1356
|
-
(props: ProviderProps<DATA>): react_jsx_runtime.JSX.Element;
|
|
1357
|
-
displayName: string;
|
|
1358
|
-
};
|
|
1359
|
-
useFieldStore: <P extends DeepKey<DATA>>(path: P) => {
|
|
1360
|
-
value: DeepValue<DATA, P>;
|
|
1361
|
-
setValue: (newValue: DeepValue<DATA, P>, meta?: META) => void;
|
|
1362
|
-
} & Record<string, unknown>;
|
|
1363
|
-
useStore: <P extends DeepKey<DATA>>(path: P) => [DeepValue<DATA, P>, (value: DeepValue<DATA, P>, meta?: META) => void];
|
|
1364
|
-
useJitStore: () => {
|
|
1365
|
-
proxyValue: DATA;
|
|
1366
|
-
setChanges: (changes: ArrayOfChanges<DATA, META>) => void;
|
|
1367
|
-
getState: () => DATA;
|
|
1368
|
-
};
|
|
1369
|
-
useSideEffects: (id: string, effects: SideEffects<DATA, META>) => void;
|
|
1370
|
-
useConcerns: <CUSTOM extends readonly ConcernType<string, any, any>[] = readonly []>(id: string, registration: ConcernRegistrationMap<DATA, readonly [...CONCERNS, ...CUSTOM]>, customConcerns?: CUSTOM) => void;
|
|
1371
|
-
withConcerns: <SELECTION extends Partial<Record<Extract<CONCERNS[number], {
|
|
1372
|
-
name: string;
|
|
1373
|
-
}>["name"], boolean>>>(selection: SELECTION) => {
|
|
1374
|
-
useFieldStore: <P extends DeepKey<DATA>>(path: P) => {
|
|
1375
|
-
value: DATA extends readonly any[] ? DATA[number] : P extends `${infer First}.${infer Rest}` ? First extends keyof DATA ? DeepValue<NonNullable<DATA[First]>, Rest> : string extends keyof DATA ? First extends "[*]" ? DeepValue<DATA[keyof DATA & string], Rest> : unknown : unknown : P extends "[*]" ? string extends keyof DATA ? DATA[keyof DATA & string] : unknown : P extends keyof DATA ? DATA[P] : unknown;
|
|
1376
|
-
setValue: (newValue: DATA extends readonly any[] ? DATA[number] : P extends `${infer First}.${infer Rest}` ? First extends keyof DATA ? DeepValue<NonNullable<DATA[First]>, Rest> : string extends keyof DATA ? First extends "[*]" ? DeepValue<DATA[keyof DATA & string], Rest> : unknown : unknown : P extends "[*]" ? string extends keyof DATA ? DATA[keyof DATA & string] : unknown : P extends keyof DATA ? DATA[P] : unknown, meta?: META) => void;
|
|
1377
|
-
} & { [K in keyof SELECTION as SELECTION[K] extends true ? K : never]?: K extends keyof EvaluatedConcerns<CONCERNS> ? EvaluatedConcerns<CONCERNS>[K] : never; };
|
|
1378
|
-
};
|
|
1379
|
-
withMeta: (presetMeta: Partial<META>) => {
|
|
1380
|
-
useFieldStore: <P extends DeepKey<DATA>>(path: P) => {
|
|
1381
|
-
value: DeepValue<DATA, P>;
|
|
1382
|
-
setValue: (newValue: DeepValue<DATA, P>, meta?: META) => void;
|
|
1383
|
-
};
|
|
1384
|
-
};
|
|
1385
|
-
};
|
|
1386
|
-
/** Return type of createGenericStore — used by testing mock for 1:1 type safety */
|
|
1387
|
-
type GenericStoreApi<DATA extends object, META extends GenericMeta = GenericMeta, CONCERNS extends readonly ConcernType<string, any, any>[] = typeof defaultConcerns> = ReturnType<typeof createGenericStore<DATA, META, CONCERNS>>;
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* Minimal field interface that useBufferedField accepts
|
|
1391
|
-
*/
|
|
1392
|
-
interface FieldInput$2<T> {
|
|
1393
|
-
value: T;
|
|
1394
|
-
setValue: (v: T) => void;
|
|
1395
|
-
}
|
|
1396
|
-
/**
|
|
1397
|
-
* Extended interface with buffering capabilities
|
|
1398
|
-
*/
|
|
1399
|
-
interface BufferedField<T> extends FieldInput$2<T> {
|
|
1400
|
-
commit: () => void;
|
|
1401
|
-
cancel: () => void;
|
|
1402
|
-
isDirty: boolean;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Adds buffered editing to any field hook.
|
|
1406
|
-
* Local changes are held until explicitly committed or cancelled.
|
|
1407
|
-
*
|
|
1408
|
-
* @param field - Field hook with { value, setValue }
|
|
1409
|
-
* @returns Buffered field with commit/cancel/isDirty
|
|
1410
|
-
*
|
|
1411
|
-
* @example
|
|
1412
|
-
* ```typescript
|
|
1413
|
-
* const field = useFieldStore('user.name')
|
|
1414
|
-
* const buffered = useBufferedField(field)
|
|
1415
|
-
*
|
|
1416
|
-
* // User types - updates local only
|
|
1417
|
-
* buffered.setValue('new value')
|
|
1418
|
-
*
|
|
1419
|
-
* // Enter/Tab - commit to store
|
|
1420
|
-
* buffered.commit()
|
|
1421
|
-
*
|
|
1422
|
-
* // Esc - revert to stored value
|
|
1423
|
-
* buffered.cancel()
|
|
1424
|
-
*
|
|
1425
|
-
* // Check if user has unsaved changes
|
|
1426
|
-
* if (buffered.isDirty) { ... }
|
|
1427
|
-
* ```
|
|
1428
|
-
*/
|
|
1429
|
-
declare const useBufferedField: <T>(field: FieldInput$2<T>) => BufferedField<T>;
|
|
1430
|
-
|
|
1431
|
-
/**
|
|
1432
|
-
* Option for keyboard selection
|
|
1433
|
-
*/
|
|
1434
|
-
interface SelectOption<T> {
|
|
1435
|
-
value: T;
|
|
1436
|
-
label: string;
|
|
1437
|
-
}
|
|
1438
|
-
/**
|
|
1439
|
-
* Configuration for keyboard select behavior
|
|
1440
|
-
*/
|
|
1441
|
-
interface KeyboardSelectConfig<T> {
|
|
1442
|
-
/** Available options to select from */
|
|
1443
|
-
options: SelectOption<T>[];
|
|
1444
|
-
/** Time window to accumulate keystrokes (ms). Default: 500 */
|
|
1445
|
-
debounceMs?: number;
|
|
1446
|
-
/** Match from start of label only. Default: true */
|
|
1447
|
-
matchFromStart?: boolean;
|
|
1448
|
-
}
|
|
1449
|
-
/**
|
|
1450
|
-
* Minimal field interface that useKeyboardSelect accepts
|
|
1451
|
-
*/
|
|
1452
|
-
interface FieldInput$1<T> {
|
|
1453
|
-
value: T;
|
|
1454
|
-
setValue: (v: T) => void;
|
|
1455
|
-
}
|
|
1456
|
-
/**
|
|
1457
|
-
* Adds keyboard-driven selection to any field hook.
|
|
1458
|
-
* Typing letters auto-selects matching options.
|
|
1459
|
-
*
|
|
1460
|
-
* @param field - Field hook with { value, setValue, ...rest }
|
|
1461
|
-
* @param config - Options and behavior configuration
|
|
1462
|
-
* @returns Field with onKeyDown handler added
|
|
1463
|
-
*
|
|
1464
|
-
* @example
|
|
1465
|
-
* ```typescript
|
|
1466
|
-
* const field = useFieldStore('user.country')
|
|
1467
|
-
* const { onKeyDown, ...rest } = useKeyboardSelect(field, {
|
|
1468
|
-
* options: [
|
|
1469
|
-
* { value: 'us', label: 'United States' },
|
|
1470
|
-
* { value: 'uk', label: 'United Kingdom' },
|
|
1471
|
-
* { value: 'ca', label: 'Canada' },
|
|
1472
|
-
* ]
|
|
1473
|
-
* })
|
|
1474
|
-
*
|
|
1475
|
-
* // User types "u" -> selects "United States"
|
|
1476
|
-
* // User types "un" quickly -> still "United States"
|
|
1477
|
-
* // User types "c" -> selects "Canada"
|
|
1478
|
-
*
|
|
1479
|
-
* <input onKeyDown={onKeyDown} {...rest} />
|
|
1480
|
-
* ```
|
|
1481
|
-
*/
|
|
1482
|
-
declare const useKeyboardSelect: <T, TField extends FieldInput$1<T>>(field: TField, config: KeyboardSelectConfig<T>) => TField & {
|
|
1483
|
-
onKeyDown: (e: React.KeyboardEvent) => void;
|
|
1484
|
-
};
|
|
1485
|
-
|
|
1486
|
-
/**
|
|
1487
|
-
* Minimal field interface for throttling
|
|
1488
|
-
* Supports setValue with optional additional arguments (e.g., meta)
|
|
1489
|
-
*/
|
|
1490
|
-
interface ThrottleFieldInput<T, Args extends unknown[] = unknown[]> {
|
|
1491
|
-
value: T;
|
|
1492
|
-
setValue: (v: T, ...args: Args) => void;
|
|
1493
|
-
}
|
|
1494
|
-
/**
|
|
1495
|
-
* Throttle configuration
|
|
1496
|
-
*/
|
|
1497
|
-
interface ThrottleConfig {
|
|
1498
|
-
/** Minimum milliseconds between setValue calls to the underlying field */
|
|
1499
|
-
ms: number;
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Adds throttling to any field hook.
|
|
1503
|
-
* First setValue executes immediately, subsequent calls are rate-limited.
|
|
1504
|
-
* Last value wins when multiple calls occur within the throttle window.
|
|
1505
|
-
* Preserves the full setValue signature including additional arguments like meta.
|
|
1506
|
-
*
|
|
1507
|
-
* @param field - Field hook with { value, setValue, ...rest }
|
|
1508
|
-
* @param config - Throttle configuration { ms }
|
|
1509
|
-
* @returns Field with throttled setValue, plus any passthrough props
|
|
1510
|
-
*
|
|
1511
|
-
* @example
|
|
1512
|
-
* ```typescript
|
|
1513
|
-
* const field = useFieldStore('spotPrice')
|
|
1514
|
-
* const throttled = useThrottledField(field, { ms: 100 })
|
|
1515
|
-
*
|
|
1516
|
-
* // Rapid updates from WebSocket
|
|
1517
|
-
* throttled.setValue(1.234) // Immediate
|
|
1518
|
-
* throttled.setValue(1.235) // Buffered
|
|
1519
|
-
* throttled.setValue(1.236) // Buffered (replaces 1.235)
|
|
1520
|
-
* // After 100ms: 1.236 is applied
|
|
1521
|
-
* ```
|
|
1522
|
-
*
|
|
1523
|
-
* @example
|
|
1524
|
-
* ```typescript
|
|
1525
|
-
* // With meta argument
|
|
1526
|
-
* const field = useFieldStore('price')
|
|
1527
|
-
* const throttled = useThrottledField(field, { ms: 100 })
|
|
1528
|
-
* throttled.setValue(42, { source: 'websocket' })
|
|
134
|
+
* },
|
|
135
|
+
* ])
|
|
136
|
+
* useSideEffects('my-listeners', { listeners: myListeners })
|
|
1529
137
|
* ```
|
|
1530
|
-
*
|
|
1531
|
-
* @example
|
|
1532
|
-
* ```typescript
|
|
1533
|
-
* // Composable with other wrappers
|
|
1534
|
-
* const field = useFieldStore('price')
|
|
1535
|
-
* const transformed = useTransformedField(field, {
|
|
1536
|
-
* to: (cents) => cents / 100,
|
|
1537
|
-
* from: (dollars) => Math.round(dollars * 100)
|
|
1538
|
-
* })
|
|
1539
|
-
* const throttled = useThrottledField(transformed, { ms: 50 })
|
|
1540
|
-
* ```
|
|
1541
|
-
*/
|
|
1542
|
-
declare const useThrottledField: <T, Args extends unknown[] = unknown[], TField extends ThrottleFieldInput<T, Args> = ThrottleFieldInput<T, Args>>(field: TField, config: ThrottleConfig) => TField;
|
|
1543
|
-
|
|
1544
|
-
/**
|
|
1545
|
-
* Transform configuration for field values
|
|
1546
|
-
*/
|
|
1547
|
-
interface TransformConfig<TStored, TDisplay> {
|
|
1548
|
-
/** Transform from stored value to display value */
|
|
1549
|
-
to: (stored: TStored) => TDisplay;
|
|
1550
|
-
/** Transform from display value to stored value */
|
|
1551
|
-
from: (display: TDisplay) => TStored;
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Minimal field interface that useTransformedField accepts
|
|
1555
|
-
*/
|
|
1556
|
-
interface FieldInput<T> {
|
|
1557
|
-
value: T;
|
|
1558
|
-
setValue: (v: T) => void;
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Adds value transformation to any field hook.
|
|
1562
|
-
* Converts between storage format and display format.
|
|
1563
|
-
* Passes through any additional properties from the input field.
|
|
1564
|
-
*
|
|
1565
|
-
* @param field - Field hook with { value, setValue, ...rest }
|
|
1566
|
-
* @param config - Transform functions { to, from }
|
|
1567
|
-
* @returns Field with transformed types, plus any passthrough props
|
|
1568
|
-
*
|
|
1569
|
-
* @example
|
|
1570
|
-
* ```typescript
|
|
1571
|
-
* const field = useFieldStore('user.birthdate')
|
|
1572
|
-
* const formatted = useTransformedField(field, {
|
|
1573
|
-
* to: (iso) => format(new Date(iso), 'MM/dd/yyyy'),
|
|
1574
|
-
* from: (display) => parse(display, 'MM/dd/yyyy').toISOString()
|
|
1575
|
-
* })
|
|
1576
|
-
*
|
|
1577
|
-
* // formatted.value is "01/15/2024"
|
|
1578
|
-
* // formatted.setValue("01/20/2024") stores ISO string
|
|
1579
|
-
* ```
|
|
1580
|
-
*
|
|
1581
|
-
* @example
|
|
1582
|
-
* ```typescript
|
|
1583
|
-
* // Works with buffered fields - passes through commit/cancel/isDirty
|
|
1584
|
-
* const field = useFieldStore('price')
|
|
1585
|
-
* const buffered = useBufferedField(field)
|
|
1586
|
-
* const formatted = useTransformedField(buffered, {
|
|
1587
|
-
* to: (cents) => (cents / 100).toFixed(2),
|
|
1588
|
-
* from: (dollars) => Math.round(parseFloat(dollars) * 100)
|
|
1589
|
-
* })
|
|
1590
|
-
*
|
|
1591
|
-
* formatted.setValue("15.99") // local only
|
|
1592
|
-
* formatted.commit() // stores 1599
|
|
1593
|
-
* ```
|
|
1594
|
-
*/
|
|
1595
|
-
declare const useTransformedField: <TStored, TDisplay, TField extends FieldInput<TStored>>(field: TField, config: TransformConfig<TStored, TDisplay>) => Omit<TField, "value" | "setValue"> & FieldInput<TDisplay>;
|
|
1596
|
-
|
|
1597
|
-
/** Legacy JS implementation - uses JS graph structure */
|
|
1598
|
-
declare const registerFlipPair: <DATA extends object, META extends GenericMeta = GenericMeta>(store: StoreInstance<DATA, META>, path1: string & {}, path2: string & {}) => (() => void);
|
|
1599
|
-
|
|
1600
|
-
/** Legacy JS implementation - uses JS listener maps and sorted paths */
|
|
1601
|
-
declare const registerListenerLegacy: <DATA extends object, META extends GenericMeta = GenericMeta>(store: StoreInstance<DATA, META>, registration: ListenerRegistration<DATA, META>) => (() => void);
|
|
1602
|
-
|
|
1603
|
-
/**
|
|
1604
|
-
* Legacy batch version of registerSyncPair. Adds all edges first, then computes
|
|
1605
|
-
* initial sync changes across all final groups and calls processChanges once.
|
|
1606
|
-
* This avoids cascading effect re-evaluations when registering many pairs.
|
|
1607
|
-
*/
|
|
1608
|
-
declare const registerSyncPairsBatch: <DATA extends object, META extends GenericMeta = GenericMeta>(store: StoreInstance<DATA, META>, pairs: [string & {}, string & {}][]) => (() => void);
|
|
1609
|
-
|
|
1610
|
-
declare const registerSideEffects: <DATA extends object, META extends GenericMeta = GenericMeta>(store: StoreInstance<DATA, META>, id: string, effects: SideEffects<DATA, META>) => (() => void);
|
|
1611
|
-
|
|
1612
|
-
declare const evaluateBoolLogic: <STATE extends object>(logic: BoolLogic<STATE>, state: STATE) => boolean;
|
|
1613
|
-
|
|
1614
|
-
/**
|
|
1615
|
-
* Template string interpolation utilities
|
|
1616
|
-
*
|
|
1617
|
-
* Core utility for interpolating state values into template strings.
|
|
1618
|
-
* Used by concerns and side effects for dynamic text generation.
|
|
1619
|
-
*/
|
|
1620
|
-
/**
|
|
1621
|
-
* Extract all {{path}} placeholders from a template string
|
|
1622
|
-
*
|
|
1623
|
-
* @param template The template string with {{path}} placeholders
|
|
1624
|
-
* @returns Array of path strings found in the template
|
|
1625
|
-
*
|
|
1626
|
-
* @example
|
|
1627
|
-
* extractPlaceholders("Hello {{user.name}}, you have {{count}} messages")
|
|
1628
|
-
* // ["user.name", "count"]
|
|
1629
|
-
*/
|
|
1630
|
-
declare const extractPlaceholders: (template: string) => string[];
|
|
1631
|
-
/**
|
|
1632
|
-
* Interpolate {{path}} placeholders with values from state
|
|
1633
|
-
*
|
|
1634
|
-
* Replaces {{path.to.value}} with actual values from state.
|
|
1635
|
-
* Only replaces if value is a string, number, or boolean.
|
|
1636
|
-
* Missing/null/undefined/object values leave the original {{path}} for debugging.
|
|
1637
|
-
*
|
|
1638
|
-
* @param template The template string with {{path}} placeholders
|
|
1639
|
-
* @param state The state object to read values from
|
|
1640
|
-
* @returns The interpolated string
|
|
1641
|
-
*
|
|
1642
|
-
* @example
|
|
1643
|
-
* interpolateTemplate("Value is {{market.spot}}", state)
|
|
1644
|
-
* // "Value is 105"
|
|
1645
|
-
*
|
|
1646
|
-
* @example
|
|
1647
|
-
* interpolateTemplate("Hello {{user.name}}, missing: {{invalid.path}}", state)
|
|
1648
|
-
* // "Hello Alice, missing: {{invalid.path}}"
|
|
1649
|
-
*/
|
|
1650
|
-
declare const interpolateTemplate: <STATE extends object>(template: string, state: STATE) => string;
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Deep access utilities for safe nested object access
|
|
1654
|
-
*
|
|
1655
|
-
* Uses native Reflect for reads (~2x faster) and writes (~3x faster) vs lodash.
|
|
1656
|
-
* Maintains valtio reactivity when setting values.
|
|
1657
|
-
*/
|
|
1658
|
-
|
|
1659
|
-
/**
|
|
1660
|
-
* Unified namespace for dot notation path operations
|
|
1661
|
-
*
|
|
1662
|
-
* Provides type-safe and unsafe variants for working with nested objects
|
|
1663
|
-
* using dot notation paths (e.g., 'user.address.city')
|
|
1664
|
-
*/
|
|
1665
|
-
declare const dot: {
|
|
1666
|
-
get: <T extends object, P extends DeepKey<T>>(obj: T, path: P) => DeepValue<T, P>;
|
|
1667
|
-
get__unsafe: <P extends string>(obj: unknown, path: P) => unknown;
|
|
1668
|
-
set: <T extends object, P extends DeepKey<T>>(obj: T, path: P, value: DeepValue<T, P>) => void;
|
|
1669
|
-
set__unsafe: <T extends object, P extends string>(obj: T, path: P, value: unknown) => void;
|
|
1670
|
-
has: <T extends object, P extends DeepKey<T>>(obj: T, path: P) => boolean;
|
|
1671
|
-
same: <T extends object, P extends string>(obj: T, ...paths: P[]) => boolean;
|
|
1672
|
-
};
|
|
1673
|
-
|
|
1674
|
-
/**
|
|
1675
|
-
* Hash key utilities
|
|
1676
|
-
*
|
|
1677
|
-
* Utilities for working with hash key notation in Record-based paths.
|
|
1678
|
-
* Hash keys ([*]) are type-level markers for indexing into Records/HashMaps.
|
|
1679
|
-
*
|
|
1680
|
-
* @example
|
|
1681
|
-
* ```typescript
|
|
1682
|
-
* import { _, hashKey } from '@sladg/apex-state'
|
|
1683
|
-
*
|
|
1684
|
-
* // Use _ in template strings
|
|
1685
|
-
* const path = `users.${_('u1')}.posts.${_('p1')}.name`
|
|
1686
|
-
*
|
|
1687
|
-
* // Use hashKey namespace for validation
|
|
1688
|
-
* hashKey.rejectDynamic(path)
|
|
1689
|
-
* ```
|
|
1690
|
-
*/
|
|
1691
|
-
|
|
1692
|
-
/**
|
|
1693
|
-
* Converts a concrete ID to hash key notation for inline template string usage
|
|
1694
|
-
* Returns the concrete ID typed as HASH_KEY for Record/HashMap indexing
|
|
1695
|
-
*
|
|
1696
|
-
* @param id - The concrete ID (e.g., "u1", "p1", "c1")
|
|
1697
|
-
* @returns The concrete ID typed as HASH_KEY
|
|
1698
|
-
*
|
|
1699
|
-
* @example
|
|
1700
|
-
* ```typescript
|
|
1701
|
-
* const path = `users.${_('u1')}.posts.${_('p1')}.name`
|
|
1702
|
-
* // → "users.u1.posts.p1.name" (typed as containing HASH_KEY)
|
|
1703
|
-
* ```
|
|
1704
|
-
*/
|
|
1705
|
-
declare const _: (id: string) => HASH_KEY;
|
|
1706
|
-
/**
|
|
1707
|
-
* Hash key utilities namespace
|
|
1708
|
-
*
|
|
1709
|
-
* Provides utilities for working with hash keys in Record-based paths
|
|
1710
|
-
*/
|
|
1711
|
-
declare const hashKey: {
|
|
1712
|
-
/** Reject paths with dynamic hash keys */
|
|
1713
|
-
readonly rejectDynamic: <P extends string>(path: P) => void;
|
|
1714
|
-
/** Alias for _ function */
|
|
1715
|
-
readonly _: (id: string) => HASH_KEY;
|
|
1716
|
-
};
|
|
1717
|
-
|
|
1718
|
-
/**
|
|
1719
|
-
* Type checking utilities — similar to lodash type guards
|
|
1720
|
-
*
|
|
1721
|
-
* Provides type-safe predicates for common type checks with TypeScript support
|
|
1722
|
-
*/
|
|
1723
|
-
|
|
1724
|
-
/**
|
|
1725
|
-
* Unified namespace for type checking
|
|
1726
|
-
*
|
|
1727
|
-
* @example
|
|
1728
|
-
* ```typescript
|
|
1729
|
-
* import { is } from './utils/is'
|
|
1730
|
-
*
|
|
1731
|
-
* if (is.object(value)) { ... }
|
|
1732
|
-
* if (is.array(value)) { ... }
|
|
1733
|
-
* if (is.nil(value)) { ... }
|
|
1734
|
-
*
|
|
1735
|
-
* // Negated versions
|
|
1736
|
-
* if (is.not.object(value)) { ... }
|
|
1737
|
-
* if (is.not.array(value)) { ... }
|
|
1738
|
-
* ```
|
|
1739
|
-
*/
|
|
1740
|
-
declare const is: {
|
|
1741
|
-
nil: (value: unknown) => value is null | undefined;
|
|
1742
|
-
undefined: (value: unknown) => value is undefined;
|
|
1743
|
-
null: (value: unknown) => value is null;
|
|
1744
|
-
object: (value: unknown) => value is Record<string, unknown>;
|
|
1745
|
-
objectOrArray: (value: unknown) => value is Record<string, unknown> | unknown[];
|
|
1746
|
-
array: (value: unknown) => value is unknown[];
|
|
1747
|
-
string: (value: unknown) => value is string;
|
|
1748
|
-
number: (value: unknown) => value is number;
|
|
1749
|
-
boolean: (value: unknown) => value is boolean;
|
|
1750
|
-
function: (value: unknown) => value is (...args: unknown[]) => unknown;
|
|
1751
|
-
symbol: (value: unknown) => value is symbol;
|
|
1752
|
-
date: (value: unknown) => value is Date;
|
|
1753
|
-
regexp: (value: unknown) => value is RegExp;
|
|
1754
|
-
numericKey: (value: string) => boolean;
|
|
1755
|
-
primitive: (value: unknown) => value is Primitive;
|
|
1756
|
-
empty: (value: unknown) => boolean;
|
|
1757
|
-
equal: (a: unknown, b: unknown) => boolean;
|
|
1758
|
-
not: {
|
|
1759
|
-
nil: <T>(value: T | null | undefined) => value is T;
|
|
1760
|
-
undefined: <T>(value: T | undefined) => value is T;
|
|
1761
|
-
null: <T>(value: T | null) => value is T;
|
|
1762
|
-
object: <T>(value: T) => value is Exclude<T, Record<string, unknown>>;
|
|
1763
|
-
objectOrArray: <T>(value: T) => value is Exclude<T, Record<string, unknown> | unknown[]>;
|
|
1764
|
-
array: <T>(value: T) => value is Exclude<T, unknown[]>;
|
|
1765
|
-
string: <T>(value: T) => value is Exclude<T, string>;
|
|
1766
|
-
number: <T>(value: T) => value is Exclude<T, number>;
|
|
1767
|
-
boolean: <T>(value: T) => value is Exclude<T, boolean>;
|
|
1768
|
-
function: <T>(value: T) => value is Exclude<T, (...args: unknown[]) => unknown>;
|
|
1769
|
-
symbol: <T>(value: T) => value is Exclude<T, symbol>;
|
|
1770
|
-
date: <T>(value: T) => value is Exclude<T, Date>;
|
|
1771
|
-
regexp: <T>(value: T) => value is Exclude<T, RegExp>;
|
|
1772
|
-
primitive: <T>(value: T) => value is Exclude<T, Primitive>;
|
|
1773
|
-
empty: (value: unknown) => boolean;
|
|
1774
|
-
equal: (a: unknown, b: unknown) => boolean;
|
|
1775
|
-
};
|
|
1776
|
-
};
|
|
1777
|
-
|
|
1778
|
-
/**
|
|
1779
|
-
* Apply Changes Utility
|
|
1780
|
-
*
|
|
1781
|
-
* Applies an array of changes to an object, returning a new object.
|
|
1782
|
-
*/
|
|
1783
|
-
|
|
1784
|
-
/**
|
|
1785
|
-
* Applies changes to an object, returning a new object with changes applied.
|
|
1786
|
-
* Does not mutate the original object.
|
|
1787
|
-
*
|
|
1788
|
-
* @param obj - Source object
|
|
1789
|
-
* @param changes - Array of [path, value, meta] tuples
|
|
1790
|
-
* @returns New object with changes applied
|
|
1791
|
-
*
|
|
1792
|
-
* @example
|
|
1793
|
-
* ```typescript
|
|
1794
|
-
* const state = { user: { name: 'Alice', age: 30 } }
|
|
1795
|
-
* const changes: ArrayOfChanges<typeof state> = [
|
|
1796
|
-
* ['user.name', 'Bob', {}],
|
|
1797
|
-
* ['user.age', 31, {}],
|
|
1798
|
-
* ]
|
|
1799
|
-
*
|
|
1800
|
-
* const newState = applyChangesToObject(state, changes)
|
|
1801
|
-
* // newState: { user: { name: 'Bob', age: 31 } }
|
|
1802
|
-
* // state is unchanged
|
|
1803
|
-
* ```
|
|
1804
|
-
*/
|
|
1805
|
-
declare const applyChangesToObject: <T extends object>(obj: T, changes: ArrayOfChanges<T>) => T;
|
|
1806
|
-
|
|
1807
|
-
/**
|
|
1808
|
-
* Deep clone utility — single swap point for the cloning implementation.
|
|
1809
|
-
*
|
|
1810
|
-
* Uses @jsbits/deep-clone in "exact" mode which preserves:
|
|
1811
|
-
* - Getter/setter property descriptors
|
|
1812
|
-
* - Property attributes (enumerable, configurable, writable)
|
|
1813
|
-
* - Prototypes
|
|
1814
|
-
*
|
|
1815
|
-
* Swap the implementation here to change cloning behavior everywhere.
|
|
1816
|
-
*/
|
|
1817
|
-
/**
|
|
1818
|
-
* Deep clone an object, preserving getters, setters, and property descriptors.
|
|
1819
|
-
* Returns a fully independent copy — mutations to the clone never affect the original.
|
|
1820
|
-
*
|
|
1821
|
-
* Throws if the input contains circular references.
|
|
1822
138
|
*/
|
|
1823
|
-
declare const
|
|
139
|
+
declare const listeners: <DATA extends object, META extends GenericMeta = GenericMeta, Depth extends number = DefaultDepth>() => <T extends readonly {
|
|
140
|
+
path: ResolvableDeepKey<DATA, Depth> | null;
|
|
141
|
+
scope?: ResolvableDeepKey<DATA, Depth> | null | undefined;
|
|
142
|
+
fn: (...args: any[]) => any;
|
|
143
|
+
}[]>(items: CheckListeners<DATA, META, T, Depth>) => ValidatedListeners<DATA, META>;
|
|
1824
144
|
|
|
1825
|
-
export {
|
|
145
|
+
export { BoolLogic, CheckAggregationPairs, CheckComputationPairs, CheckSyncPairs, ComputationOp, DeepKeyFiltered, GenericMeta, aggregationPairs, computationPairs, flipPairs, listeners, syncPairs };
|