@sladg/apex-state 3.1.0 → 3.2.1

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.js CHANGED
@@ -718,7 +718,7 @@ var init_apex_state_wasm = __esm({
718
718
  });
719
719
 
720
720
  // src/store/create-store.ts
721
- import { useCallback, useLayoutEffect } from "react";
721
+ import { useCallback, useLayoutEffect as useLayoutEffect2 } from "react";
722
722
  import { snapshot as snapshot3, useSnapshot } from "valtio";
723
723
 
724
724
  // src/concerns/registration.ts
@@ -2305,157 +2305,8 @@ var registerSideEffects2 = (store, id, effects) => {
2305
2305
  return cleanup;
2306
2306
  };
2307
2307
 
2308
- // src/utils/json.ts
2309
- var createFastJson = (placeholders = []) => {
2310
- const encodeMap = /* @__PURE__ */ new Map();
2311
- const decodeMap = /* @__PURE__ */ new Map();
2312
- for (const p of placeholders) {
2313
- encodeMap.set(p.value, p.encoded);
2314
- decodeMap.set(p.encoded, p.value);
2315
- }
2316
- const stringify = (value) => {
2317
- if (encodeMap.has(value)) return encodeMap.get(value);
2318
- if (typeof value === "number" || typeof value === "boolean")
2319
- return String(value);
2320
- if (value === null) return "null";
2321
- return JSON.stringify(value);
2322
- };
2323
- const parse = (json) => {
2324
- if (decodeMap.has(json)) return decodeMap.get(json);
2325
- const c = json.charCodeAt(0);
2326
- if (c >= 48 && c <= 57 || c === 45) return Number(json);
2327
- if (json === "true") return true;
2328
- if (json === "false") return false;
2329
- if (json === "null") return null;
2330
- return JSON.parse(json);
2331
- };
2332
- return { stringify, parse };
2333
- };
2334
-
2335
- // src/wasm/bridge.ts
2336
- var UNDEFINED_SENTINEL_JSON = '"__APEX_UNDEFINED__"';
2337
- var { stringify: fastStringify, parse: fastParse } = createFastJson([
2338
- { value: void 0, encoded: UNDEFINED_SENTINEL_JSON }
2339
- ]);
2340
- var changesToWasm = (changes) => changes.map(({ path, value }) => ({ path, value_json: fastStringify(value) }));
2341
- var wasmChangesToJs = (wasmChanges) => wasmChanges.map(({ path, value_json }) => ({
2342
- path,
2343
- value: fastParse(value_json)
2344
- }));
2345
- var createWasmPipeline = () => {
2346
- const wasm2 = getWasmInstance();
2347
- const id = wasm2.pipeline_create();
2348
- const schemas = /* @__PURE__ */ new Map();
2349
- return {
2350
- id,
2351
- shadowInit: (state) => {
2352
- wasm2.shadow_init(id, state);
2353
- },
2354
- shadowDump: () => JSON.parse(wasm2.shadow_dump(id)),
2355
- processChanges: (changes) => {
2356
- const result = wasm2.process_changes(
2357
- id,
2358
- changesToWasm(changes)
2359
- );
2360
- const stateChanges = wasmChangesToJs(result.state_changes);
2361
- return {
2362
- state_changes: stateChanges,
2363
- changes: stateChanges,
2364
- validators_to_run: result.validators_to_run ?? [],
2365
- execution_plan: result.execution_plan ?? null,
2366
- has_work: result.has_work ?? false
2367
- };
2368
- },
2369
- pipelineFinalize: (jsChanges) => {
2370
- const result = wasm2.pipeline_finalize(
2371
- id,
2372
- changesToWasm(jsChanges)
2373
- );
2374
- return {
2375
- state_changes: wasmChangesToJs(result.state_changes)
2376
- };
2377
- },
2378
- registerSideEffects: (reg) => {
2379
- const resultJson = wasm2.register_side_effects(
2380
- id,
2381
- JSON.stringify(reg)
2382
- );
2383
- return {
2384
- sync_changes: wasmChangesToJs(resultJson.sync_changes),
2385
- aggregation_changes: wasmChangesToJs(resultJson.aggregation_changes),
2386
- registered_listener_ids: resultJson.registered_listener_ids
2387
- };
2388
- },
2389
- unregisterSideEffects: (registrationId) => {
2390
- wasm2.unregister_side_effects(id, registrationId);
2391
- },
2392
- registerConcerns: (reg) => {
2393
- const resultJson = wasm2.register_concerns(
2394
- id,
2395
- JSON.stringify(reg)
2396
- );
2397
- return {
2398
- bool_logic_changes: wasmChangesToJs(resultJson.bool_logic_changes),
2399
- registered_logic_ids: resultJson.registered_logic_ids,
2400
- registered_validator_ids: resultJson.registered_validator_ids,
2401
- value_logic_changes: wasmChangesToJs(
2402
- resultJson.value_logic_changes ?? []
2403
- ),
2404
- registered_value_logic_ids: resultJson.registered_value_logic_ids ?? []
2405
- };
2406
- },
2407
- unregisterConcerns: (registrationId) => {
2408
- wasm2.unregister_concerns(id, registrationId);
2409
- },
2410
- registerBoolLogic: (outputPath, tree) => wasm2.register_boollogic(id, outputPath, JSON.stringify(tree)),
2411
- unregisterBoolLogic: (logicId) => {
2412
- wasm2.unregister_boollogic(id, logicId);
2413
- },
2414
- pipelineReset: () => {
2415
- wasm2.pipeline_reset(id);
2416
- },
2417
- destroy: () => {
2418
- wasm2.pipeline_destroy(id);
2419
- schemas.clear();
2420
- },
2421
- validatorSchemas: schemas
2422
- };
2423
- };
2424
-
2425
- // src/wasm/lifecycle.ts
2426
- var wasmInstance = null;
2427
- var loadingPromise = null;
2428
- var loadWasm2 = async () => {
2429
- if (wasmInstance) return wasmInstance;
2430
- if (loadingPromise) {
2431
- await loadingPromise;
2432
- return wasmInstance;
2433
- }
2434
- loadingPromise = (async () => {
2435
- const wasmModule = await init_apex_state_wasm().then(() => apex_state_wasm_exports);
2436
- if (typeof wasmModule.default === "function") {
2437
- await wasmModule.default();
2438
- }
2439
- wasmInstance = wasmModule;
2440
- })();
2441
- await loadingPromise;
2442
- return wasmInstance;
2443
- };
2444
- var isWasmLoaded = () => wasmInstance !== null;
2445
- var initPipeline = (internal, initialState) => {
2446
- const pipeline = createWasmPipeline();
2447
- pipeline.shadowInit(initialState);
2448
- internal.pipeline = pipeline;
2449
- };
2450
- var getWasmInstance = () => {
2451
- if (!wasmInstance) {
2452
- throw new Error("WASM not loaded. Call loadWasm() first.");
2453
- }
2454
- return wasmInstance;
2455
- };
2456
-
2457
2308
  // src/store/provider.tsx
