@mmstack/primitives 19.5.1 → 19.6.0

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/lib/chunked.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type Injector, type Signal, type ValueEqualityFn } from '@angular/core';
2
+ import { type PauseOption } from './concurrent/pausable';
2
3
  export type CreateChunkedOptions<T> = {
3
4
  /**
4
5
  * The number of items to process in each chunk.
@@ -18,6 +19,12 @@ export type CreateChunkedOptions<T> = {
18
19
  * An optional `Injector` to use for the internal effect. This allows the effect to have access to dependency injection if needed.
19
20
  */
20
21
  injector?: Injector;
22
+ /**
23
+ * Opt-in pause: gate the chunk-scheduling effect on an ambient Activity boundary (`true`), a
24
+ * custom predicate, or `false` (default — no pausing). While paused, scheduling stops and resumes
25
+ * from the current chunk on resume. See {@link PauseOption}.
26
+ */
27
+ pause?: PauseOption;
21
28
  };
22
29
  /**
23
30
  * Creates a new `Signal` that processes an array of items in time-sliced chunks. This is useful for handling large lists without blocking the main thread.
@@ -0,0 +1,12 @@
1
+ import { type CreateEffectOptions, type EffectCleanupRegisterFn, type EffectRef } from '@angular/core';
2
+ import { type PausableOptions } from './pausable';
3
+ /**
4
+ * @internal The plain-`effect` sibling of the public {@link pausableEffect} (which is built on
5
+ * `nestedEffect`). For infra utilities that own a single top-level effect/subscription and don't
6
+ * need frame/nesting semantics. Opt-in (default off): with no `pause` (call site or
7
+ * `providePausableOptions` default) it returns a bare `effect` (zero overhead, byte-identical to
8
+ * today); otherwise it gates the body on the resolved predicate — read FIRST so the dependency set
9
+ * collapses to just the predicate while paused, re-tracking on resume. Deliberately NOT re-exported
10
+ * from the public barrel.
11
+ */
12
+ export declare function pausablePureEffect(effectFn: (registerCleanup: EffectCleanupRegisterFn) => void, options?: CreateEffectOptions & PausableOptions): EffectRef;
@@ -1,4 +1,4 @@
1
- import { type CreateComputedOptions, type CreateEffectOptions, type CreateSignalOptions, type EffectCleanupRegisterFn, type EffectRef, type Injector, type Signal, type WritableSignal } from '@angular/core';
1
+ import { type CreateComputedOptions, type CreateEffectOptions, type CreateSignalOptions, type EffectCleanupRegisterFn, type EffectRef, InjectionToken, type Injector, type Provider, type Signal, type WritableSignal } from '@angular/core';
2
2
  /**
3
3
  * How a pausable primitive decides whether it is currently paused:
4
4
  * - omitted (the default) or `true` — read the ambient {@link PAUSED_CONTEXT} (via `injector`, or the
@@ -20,6 +20,27 @@ export type PausableOptions = {
20
20
  */
21
21
  readonly injector?: Injector;
22
22
  };
23
+ /**
24
+ * @internal Token carrying an app-wide default {@link PauseOption}, set via
25
+ * {@link providePausableOptions}. {@link resolvePause} consults it when the call site didn't
26
+ * specify `pause`, so users can opt every pausable-aware primitive in (or out) from one place.
27
+ */
28
+ export declare const PAUSABLE_OPTIONS: InjectionToken<{
29
+ pause?: PauseOption;
30
+ }>;
31
+ /**
32
+ * Provides an app-wide default {@link PauseOption} for every pausable-aware primitive (the public
33
+ * `pausable*` family plus the opt-in integrations like `stored` / `chunked`). A call-site `pause`
34
+ * always wins; this only fills in when the call didn't specify one.
35
+ *
36
+ * @example
37
+ * // Make everything that can pause honour the ambient Activity boundary by default:
38
+ * providePausableOptions({ pause: true })
39
+ */
40
+ export declare function providePausableOptions(opt: {
41
+ /** Default pause source for pausable-aware primitives that don't set their own. */
42
+ pause?: PauseOption;
43
+ }): Provider;
23
44
  /**
24
45
  * Resolve a {@link PauseOption} into a pause predicate, or `null` meaning "do not pause".
25
46
  * `null` tells the caller to return the bare primitive — no wrapper is created.
@@ -34,7 +55,7 @@ export type PausableOptions = {
34
55
  *
35
56
  * Encapsulating this here keeps every pausable primitive's branching identical and in one place.
36
57
  */
