@mmstack/primitives 21.0.27 → 21.0.29

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.
@@ -2404,9 +2404,120 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
2404
2404
  * has a `unique symbol` type, so the same symbol serves as both the property key written
2405
2405
  * by {@link opaque} and the type-level brand carried by {@link Opaque}.
2406
2406
  */
2407
- const OPAQUE = Symbol('MMSTACK::OPAQUE');
2408
- const IS_STORE = Symbol('MMSTACK::IS_STORE');
2409
- const SCOPE_PARENT = Symbol('MMSTACK::SCOPE_PARENT');
2407
+ const OPAQUE = Symbol('@mmstack/primitives::store/OPAQUE');
2408
+ /**
2409
+ * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
2410
+ * (returned whole, never deep-proxied) — the same way it treats a `Date` or `RegExp`.
2411
+ * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
2412
+ * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
2413
+ *
2414
+ * @example
2415
+ * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
2416
+ * s.config(); // the whole object, not a child store
2417
+ * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
2418
+ */
2419
+ function opaque(value) {
2420
+ if (value[OPAQUE] !== true)
2421
+ Object.defineProperty(value, OPAQUE, { value: true, enumerable: false });
2422
+ return value;
2423
+ }
2424
+ /**
2425
+ * Type guard companion to {@link opaque}: returns `true` when `value` carries the
2426
+ * {@link OPAQUE} brand, narrowing it to {@link Opaque}. This is the same check the
2427
+ * store uses to route opaque values to its leaf branch (alongside `Date`/`RegExp`).
2428
+ *
2429
+ * @internal Exposed for advanced/niche interop only — not part of the supported public
2430
+ * surface and may change without a major version bump. Reach for {@link opaque} for
2431
+ * normal usage.
2432
+ *
2433
+ * @example
2434
+ * if (isOpaque(value)) {
2435
+ * // value: Opaque<object> — `store` would treat it as an indivisible leaf
2436
+ * }
2437
+ */
2438
+ function isOpaque(value) {
2439
+ return (typeof value === 'object' &&
2440
+ value !== null &&
2441
+ value[OPAQUE] === true);
2442
+ }
2443
+ /**
2444
+ * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
2445
+ * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
2446
+ * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
2447
+ */
2448
+ const LEAF = Symbol('@mmstack/primitives::store/LEAF');
2449
+ /**
2450
+ * @internal Whether a value is a terminal leaf: a concrete non-record/non-array value always is;
2451
+ * `null`/`undefined` is a leaf only when vivification is disabled (with vivify on it can still
2452
+ * materialize a container, so it stays a descendable substore).
2453
+ */
2454
+ function isLeafValue(value, vivifyEnabled) {
2455
+ if (value == null)
2456
+ return !vivifyEnabled;
2457
+ if (isOpaque(value))
2458
+ return true; // opaque always wins — even arrays
2459
+ return !Array.isArray(value) && !isRecord(value);
2460
+ }
2461
+ /**
2462
+ * @internal Constant leaf probes for nodes whose leaf-ness is statically known, so the reactive
2463
+ * `computed` can be skipped entirely.
2464
+ */
2465
+ function alwaysTrue() {
2466
+ return true;
2467
+ }
2468
+ function alwaysFalse() {
2469
+ return false;
2470
+ }
2471
+ /**
2472
+ * @internal Attaches a lazy, memoized leaf probe to a store node. The probe (`() => boolean`)
2473
+ * closes over the node's value signal and its (stable) vivify setting, building the backing
2474
+ * `computed` on first call so leaf-ness tracks the live value reactively without taxing every
2475
+ * node access. Idempotent.
2476
+ */
2477
+ function markAsLeaf(sig, value, vivifyEnabled, noUnionLeaves) {
2478
+ if (typeof sig[LEAF] !== 'function') {
2479
+ let memo;
2480
+ const probe = () => {
2481
+ if (memo)
2482
+ return memo();
2483
+ const v = untracked(value);
2484
+ memo =
2485
+ isOpaque(v) || (v == null && !vivifyEnabled) || noUnionLeaves
2486
+ ? isLeafValue(v, vivifyEnabled)
2487
+ ? alwaysTrue
2488
+ : alwaysFalse
2489
+ : computed(() => isLeafValue(value(), vivifyEnabled));
2490
+ return memo();
2491
+ };
2492
+ Object.defineProperty(sig, LEAF, {
2493
+ value: probe,
2494
+ enumerable: false,
2495
+ configurable: true,
2496
+ });
2497
+ }
2498
+ return sig;
2499
+ }
2500
+ /**
2501
+ * Reports whether a store node is currently a **leaf** — a terminal value the store does not
2502
+ * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
2503
+ * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
2504
+ *
2505
+ * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
2506
+ * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
2507
+ *
2508
+ * @internal Exposed for advanced/niche interop only — not part of the supported public surface
2509
+ * and may change without a major version bump.
2510
+ *
2511
+ * @example
2512
+ * const s = store({ name: 'Ada', address: { city: 'London' } });
2513
+ * isLeaf(s.name); // true
2514
+ * isLeaf(s.address); // false — a substore
2515
+ */
2516
+ function isLeaf(value) {
2517
+ return isStore(value) && value[LEAF]?.() === true;
2518
+ }
2519
+ const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
2520
+ const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
2410
2521
  /**
2411
2522
  * @internal
2412
2523
  * Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
@@ -2440,10 +2551,8 @@ function isStore(value) {
2440
2551
  value[IS_STORE] === true);
2441
2552
  }
2442
2553
  function isRecord(value) {
2443
- if (value === null || typeof value !== 'object')
2554
+ if (value === null || typeof value !== 'object' || isOpaque(value))
2444
2555
  return false;
2445
- if (value[OPAQUE] === true)
2446
- return false; // opaque → leaf
2447
2556
  const proto = Object.getPrototypeOf(value);
2448
2557
  return proto === Object.prototype || proto === null;
2449
2558
  }
@@ -2469,7 +2578,7 @@ function hasOwnKey(value, key) {
2469
2578
  * @internal
2470
2579
  * Makes an array store
2471
2580
  */
