@mmstack/primitives 19.3.7 → 19.3.8

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/README.md CHANGED
@@ -69,6 +69,14 @@ const upper = derived(user, {
69
69
 
70
70
  When the source is a `MutableSignal`, the derived signal is also a `MutableSignal` — `derived(state, 'items').mutate(arr => { arr.push(...); return arr })` propagates correctly.
71
71
 
72
+ Pass `vivify` on the key/index form to create a missing container when writing through a `null`/`undefined` source — instead of throwing (mutable / array) or dropping the write. Choose `'object'`, `'array'`, `'auto'` (an array for index keys, an object otherwise), or a `() => container` factory; it defaults to off.
73
+
74
+ ```typescript
75
+ const user = signal<{ name: string } | null>(null);
76
+ derived(user, 'name', { vivify: 'object' }).set('Ada');
77
+ // user() === { name: 'Ada' }
78
+ ```
79
+
72
80
  ### `store` / `mutableStore`
73
81
 
74
82
  Proxies an object (or signal of an object) into a tree of `WritableSignal`s — one per property, lazily created and cached via `WeakRef`. Arrays expose indices as signals plus a `.length` signal and `Symbol.iterator`. Mutability propagates: if the root is a `MutableSignal`, every child is too.
@@ -92,8 +100,52 @@ settings.notifications.mutate((n) => {
92
100
  });
93
101
  ```
94
102
 
103
+ **Autovivification (opt-in).** By default, a write through a `null`/`undefined` path is dropped. Pass `vivify` to create the missing intermediate containers instead:
104
+
105
+ ```typescript
106
+ const form = store(
107
+ { user: null as { address?: { city: string } } | null },
108
+ { vivify: 'auto' },
109
+ );
110
+
111
+ form.user.address.city.set('NYC');
112
+ // form() === { user: { address: { city: 'NYC' } } }
113
+ ```
114
+
115
+ Each level's shape is resolved from what's known: a value that is currently an object/array re-creates as that same shape (resolved per path and cached, so it survives the value later being nulled), while genuinely-unknown levels follow your option — `'auto'` (an array for index keys, an object otherwise), `'object'`, `'array'`, or a `() => container` factory. `false` (the default) keeps writes through `null` as no-ops. Adding a key that simply wasn't present on an existing object always works and needs no `vivify`.
116
+
95
117
  Top-level array support isn't exposed yet — use `indexArray` / `keyArray` for those.
96
118
 
119
+ ### `extend` (scoped overlay)
120
+
121
+ `store.extend(seed)` (on any store kind) creates a **scoped overlay** — a child store that **shares** the parent's signals for inherited keys (the same `WritableSignal`: writes go through to the parent and parent changes flow down) while keeping the seed and any new keys in a **local layer** that never propagates upward. No diffing, no syncing — local keys simply aren't wired to the parent.
122
+
123
+ ```typescript
124
+ const app = store({ user: { name: 'Alice' }, theme: 'dark' });
125
+
126
+ const scope = app.extend({ draft: '' }); // inherits user + theme, adds a local draft
127
+
128
+ scope.user === app.user; // true — the same signal (shared, two-way)
129
+ scope.user.name.set('Bob'); // writes through to the parent
130
+ scope.draft.set('hello'); // local only — `app` never gains `draft`
131
+ scope(); // { user: { name: 'Bob' }, theme: 'dark', draft: 'hello' }
132
+ ```
133
+
134
+ Resolution per key is **local → parent → local**: a seed key (or one set on the scope before it exists on the parent) is local and _shadows_ the parent — and keeps shadowing even if the parent later grows that key; a key that exists only on the parent writes through to it; a brand-new key lands locally. `scope()` is the merged view (local shadowing), and `Object.keys(scope)` / `key in scope` are the union of both layers. `extend` composes — `a.extend(x).extend(y)` chains parents.
135
+
136
+ The seed may also be a **signal** of the matching kind, so an existing (externally-owned, reactive) signal becomes the local layer:
137
+
138
+ ```typescript
139
+ const draft = signal({ title: '' });
140
+ const scope = app.extend(draft); // writes to scope.title flow out to `draft`, and back in
141
+ ```
142
+
143
+ A few release notes:
144
+
145
+ - The local layer is a plain store (vivify off). Inherited paths vivify when the _parent_ was created with `vivify`; to autovivify local keys, seed with a vivify-enabled store — `app.extend(store(seed, { vivify: 'auto' }))`.
146
+ - Reserved names — `extend`, `asReadonlyStore`, and the signal methods (`set` / `update` / `mutate` / `inline` / `asReadonly`) — shadow same-named data keys, as on any store.
147
+ - `scope.asReadonlyStore()` returns a read-only **snapshot view** of the merge (reactive reads, no writes); it does not share sub-store identity.
148
+
97
149
  ### `toWritable`
98
150
 
99
151
  Turn any read-only `Signal<T>` into a `WritableSignal<T>` by providing custom `set` / `update` implementations. Powers `derived` internally; use it directly when you have a `computed` you want to expose as writable.
@@ -399,37 +399,158 @@ function isMutable(value) {
399
399
  return 'mutate' in value && typeof value.mutate === 'function';
400
400
  }
401
401
 
402
+ /**
403
+ * @internal
404
+ * Type guard for an array-index-like property key: a non-empty string that parses to a finite
405
+ * number (e.g. `'0'`, `'42'`). Used to choose array-vs-object shape during autovivification and
406
+ * deep store proxying.
407
+ */
408
+ function isIndexProp(prop) {
409
+ return typeof prop === 'string' && prop.trim() !== '' && !isNaN(+prop);
410
+ }
411
+
412
+ // Container resolvers used by createVivify: each returns the current value when present and
413
+ // only creates a new container when it is null/undefined.
414
+ function identity(x) {
415
+ return x;
416
+ }
417
+ function createArray(cur) {
418
+ if (cur === null || cur === undefined)
419
+ return [];
420
+ return cur;
421
+ }
422
+ function createObject(cur) {
423
+ if (cur === null || cur === undefined)
424
+ return {};
425
+ return cur;
426
+ }
427
+ function createAuto(cur, key) {
428
+ if (cur === null || cur === undefined) {
429
+ return typeof key === 'number' || isIndexProp(key)
430
+ ? []
431
+ : {};
432
+ }
433
+ return cur;
434
+ }
435
+ /**
436
+ * @internal
437
+ * Resolves a {@link Vivify} option into a {@link VivifyFn}. The returned function leaves a
438
+ * present value untouched and only creates a new container — object, array, or factory result —
439
+ * when the current value is `null`/`undefined`.
440
+ */
441
+ function createVivify(option) {
442
+ switch (option) {
443
+ case false:
444
+ return identity;
445
+ case 'array':
446
+ return createArray;
447
+ case 'object':
448
+ return createObject;
449
+ case 'auto':
450
+ case true:
451
+ return createAuto;
452
+ default:
453
+ return typeof option === 'function'
454
+ ? (cur) => cur === null || cur === undefined ? option() : cur
455
+ : identity;
456
+ }
457
+ }
458
+
459
+ function createMutableArrayUpdater(source, index, vivifyFn) {
460
+ return (next) => source.mutate((cur) => {
461
+ const vivified = vivifyFn(cur, index);
462
+ if (vivified === null || vivified === undefined)
463
+ return vivified;
464
+ vivified[index] = next;
465
+ return vivified;
466
+ });
467
+ }
468
+ function createImmutableArrayUpdater(source, index, vivifyFn) {
469
+ return (next) => source.update((cur) => {
470
+ const vivified = vivifyFn(cur, index)?.slice();
471
+ if (vivified === null || vivified === undefined)
472
+ return vivified;
473
+ vivified[index] = next;
474
+ return vivified;
475
+ });
476
+ }
477
+ function createMutableObjectUpdater(source, key, vivifyFn) {
478
+ return (next) => source.mutate((cur) => {
479
+ const vivified = vivifyFn(cur, key);
480
+ if (vivified === null || vivified === undefined)
481
+ return vivified;
482
+ vivified[key] = next;
483
+ return vivified;
484
+ });
485
+ }
486
+ function createImmutableObjectUpdater(source, key, vivifyFn) {
487
+ return (next) => source.update((cur) => {
488
+ const vivified = vivifyFn(cur, key);
489
+ if (vivified === null || vivified === undefined)
490
+ return vivified;
491
+ return { ...vivified, [key]: next };
492
+ });
493
+ }
494
+ function createUpdater(source, key, vivify) {
495
+ const sample = untracked(source);
496
+ // fast path for when vivification is off
497
+ if (!vivify) {
498
+ if (Array.isArray(sample) && typeof key === 'number') {
499
+ const idx = key;
500
+ return isMutable(source)
501
+ ? (next) => source.mutate((cur) => {
502
+ cur[idx] = next;
503
+ return cur;
504
+ })
505
+ : (next) => source.update((cur) => {
506
+ const copy = cur.slice();
507
+ copy[idx] = next;
508
+ return copy;
509
+ });
510
+ }
511
+ return isMutable(source)
512
+ ? (next) => source.mutate((cur) => {
513
+ cur[key] = next;
514
+ return cur;
515
+ })
516
+ : (next) => source.update((cur) => ({
517
+ ...cur,
518
+ [key]: next,
519
+ }));
520
+ }
521
+ const present = sample !== null && sample !== undefined;
522
+ const keyIsIndex = typeof key === 'number' || isIndexProp(key);
523
+ let vivifyOpt = vivify;
524
+ if (vivifyOpt === 'auto' || vivifyOpt === true) {
525
+ vivifyOpt = ((present ? Array.isArray(sample) : keyIsIndex) ? 'array' : 'object');
526
+ }
527
+ const vivifyFn = createVivify(vivifyOpt);
528
+ // Route to the array updater whenever the container is (or will be vivified as) an
529
+ // array, so the updater and the created container agree on shape for a nullish source.
530
+ const isArray = vivifyOpt === 'array'
531
+ ? keyIsIndex
532
+ : vivifyOpt === 'object'
533
+ ? false
534
+ : Array.isArray(sample) && typeof key === 'number';
535
+ if (isArray)
536
+ return isMutable(source)
537
+ ? createMutableArrayUpdater(source, key, vivifyFn)
538
+ : createImmutableArrayUpdater(source, key, vivifyFn);
539
+ return isMutable(source)
540
+ ? createMutableObjectUpdater(source, key, vivifyFn)
541
+ : createImmutableObjectUpdater(source, key, vivifyFn);
542
+ }
402
543
  function derived(source, optOrKey, opt) {
403
- const isArray = Array.isArray(untracked(source)) && typeof optOrKey === 'number';
404
- const from = typeof optOrKey === 'object' ? optOrKey.from : (v) => v[optOrKey];
544
+ const vivify = typeof optOrKey === 'object' ? false : (opt?.vivify ?? false);
545
+ // With vivification the source may legitimately be null/undefined
546
+ const from = typeof optOrKey === 'object'
547
+ ? optOrKey.from
548
+ : vivify
549
+ ? (v) => v?.[optOrKey]
550
+ : (v) => v[optOrKey];
405
551
  const onChange = typeof optOrKey === 'object'
406
552
  ? optOrKey.onChange
407
- : isArray
408
- ? isMutable(source)
409
- ? (next) => {
410
- source.mutate((cur) => {
411
- cur[optOrKey] = next;
412
- return cur;
413
- });
414
- }
415
- : (next) => {
416
- source.update((cur) => {
417
- const newArray = [...cur];
418
- newArray[optOrKey] = next;
419
- return newArray;
420
- });
421
- }
422
- : isMutable(source)
423
- ? (next) => {
424
- source.mutate((cur) => {
425
- cur[optOrKey] =
426
- next;
427
- return cur;
428
- });
429
- }
430
- : (next) => {
431
- source.update((cur) => ({ ...cur, [optOrKey]: next }));
432
- };
553
+ : createUpdater(source, optOrKey, vivify);
433
554
  const rest = typeof optOrKey === 'object' ? { ...optOrKey, ...opt } : opt;
434
555
  const baseEqual = rest?.equal ?? Object.is;
435
556
  let cnt = 0;
@@ -2282,6 +2403,12 @@ function signalFromEvent(target, eventName, initial, projectOrOpt, maybeOpt) {
2282
2403
  }
2283
2404
 
2284
2405
  const IS_STORE = Symbol('MMSTACK::IS_STORE');
2406
+ const SCOPE_PARENT = Symbol('MMSTACK::SCOPE_PARENT');
2407
+ /**
2408
+ * @internal
2409
+ * Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
2410
+ * Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
2411
+ */
2285
2412
  const PROXY_CACHE = new WeakMap();
2286
2413
  const SIGNAL_FN_PROP = new Set([
2287
2414
  'set',
@@ -2290,6 +2417,11 @@ const SIGNAL_FN_PROP = new Set([
2290
2417
  'inline',
2291
2418
  'asReadonly',
2292
2419
  ]);
2420
+ /**
2421
+ * @internal
2422
+ * Test-only handle on the finalization registry (deliberately NOT re-exported from the public
2423
+ * barrel). Prunes a cache entry once its proxy is reclaimed by the GC.
2424
+ */
2293
2425
  const PROXY_CLEANUP = new FinalizationRegistry(({ target, prop }) => {
2294
2426
  const storeCache = PROXY_CACHE.get(target);
2295
2427
  if (storeCache)
@@ -2304,20 +2436,35 @@ function isStore(value) {
2304
2436
  value !== null &&
2305
2437
  value[IS_STORE] === true);
2306
2438
  }
2307
- function isIndexProp(prop) {
2308
- return typeof prop === 'string' && prop.trim() !== '' && !isNaN(+prop);
2309
- }
2310
2439
  function isRecord(value) {
2311
2440
  if (value === null || typeof value !== 'object')
2312
2441
  return false;
2313
2442
  const proto = Object.getPrototypeOf(value);
2314
2443
  return proto === Object.prototype || proto === null;
2315
2444
  }
2445
+ /**
2446
+ * @internal
2447
+ * Resolves the vivify shape for a node from its current value: a present record/array is a
2448
+ * certainty we keep (cached in the derivation, so it survives the value being nulled); an
2449
+ * unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
2450
+ */
2451
+ function resolveVivify(sample, option) {
2452
+ if (!option)
2453
+ return false;
2454
+ if (Array.isArray(sample))
2455
+ return 'array';
2456
+ if (isRecord(sample))
2457
+ return 'object';
2458
+ return 'auto';
2459
+ }
2460
+ function hasOwnKey(value, key) {
2461
+ return value != null && Object.hasOwn(value, key);
2462
+ }
2316
2463
  /**
2317
2464
  * @internal
2318
2465
  * Makes an array store
2319
2466
  */
2320
- function toArrayStore(source, injector) {
2467
+ function toArrayStore(source, injector, vivify) {
2321
2468
  if (isStore(source))
2322
2469
  return source;
2323
2470
  const isMutableSource = isMutable(source);
@@ -2397,31 +2544,39 @@ function toArrayStore(source, injector) {
2397
2544
  const value = untracked(target);
2398
2545
  const valueIsArray = Array.isArray(value);
2399
2546
  const valueIsRecord = isRecord(value);
2547
+ const nodeVivify = resolveVivify(value, vivify);
2548
+ const vivifyFn = createVivify(nodeVivify);
2400
2549
  const equalFn = (valueIsRecord || valueIsArray) &&
2401
2550
  isMutableSource &&
2402
2551
  typeof value[idx] === 'object'
2403
2552
  ? () => false
2404
2553
  : undefined;
2405
2554
  const computation = valueIsRecord
2406
- ? derived(target, idx, { equal: equalFn })
2555
+ ? derived(target, idx, {
2556
+ equal: equalFn,
2557
+ vivify: nodeVivify,
2558
+ })
2407
2559
  : derived(target, {
2408
2560
  from: (v) => v?.[idx],
2409
2561
  onChange: (newValue) => target.update((v) => {
2410
- if (v === null || v === undefined)
2411
- return v;
2562
+ const container = vivifyFn(v, idx);
2563
+ if (container === null || container === undefined)
2564
+ return container;
2412
2565
  try {
2413
- v[idx] = newValue;
2566
+ container[idx] = newValue;
2414
2567
  }
2415
2568
  catch (e) {
2416
2569
  if (isDevMode())
2417
2570
  console.error(`[store] Failed to set property "${String(idx)}"`, e);
2418
2571
  }
2419
- return v;
2572
+ return container;
2420
2573
  }),
2421
2574
  });
2422
- const proxy = Array.isArray(untracked(computation))
2423
- ? toArrayStore(computation, injector)
2424
- : toStore(computation, injector);
2575
+ const childSample = untracked(computation);
2576
+ const childVivify = resolveVivify(childSample, vivify);
2577
+ const proxy = Array.isArray(childSample)
2578
+ ? toArrayStore(computation, injector, childVivify)
2579
+ : toStore(computation, injector, childVivify);
2425
2580
  const ref = new WeakRef(proxy);
2426
2581
  storeCache.set(idx, ref);
2427
2582
  PROXY_CLEANUP.register(proxy, { target, prop: idx }, ref);
@@ -2438,7 +2593,7 @@ function toArrayStore(source, injector) {
2438
2593
  * const state = store({ user: { name: 'John' } });
2439
2594
  * const nameSignal = state.user.name; // WritableSignal<string>
2440
2595
  */
2441
- function toStore(source, injector) {
2596
+ function toStore(source, injector, vivify = false) {
2442
2597
  if (isStore(source))
2443
2598
  return source;
2444
2599
  if (!injector)
@@ -2448,7 +2603,8 @@ function toStore(source, injector) {
2448
2603
  : toWritable(source, () => {
2449
2604
  // noop
2450
2605
  });
2451
- const isMutableSource = isMutable(writableSource);
2606
+ const isWritableSource = isWritableSignal(source);
2607
+ const isMutableSource = isWritableSource && isMutable(writableSource);
2452
2608
  const s = new Proxy(writableSource, {
2453
2609
  has(_, prop) {
2454
2610
  return Reflect.has(untracked(source), prop);
@@ -2476,10 +2632,16 @@ function toStore(source, injector) {
2476
2632
  return true;
2477
2633
  if (prop === 'asReadonlyStore')
2478
2634
  return () => {
2479
- if (!isWritableSignal(source))
2635
+ if (!isWritableSource)
2480
2636
  return s;
2481
- return untracked(() => toStore(source.asReadonly(), injector));
2637
+ return untracked(() => toStore(source.asReadonly(), injector, vivify));
2482
2638
  };
2639
+ if (prop === 'extend')
2640
+ return (seed) => scopedStore(s, seed, isMutableSource
2641
+ ? 'mutable'
2642
+ : isWritableSource
2643
+ ? 'writable'
2644
+ : 'readonly', injector);
2483
2645
  if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
2484
2646
  return target[prop];
2485
2647
  let storeCache = PROXY_CACHE.get(target);
@@ -2498,31 +2660,36 @@ function toStore(source, injector) {
2498
2660
  const value = untracked(target);
2499
2661
  const valueIsRecord = isRecord(value);
2500
2662
  const valueIsArray = Array.isArray(value);
2663
+ const nodeVivify = resolveVivify(value, vivify);
2664
+ const vivifyFn = createVivify(nodeVivify);
2501
2665
  const equalFn = (valueIsRecord || valueIsArray) &&
2502
2666
  isMutableSource &&
2503
2667
  typeof value[prop] === 'object'
2504
2668
  ? () => false
2505
2669
  : undefined;
2506
2670
  const computation = valueIsRecord
2507
- ? derived(target, prop, { equal: equalFn })
2671
+ ? derived(target, prop, { equal: equalFn, vivify: nodeVivify })
2508
2672
  : derived(target, {
2509
2673
  from: (v) => v?.[prop],
2510
2674
  onChange: (newValue) => target.update((v) => {
2511
- if (v === null || v === undefined)
2512
- return v;
2675
+ const container = vivifyFn(v, prop);
2676
+ if (container === null || container === undefined)
2677
+ return container;
2513
2678
  try {
2514
- v[prop] = newValue;
2679
+ container[prop] = newValue;
2515
2680
  }
2516
2681
  catch (e) {
2517
2682
  if (isDevMode())
2518
2683
  console.error(`[store] Failed to set property "${String(prop)}"`, e);
2519
2684
  }
2520
- return v;
2685
+ return container;
2521
2686
  }),
2522
2687
  });
2523
- const proxy = Array.isArray(untracked(computation))
2524
- ? toArrayStore(computation, injector)
2525
- : toStore(computation, injector);
2688
+ const childSample = untracked(computation);
2689
+ const childVivify = resolveVivify(childSample, vivify);
2690
+ const proxy = Array.isArray(childSample)
2691
+ ? toArrayStore(computation, injector, childVivify)
2692
+ : toStore(computation, injector, childVivify);
2526
2693
  const ref = new WeakRef(proxy);
2527
2694
  storeCache.set(prop, ref);
2528
2695
  PROXY_CLEANUP.register(proxy, { target, prop }, ref);
@@ -2531,19 +2698,103 @@ function toStore(source, injector) {
2531
2698
  });
2532
2699
  return s;
2533
2700
  }
2701
+ /**
2702
+ * @internal
2703
+ * Backs `store.extend(...)`. Builds a scoped overlay over `parent`: the local layer (the seed
2704
+ * plus any keys created later) is its own signal and `parent` is its own signal, so the getter
2705
+ * routes each key by consulting BOTH — local first, then parent, else local (so a write to an
2706
+ * as-yet-unknown key lands locally). Inherited keys return the parent's own sub-store (shared
2707
+ * identity + two-way), while local keys never propagate upward. A merged `computed` is derived
2708
+ * only for whole-object reads / `has` / iteration — never for routing.
2709
+ */
2710
+ function scopedStore(parent, seed, kind, injector) {
2711
+ const local = isSignal(seed)
2712
+ ? toStore(seed, injector)
2713
+ : kind === 'mutable'
2714
+ ? mutableStore(seed, { injector })
2715
+ : kind === 'readonly'
2716
+ ? store(seed, { injector }).asReadonlyStore()
2717
+ : store(seed, { injector });
2718
+ const localValue = () => untracked(local);
2719
+ const parentValue = () => untracked(parent);
2720
+ const view = computed(() => ({
2721
+ ...parent(),
2722
+ ...local(),
2723
+ }));
2724
+ const splitSet = (next) => {
2725
+ const lv = localValue();
2726
+ const pv = parentValue();
2727
+ for (const key of Reflect.ownKeys(next)) {
2728
+ const layer = hasOwnKey(lv, key)
2729
+ ? local
2730
+ : hasOwnKey(pv, key)
2731
+ ? parent
2732
+ : local;
2733
+ layer[key].set(next[key]);
2734
+ }
2735
+ };
2736
+ const base = toWritable(view, kind === 'readonly' ? () => undefined : splitSet, undefined, { pure: false });
2737
+ if (kind === 'mutable') {
2738
+ base.mutate = (updater) => splitSet(updater(untracked(view)));
2739
+ base.inline = (updater) => base.mutate((prev) => {
2740
+ updater(prev);
2741
+ return prev;
2742
+ });
2743
+ }
2744
+ const scope = new Proxy(base, {
2745
+ get(target, prop) {
2746
+ if (prop === IS_STORE)
2747
+ return true;
2748
+ if (prop === SCOPE_PARENT)
2749
+ return parent;
2750
+ if (prop === 'extend')
2751
+ return (childSeed) => scopedStore(scope, childSeed, kind, injector);
2752
+ if (prop === 'asReadonlyStore')
2753
+ return () => toStore(computed(() => ({ ...parent(), ...local() })), injector);
2754
+ if (typeof prop === 'symbol' || SIGNAL_FN_PROP.has(prop))
2755
+ return target[prop];
2756
+ // Route by consulting both signals: local first, then parent, else local (new → local).
2757
+ if (hasOwnKey(localValue(), prop))
2758
+ return local[prop];
2759
+ if (hasOwnKey(parentValue(), prop))
2760
+ return parent[prop];
2761
+ return local[prop];
2762
+ },
2763
+ has(_, prop) {
2764
+ return hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop);
2765
+ },
2766
+ ownKeys() {
2767
+ return [
2768
+ ...new Set([
2769
+ ...Reflect.ownKeys(parentValue()),
2770
+ ...Reflect.ownKeys(localValue()),
2771
+ ]),
2772
+ ];
2773
+ },
2774
+ getOwnPropertyDescriptor(_, prop) {
2775
+ if (hasOwnKey(localValue(), prop) || hasOwnKey(parentValue(), prop))
2776
+ return { enumerable: true, configurable: true };
2777
+ return undefined;
2778
+ },
2779
+ getPrototypeOf() {
2780
+ return Object.prototype;
2781
+ },
2782
+ });
2783
+ return scope;
2784
+ }
2534
2785
  /**
2535
2786
  * Creates a WritableSignalStore from a value.
2536
2787
  * @see {@link toStore}
2537
2788
  */
2538
2789
  function store(value, opt) {
2539
- return toStore(signal(value, opt), opt?.injector);
2790
+ return toStore(signal(value, opt), opt?.injector, opt?.vivify ?? false);
2540
2791
  }
2541
2792
  /**
2542
2793
  * Creates a MutableSignalStore from a value.
2543
2794
  * @see {@link toStore}
2544
2795
  */
2545
2796
  function mutableStore(value, opt) {
2546
- return toStore(mutable(value, opt), opt?.injector);
2797
+ return toStore(mutable(value, opt), opt?.injector, opt?.vivify ?? false);
2547
2798
  }
2548
2799
 
2549
2800
  // Internal dummy store for server-side rendering