@mmstack/primitives 20.5.9 → 20.5.11

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.
@@ -2410,9 +2410,120 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
2410
2410
  * has a `unique symbol` type, so the same symbol serves as both the property key written
2411
2411
  * by {@link opaque} and the type-level brand carried by {@link Opaque}.
2412
2412
  */
2413
- const OPAQUE = Symbol('MMSTACK::OPAQUE');
2414
- const IS_STORE = Symbol('MMSTACK::IS_STORE');
2415
- const SCOPE_PARENT = Symbol('MMSTACK::SCOPE_PARENT');
2413
+ const OPAQUE = Symbol('@mmstack/primitives::store/OPAQUE');
2414
+ /**
2415
+ * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
2416
+ * (returned whole, never deep-proxied) — the same way it treats a `Date` or `RegExp`.
2417
+ * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
2418
+ * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
2419
+ *
2420
+ * @example
2421
+ * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
2422
+ * s.config(); // the whole object, not a child store
2423
+ * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
2424
+ */
2425
+ function opaque(value) {
2426
+ if (value[OPAQUE] !== true)
2427
+ Object.defineProperty(value, OPAQUE, { value: true, enumerable: false });
2428
+ return value;
2429
+ }
2430
+ /**
2431
+ * Type guard companion to {@link opaque}: returns `true` when `value` carries the
2432
+ * {@link OPAQUE} brand, narrowing it to {@link Opaque}. This is the same check the
2433
+ * store uses to route opaque values to its leaf branch (alongside `Date`/`RegExp`).
2434
+ *
2435
+ * @internal Exposed for advanced/niche interop only — not part of the supported public
2436
+ * surface and may change without a major version bump. Reach for {@link opaque} for
2437
+ * normal usage.
2438
+ *
2439
+ * @example
2440
+ * if (isOpaque(value)) {
2441
+ * // value: Opaque<object> — `store` would treat it as an indivisible leaf
2442
+ * }
2443
+ */
2444
+ function isOpaque(value) {
2445
+ return (typeof value === 'object' &&
2446
+ value !== null &&
2447
+ value[OPAQUE] === true);
2448
+ }
2449
+ /**
2450
+ * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
2451
+ * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
2452
+ * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
2453
+ */
2454
+ const LEAF = Symbol('@mmstack/primitives::store/LEAF');
2455
+ /**
2456
+ * @internal Whether a value is a terminal leaf: a concrete non-record/non-array value always is;
2457
+ * `null`/`undefined` is a leaf only when vivification is disabled (with vivify on it can still
2458
+ * materialize a container, so it stays a descendable substore).
2459
+ */
2460
+ function isLeafValue(value, vivifyEnabled) {
2461
+ if (value == null)
2462
+ return !vivifyEnabled;
2463
+ if (isOpaque(value))
2464
+ return true; // opaque always wins — even arrays
2465
+ return !Array.isArray(value) && !isRecord(value);
2466
+ }
2467
+ /**
2468
+ * @internal Constant leaf probes for nodes whose leaf-ness is statically known, so the reactive
2469
+ * `computed` can be skipped entirely.
2470
+ */
2471
+ function alwaysTrue() {
2472
+ return true;
2473
+ }
2474
+ function alwaysFalse() {
2475
+ return false;
2476
+ }
2477
+ /**
2478
+ * @internal Attaches a lazy, memoized leaf probe to a store node. The probe (`() => boolean`)
2479
+ * closes over the node's value signal and its (stable) vivify setting, building the backing
2480
+ * `computed` on first call so leaf-ness tracks the live value reactively without taxing every
2481
+ * node access. Idempotent.
2482
+ */
2483
+ function markAsLeaf(sig, value, vivifyEnabled, noUnionLeaves) {
2484
+ if (typeof sig[LEAF] !== 'function') {
2485
+ let memo;
2486
+ const probe = () => {
2487
+ if (memo)
2488
+ return memo();
2489
+ const v = untracked(value);
2490
+ memo =
2491
+ isOpaque(v) || (v == null && !vivifyEnabled) || noUnionLeaves
2492
+ ? isLeafValue(v, vivifyEnabled)
2493
+ ? alwaysTrue
2494
+ : alwaysFalse
2495
+ : computed(() => isLeafValue(value(), vivifyEnabled));
2496
+ return memo();
2497
+ };
2498
+ Object.defineProperty(sig, LEAF, {
2499
+ value: probe,
2500
+ enumerable: false,
2501
+ configurable: true,
2502
+ });
2503
+ }
2504
+ return sig;
2505
+ }
2506
+ /**
2507
+ * Reports whether a store node is currently a **leaf** — a terminal value the store does not
2508
+ * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
2509
+ * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
2510
+ *
2511
+ * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
2512
+ * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
2513
+ *
2514
+ * @internal Exposed for advanced/niche interop only — not part of the supported public surface
2515
+ * and may change without a major version bump.
2516
+ *
2517
+ * @example
2518
+ * const s = store({ name: 'Ada', address: { city: 'London' } });
2519
+ * isLeaf(s.name); // true
2520
+ * isLeaf(s.address); // false — a substore
2521
+ */
2522
+ function isLeaf(value) {
2523
+ return isStore(value) && value[LEAF]?.() === true;
2524
+ }
2525
+ const IS_STORE = Symbol('@mmstack/primitives::store/IS_STORE');
2526
+ const SCOPE_PARENT = Symbol('@mmstack/primitives::store/SCOPE_PARENT');
2416
2527
  /**
2417
2528
  * @internal
2418
2529
  * Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
@@ -2446,10 +2557,8 @@ function isStore(value) {
2446
2557
  value[IS_STORE] === true);
2447
2558
  }
2448
2559
  function isRecord(value) {
2449
- if (value === null || typeof value !== 'object')
2560
+ if (value === null || typeof value !== 'object' || isOpaque(value))
2450
2561
  return false;
2451
- if (value[OPAQUE] === true)
2452
- return false; // opaque → leaf
2453
2562
  const proto = Object.getPrototypeOf(value);
2454
2563
  return proto === Object.prototype || proto === null;
2455
2564
  }
@@ -2475,7 +2584,7 @@ function hasOwnKey(value, key) {
2475
2584
  * @internal
2476
2585
  * Makes an array store
2477
2586
  */