2458
- import { useEffect, useMemo, useState } from "react";
2309
+ import { useLayoutEffect, useRef } from "react";
2459
2310
  import { proxy, ref } from "valtio";
2460
2311
 
2461
2312
  // src/core/defaults.ts
@@ -2635,8 +2486,179 @@ var createTiming = (options) => {
2635
2486
  };
2636
2487
  };
2637
2488
 
2489
+ // src/wasm/lifecycle.tsx
2490
+ import { useEffect, useState } from "react";
2491
+
2492
+ // src/utils/json.ts
2493
+ var createFastJson = (placeholders = []) => {
2494
+ const encodeMap = /* @__PURE__ */ new Map();
2495
+ const decodeMap = /* @__PURE__ */ new Map();
2496
+ for (const p of placeholders) {
2497
+ encodeMap.set(p.value, p.encoded);
2498
+ decodeMap.set(p.encoded, p.value);
2499
+ }
2500
+ const stringify = (value) => {
2501
+ if (encodeMap.has(value)) return encodeMap.get(value);
2502
+ if (typeof value === "number" || typeof value === "boolean")
2503
+ return String(value);
2504
+ if (value === null) return "null";
2505
+ return JSON.stringify(value);
2506
+ };
2507
+ const parse = (json) => {
2508
+ if (decodeMap.has(json)) return decodeMap.get(json);
2509
+ const c = json.charCodeAt(0);
2510
+ if (c >= 48 && c <= 57 || c === 45) return Number(json);
2511
+ if (json === "true") return true;
2512
+ if (json === "false") return false;
2513
+ if (json === "null") return null;
2514
+ return JSON.parse(json);
2515
+ };
2516
+ return { stringify, parse };
2517
+ };
2518
+
2519
+ // src/wasm/bridge.ts
2520
+ var UNDEFINED_SENTINEL_JSON = '"__APEX_UNDEFINED__"';
2521
+ var { stringify: fastStringify, parse: fastParse } = createFastJson([
2522
+ { value: void 0, encoded: UNDEFINED_SENTINEL_JSON }
2523
+ ]);
2524
+ var changesToWasm = (changes) => changes.map(({ path, value }) => ({ path, value_json: fastStringify(value) }));
2525
+ var wasmChangesToJs = (wasmChanges) => wasmChanges.map(({ path, value_json }) => ({
2526
+ path,
2527
+ value: fastParse(value_json)
2528
+ }));
2529
+ var createWasmPipeline = () => {
2530
+ const wasm2 = getWasmInstance();
2531
+ const id = wasm2.pipeline_create();
2532
+ const schemas = /* @__PURE__ */ new Map();
2533
+ return {
2534
+ id,
2535
+ shadowInit: (state) => {
2536
+ wasm2.shadow_init(id, state);
2537
+ },
2538
+ shadowDump: () => JSON.parse(wasm2.shadow_dump(id)),
2539
+ processChanges: (changes) => {
2540
+ const result = wasm2.process_changes(
2541
+ id,
2542
+ changesToWasm(changes)
2543
+ );
2544
+ const stateChanges = wasmChangesToJs(result.state_changes);
2545
+ return {
2546
+ state_changes: stateChanges,
2547
+ changes: stateChanges,
2548
+ validators_to_run: result.validators_to_run ?? [],
2549
+ execution_plan: result.execution_plan ?? null,
2550
+ has_work: result.has_work ?? false
2551
+ };
2552
+ },
2553
+ pipelineFinalize: (jsChanges) => {
2554
+ const result = wasm2.pipeline_finalize(
2555
+ id,
2556
+ changesToWasm(jsChanges)
2557
+ );
2558
+ return {
2559
+ state_changes: wasmChangesToJs(result.state_changes)
2560
+ };
2561
+ },
2562
+ registerSideEffects: (reg) => {
2563
+ const resultJson = wasm2.register_side_effects(
2564
+ id,
2565
+ JSON.stringify(reg)
2566
+ );
2567
+ return {
2568
+ sync_changes: wasmChangesToJs(resultJson.sync_changes),
2569
+ aggregation_changes: wasmChangesToJs(resultJson.aggregation_changes),
2570
+ registered_listener_ids: resultJson.registered_listener_ids
2571
+ };
2572
+ },
2573
+ unregisterSideEffects: (registrationId) => {
2574
+ wasm2.unregister_side_effects(id, registrationId);
2575
+ },
2576
+ registerConcerns: (reg) => {
2577
+ const resultJson = wasm2.register_concerns(
2578
+ id,
2579
+ JSON.stringify(reg)
2580
+ );
2581
+ return {
2582
+ bool_logic_changes: wasmChangesToJs(resultJson.bool_logic_changes),
2583
+ registered_logic_ids: resultJson.registered_logic_ids,
2584
+ registered_validator_ids: resultJson.registered_validator_ids,
2585
+ value_logic_changes: wasmChangesToJs(
2586
+ resultJson.value_logic_changes ?? []
2587
+ ),
2588
+ registered_value_logic_ids: resultJson.registered_value_logic_ids ?? []
2589
+ };
2590
+ },
2591
+ unregisterConcerns: (registrationId) => {
2592
+ wasm2.unregister_concerns(id, registrationId);
2593
+ },
2594
+ registerBoolLogic: (outputPath, tree) => wasm2.register_boollogic(id, outputPath, JSON.stringify(tree)),
2595
+ unregisterBoolLogic: (logicId) => {
2596
+ wasm2.unregister_boollogic(id, logicId);
2597
+ },
2598
+ pipelineReset: () => {
2599
+ wasm2.pipeline_reset(id);
2600
+ },
2601
+ destroy: () => {
2602
+ wasm2.pipeline_destroy(id);
2603
+ schemas.clear();
2604
+ },
2605
+ validatorSchemas: schemas
2606
+ };
2607
+ };
2608
+
2609
+ // src/wasm/lifecycle.tsx
2610
+ import { Fragment, jsx } from "react/jsx-runtime";
2611
+ var wasmInstance = null;
2612
+ var loadingPromise = null;
2613
+ var loadWasm2 = async () => {
2614
+ if (wasmInstance) return wasmInstance;
2615
+ if (loadingPromise) {
2616
+ await loadingPromise;
2617
+ return wasmInstance;
2618
+ }
2619
+ loadingPromise = (async () => {
2620
+ const wasmModule = await init_apex_state_wasm().then(() => apex_state_wasm_exports);
2621
+ if (typeof wasmModule.default === "function") {
2622
+ await wasmModule.default();
2623
+ }
2624
+ wasmInstance = wasmModule;
2625
+ })();
2626
+ await loadingPromise;
2627
+ return wasmInstance;
2628
+ };
2629
+ var isWasmLoaded = () => wasmInstance !== null;
2630
+ var initPipeline = (internal, initialState) => {
2631
+ const pipeline = createWasmPipeline();
2632
+ pipeline.shadowInit(initialState);
2633
+ internal.pipeline = pipeline;
2634
+ };
2635
+ var WasmGate = ({ children }) => {
2636
+ const [loaded, setLoaded] = useState(isWasmLoaded);
2637
+ useEffect(() => {
2638
+ if (loaded) return;
2639
+ let cancelled = false;
2640
+ loadWasm2().then(() => {
2641
+ if (!cancelled) setLoaded(true);
2642
+ }).catch((error) => {
2643
+ console.error("[apex-state] Failed to load WASM:", error);
2644
+ });
2645
+ return () => {
2646
+ cancelled = true;
2647
+ };
2648
+ }, [loaded]);
2649
+ if (!loaded) return null;
2650
+ return /* @__PURE__ */ jsx(Fragment, { children });
2651
+ };
2652
+ WasmGate.displayName = "WasmGate";
2653
+ var getWasmInstance = () => {
2654
+ if (!wasmInstance) {
2655
+ throw new Error("WASM not loaded. Call loadWasm() first.");
2656
+ }
2657
+ return wasmInstance;
2658
+ };
2659
+
2638
2660
  // src/store/provider.tsx
