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