2478
- function toArrayStore(source, injector, vivify) {
2587
+ function toArrayStore(source, injector, vivify, noUnionLeaves = false) {
2479
2588
  if (isStore(source))
2480
2589
  return source;
2481
2590
  const isMutableSource = isMutable(source);
@@ -2585,9 +2694,10 @@ function toArrayStore(source, injector, vivify) {
2585
2694
  });
2586
2695
  const childSample = untracked(computation);
2587
2696
  const childVivify = resolveVivify(childSample, vivify);
2588
- const proxy = Array.isArray(childSample)
2589
- ? toArrayStore(computation, injector, childVivify)
2590
- : toStore(computation, injector, childVivify);
2697
+ const proxy = Array.isArray(childSample) && !isOpaque(childSample)
2698
+ ? toArrayStore(computation, injector, childVivify, noUnionLeaves)
2699
+ : toStore(computation, injector, childVivify, noUnionLeaves);
2700
+ markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
2591
2701
  const ref = new WeakRef(proxy);
2592
2702
  storeCache.set(idx, ref);
2593
2703
  PROXY_CLEANUP.register(proxy, { target, prop: idx }, ref);
@@ -2604,7 +2714,7 @@ function toArrayStore(source, injector, vivify) {
2604
2714
  * const state = store({ user: { name: 'John' } });
2605
2715
  * const nameSignal = state.user.name; // WritableSignal<string>
2606
2716
  */
2607
- function toStore(source, injector, vivify = false) {
2717
+ function toStore(source, injector, vivify = false, noUnionLeaves = false) {
2608
2718
  if (isStore(source))
2609
2719
  return source;
2610
2720
  if (!injector)
@@ -2645,7 +2755,7 @@ function toStore(source, injector, vivify = false) {
2645
2755
  return () => {
2646
2756
  if (!isWritableSource)
2647
2757
  return s;
2648
- return untracked(() => toStore(source.asReadonly(), injector, vivify));
2758
+ return untracked(() => toStore(source.asReadonly(), injector, vivify, noUnionLeaves));
2649
2759
  };
2650
2760
  if (prop === 'extend')
2651
2761
  return (seed) => scopedStore(s, seed, isMutableSource
@@ -2698,9 +2808,10 @@ function toStore(source, injector, vivify = false) {
2698
2808
  });
2699
2809
  const childSample = untracked(computation);
2700
2810
  const childVivify = resolveVivify(childSample, vivify);
2701
- const proxy = Array.isArray(childSample)
2702
- ? toArrayStore(computation, injector, childVivify)
2703
- : toStore(computation, injector, childVivify);
2811
+ const proxy = Array.isArray(childSample) && !isOpaque(childSample)
2812
+ ? toArrayStore(computation, injector, childVivify, noUnionLeaves)
2813
+ : toStore(computation, injector, childVivify, noUnionLeaves);
2814
+ markAsLeaf(proxy, computation, childVivify !== false, noUnionLeaves);
2704
2815
  const ref = new WeakRef(proxy);
2705
2816
  storeCache.set(prop, ref);
2706
2817
  PROXY_CLEANUP.register(proxy, { target, prop }, ref);
@@ -2775,12 +2886,7 @@ function scopedStore(parent, seed, kind, injector) {
2775
2886
  return hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop);
2776
2887
  },
2777
2888
  ownKeys() {
2778
- return [
2779
- ...new Set([
2780
- ...Reflect.ownKeys(parentValue()),
2781
- ...Reflect.ownKeys(localValue()),
2782
- ]),
2783
- ];
2889
+ return Reflect.ownKeys(untracked(view));
2784
2890
  },
2785
2891
  getOwnPropertyDescriptor(_, prop) {
2786
2892
  if (hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop))
@@ -2798,30 +2904,14 @@ function scopedStore(parent, seed, kind, injector) {
2798
2904
  * @see {@link toStore}
2799
2905
  */
2800
2906
  function store(value, opt) {
2801
- return toStore(signal(value, opt), opt?.injector, opt?.vivify ?? false);
2907
+ return toStore(signal(value, opt), opt?.injector, opt?.vivify ?? false, opt?.noUnionLeaves ?? false);
2802
2908
  }
2803
2909
  /**
2804
2910
  * Creates a MutableSignalStore from a value.
2805
2911
  * @see {@link toStore}
2806
2912
  */
2807
2913
  function mutableStore(value, opt) {
2808
- return toStore(mutable(value, opt), opt?.injector, opt?.vivify ?? false);
2809
- }
2810
- /**
2811
- * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
2812
- * (returned whole, never deep-proxied) — the same way it treats a `Date` or `RegExp`.
2813
- * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
2814
- * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
2815
- *
2816
- * @example
2817
- * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
2818
- * s.config(); // the whole object, not a child store
2819
- * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
2820
- */
2821
- function opaque(value) {
2822
- if (value[OPAQUE] !== true)
2823
- Object.defineProperty(value, OPAQUE, { value: true, enumerable: false });
2824
- return value;
2914
+ return toStore(mutable(value, opt), opt?.injector, opt?.vivify ?? false, opt?.noUnionLeaves ?? false);
2825
2915
  }
2826
2916
 
2827
2917
  // Internal dummy store for server-side rendering
@@ -3307,5 +3397,5 @@ function withHistory(sourceOrValue, opt) {
3307
3397
  * Generated bundle index. Do not edit.
3308
3398
  */
3309
3399
 
3310
- 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 };
3400
+ 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 };
3311
3401
  //# sourceMappingURL=mmstack-primitives.mjs.map