2639
- import { jsx } from "react/jsx-runtime";
2661
+ import { jsx as jsx2 } from "react/jsx-runtime";
2640
2662
  var createInternalState = (config) => ({
2641
2663
  graphs: {
2642
2664
  sync: createPathGroups("sync"),
@@ -2660,84 +2682,58 @@ var createInternalState = (config) => ({
2660
2682
  });
2661
2683
  var createProvider = (storeConfig) => {
2662
2684
  const resolvedConfig = deepMerge(DEFAULT_STORE_CONFIG, storeConfig);
2663
- const isLegacy = resolvedConfig.useLegacyImplementation;
2664
- const Provider = ({
2665
- initialState: rawInitialState,
2666
- children
2667
- }) => {
2668
- const prepared = useMemo(
2669
- () => prepareInitialState(rawInitialState),
2670
- // Only initialize once - ignore changes to initialState after mount
2671
- []
2672
- );
2673
- const store = useMemo(() => {
2674
- const internal = createInternalState(resolvedConfig);
2675
- if (!isLegacy && isWasmLoaded()) {
2676
- initPipeline(internal, prepared.initialState);
2685
+ const buildStore = (rawInitialState) => {
2686
+ const prepared = prepareInitialState(rawInitialState);
2687
+ const internal = createInternalState(resolvedConfig);
2688
+ initPipeline(internal, prepared.initialState);
2689
+ const debugTrack = resolvedConfig.debug.track ? {
2690
+ calls: [],
2691
+ clear: () => {
2692
+ debugTrack.calls.length = 0;
2677
2693
  }
2678
- const debugTrack = resolvedConfig.debug.track ? {
2679
- calls: [],
2680
- clear: () => {
2681
- debugTrack.calls.length = 0;
2682
- }
2683
- } : null;
2684
- const stateProxy = proxy(prepared.initialState);
2685
- attachComputedGetters(stateProxy, prepared.getterMap);
2686
- return {
2687
- // state: Application data (tracked by valtio)
2688
- // User actions WRITE to this, effects READ from this
2689
- state: stateProxy,
2690
- // _concerns: Computed concern values (tracked by valtio)
2691
- // Effects WRITE to this, UI components READ from this
2692
- _concerns: proxy({}),
2693
- // _internal: Graphs, registrations, processing (NOT tracked)
2694
- // Wrapped in ref() to prevent tracking even if store is later wrapped in a proxy
2695
- _internal: ref(internal),
2696
- // _debug: Tracking data for testing/debugging (only when debug.track enabled)
2697
- _debug: debugTrack ? ref(debugTrack) : null
2698
- };
2699
- }, []);
2700
- const [ready, setReady] = useState(
2701
- () => isLegacy || store._internal.pipeline !== null
2702
- );
2703
- useEffect(() => {
2704
- if (isLegacy) return;
2705
- let cancelled = false;
2706
- const ensurePipeline = () => {
2707
- if (cancelled) return;
2708
- if (!store._internal.pipeline) {
2709
- initPipeline(
2710
- store._internal,
2711
- prepared.initialState
2712
- );
2713
- }
2714
- setReady(true);
2715
- };
2716
- if (isWasmLoaded()) {
2717
- ensurePipeline();
2718
- } else {
2719
- loadWasm2().then(ensurePipeline).catch((error) => {
2720
- console.error("[apex-state] Failed to load WASM:", error);
2721
- });
2694
+ } : null;
2695
+ const stateProxy = proxy(prepared.initialState);
2696
+ attachComputedGetters(stateProxy, prepared.getterMap);
2697
+ return {
2698
+ // state: Application data (tracked by valtio)
2699
+ // User actions WRITE to this, effects READ from this
2700
+ state: stateProxy,
2701
+ // _concerns: Computed concern values (tracked by valtio)
2702
+ // Effects WRITE to this, UI components READ from this
2703
+ _concerns: proxy({}),
2704
+ // _internal: Graphs, registrations, processing (NOT tracked)
2705
+ // Wrapped in ref() to prevent tracking even if store is later wrapped in a proxy
2706
+ _internal: ref(internal),
2707
+ // _debug: Tracking data for testing/debugging (only when debug.track enabled)
2708
+ _debug: debugTrack ? ref(debugTrack) : null
2709
+ };
2710
+ };
2711
+ const StoreProvider = ({ initialState, children }) => {
2712
+ const storeRef = useRef(buildStore(initialState));
2713
+ const internal = storeRef.current._internal;
2714
+ useLayoutEffect(() => {
2715
+ if (internal._destroyTimer) {
2716
+ clearTimeout(internal._destroyTimer);
2717
+ internal._destroyTimer = void 0;
2722
2718
  }
2723
2719
  return () => {
2724
- cancelled = true;
2725
- store._internal.pipeline?.destroy();
2726
- store._internal.pipeline = null;
2727
- setReady(false);
2720
+ internal._destroyTimer = setTimeout(() => {
2721
+ internal.pipeline?.destroy();
2722
+ internal.pipeline = null;
2723
+ internal._destroyTimer = void 0;
2724
+ }, 1e4);
2728
2725
  };
2729
- }, []);
2730
- if (!ready) {
2731
- return null;
2732
- }
2733
- return /* @__PURE__ */ jsx(
2726
+ }, [internal]);
2727
+ return /* @__PURE__ */ jsx2(
2734
2728
  StoreContext.Provider,
2735
2729
  {
2736
- value: store,
2730
+ value: storeRef.current,
2737
2731
  children
2738
2732
  }
2739
2733
  );
2740
2734
  };
2735
+ StoreProvider.displayName = "StoreProvider";
2736
+ const Provider = (props) => /* @__PURE__ */ jsx2(WasmGate, { children: /* @__PURE__ */ jsx2(StoreProvider, { ...props }) });
2741
2737
  Provider.displayName = "StoreProvider";
2742
2738
  return Provider;
2743
2739
  };
@@ -2745,9 +2741,6 @@ var createProvider = (storeConfig) => {
2745
2741
  // src/store/create-store.ts
2746
2742
  var createGenericStore = (config) => {
2747
2743
  const Provider = createProvider(config);
2748
- if (!config?.useLegacyImplementation) {
2749
- void loadWasm2();
2750
- }
2751
2744
  const _useFieldValue = (path) => {
2752
2745
  const store = useStoreContext();
2753
2746
  const snap = useSnapshot(store.state);
@@ -2791,7 +2784,7 @@ var createGenericStore = (config) => {
2791
2784
  };
2792
2785
  const useSideEffects = (id, effects) => {
2793
2786
  const store = useStoreContext();
2794
- useLayoutEffect(() => {
2787
+ useLayoutEffect2(() => {
2795
2788
  const registerSideEffects3 = store._internal.config.useLegacyImplementation ? registerSideEffects : registerSideEffects2;
2796
2789
  return registerSideEffects3(store, id, effects);
2797
2790
  }, [store, id, effects]);
@@ -2799,7 +2792,7 @@ var createGenericStore = (config) => {
2799
2792
  const useConcerns = (id, registration, customConcerns) => {
2800
2793
  const store = useStoreContext();
2801
2794
  const concerns = customConcerns || defaultConcerns;
2802
- useLayoutEffect(() => {
2795
+ useLayoutEffect2(() => {
2803
2796
  const registerConcernEffects3 = store._internal.config.useLegacyImplementation ? registerConcernEffects : registerConcernEffects2;
2804
2797
  return registerConcernEffects3(store, registration, concerns);
2805
2798
  }, [store, id, registration, customConcerns]);
@@ -2858,12 +2851,12 @@ var useBufferedField = (field) => {
2858
2851
  };
2859
2852
 
2860
2853
  // src/hooks/use-keyboard-select.ts
2861
- import { useCallback as useCallback3, useRef } from "react";
2854
+ import { useCallback as useCallback3, useRef as useRef2 } from "react";
2862
2855
  var useKeyboardSelect = (field, config) => {
2863
2856
  const { setValue } = field;
2864
2857
  const { options, debounceMs = 500, matchFromStart = true } = config;
2865
- const searchRef = useRef("");
2866
- const timeoutRef = useRef(null);
2858
+ const searchRef = useRef2("");
2859
+ const timeoutRef = useRef2(null);
2867
2860
  const onKeyDown = useCallback3(
2868
2861
  (e) => {
2869
2862
  if (e.key.length !== 1) return;
@@ -2891,11 +2884,11 @@ var useKeyboardSelect = (field, config) => {
2891
2884
  };
2892
2885
 
2893
2886
  // src/hooks/use-throttled-field.ts
2894
- import { useCallback as useCallback4, useEffect as useEffect2, useRef as useRef2 } from "react";
2887
+ import { useCallback as useCallback4, useEffect as useEffect2, useRef as useRef3 } from "react";
2895
2888
  var useThrottledField = (field, config) => {
2896
2889
  const { setValue: originalSetValue, ...rest } = field;
2897
2890
  const { ms } = config;
2898
- const throttleRef = useRef2({
2891
+ const throttleRef = useRef3({
2899
2892
  timeoutId: null,
2900
2893
  lastFlushTime: 0,
2901
2894
  pendingValue: void 0,
@@ -2945,11 +2938,11 @@ var useThrottledField = (field, config) => {
2945
2938
  };
2946
2939
 
2947
2940
  // src/hooks/use-transformed-field.ts
2948
- import { useCallback as useCallback5, useMemo as useMemo2 } from "react";
2941
+ import { useCallback as useCallback5, useMemo } from "react";
2949
2942
  var useTransformedField = (field, config) => {
2950
2943
  const { value: storedValue, setValue: setStoredValue, ...rest } = field;
2951
2944
  const { to, from } = config;
2952
- const value = useMemo2(() => to(storedValue), [storedValue, to]);
2945
+ const value = useMemo(() => to(storedValue), [storedValue, to]);
2953
2946
  const setValue = useCallback5(
2954
2947
  (displayValue) => {
2955
2948
  const storedVal = from(displayValue);