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