37
- export declare function resolvePause(opt?: PausableOptions): (() => boolean) | null;
58
+ export declare function resolvePause(opt?: PausableOptions, defaultPause?: PauseOption): (() => boolean) | null;
38
59
  /**
39
60
  * Like {@link nestedEffect}, but pausable. While paused the effect does NOT run its body — and,
40
61
  * crucially, it reads the pause predicate FIRST, so while paused its dependency set collapses to just
@@ -14,6 +14,12 @@ import * as i0 from "@angular/core";
14
14
  *
15
15
  * `type` selects what "not ready" means: `'value'` (default) suspends only until a first value lands
16
16
  * then holds through reloads; `'loading'` suspends on every in-flight load (strict suspense).
17
+ *
18
+ * SSR: the server serializes whatever the scope reports at stabilization, so a registered resource
19
+ * must keep the app unstable until it settles or the placeholder is what gets serialized (then
20
+ * flashes/mismatches on hydration). HttpClient-backed resources, httpResource & all of `@mmstack/resource`
21
+ * do this automatically via the HTTP layer's `PendingTasks` + transfer cache. A custom loader (raw
22
+ * `fetch`/promise/timer) must opt in itself: wrap it with `inject(PendingTasks).run(() => promise)`.
17
23
  */
18
24
  export declare abstract class SuspenseBoundaryBase {
19
25
  protected readonly scope: import("@mmstack/primitives").TransitionScope;
@@ -10,6 +10,7 @@ export * from './media-query';
10
10
  export * from './mouse-position';
11
11
  export * from './network-status';
12
12
  export * from './orientation';
13
+ export * from './pointer-drag';
13
14
  export * from './page-visibility';
14
15
  export * from './scroll-position';
15
16
  export * from './sensor';
@@ -0,0 +1,88 @@
1
+ import { ElementRef, type Signal } from '@angular/core';
2
+ import { type SensorRunOptions } from './sensor-options';
3
+ export type PointerPoint = {
4
+ x: number;
5
+ y: number;
6
+ };
7
+ export type PointerModifiers = {
8
+ shift: boolean;
9
+ alt: boolean;
10
+ ctrl: boolean;
11
+ meta: boolean;
12
+ };
13
+ export type PointerDragState = {
14
+ /** A gesture is past the activation threshold (distinguishes a drag from a click). */
15
+ active: boolean;
16
+ /** Pointer position at the pointerdown that began the gesture. */
17
+ start: PointerPoint;
18
+ /** Latest pointer position. */
19
+ current: PointerPoint;
20
+ /** `current - start`, computed on the same update as `current` (never torn). */
21
+ delta: PointerPoint;
22
+ /** The captured pointer id, or `null` when idle. */
23
+ pointerId: number | null;
24
+ /** Modifier keys at the latest update. */
25
+ modifiers: PointerModifiers;
26
+ /** Mouse button that started the gesture (`-1` when idle). */
27
+ button: number;
28
+ /** The pointing device: `'mouse' | 'touch' | 'pen'` (`''` when idle). */
29
+ pointerType: string;
30
+ /**
31
+ * The element the gesture started on: the `handleSelector` match when one is
32
+ * set (so a single delegated listener can tell which child started the drag),
33
+ * otherwise the listener's element. `null` when idle.
34
+ */
35
+ origin: HTMLElement | null;
36
+ };
37
+ export type PointerDragOptions = SensorRunOptions & {
38
+ /**
39
+ * Element that receives `pointerdown`. An `HTMLElement`, `ElementRef`, or a
40
+ * `Signal` of one (listeners re-attach when it changes). Defaults to the host
41
+ * `ElementRef`.
42
+ */
43
+ target?: HTMLElement | ElementRef<HTMLElement> | Signal<HTMLElement | ElementRef<HTMLElement> | null | undefined>;
44
+ /** `'client'` (viewport) or `'page'` coordinates. @default 'client' */
45
+ coordinateSpace?: 'client' | 'page';
46
+ /** Pixels the pointer must travel before `active` flips true. @default 3 */
47
+ activationThreshold?: number;
48
+ /**
49
+ * Throttle (ms) for `current`/`delta` updates. @default 16
50
+ *
51
+ * Note: a final sub-throttle move right before `pointerup` may not surface on
52
+ * the throttled view (it coalesces into the terminal idle). Logic that must act
53
+ * on the *exact* release position should read {@link PointerDragSignal.unthrottled}.
54
+ */
55
+ throttle?: number;
56
+ /** Only start when the pointerdown target matches this selector (delegated handles). */
57
+ handleSelector?: string;
58
+ /** Mouse buttons that may start a gesture. @default [0] (primary) */
59
+ buttons?: number[];
60
+ /**
61
+ * Stop the `pointerdown` from propagating once this sensor claims it. Lets an
62
+ * inner sensor win over an outer one on the same element tree (e.g. a nested
63
+ * sortable inside another). @default false
64
+ */
65
+ stopPropagation?: boolean;
66
+ };
67
+ /** A gesture signal with an `unthrottled` view and an imperative `cancel()`. */
68
+ export type PointerDragSignal = Signal<PointerDragState> & {
69
+ readonly unthrottled: Signal<PointerDragState>;
70
+ /** Abort the current gesture and reset to idle (e.g. on Escape / programmatically). */
71
+ cancel: () => void;
72
+ };
73
+ /**
74
+ * Tracks a pointer *gesture* (pointerdown → capture → move → up) as a signal —
75
+ * the foundation for pointer-based drag/move/resize/marquee on a canvas. Unlike
76
+ * native HTML5 drag, pointer events fire continuously and coordinates are
77
+ * reliable. SSR-safe; cleans up its listeners automatically.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * const drag = pointerDrag({ activationThreshold: 4 });
82
+ * const position = computed(() => {
83
+ * const d = drag();
84
+ * return d.active ? { x: base.x + d.delta.x, y: base.y + d.delta.y } : base;
85
+ * });
86
+ * ```
87
+ */
88
+ export declare function pointerDrag(opt?: PointerDragOptions): PointerDragSignal;
@@ -8,6 +8,7 @@ import { type GeolocationOptions, type GeolocationSignal } from './geolocation';
8
8
  import { type IdleOptions, type IdleSignal } from './idle';
9
9
  import { type MousePositionOptions, type MousePositionSignal } from './mouse-position';
10
10
  import { type NetworkStatusSignal } from './network-status';
11
+ import { type PointerDragOptions, type PointerDragSignal } from './pointer-drag';
11
12
  import { type ScreenOrientationState } from './orientation';
12
13
  import { type ScrollPositionOptions, type ScrollPositionSignal } from './scroll-position';
13
14
  import { type WindowSizeOptions, type WindowSizeSignal } from './window-size';
@@ -28,6 +29,10 @@ type SensorTypedOptions = {
28
29
  opt: MousePositionOptions;
29
30
  returnType: MousePositionSignal;
30
31
  };
32
+ pointerDrag: {
33
+ opt: PointerDragOptions;
34
+ returnType: PointerDragSignal;
35
+ };
31
36
  networkStatus: {
32
37
  opt: SensorRunOptions;
33
38
  returnType: NetworkStatusSignal;
@@ -112,6 +117,15 @@ export declare function sensor(type: 'elementSize', options?: SensorTypedOptions
112
117
  * @example const pos = sensor('mousePosition', { coordinateSpace: 'page', throttle: 50 });
113
118
  */
114
119
  export declare function sensor(type: 'mousePosition', options?: SensorTypedOptions['mousePosition']['opt']): MousePositionSignal;
120
+ /**
121
+ * Creates a sensor signal tracking a pointer drag gesture (down → move → up).
122
+ * @param type Must be `'pointerDrag'`.
123
+ * @param options Optional configuration (target, activationThreshold, throttle, …).
124
+ * @returns A `PointerDragSignal` with `active`/`start`/`current`/`delta`, plus `unthrottled` and `cancel()`.
125
+ * @see {pointerDrag} for detailed documentation and examples.
126
+ * @example const drag = sensor('pointerDrag', { activationThreshold: 4 });
127
+ */
128
+ export declare function sensor(type: 'pointerDrag', options?: SensorTypedOptions['pointerDrag']['opt']): PointerDragSignal;
115
129
  /**
116
130
  * Creates a sensor signal that tracks the browser's online/offline status.
117
131
  * @param type Must be `'networkStatus'`.
@@ -1,6 +1,5 @@
1
- import { type Injector } from '@angular/core';
2
- import { type Vivify } from '../util';
3
- import { type WritableSignalStore } from './store';
1
+ import { type toStoreOptions } from './store';
2
+ import type { WritableSignalStore } from './types';
4
3
  /**
5
4
  * A 3-way merge of a forked value against a changed base: given the common `ancestor` (the base
6
5
  * value the fork last diverged from), `mine` (the fork's current value), and `theirs` (the base
@@ -61,19 +60,10 @@ export type Fork<T> = {
61
60
  * leaves compare by value, so equal primitives are correctly seen as unchanged.
62
61
  */
63
62
  export declare function merge3<T>(ancestor: T, mine: T, theirs: T): T;
64
- export declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: {
63
+ /**
64
+ * ForkStoreOptions, vivify/noUnionLeaves should remain the same & are automatically inherited, override carefully (advanced usecases)
65
+ */
66
+ export type ForkStoreOptions<T> = toStoreOptions & {
65
67
  strategy?: ForkStrategy<T>;
66
- injector?: Injector;
67
- /**
68
- * Store config for the FORK's store — NOT inherited from `base` (it's closed over inside
69
- * the base's `toStore` and can't be read back). If the base was created with these, pass
70
- * the same values or the fork's write semantics will differ:
71
- * - `vivify`: without it, a write through a `null`/`undefined` path is silently dropped on
72
- * the fork even though the base would have created the container. Match the base.
73
- * - `noUnionLeaves`: a perf promise; off just means the slower reactive leaf-probe. NOTE it
74
- * is a whole-store guarantee — a fork that flips a node's type (leaf↔substore) violates it,
75
- * and on `commit` the base receives the flipped value with stale cached leaf-ness.
76
- */
77
- vivify?: Vivify;
78
- noUnionLeaves?: boolean;
79
- }): Fork<T>;
68
+ };
69
+ export declare function forkStore<T extends Record<string, any>>(base: WritableSignalStore<T>, opt?: ForkStoreOptions<T>): Fork<T>;
@@ -0,0 +1,34 @@
1
+ import { InjectionToken, type Signal } from '@angular/core';
2
+ import { type SignalStore } from './types';
3
+ export declare const IS_STORE: unique symbol;
4
+ export declare const SCOPE_PARENT: unique symbol;
5
+ export declare const STORE_SHARED_GLOBALS: unique symbol;
6
+ export declare const STORE_SHARED_OPTIONS: unique symbol;
7
+ /**
8
+ * @internal Brand carrying a store's writability ('mutable' | 'writable' | 'readonly'), stamped
9
+ * on every store proxy. Read by `extendStore` instead of re-deriving via `isWritableSignal`,
10
+ * which would mis-classify a readonly scoped store (its backing `toWritable` still has a `set`).
11
+ */
12
+ export declare const STORE_KIND: unique symbol;
13
+ export type StoreKind = 'mutable' | 'writable' | 'readonly';
14
+ export declare const SIGNAL_FN_PROP: Set<string>;
15
+ /**
16
+ * @internal
17
+ * Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
18
+ */
19
+ export type ProxyCache = WeakMap<object, Map<PropertyKey, WeakRef<Signal<any>>>>;
20
+ /**
21
+ * @internal
22
+ * Prunes a cache entry once its proxy is reclaimed by the GC.
23
+ */
24
+ export type ProxyCleanupRegistry = FinalizationRegistry<{
25
+ target: object;
26
+ prop: PropertyKey;
27
+ }>;
28
+ export declare const PROXY_CACHE_TOKEN: InjectionToken<ProxyCache>;
29
+ export declare const PROXY_CLEANUP_TOKEN: InjectionToken<ProxyCleanupRegistry>;
30
+ /**
31
+ * @internal
32
+ * Validates whether a value is a Signal Store.
33
+ */
34
+ export declare function isStore<T>(value: unknown): value is SignalStore<T>;
@@ -0,0 +1,36 @@
1
+ import { type Signal } from '@angular/core';
2
+ /**
3
+ * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
4
+ * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
5
+ * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
6
+ */
7
+ export declare const LEAF: unique symbol;
8
+ /**
9
+ * @internal Attaches a lazy, memoized leaf probe to a store node. The probe (`() => boolean`)
10
+ * closes over the node's value signal and its (stable) vivify setting, building the backing
11
+ * `computed` on first call so leaf-ness tracks the live value reactively without taxing every
12
+ * node access. Under `noUnionLeaves` the caller promises shapes never flip, so the probe is
13
+ * resolved once from the first sample and frozen as a constant. Idempotent.
14
+ */
15
+ export declare function markAsLeaf<TSig>(sig: TSig, value: Signal<unknown>, vivifyEnabled: boolean, noUnionLeaves: boolean): TSig & {
16
+ readonly [LEAF]: () => boolean;
17
+ };
18
+ /**
19
+ * Reports whether a store node is currently a **leaf** — a terminal value the store does not
20
+ * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
21
+ * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
22
+ *
23
+ * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
24
+ * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
25
+ *
26
+ * @internal Exposed for advanced/niche interop only — not part of the supported public surface
27
+ * and may change without a major version bump.
28
+ *
29
+ * @example
30
+ * const s = store({ name: 'Ada', address: { city: 'London' } });
31
+ * isLeaf(s.name); // true
32
+ * isLeaf(s.address); // false — a substore
33
+ */
34
+ export declare function isLeaf<T = unknown>(value: unknown): value is Signal<T> & {
35
+ readonly [LEAF]: () => boolean;
36
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Runtime marker + compile-time brand for an opaque value. A `const`-declared `Symbol`
3
+ * has a `unique symbol` type, so the same symbol serves as both the property key written
4
+ * by {@link opaque} and the type-level brand carried by {@link Opaque}.
5
+ */
6
+ export declare const OPAQUE: unique symbol;
7
+ /**
8
+ * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
9
+ * (returned whole, never deep-proxied) — the same way it treats a `Date` or `RegExp`.
10
+ * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
11
+ * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
12
+ *
13
+ * @example
14
+ * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
15
+ * s.config(); // the whole object, not a child store
16
+ * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
17
+ */
18
+ export declare function opaque<T extends object>(value: T): Opaque<T>;
19
+ /**
20
+ * Type guard companion to {@link opaque}: returns `true` when `value` carries the
21
+ * {@link OPAQUE} brand, narrowing it to {@link Opaque}. This is the same check the
22
+ * store uses to route opaque values to its leaf branch (alongside `Date`/`RegExp`).
23
+ *
24
+ * @internal Exposed for advanced/niche interop only — not part of the supported public
25
+ * surface and may change without a major version bump. Reach for {@link opaque} for
26
+ * normal usage.
27
+ *
28
+ * @example
29
+ * if (isOpaque(value)) {
30
+ * // value: Opaque<object> — `store` would treat it as an indivisible leaf
31
+ * }
32
+ */
33
+ export declare function isOpaque<T = object>(value: unknown): value is Opaque<T>;
34
+ /**
35
+ * An object marked via {@link opaque} — the store treats it as an indivisible leaf
36
+ * (like a `Date`), returning it whole instead of deep-proxying its keys.
37
+ */
38
+ export type Opaque<T> = T & {
39
+ readonly [OPAQUE]: true;
40
+ };
41
+ /** @internal Strips the opaque brand from the value a leaf signal carries. */
42
+ export type UnwrapOpaque<T> = T extends {
43
+ readonly [OPAQUE]: true;
44
+ } ? Omit<T, typeof OPAQUE> : T;
@@ -0,0 +1,28 @@
1
+ import { type WritableSignal } from '@angular/core';
2
+ import { type MutableSignal } from '../mutable';
3
+ import { type Vivify, type VivifyFn } from '../util';
4
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
5
+ /**
6
+ * @internal Whether a value is a terminal leaf: a concrete non-record/non-array value always is;
7
+ * `null`/`undefined` is a leaf only when vivification is disabled (with vivify on it can still
8
+ * materialize a container, so it stays a descendable substore).
9
+ */
10
+ export declare function isLeafValue(value: unknown, vivifyEnabled: boolean): boolean;
11
+ /**
12
+ * @internal
13
+ * Resolves the vivify shape for a node from its current value: a present record/array is a
14
+ * certainty we keep (cached in the derivation, so it survives the value being nulled); an
15
+ * unknown value (`null`/`undefined`) defers to the caller's option. Off stays off.
16
+ */
17
+ export declare function resolveVivify(sample: unknown, option: Vivify): Vivify;
18
+ export declare function hasOwnKey(value: object | null | undefined, key: PropertyKey): boolean;
19
+ /**
20
+ * @internal
21
+ * Builds the `onChange` for the fallback (non-record container) derivation branch. For an
22
+ * immutable source the container is copied before the write — returning the same mutated
23
+ * reference would let the source's equality cut propagation (leaving child signals permanently
24
+ * stale) and alias the caller's original object, breaking the structural-sharing contract
25
+ * `forkStore` relies on. For a mutable source the write goes through `mutate`, so the chain's
26
+ * force-notify engages (plain `update` with the same reference would never notify).
27
+ */
28
+ export declare function createFallbackOnChange(target: WritableSignal<any> | MutableSignal<any>, prop: PropertyKey, vivifyFn: VivifyFn<any>, isMutableSource: boolean): (newValue: any) => void;
@@ -1,2 +1,6 @@
1
1
  export * from './fork-store';
2
- export { isLeaf, isOpaque, isStore, mutableStore, opaque, store, toStore, type MutableSignalStore, type Opaque, type SignalStore, type WritableSignalStore, } from './store';
2
+ export { isStore } from './internals';
3
+ export { isLeaf } from './leaf';
4
+ export { isOpaque, opaque, type Opaque } from './opaque';
5
+ export { extendStore, mutableStore, store, toStore, type ExtendStoreOptions, type StoreOptions, type toStoreOptions, } from './store';
6
+ export type { MutableSignalStore, SignalStore, WritableSignalStore, } from './types';
@@ -1,176 +1,46 @@
1
1
  import { Injector, type CreateSignalOptions, type Signal, type WritableSignal } from '@angular/core';
2
2
  import { type MutableSignal } from '../mutable';
3
3
  import { type Vivify } from '../util';
4
- export declare function isWritableSignal<T>(value: Signal<T>): value is WritableSignal<T>;
5
- /**
6
- * Runtime marker + compile-time brand for an opaque value. A `const`-declared `Symbol`
7
- * has a `unique symbol` type, so the same symbol serves as both the property key written
8
- * by {@link opaque} and the type-level brand carried by {@link Opaque}.
9
- */
10
- export declare const OPAQUE: unique symbol;
11
- /**
12
- * Marks a plain object as opaque so {@link store} treats it as an indivisible leaf
13
- * (returned whole, never deep-proxied) the same way it treats a `Date` or `RegExp`.
14
- * The marker is a non-enumerable symbol, so it never appears in spreads or iteration.
15
- * Idempotent. Call before freezing (`defineProperty` fails on a frozen object).
16
- *
17
- * @example
18
- * const s = store({ config: opaque({ theme: 'dark', nested: { a: 1 } }) });
19
- * s.config(); // the whole object, not a child store
20
- * s.config.set(opaque({ theme: 'light', nested: { a: 2 } }));
21
- */
22
- export declare function opaque<T extends object>(value: T): Opaque<T>;
23
- /**
24
- * Type guard companion to {@link opaque}: returns `true` when `value` carries the
25
- * {@link OPAQUE} brand, narrowing it to {@link Opaque}. This is the same check the
26
- * store uses to route opaque values to its leaf branch (alongside `Date`/`RegExp`).
27
- *
28
- * @internal Exposed for advanced/niche interop only — not part of the supported public
29
- * surface and may change without a major version bump. Reach for {@link opaque} for
30
- * normal usage.
31
- *
32
- * @example
33
- * if (isOpaque(value)) {
34
- * // value: Opaque<object> — `store` would treat it as an indivisible leaf
35
- * }
36
- */
37
- export declare function isOpaque<T = object>(value: unknown): value is Opaque<T>;
38
- /**
39
- * @internal Runtime brand carrying a store node's lazily-built leaf probe. Exported (like
40
- * {@link OPAQUE}) only so the `{ readonly [LEAF]: () => boolean }` brand on the store types is
41
- * nameable in the emitted declarations — not part of the supported surface; use {@link isLeaf}.
42
- */
43
- export declare const LEAF: unique symbol;
44
- /**
45
- * Reports whether a store node is currently a **leaf** — a terminal value the store does not
46
- * descend into (a primitive, `Date`, `RegExp`, {@link opaque} object, class instance, or a
47
- * `null`/`undefined` hole when vivification is off) rather than a record/array substore.
48
- *
49
- * Leaf-ness reflects the node's **live** value: the probe is reactive and memoized, so calling
50
- * `isLeaf` inside a `computed`/`effect` re-evaluates when the node's shape changes.
51
- *
52
- * @internal Exposed for advanced/niche interop only — not part of the supported public surface
53
- * and may change without a major version bump.
54
- *
55
- * @example
56
- * const s = store({ name: 'Ada', address: { city: 'London' } });
57
- * isLeaf(s.name); // true
58
- * isLeaf(s.address); // false — a substore
59
- */
60
- export declare function isLeaf<T = unknown>(value: unknown): value is Signal<T> & {
61
- readonly [LEAF]: () => boolean;
62
- };
63
- /**
64
- * An object marked via {@link opaque} — the store treats it as an indivisible leaf
65
- * (like a `Date`), returning it whole instead of deep-proxying its keys.
66
- */
67
- export type Opaque<T> = T & {
68
- readonly [OPAQUE]: true;
69
- };
70
- /** @internal Strips the opaque brand from the value a leaf signal carries. */
71
- export type UnwrapOpaque<T> = T extends {
72
- readonly [OPAQUE]: true;
73
- } ? Omit<T, typeof OPAQUE> : T;
74
- type BaseType = string | number | boolean | symbol | bigint | undefined | null | Function | Date | RegExp | {
75
- readonly [OPAQUE]: true;
76
- };
77
- type Key = string | number;
78
- type AnyRecord = Record<Key, any>;
79
- /**
80
- * @internal
81
- * Test-only handle on the proxy cache (deliberately NOT re-exported from the public barrel).
82
- * Maps a store's backing signal to its lazily-built child proxies, each held via a `WeakRef`.
83
- */
84
- export declare const PROXY_CACHE: WeakMap<object, Map<PropertyKey, WeakRef<Signal<any>>>>;
85
- /**
86
- * @internal
87
- * Test-only handle on the finalization registry (deliberately NOT re-exported from the public
88
- * barrel). Prunes a cache entry once its proxy is reclaimed by the GC.
89
- */
90
- export declare const PROXY_CLEANUP: FinalizationRegistry<{
91
- target: object;
92
- prop: PropertyKey;
93
- }>;
94
- /**
95
- * @internal
96
- * Validates whether a value is a Signal Store.
97
- */
98
- export declare function isStore<T>(value: unknown): value is SignalStore<T>;
99
- type SignalArrayStore<T extends any[]> = Signal<T> & {
100
- readonly [index: number]: SignalStore<T[number]>;
101
- readonly length: Signal<number>;
102
- [Symbol.iterator](): Iterator<SignalStore<T[number]>>;
103
- };
104
- type WritableArrayStore<T extends any[]> = WritableSignal<T> & {
105
- readonly asReadonlyStore: () => SignalArrayStore<T>;
106
- readonly [index: number]: WritableSignalStore<T[number]>;
107
- readonly length: Signal<number>;
108
- [Symbol.iterator](): Iterator<WritableSignalStore<T[number]>>;
109
- };
110
- type MutableArrayStore<T extends any[]> = MutableSignal<T> & {
111
- readonly asReadonlyStore: () => SignalArrayStore<T>;
112
- readonly [index: number]: MutableSignalStore<T[number]>;
113
- readonly length: Signal<number>;
114
- [Symbol.iterator](): Iterator<MutableSignalStore<T[number]>>;
115
- };
116
- /**
117
- * @internal Resolves to `true` only for `any`. In a conditional type, `any` distributes across
118
- * *both* branches (`unknown | object`), and `unknown | X` collapses to `unknown` — which would
119
- * erase a store's property access and `extend`. Guarding on this routes an `any`-typed store to
120
- * the full object shape instead.
121
- */
122
- type IsAny<T> = 0 extends 1 & T ? true : false;
123
- /**
124
- * @internal Flattens an intersection (`A & B & C`) into a single object literal so editor
125
- * tooltips show the resolved members instead of the raw intersection chain. Display-only —
126
- * structurally identical to its input.
127
- */
128
- type Simplify<T> = {
129
- [K in keyof T]: T[K];
130
- } & {};
131
- /** @internal The object shape of a readonly store: a child store per key, plus `extend`. */
132
- type SignalStoreObject<T> = Simplify<Readonly<{
133
- [K in keyof Required<T>]: SignalStore<NonNullable<T>[K]>;
134
- }> & {
135
- readonly extend: {
136
- <L extends AnyRecord>(source: Signal<L>): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
137
- <L extends AnyRecord>(props: L): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
138
- };
139
- }>;
140
- /** @internal The object shape of a writable store. */
141
- type WritableSignalStoreObject<T> = Simplify<Readonly<{
142
- [K in keyof Required<T>]: WritableSignalStore<NonNullable<T>[K]>;
143
- }> & {
144
- readonly extend: {
145
- <L extends AnyRecord>(source: WritableSignal<L>): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
146
- <L extends AnyRecord>(props: L): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
147
- };
148
- }>;
149
- /** @internal The object shape of a mutable store. */
150
- type MutableSignalStoreObject<T> = Simplify<Readonly<{
151
- [K in keyof Required<T>]: MutableSignalStore<NonNullable<T>[K]>;
152
- }> & {
153
- readonly extend: {
154
- <L extends AnyRecord>(source: MutableSignal<L>): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
155
- <L extends AnyRecord>(props: L): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
4
+ import { STORE_SHARED_GLOBALS, type ProxyCache, type ProxyCleanupRegistry } from './internals';
5
+ import { type AnyRecord, type MutableSignalStore, type SignalStore, type Simplify, type WritableSignalStore } from './types';
6
+ export type toStoreOptions = {
7
+ injector?: Injector;
8
+ /**
9
+ * Opt-in autovivification: when writing through a `null`/`undefined` path, create the
10
+ * missing intermediate containers instead of dropping the write. Off by default.
11
+ *
12
+ * Levels whose current value is a known object/array re-vivify as that same shape the
13
+ * knowledge is captured when the path is first accessed and cached, so it holds even after
14
+ * the value is later nulled. This option governs only genuinely-unknown (currently
15
+ * `null`/`undefined`) levels: `'auto'` (an array for index keys, an object otherwise), an
16
+ * explicit `'object'`/`'array'`, or a `() => container` factory. See {@link Vivify}.
17
+ */
18
+ vivify?: Vivify;
19
+ /**
20
+ * Performance opt-in: promise that no node ever switches between leaf and substore (i.e. no
21
+ * unions mixing a primitive with an object/array). With this on, each node's leaf-ness is
22
+ * resolved once on the first {@link isLeaf} probe and cached as a constant, skipping the
23
+ * reactive `computed`. If a node's shape does change anyway, {@link isLeaf} keeps its first
24
+ * answer. Off by default.
25
+ */
26
+ noUnionLeaves?: boolean;
27
+ /**
28
+ * @internal
29
+ * Shared cleanup singletons, they get injected/passed automatically
30
+ */
31
+ [STORE_SHARED_GLOBALS]?: {
32
+ cache: ProxyCache;
33
+ registry: ProxyCleanupRegistry;
156
34
  };
157
- }>;
158
- export type SignalStore<T> = Signal<UnwrapOpaque<T>> & (IsAny<T> extends true ? SignalStoreObject<T> : NonNullable<T> extends BaseType ? {
159
- readonly [LEAF]: () => boolean;
160
- } : NonNullable<T> extends any[] ? SignalArrayStore<NonNullable<T>> : SignalStoreObject<T>);
161
- export type WritableSignalStore<T> = WritableSignal<UnwrapOpaque<T>> & {
162
- readonly asReadonlyStore: () => SignalStore<T>;
163
- } & (IsAny<T> extends true ? WritableSignalStoreObject<T> : NonNullable<T> extends BaseType ? {
164
- readonly [LEAF]: () => boolean;
165
- } : NonNullable<T> extends any[] ? WritableArrayStore<NonNullable<T>> : WritableSignalStoreObject<T>);
166
- export type MutableSignalStore<T> = MutableSignal<UnwrapOpaque<T>> & {
167
- readonly asReadonlyStore: () => SignalStore<T>;
168
- } & (IsAny<T> extends true ? MutableSignalStoreObject<T> : NonNullable<T> extends BaseType ? {
169
- readonly [LEAF]: () => boolean;
170
- } : NonNullable<T> extends any[] ? MutableArrayStore<NonNullable<T>> : MutableSignalStoreObject<T>);
171
- export declare function toStore<T extends AnyRecord>(source: MutableSignal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): MutableSignalStore<T>;
172
- export declare function toStore<T extends AnyRecord>(source: WritableSignal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): WritableSignalStore<T>;
173
- export declare function toStore<T extends AnyRecord>(source: Signal<T>, injector?: Injector, vivify?: Vivify, noUnionLeaves?: boolean): SignalStore<T>;
35
+ };
36
+ export type StoreOptions<T> = CreateSignalOptions<T> & toStoreOptions;
37
+ export declare function toStore<T extends AnyRecord>(source: MutableSignal<T>, options?: toStoreOptions): MutableSignalStore<T>;
38
+ export declare function toStore<T extends AnyRecord>(source: WritableSignal<T>, options?: toStoreOptions): WritableSignalStore<T>;
39
+ export declare function toStore<T extends AnyRecord>(source: Signal<T>, options?: toStoreOptions): SignalStore<T>;
40
+ export type ExtendStoreOptions = Omit<toStoreOptions, 'vivify' | 'noUnionLeaves' | typeof STORE_SHARED_GLOBALS>;
41
+ export declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: MutableSignalStore<T>, source: MutableSignal<L> | L, options?: ExtendStoreOptions): MutableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
42
+ export declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: WritableSignalStore<T>, source: WritableSignal<L> | L, options?: ExtendStoreOptions): WritableSignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
43
+ export declare function extendStore<T extends AnyRecord, L extends AnyRecord>(store: SignalStore<T>, source: Signal<L> | L, options?: ExtendStoreOptions): SignalStore<Simplify<Omit<NonNullable<T>, keyof L> & L>>;
174
44
  /**
175
45
  * Creates a WritableSignalStore from a value.
176
46
  * @see {@link toStore}
@@ -223,4 +93,3 @@ export declare function mutableStore<T extends AnyRecord>(value: T, opt?: Create
223
93
  */
224
94
  noUnionLeaves?: boolean;
225
95
  }): MutableSignalStore<T>;
226
- export {};