2472
- function toArrayStore(source, injector, vivify) {
2581
+ function toArrayStore(source, injector, vivify, noUnionLeaves = false) {
2473
2582
  if (isStore(source))
2474
2583
  return source;
2475
2584
  const isMutableSource = isMutable(source);
@@ -2579,9 +2688,10 @@ function toArrayStore(source, injector, vivify) {
2579
2688
  });
2580
2689
  const childSample = untracked(computation);
2581
2690
  const childVivify = resolveVivify(childSample, vivify);
2582
- const proxy = Array.isArray(childSample)
2583
- ? toArrayStore(computation, injector, childVivify)
2584
- : toStore(computation, injector, childVivify);
2691
+ const proxy = Array.isArray(childSample) && !isOpaque(childSample)
2692
+ ? toArrayStore(computation, injector, childVivify, noUnionLeaves)
2693
+ : toStore(computation, injector, childVivify, noUnionLeaves);
2694
+ markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
2585
2695
  const ref = new WeakRef(proxy);
2586
2696
  storeCache.set(idx, ref);
2587
2697
  PROXY_CLEANUP.register(proxy, { target, prop: idx }, ref);
@@ -2598,7 +2708,7 @@ function toArrayStore(source, injector, vivify) {
2598
2708
  * const state = store({ user: { name: 'John' } });
2599
2709
  * const nameSignal = state.user.name; // WritableSignal<string>
2600
2710
  */
2601
- function toStore(source, injector, vivify = false) {
2711
+ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
2602
2712
  if (isStore(source))
2603
2713
  return source;
2604
2714
  if (!injector)
@@ -2639,7 +2749,7 @@ function toStore(source, injector, vivify = false) {
2639
2749
  return () => {
2640
2750
  if (!isWritableSource)
2641
2751
  return s;
2642
- return untracked(() => toStore(source.asReadonly(), injector, vivify));
2752
+ return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
2643
2753
  };
2644
2754
  if (prop === 'extend')
2645
2755
  return (seed) => scopedStore(s, seed, isMutableSource
@@ -2692,9 +2802,10 @@ function toStore(source, injector, vivify = false) {
2692
2802
  });
2693
2803
  const childSample = untracked(computation);
2694
2804
  const childVivify = resolveVivify(childSample, vivify);
2695
- const proxy = Array.isArray(childSample)
2696
- ? toArrayStore(computation, injector, childVivify)
2697
- : toStore(computation, injector, childVivify);
2805
+ const proxy = Array.isArray(childSample) && !isOpaque(childSample)
2806
+ ? toArrayStore(computation, injector, childVivify, noUnionLeaves)
2807
+ : toStore(computation, injector, childVivify, noUnionLeaves);
2808
+ markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
2698
2809
  const ref = new WeakRef(proxy);
2699
2810
  storeCache.set(prop, ref);
2700
2811
  PROXY_CLEANUP.register(proxy, { target, prop }, ref);
@@ -2769,12 +2880,7 @@ function scopedStore(parent, seed, kind, injector) {
2769
2880
  return hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop);
2770
2881
  },
2771
2882
  ownKeys() {
2772
- return [
2773
- ...new Set([
2774
- ...Reflect.ownKeys(parentValue()),
2775
- ...Reflect.ownKeys(localValue()),
2776
- ]),
2777
- ];
2883
+ return Reflect.ownKeys(untracked(view));
2778
2884
  },
2779
2885
  getOwnPropertyDescriptor(_, prop) {
2780
2886
  if (hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop))
@@ -2792,30 +2898,14 @@ function scopedStore(parent, seed, kind, injector) {
2792
2898
  * @see {@link toStore}
2793
2899
  */
2794
2900
  function store(value, opt) {
2795
- return toStore(signal(value, opt), opt?.injector, opt?.vivify ?? false);
2901
+ return toStore(signal(value, opt), opt?.injector, opt?.vivify ?? false, opt?.noUnionLeaves ?? false);
2796
2902
  }
2797
2903
  /**
2798
2904
  * Creates a MutableSignalStore from a value.
2799
2905
  * @see {@link toStore}
2800
2906
  */
2801
2907
  function mutableStore(value, opt) {
2802
- return toStore(mutable(value, opt), opt?.injector, opt?.vivify ?? false);
2803
- }
2804
- /**
2805
- * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
2806
- * (returned whole, never deep-proxied) — the same way it treats a `Date` or `RegExp`.
2807
- * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
2808
- * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
2809
- *
2810
- * @example
2811
- * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
2812
- * s.config(); // the whole object, not a child store
2813
- * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
2814
- */
2815
- function opaque(value) {
2816
- if (value[OPAQUE] !== true)
2817
- Object.defineProperty(value, OPAQUE, { value: true, enumerable: false });
2818
- return value;
2908
+ return toStore(mutable(value, opt), opt?.injector, opt?.vivify ?? false, opt?.noUnionLeaves ?? false);
2819
2909
  }
2820
2910
 
2821
2911
  // Internal dummy store for server-side rendering
@@ -3292,5 +3382,5 @@ function withHistory(sourceOrValue, opt) {
3292
3382
  * Generated bundle index. Do not edit.
3293
3383
  */
3294
3384
 
3295
- export { batteryStatus, chunked, clipboard, combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, filterWith, focusWithin, geolocation, idle, indexArray, isDerivation, isMutable, isStore, keyArray, map, mapArray, mapObject, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
3385
+ export { batteryStatus, chunked, clipboard, combineWith, debounce, debounced, derived, distinct, elementSize, elementVisibility, filter, filterWith, focusWithin, geolocation, idle, indexArray, isDerivation, isLeaf, isMutable, isOpaque, isStore, keyArray, map, mapArray, mapObject, mediaQuery, mousePosition, mutable, mutableStore, nestedEffect, networkStatus, opaque, orientation, pageVisibility, pairwise, pipeable, piped, pooled, pooledArray, pooledMap, pooledSet, prefersDarkMode, prefersReducedMotion, scan, scrollPosition, select, sensor, sensors, signalFromEvent, startWith, store, stored, tabSync, tap, throttle, throttled, toFakeDerivation, toFakeSignalDerivation, toStore, toWritable, until, windowSize, withHistory };
3296
3386
  //# sourceMappingURL=mmstack-primitives.mjs.map