@solidjs/signals 2.0.0-rc.2 → 2.0.0-rc.4
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/dist/dev.js +1454 -118
- package/dist/node.cjs +2709 -1396
- package/dist/prod/affects.js +13 -12
- package/dist/prod/boundaries.js +39 -34
- package/dist/prod/core/action.js +3 -3
- package/dist/prod/core/async.js +48 -46
- package/dist/prod/core/core.js +99 -67
- package/dist/prod/core/effect.js +25 -28
- package/dist/prod/core/external.js +2 -2
- package/dist/prod/core/graph.js +85 -49
- package/dist/prod/core/heap.js +10 -10
- package/dist/prod/core/lanes.js +19 -19
- package/dist/prod/core/optimistic.js +66 -41
- package/dist/prod/core/owner.js +13 -13
- package/dist/prod/core/scheduler.js +131 -85
- package/dist/prod/core/verdict.js +36 -15
- package/dist/prod/index.js +4 -0
- package/dist/prod/map.js +101 -101
- package/dist/prod/signals.js +1 -1
- package/dist/prod/store/index.js +2 -0
- package/dist/prod/store/next/optimistic.js +65 -11
- package/dist/prod/store/next/patch-hooks.js +13 -0
- package/dist/prod/store/next/patch.js +614 -0
- package/dist/prod/store/next/projection.js +107 -45
- package/dist/prod/store/next/reconcile.js +307 -120
- package/dist/prod/store/next/store.js +321 -92
- package/dist/prod/store/next/target.js +13 -4
- package/dist/prod/store/store.js +5 -5
- package/dist/types/core/core.d.ts +15 -1
- package/dist/types/core/dev.d.ts +8 -0
- package/dist/types/core/graph.d.ts +22 -0
- package/dist/types/core/invariants.d.ts +1 -1
- package/dist/types/core/scheduler.d.ts +12 -0
- package/dist/types/store/index.d.ts +2 -0
- package/dist/types/store/next/patch-hooks.d.ts +41 -0
- package/dist/types/store/next/patch.d.ts +91 -0
- package/dist/types/store/next/reconcile.d.ts +14 -0
- package/dist/types/store/next/store.d.ts +30 -2
- package/dist/types/store/next/target.d.ts +58 -8
- package/dist/types-cjs/core/core.d.cts +15 -1
- package/dist/types-cjs/core/dev.d.cts +8 -0
- package/dist/types-cjs/core/graph.d.cts +22 -0
- package/dist/types-cjs/core/invariants.d.cts +1 -1
- package/dist/types-cjs/core/scheduler.d.cts +12 -0
- package/dist/types-cjs/store/index.d.cts +2 -0
- package/dist/types-cjs/store/next/patch-hooks.d.cts +41 -0
- package/dist/types-cjs/store/next/patch.d.cts +91 -0
- package/dist/types-cjs/store/next/reconcile.d.cts +14 -0
- package/dist/types-cjs/store/next/store.d.cts +30 -2
- package/dist/types-cjs/store/next/target.d.cts +58 -8
- package/package.json +14 -14
- package/dist/types/store/optimistic.d.ts +0 -45
- package/dist/types/store/projection.d.ts +0 -70
- package/dist/types/store/reconcile.d.ts +0 -46
- package/dist/types-cjs/store/optimistic.d.cts +0 -45
- package/dist/types-cjs/store/projection.d.cts +0 -70
- package/dist/types-cjs/store/reconcile.d.cts +0 -46
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { StoreNextTarget } from "./target.cjs";
|
|
2
|
+
import type { RowOps } from "./patch.cjs";
|
|
3
|
+
/**
|
|
4
|
+
* Patch-channel emission seams (pay-for-use). The store/reconcile/optimistic
|
|
5
|
+
* write paths emit through these installed hook objects instead of importing
|
|
6
|
+
* `patch.js` statically, so the channel tree-shakes out of apps that never
|
|
7
|
+
* register a patch consumer.
|
|
8
|
+
*
|
|
9
|
+
* TWO TIERS, armed at registration (patch.js installs them; it is retained
|
|
10
|
+
* only through its registration exports, which only compiled patch-mode
|
|
11
|
+
* output — via the web runtime's driver module — imports):
|
|
12
|
+
* - VALUE hooks (`patchHooks`): record patches. Armed by `registerPatch` —
|
|
13
|
+
* present in any bundle with one eligible template under patch mode.
|
|
14
|
+
* - ROW hooks (`rowHooks`): list structure (row ops, slot ticks, the
|
|
15
|
+
* identity/keyed diff builders in reconcile.js they drag in). Armed by
|
|
16
|
+
* `registerRowOps`/`registerSlotPatchNext` — the LIST driver's
|
|
17
|
+
* registrations, so value-only bundles never retain the row machinery.
|
|
18
|
+
*
|
|
19
|
+
* Soundness: every emission site is guarded by the matching `pc` channel
|
|
20
|
+
* (`pc.p` for value, `pc.ro`/`pc.sp` for rows), and a target can only
|
|
21
|
+
* acquire that channel through the corresponding registration — so each
|
|
22
|
+
* hook object is installed by the time any guard passes. Type-only imports
|
|
23
|
+
* from `patch.js` are erased.
|
|
24
|
+
*/
|
|
25
|
+
export interface PatchValueHooks {
|
|
26
|
+
emitPatch(t: StoreNextTarget, next: any, prev: any): void;
|
|
27
|
+
emitPatchLocal(t: StoreNextTarget, next: any, prev: any): void;
|
|
28
|
+
emitPatchOptimistic(t: StoreNextTarget, next: any, prev: any): void;
|
|
29
|
+
hasPatches(): boolean;
|
|
30
|
+
demoteToEffects(t: StoreNextTarget): void;
|
|
31
|
+
}
|
|
32
|
+
export interface PatchRowHooks {
|
|
33
|
+
emitRowOps(t: StoreNextTarget, next: any[], ops: RowOps): void;
|
|
34
|
+
emitSlotPatch(t: StoreNextTarget, index: number, next: any, prev: any): void;
|
|
35
|
+
emitSetterRowOps(t: StoreNextTarget, prevRows: any[], nextRows: any[]): void;
|
|
36
|
+
emitRowOpsOptimistic(t: StoreNextTarget, next: any[] | null, ops: RowOps | null): void;
|
|
37
|
+
}
|
|
38
|
+
export declare let patchHooks: PatchValueHooks | null;
|
|
39
|
+
export declare let rowHooks: PatchRowHooks | null;
|
|
40
|
+
export declare function installPatchHooks(hooks: PatchValueHooks): void;
|
|
41
|
+
export declare function installRowHooks(hooks: PatchRowHooks): void;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Owner } from "../../core/types.cjs";
|
|
2
|
+
import { type StoreNextTarget } from "./target.cjs";
|
|
3
|
+
export type PatchFn = (next: any, prev: any, force?: boolean) => void;
|
|
4
|
+
interface PatchEntry {
|
|
5
|
+
fn: PatchFn;
|
|
6
|
+
owner: Owner | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Emit a record's visibility transition. Callers gate on `hasPatches()` and
|
|
10
|
+
* `t.d` cheaply; this function re-checks and walks ancestors (§4b).
|
|
11
|
+
*/
|
|
12
|
+
export declare function emitPatch(t: StoreNextTarget, next: any, prev: any): void;
|
|
13
|
+
/** Emission for sites that already stand at the record with both sides in
|
|
14
|
+
* hand and have already handled ancestors (the adoption walk descends —
|
|
15
|
+
* parents were visited first), so no bubbling walk. */
|
|
16
|
+
export declare function emitPatchLocal(t: StoreNextTarget, next: any, prev: any): void;
|
|
17
|
+
export declare function emitPatchOptimistic(t: StoreNextTarget, next: any, prev: any): void;
|
|
18
|
+
/** Row-ops emission at OPTIMISTIC (lane) timing: user drafts on an
|
|
19
|
+
* optimistic family must show structure IN FLIGHT — bypassing the
|
|
20
|
+
* transition stash exactly like emitPatchOptimistic. Two forms:
|
|
21
|
+
* - `ops` given (write site): `nextRows` is the draft's intended visible
|
|
22
|
+
* list, ops the identity diff against the pre-write optimistic view.
|
|
23
|
+
* - `ops === null` (revert site): RESYNC — the consumer rebuilds retention
|
|
24
|
+
* by row identity against the live post-revert view, resolved from the
|
|
25
|
+
* target at drain time (overrides are gone by then, so `pb ?? v` IS the
|
|
26
|
+
* committed truth). */
|
|
27
|
+
export declare function emitRowOpsOptimistic(t: StoreNextTarget, nextRows: any[] | null, ops: RowOps | null): void;
|
|
28
|
+
/** Test-only accounting probe: the live registration count must return to
|
|
29
|
+
* baseline across register/unbind/demote cycles. @internal */
|
|
30
|
+
export declare function patchCountForTests(): number;
|
|
31
|
+
export declare function hasPatches(): boolean;
|
|
32
|
+
export declare function registerPatch(record: any, fn: PatchFn): () => void;
|
|
33
|
+
/** Dual-driver bind probe (compiler runtime contract): when `record` is a
|
|
34
|
+
* patchable store record, returns its CURRENT raw backing (the driver's
|
|
35
|
+
* initial force-apply reads it directly — no proxy traffic, no tracking);
|
|
36
|
+
* returns undefined otherwise (driver falls back to the effect path).
|
|
37
|
+
* Not patchable: non-records, non-proxies, accessor-bearing records
|
|
38
|
+
* (patches read raw — getters need tracked evaluation), broken chains. */
|
|
39
|
+
export declare function patchableRaw(record: any): Record<PropertyKey, any> | undefined;
|
|
40
|
+
/** Accessor demotion (design §5): a record that acquires an accessor after
|
|
41
|
+
* registration stops being patchable — reads must go through tracked
|
|
42
|
+
* evaluation. Clears patches and repairs the global count; callers re-drive
|
|
43
|
+
* the pulled bodies (demoteToEffects). */
|
|
44
|
+
export declare function demotePatches(t: StoreNextTarget): PatchEntry[] | null;
|
|
45
|
+
/** The demotion re-drive (re-audit blocker 3): each pulled body becomes the
|
|
46
|
+
* SAME dual-driver effect fallback the web runtime would have chosen had the
|
|
47
|
+
* record carried the accessor at bind — a tracked compute pass (next === prev
|
|
48
|
+
* short-circuits every compare into a pure read THROUGH THE PROXY, so getter
|
|
49
|
+
* dependencies track) plus an untracked force-apply at effect timing.
|
|
50
|
+
*
|
|
51
|
+
* Creation is DEFERRED to the effect phase: the trap that discovers the
|
|
52
|
+
* accessor runs mid-draft, and an effect's initial pass must not read
|
|
53
|
+
* through the proxy inside the write window. The record's own transition
|
|
54
|
+
* for that draft is covered by the new effect's initial force-apply.
|
|
55
|
+
*
|
|
56
|
+
* Known edge (documented): a demoted LIST-ROW body re-drives under its
|
|
57
|
+
* registering owner (the list owner), so per-row severing on removal is
|
|
58
|
+
* lost for demoted rows — the effect lives until the LIST disposes. Rows
|
|
59
|
+
* only demote when user code defines an accessor on a row record at
|
|
60
|
+
* runtime. */
|
|
61
|
+
export declare function demoteToEffects(t: StoreNextTarget): void;
|
|
62
|
+
/** Structural ops for one keyed-array transition. `prefix` rows key-matched
|
|
63
|
+
* in place; for each later index i (absolute), `sources[i - prefix]` is the
|
|
64
|
+
* OLD index its row retained from, or -1 for a new row. `removed` holds the
|
|
65
|
+
* dropped old row values (unbind/teardown handles). Aligned value ticks emit
|
|
66
|
+
* NOTHING — ops exist only when structure changed. */
|
|
67
|
+
export interface RowOps {
|
|
68
|
+
prefix: number;
|
|
69
|
+
sources: number[];
|
|
70
|
+
removed: any[];
|
|
71
|
+
}
|
|
72
|
+
/** `ops === null` is the RESYNC form (optimistic revert): the consumer
|
|
73
|
+
* rebuilds retention by row identity against `next` (the live view). */
|
|
74
|
+
export type RowOpsFn = (next: any[], ops: RowOps | null) => void;
|
|
75
|
+
/** Register a structural-ops consumer on a keyed store array (the list
|
|
76
|
+
* container's channel — what `For` consumes through the seam). */
|
|
77
|
+
export declare function registerRowOps(array: any, fn: RowOpsFn): () => void;
|
|
78
|
+
/** Slot patches (shallow arrays) ride the same apply queue: the walk emits
|
|
79
|
+
* per aligned value-replaced slot; application happens at effect phase under
|
|
80
|
+
* the registration owner's lifetime. */
|
|
81
|
+
export declare function emitSlotPatch(t: StoreNextTarget, index: number, next: any, prev: any): void;
|
|
82
|
+
/** Slot patch for shallow arrays: the reconcile walk emits (index, next,
|
|
83
|
+
* prev) for KEY-ALIGNED value-replaced slots (structure rides row ops), and
|
|
84
|
+
* the emission queues through the patch apply queue — effect-phase timing,
|
|
85
|
+
* transition stamping, disposed-owner drop — like every other channel. */
|
|
86
|
+
export declare function registerSlotPatchNext(arr: any, fn: (index: number, next: any, prev: any) => void): () => void;
|
|
87
|
+
/** Row-ops ride the SAME apply queue/timing as record patches: transition-
|
|
88
|
+
* stamped, applied at effect phase, in emission order (structure before the
|
|
89
|
+
* new rows' own patches can exist; retained rows' value patches commute). */
|
|
90
|
+
export declare function emitRowOps(t: StoreNextTarget, next: any[], ops: RowOps): void;
|
|
91
|
+
export {};
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import type { RowOps } from "./patch.cjs";
|
|
2
|
+
import { type StoreNextTarget } from "./target.cjs";
|
|
1
3
|
type KeyFn = (item: any) => any;
|
|
2
4
|
export declare function reconcileNextState(value: any, state: any, key: string | KeyFn | null | undefined, replace?: boolean): void;
|
|
5
|
+
/** Key equality for EVERY key comparison in this module (re-audit 2, P1-5):
|
|
6
|
+
* SameValueZero, matching the Map-based matchers (buildRowOps, the adoption
|
|
7
|
+
* window) — NaN keys are equal to themselves, so aligned NaN rows stay
|
|
8
|
+
* aligned in the prefix walk instead of forever misaligning. Adoption and
|
|
9
|
+
* row ops MUST agree on key equality or retained DOM rows go stale. */
|
|
10
|
+
export declare function sameKey(a: any, b: any): boolean;
|
|
11
|
+
export declare function emitSetterRowOps(t: StoreNextTarget, prevRows: any[], nextRows: any[]): void;
|
|
12
|
+
/** Identity-keyed structural diff, returned rather than emitted: shared by
|
|
13
|
+
* the setter channel (regular queue) and the OPTIMISTIC write channel (lane
|
|
14
|
+
* queue) — same retention semantics, different dispatch timing. Returns
|
|
15
|
+
* null when the lists are identity-aligned (no structure changed). */
|
|
16
|
+
export declare function buildIdentityRowOps(prevRows: any[], nextRows: any[]): RowOps | null;
|
|
3
17
|
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Signal } from "../../core/types.cjs";
|
|
2
|
-
import { type StoreNextFamily, type StoreNextTarget } from "./target.cjs";
|
|
2
|
+
import { type StoreNextFamily, type StoreNextTarget, type PatchChannel } from "./target.cjs";
|
|
3
|
+
/** Lazily allocate the patch-channel extension (one literal shape). */
|
|
4
|
+
export declare function pcOf(t: StoreNextTarget): PatchChannel;
|
|
3
5
|
export declare function wrapNext<T extends Record<PropertyKey, any>>(value: T, parent?: StoreNextTarget | null, parentKey?: PropertyKey | null, fam?: StoreNextFamily | null): T;
|
|
4
6
|
/** Unwrap our own proxies to their current backing; leave everything else. */
|
|
5
7
|
export declare function unwrapValue(v: any): any;
|
|
@@ -9,6 +11,10 @@ export declare function getKeySetNode(target: StoreNextTarget): Signal<number>;
|
|
|
9
11
|
/** Deep-witness bump: any value/shape change on a record with a live deep()
|
|
10
12
|
* subscriber notifies it. One null check when unused. */
|
|
11
13
|
export declare function bumpDeep(t: StoreNextTarget): void;
|
|
14
|
+
/** Scanned plainness for patch admission (patchableRaw): runs the one-time
|
|
15
|
+
* accessor scan if it hasn't happened yet — the sticky `a` flag alone is not
|
|
16
|
+
* trustworthy before a scan (it starts false and is discovered lazily). */
|
|
17
|
+
export declare function targetIsPlain(target: StoreNextTarget): boolean;
|
|
12
18
|
/** Downgrade a prototype-overlay pending backing to the clone path: builds
|
|
13
19
|
* the real container (committed + overlay writes − deletes) that fold will
|
|
14
20
|
* SWAP in as the committed backing, exactly as if the draft had started on
|
|
@@ -63,8 +69,30 @@ export type SetStoreNextFunction<T> = (fn: (draft: T) => T | void) => void;
|
|
|
63
69
|
* projection recomputes legitimately write from inside their computed. */
|
|
64
70
|
export declare function storeSetterNext<T>(proxy: T, fn: (draft: T) => T | void, guard?: boolean): void;
|
|
65
71
|
export declare function createStoreNext<T extends Record<PropertyKey, any>>(init: T, shallow?: boolean): [T, SetStoreNextFunction<T>];
|
|
72
|
+
/** True when `proxy` is a SHALLOW store (children served verbatim, slots
|
|
73
|
+
* replaced by reference — #2932). The list driver uses this to choose the
|
|
74
|
+
* slot-patch channel (collected row bodies) over per-record registration. */
|
|
75
|
+
export declare function storeIsShallow(proxy: any): boolean;
|
|
76
|
+
/** True when `proxy` belongs to a projection/optimistic FAMILY. The list
|
|
77
|
+
* driver must DECLINE family arrays (external audit finding): family
|
|
78
|
+
* structural changes never emit row/slot ops (the setter channel is
|
|
79
|
+
* fam-gated; optimistic writes ride node overrides), and the proxy identity
|
|
80
|
+
* is stable so the each-watch cannot catch the change either — an engaged
|
|
81
|
+
* list would freeze on optimistic/projection structural updates. Record-
|
|
82
|
+
* level family patches are unaffected (they have their own emission). */
|
|
83
|
+
export declare function storeHasFamily(proxy: any): boolean;
|
|
84
|
+
/** True when `proxy` belongs to an OPTIMISTIC family specifically. The list
|
|
85
|
+
* driver declines these (audit finding, narrowed): optimistic user writes
|
|
86
|
+
* ride node-level overrides — they never enter the reconcile walk, so no
|
|
87
|
+
* row/slot ops are emitted and an engaged list would freeze on optimistic
|
|
88
|
+
* structural changes. PROJECTION (non-optimistic) families are drivable:
|
|
89
|
+
* their recomputes go through the reconcile walk, whose emissions are
|
|
90
|
+
* transition-stamped in the apply queue like any other (equivalence-matrix
|
|
91
|
+
* gated). Re-admitting optimistic families requires a lane-timed structural
|
|
92
|
+
* emission mirroring emitPatchOptimistic, plus revert resync. */
|
|
93
|
+
export declare function storeHasOptimisticFamily(proxy: any): boolean;
|
|
66
94
|
/** Tracking deep snapshot (`deep()` for next targets): subscribes to the
|
|
67
|
-
* key-set and
|
|
95
|
+
* key-set and deep-witness node at every reachable level, then returns the
|
|
68
96
|
* plain view. Shared references and cycles handled via the visited set. */
|
|
69
97
|
export declare function deepNext<T>(value: T): T;
|
|
70
98
|
/**
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* entry per read-through object; zero layer slots; nodes, has-nodes, and the
|
|
13
13
|
* key-set node are lazy, materialized only by subscription.
|
|
14
14
|
*/
|
|
15
|
-
import type { Computed, Signal } from "../../core/types.cjs";
|
|
15
|
+
import type { Computed, Owner, Signal } from "../../core/types.cjs";
|
|
16
16
|
/** Projection family (§7b): children wrap into the family's own map (writes
|
|
17
17
|
* land in the projection, never the source family), and every node created
|
|
18
18
|
* under the family carries the projection computed as its firewall. */
|
|
@@ -32,6 +32,40 @@ export interface StoreNextFamily {
|
|
|
32
32
|
node: Computed<any> | null;
|
|
33
33
|
shallow?: boolean;
|
|
34
34
|
}
|
|
35
|
+
/** Write-side patch-channel state (stage 2), grouped off the target's named
|
|
36
|
+
* fields — see the shape rule on `StoreNextTarget.pc`. One literal shape,
|
|
37
|
+
* allocated by `pcOf` on first use. */
|
|
38
|
+
export interface PatchChannel {
|
|
39
|
+
/** Slot-patch hooks for shallow arrays — the reconcile walk emits
|
|
40
|
+
* (i, next, prev) for key-aligned value-replaced slots through the patch
|
|
41
|
+
* apply queue (records are raw, no per-record targets exist).
|
|
42
|
+
* MULTI-CONSUMER (external audit): one array can drive several lists. */
|
|
43
|
+
sp: {
|
|
44
|
+
fn: (index: number, next: any, prev: any) => void;
|
|
45
|
+
owner: Owner | null;
|
|
46
|
+
}[] | null;
|
|
47
|
+
/** Patch-channel consumers (next/patch.ts): per-record compiled patch
|
|
48
|
+
* entries, multi-consumer. null when unpatched (the common case). */
|
|
49
|
+
p: object[] | null;
|
|
50
|
+
/** Same-batch coalescing stamp (re-audit 2/3): the container array this
|
|
51
|
+
* channel last pushed a non-forced SELF entry into, plus that entry. A
|
|
52
|
+
* later same-batch emission UPDATES the queued entry's `next` in place
|
|
53
|
+
* (latest state wins — adoption REPLACES the captured object, so dropping
|
|
54
|
+
* the later emission would apply stale state) while `prev` stays the
|
|
55
|
+
* batch's earliest. The drain clears both stamps so a quiet record
|
|
56
|
+
* retains nothing from its last batch. */
|
|
57
|
+
qa: unknown;
|
|
58
|
+
qe: unknown;
|
|
59
|
+
/** Row-ops consumers (next/patch.ts, PR-B): structural list ops —
|
|
60
|
+
* (nextRows, { prefix, sources, removed }) at apply timing. */
|
|
61
|
+
ro: object[] | null;
|
|
62
|
+
/** Keys written through the traps since the last fold commit. Bounds the
|
|
63
|
+
* setter notify/hold-check to O(written) instead of O(subscribed nodes) —
|
|
64
|
+
* a record with thousands of per-key subscriptions (selection maps) would
|
|
65
|
+
* otherwise pay a full node scan on every write. null = no trap writes
|
|
66
|
+
* this batch (bulk paths fall back to the full scan). */
|
|
67
|
+
wk: Set<PropertyKey> | null;
|
|
68
|
+
}
|
|
35
69
|
export interface StoreNextTarget {
|
|
36
70
|
/** Committed backing: source object (shared) or owned clone. */
|
|
37
71
|
v: Record<PropertyKey, any>;
|
|
@@ -47,6 +81,15 @@ export interface StoreNextTarget {
|
|
|
47
81
|
h: Record<PropertyKey, Signal<boolean>> | null;
|
|
48
82
|
/** Lazy key-set node: membership/iteration/$TRACK subscriptions (§6). */
|
|
49
83
|
k: Signal<number> | null;
|
|
84
|
+
/** Patch-channel extension (lazily allocated on first use): groups the
|
|
85
|
+
* write-side stage-2 fields so they never widen the TARGET's own named
|
|
86
|
+
* field count. LOAD-BEARING SHAPE RULE: array proxy targets carry their
|
|
87
|
+
* fields as named properties on a real array, and V8 normalizes an array
|
|
88
|
+
* to dictionary properties as the named count grows (empirically at
|
|
89
|
+
* counts ≡ 0 mod 3 from 18 up on V8 13.x) — every trap field read then
|
|
90
|
+
* becomes a hash lookup (~15% uibench, tree suites worst). New
|
|
91
|
+
* patch-channel state MUST go inside this object, not on the target. */
|
|
92
|
+
pc: PatchChannel | null;
|
|
50
93
|
/** Lazy deep-witness node: `deep()` subscribes ONE node per record instead
|
|
51
94
|
* of one per path; write paths bump it only when it exists. Separate from
|
|
52
95
|
* `k` so $TRACK/mapArray never rerun on leaf value changes (R9). */
|
|
@@ -79,17 +122,21 @@ export interface StoreNextTarget {
|
|
|
79
122
|
/** Keys deleted in the overlay window (a prototype overlay cannot shadow
|
|
80
123
|
* a delete); null when none. */
|
|
81
124
|
del: Set<PropertyKey> | null;
|
|
82
|
-
/** Keys written through the traps since the last fold commit. Bounds the
|
|
83
|
-
* setter notify/hold-check to O(written) instead of O(subscribed nodes) —
|
|
84
|
-
* a record with thousands of per-key subscriptions (selection maps) would
|
|
85
|
-
* otherwise pay a full node scan on every write. null = no trap writes
|
|
86
|
-
* this batch (bulk paths fall back to the full scan); WK_ALL sentinel =
|
|
87
|
-
* bound unusable this batch (array length write implies index deletes). */
|
|
88
|
-
wk: Set<PropertyKey> | null;
|
|
89
125
|
/** Projection family, null for plain stores (§7b). */
|
|
90
126
|
fam: StoreNextFamily | null;
|
|
91
127
|
/** Shallow store root (values served raw). */
|
|
92
128
|
s: boolean;
|
|
129
|
+
/** Held committed view (#3074/#3075): the pre-hold committed backing,
|
|
130
|
+
* served to committed-visibility readers while `ht` is live. Adoption is
|
|
131
|
+
* eager by contract, but a projection recompute deriving from uncommitted
|
|
132
|
+
* inputs (a transition-held source, or a latest()-pull ahead of the flush)
|
|
133
|
+
* swaps the backing SPECULATIVELY — the old view must stay servable until
|
|
134
|
+
* the hold resolves. */
|
|
135
|
+
hv: Record<PropertyKey, any> | null;
|
|
136
|
+
/** The holder for `hv`: a live transition (cleared lazily when it is done)
|
|
137
|
+
* or the PLAIN_HOLD sentinel (a latest()-pull staging — cleared by the
|
|
138
|
+
* fold commit). null = no hold. */
|
|
139
|
+
ht: any;
|
|
93
140
|
}
|
|
94
141
|
/**
|
|
95
142
|
* Ownership (first cut, decision 2026-08-16d): one WeakSet of store-owned
|
|
@@ -115,3 +162,6 @@ export interface OptStoreHooks {
|
|
|
115
162
|
}
|
|
116
163
|
export declare let optHooks: OptStoreHooks | null;
|
|
117
164
|
export declare function setOptHooks(h: OptStoreHooks): void;
|
|
165
|
+
/** Sticky descendants flag walk (§6d): reconcile's keyed pruning descends
|
|
166
|
+
* only where subscriptions exist at/below. Nodes AND patches count. */
|
|
167
|
+
export declare function markDescendants(target: StoreNextTarget): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/signals",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.4",
|
|
4
4
|
"description": "Solid's reactive primitives: signals, memos, effects, stores, and async-aware computations.",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/solidjs/solid.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/signals"
|
|
12
12
|
},
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
@@ -39,6 +39,18 @@
|
|
|
39
39
|
},
|
|
40
40
|
"./package.json": "./package.json"
|
|
41
41
|
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "npm-run-all -nl build:* && pnpm types",
|
|
44
|
+
"build:clean": "rimraf dist/dev dist/prod dist/node dist/dev.js dist/prod.js dist/node.cjs",
|
|
45
|
+
"build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/node.cjs && node ./scripts/check-pure.mjs dist/prod",
|
|
46
|
+
"types": "tsc -p tsconfig.build.json && node ../../scripts/sync-dual-types.mjs ./dist/types ./dist/types-cjs",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest watch tests",
|
|
49
|
+
"test:gc": "node --expose-gc ./vitest.js",
|
|
50
|
+
"test:gc:watch": "node --expose-gc ./vitest.js --watch",
|
|
51
|
+
"coverage": "vitest run --coverage",
|
|
52
|
+
"bench": "vitest bench --run"
|
|
53
|
+
},
|
|
42
54
|
"devDependencies": {
|
|
43
55
|
"@codspeed/vitest-plugin": "^5.4.0",
|
|
44
56
|
"@ianvs/prettier-plugin-sort-imports": "^4.1.1",
|
|
@@ -53,17 +65,5 @@
|
|
|
53
65
|
"typescript": "^6.0.3",
|
|
54
66
|
"vite": "^7.0.0",
|
|
55
67
|
"vitest": "^4.1.6"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"build": "npm-run-all -nl build:* && pnpm types",
|
|
59
|
-
"build:clean": "rimraf dist/dev dist/prod dist/node dist/dev.js dist/prod.js dist/node.cjs",
|
|
60
|
-
"build:js": "rollup -c && node ./scripts/mangle-props.mjs dist/prod dist/node.cjs && node ./scripts/check-pure.mjs dist/prod",
|
|
61
|
-
"types": "tsc -p tsconfig.build.json && node ../../scripts/sync-dual-types.mjs ./dist/types ./dist/types-cjs",
|
|
62
|
-
"test": "vitest run",
|
|
63
|
-
"test:watch": "vitest watch tests",
|
|
64
|
-
"test:gc": "node --expose-gc ./vitest.js",
|
|
65
|
-
"test:gc:watch": "node --expose-gc ./vitest.js --watch",
|
|
66
|
-
"coverage": "vitest run --coverage",
|
|
67
|
-
"bench": "vitest bench --run"
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type Refreshable } from "../core/index.js";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from "./store.js";
|
|
3
|
-
/**
|
|
4
|
-
* The store equivalent of `createOptimistic`. Writes inside an `action`
|
|
5
|
-
* transition are tentative — they show up immediately but auto-revert (or
|
|
6
|
-
* reconcile to the action's resolved value) once the transition finishes.
|
|
7
|
-
*
|
|
8
|
-
* Use this for optimistic UI on collection-shaped data. For single-value
|
|
9
|
-
* optimistic state, prefer `createOptimistic`.
|
|
10
|
-
*
|
|
11
|
-
* - Plain form: `createOptimisticStore(initialValue)`.
|
|
12
|
-
* - Derived form: `createOptimisticStore(fn, seed, options?)` — a projection
|
|
13
|
-
* store whose authoritative value is recomputed by `fn` and whose
|
|
14
|
-
* optimistic overlay reverts after each transition.
|
|
15
|
-
*
|
|
16
|
-
* `options.key` defaults to `"id"`; specify it only when your data uses a
|
|
17
|
-
* different identity field (e.g. `{ key: "uuid" }` or `{ key: t => t.slug }`),
|
|
18
|
-
* or `null` to merge positionally. Restating the default just adds noise.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const [todos, setTodos] = createOptimisticStore<Todo[]>([]);
|
|
23
|
-
*
|
|
24
|
-
* // Mutation: optimistic add, then in-place reconcile to the saved row.
|
|
25
|
-
* const addTodo = action(function* (text: string) {
|
|
26
|
-
* const tempId = crypto.randomUUID();
|
|
27
|
-
* setTodos(t => { t.push({ id: tempId, text, pending: true }); });
|
|
28
|
-
* const saved = yield api.createTodo(text);
|
|
29
|
-
* setTodos(t => {
|
|
30
|
-
* const i = t.findIndex(x => x.id === tempId);
|
|
31
|
-
* if (i >= 0) t[i] = saved;
|
|
32
|
-
* });
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* // Return form: filter is the natural shape for removal.
|
|
36
|
-
* const removeTodo = action(function* (id: string) {
|
|
37
|
-
* setTodos(t => t.filter(x => x.id !== id));
|
|
38
|
-
* yield api.removeTodo(id);
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @returns `[store: Store<T>, setStore: StoreSetter<T>]`
|
|
43
|
-
*/
|
|
44
|
-
export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>];
|
|
45
|
-
export declare function createOptimisticStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Refreshable<Store<T>>, set: StoreSetter<T>];
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Computed, type Refreshable } from "../core/index.js";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store } from "./store.js";
|
|
3
|
-
export declare function createProjectionInternal<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T>, options?: ProjectionOptions): {
|
|
4
|
-
store: Refreshable<Store<T>>;
|
|
5
|
-
node: Computed<void | T>;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Creates a derived (projected) store. Like `createMemo` but for stores: the
|
|
9
|
-
* derive function receives a mutable draft and either mutates it in place
|
|
10
|
-
* (canonical) or returns a new value. Either way the result is reconciled
|
|
11
|
-
* against the previous draft by `options.key` (default `"id"`), so surviving
|
|
12
|
-
* items keep their proxy identity — only added/removed items are
|
|
13
|
-
* created/disposed.
|
|
14
|
-
*
|
|
15
|
-
* If the derive returns a different entity than the one currently held (the
|
|
16
|
-
* `/users/1` → `/users/2` shape), the store swaps to it rather than merging,
|
|
17
|
-
* and nothing below it is treated as surviving.
|
|
18
|
-
*
|
|
19
|
-
* Returns the projected store directly (no setter — reads only).
|
|
20
|
-
*
|
|
21
|
-
* Use this when you want the structural-sharing / per-property tracking
|
|
22
|
-
* behaviour of a store on top of a derived computation. For simple read-only
|
|
23
|
-
* derivations, `createMemo` is lighter.
|
|
24
|
-
*
|
|
25
|
-
* @param fn receives the current draft; mutate it in place or return new
|
|
26
|
-
* data. Return is convenient for filter/derive shapes where mutation is
|
|
27
|
-
* awkward.
|
|
28
|
-
* @param seed the backing store value to wrap and reconcile into
|
|
29
|
-
* @param options `ProjectionOptions` — `name`, `key`. `key` defaults to
|
|
30
|
-
* `"id"`; specify it only when your data uses a different identity field
|
|
31
|
-
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`), or `null` to merge
|
|
32
|
-
* positionally with no keyed pass.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* // Mutation form — update individual fields on the draft.
|
|
37
|
-
* const summary = createProjection<{ total: number; active: number }>(
|
|
38
|
-
* draft => {
|
|
39
|
-
* draft.total = users().length;
|
|
40
|
-
* draft.active = users().filter(u => u.active).length;
|
|
41
|
-
* },
|
|
42
|
-
* { total: 0, active: 0 }
|
|
43
|
-
* );
|
|
44
|
-
*
|
|
45
|
-
* // Return form — produce a derived collection. Reconciled by `id` so each
|
|
46
|
-
* // surviving user keeps the same store identity across recomputes.
|
|
47
|
-
* const activeUsers = createProjection<User[]>(
|
|
48
|
-
* () => allUsers().filter(u => u.active),
|
|
49
|
-
* []
|
|
50
|
-
* );
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
|
|
54
|
-
*/
|
|
55
|
-
export declare function createProjection<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): Refreshable<Store<T>>;
|
|
56
|
-
/**
|
|
57
|
-
* Shared projection computed body used by both `createProjection` and the derived
|
|
58
|
-
* form of `createOptimisticStore`. Encapsulates the write-trap draft, `storeSetter`
|
|
59
|
-
* wrapping, the `handleAsync` subscription with a setter callback, and the commit
|
|
60
|
-
* path (which must always go through `storeSetter` so the `writeOnly` guard is
|
|
61
|
-
* engaged during `reconcile`'s property reads).
|
|
62
|
-
*
|
|
63
|
-
* `wrapCommit` is invoked for every commit (sync return and each async yield) and
|
|
64
|
-
* lets callers layer extra context around the write — e.g. the optimistic store
|
|
65
|
-
* re-enters `setProjectionWriteActive` so reconciles target `STORE_OVERRIDE`
|
|
66
|
-
* instead of `STORE_OPTIMISTIC_OVERRIDE` even when an async yield fires outside
|
|
67
|
-
* the outer `setProjectionWriteActive` scope.
|
|
68
|
-
*/
|
|
69
|
-
export declare function runProjectionComputed<T extends object>(wrappedStore: Store<T>, fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, key: string | ((item: NonNullable<any>) => any) | null, wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
|
|
70
|
-
export declare function createWriteTraps(isActive?: () => boolean, onDraftWrite?: () => void): ProxyHandler<any>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared body of `reconcile()` and the projection commit. `replace` is the
|
|
3
|
-
* only difference: a projection commit is a value swap, not a merge — its root
|
|
4
|
-
* proxy is a cell handed out by `createProjection` that can never change
|
|
5
|
-
* reference, so a derive returning a different entity is not the slot mistake
|
|
6
|
-
* `reconcile()` throws on. Nothing below the root survives that swap, which is
|
|
7
|
-
* the rule the keyed diff already applies at a nested slot on a key mismatch.
|
|
8
|
-
*
|
|
9
|
-
* @internal
|
|
10
|
-
*/
|
|
11
|
-
export declare function reconcileState(value: any, state: any, key: any, replace: boolean): void;
|
|
12
|
-
/**
|
|
13
|
-
* Returns a draft-mutating function that smart-merges `value` into a store,
|
|
14
|
-
* preserving fine-grained reactivity: only changed leaves trigger updates.
|
|
15
|
-
*
|
|
16
|
-
* With a `key` (default `"id"`), array items whose key matches between old
|
|
17
|
-
* and new states keep their identity (updated in place, moves and removals
|
|
18
|
-
* update the corresponding signals) — the shape for keyed server payloads.
|
|
19
|
-
* Items without the key field fall back to positional matching.
|
|
20
|
-
*
|
|
21
|
-
* With `key: null`, matching is purely positional: index N of the new array
|
|
22
|
-
* merges into index N of the old, and object properties merge recursively —
|
|
23
|
-
* the classic pattern for fixed-shape data that churns in place (dashboards,
|
|
24
|
-
* monitors), where no keyed diff pass is needed or wanted.
|
|
25
|
-
*
|
|
26
|
-
* Merging into a slot that holds a *different* entity throws — the caller
|
|
27
|
-
* picked the slot, so a key mismatch there is a bug.
|
|
28
|
-
*
|
|
29
|
-
* @param value the next state to merge in
|
|
30
|
-
* @param key property name (string) or extractor function for stable
|
|
31
|
-
* identity (default `"id"`); pass `null` for positional merging
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* const [todos, setTodos] = createStore<Todo[]>([]);
|
|
36
|
-
*
|
|
37
|
-
* async function refresh() {
|
|
38
|
-
* const fresh = await api.getTodos();
|
|
39
|
-
* setTodos(reconcile(fresh)); // diff-merge by `id`
|
|
40
|
-
* }
|
|
41
|
-
*
|
|
42
|
-
* // fixed-shape polling data — positional merge
|
|
43
|
-
* setStats(reconcile(nextStats, null));
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export declare function reconcile<T extends U, U>(value: T, key?: string | ((item: NonNullable<any>) => any) | null): (state: U) => void;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { type Refreshable } from "../core/index.cjs";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store, type StoreSetter } from "./store.cjs";
|
|
3
|
-
/**
|
|
4
|
-
* The store equivalent of `createOptimistic`. Writes inside an `action`
|
|
5
|
-
* transition are tentative — they show up immediately but auto-revert (or
|
|
6
|
-
* reconcile to the action's resolved value) once the transition finishes.
|
|
7
|
-
*
|
|
8
|
-
* Use this for optimistic UI on collection-shaped data. For single-value
|
|
9
|
-
* optimistic state, prefer `createOptimistic`.
|
|
10
|
-
*
|
|
11
|
-
* - Plain form: `createOptimisticStore(initialValue)`.
|
|
12
|
-
* - Derived form: `createOptimisticStore(fn, seed, options?)` — a projection
|
|
13
|
-
* store whose authoritative value is recomputed by `fn` and whose
|
|
14
|
-
* optimistic overlay reverts after each transition.
|
|
15
|
-
*
|
|
16
|
-
* `options.key` defaults to `"id"`; specify it only when your data uses a
|
|
17
|
-
* different identity field (e.g. `{ key: "uuid" }` or `{ key: t => t.slug }`),
|
|
18
|
-
* or `null` to merge positionally. Restating the default just adds noise.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const [todos, setTodos] = createOptimisticStore<Todo[]>([]);
|
|
23
|
-
*
|
|
24
|
-
* // Mutation: optimistic add, then in-place reconcile to the saved row.
|
|
25
|
-
* const addTodo = action(function* (text: string) {
|
|
26
|
-
* const tempId = crypto.randomUUID();
|
|
27
|
-
* setTodos(t => { t.push({ id: tempId, text, pending: true }); });
|
|
28
|
-
* const saved = yield api.createTodo(text);
|
|
29
|
-
* setTodos(t => {
|
|
30
|
-
* const i = t.findIndex(x => x.id === tempId);
|
|
31
|
-
* if (i >= 0) t[i] = saved;
|
|
32
|
-
* });
|
|
33
|
-
* });
|
|
34
|
-
*
|
|
35
|
-
* // Return form: filter is the natural shape for removal.
|
|
36
|
-
* const removeTodo = action(function* (id: string) {
|
|
37
|
-
* setTodos(t => t.filter(x => x.id !== id));
|
|
38
|
-
* yield api.removeTodo(id);
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*
|
|
42
|
-
* @returns `[store: Store<T>, setStore: StoreSetter<T>]`
|
|
43
|
-
*/
|
|
44
|
-
export declare function createOptimisticStore<T extends object = {}>(store: NoFn<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Store<T>, set: StoreSetter<T>];
|
|
45
|
-
export declare function createOptimisticStore<T extends object = {}>(fn: (store: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, store: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): [get: Refreshable<Store<T>>, set: StoreSetter<T>];
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { type Computed, type Refreshable } from "../core/index.cjs";
|
|
2
|
-
import { type NoFn, type ProjectionOptions, type Store } from "./store.cjs";
|
|
3
|
-
export declare function createProjectionInternal<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T>, options?: ProjectionOptions): {
|
|
4
|
-
store: Refreshable<Store<T>>;
|
|
5
|
-
node: Computed<void | T>;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Creates a derived (projected) store. Like `createMemo` but for stores: the
|
|
9
|
-
* derive function receives a mutable draft and either mutates it in place
|
|
10
|
-
* (canonical) or returns a new value. Either way the result is reconciled
|
|
11
|
-
* against the previous draft by `options.key` (default `"id"`), so surviving
|
|
12
|
-
* items keep their proxy identity — only added/removed items are
|
|
13
|
-
* created/disposed.
|
|
14
|
-
*
|
|
15
|
-
* If the derive returns a different entity than the one currently held (the
|
|
16
|
-
* `/users/1` → `/users/2` shape), the store swaps to it rather than merging,
|
|
17
|
-
* and nothing below it is treated as surviving.
|
|
18
|
-
*
|
|
19
|
-
* Returns the projected store directly (no setter — reads only).
|
|
20
|
-
*
|
|
21
|
-
* Use this when you want the structural-sharing / per-property tracking
|
|
22
|
-
* behaviour of a store on top of a derived computation. For simple read-only
|
|
23
|
-
* derivations, `createMemo` is lighter.
|
|
24
|
-
*
|
|
25
|
-
* @param fn receives the current draft; mutate it in place or return new
|
|
26
|
-
* data. Return is convenient for filter/derive shapes where mutation is
|
|
27
|
-
* awkward.
|
|
28
|
-
* @param seed the backing store value to wrap and reconcile into
|
|
29
|
-
* @param options `ProjectionOptions` — `name`, `key`. `key` defaults to
|
|
30
|
-
* `"id"`; specify it only when your data uses a different identity field
|
|
31
|
-
* (e.g. `{ key: "uuid" }` or `{ key: u => u.slug }`), or `null` to merge
|
|
32
|
-
* positionally with no keyed pass.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* // Mutation form — update individual fields on the draft.
|
|
37
|
-
* const summary = createProjection<{ total: number; active: number }>(
|
|
38
|
-
* draft => {
|
|
39
|
-
* draft.total = users().length;
|
|
40
|
-
* draft.active = users().filter(u => u.active).length;
|
|
41
|
-
* },
|
|
42
|
-
* { total: 0, active: 0 }
|
|
43
|
-
* );
|
|
44
|
-
*
|
|
45
|
-
* // Return form — produce a derived collection. Reconciled by `id` so each
|
|
46
|
-
* // surviving user keeps the same store identity across recomputes.
|
|
47
|
-
* const activeUsers = createProjection<User[]>(
|
|
48
|
-
* () => allUsers().filter(u => u.active),
|
|
49
|
-
* []
|
|
50
|
-
* );
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
|
|
54
|
-
*/
|
|
55
|
-
export declare function createProjection<T extends object = {}>(fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, seed: Partial<T> | Store<NoFn<T>>, options?: ProjectionOptions): Refreshable<Store<T>>;
|
|
56
|
-
/**
|
|
57
|
-
* Shared projection computed body used by both `createProjection` and the derived
|
|
58
|
-
* form of `createOptimisticStore`. Encapsulates the write-trap draft, `storeSetter`
|
|
59
|
-
* wrapping, the `handleAsync` subscription with a setter callback, and the commit
|
|
60
|
-
* path (which must always go through `storeSetter` so the `writeOnly` guard is
|
|
61
|
-
* engaged during `reconcile`'s property reads).
|
|
62
|
-
*
|
|
63
|
-
* `wrapCommit` is invoked for every commit (sync return and each async yield) and
|
|
64
|
-
* lets callers layer extra context around the write — e.g. the optimistic store
|
|
65
|
-
* re-enters `setProjectionWriteActive` so reconciles target `STORE_OVERRIDE`
|
|
66
|
-
* instead of `STORE_OPTIMISTIC_OVERRIDE` even when an async yield fires outside
|
|
67
|
-
* the outer `setProjectionWriteActive` scope.
|
|
68
|
-
*/
|
|
69
|
-
export declare function runProjectionComputed<T extends object>(wrappedStore: Store<T>, fn: (draft: T) => void | T | Promise<void | T> | AsyncIterable<void | T>, key: string | ((item: NonNullable<any>) => any) | null, wrapCommit?: (write: () => void) => void, onDraftWrite?: () => void): Computed<void | T>;
|
|
70
|
-
export declare function createWriteTraps(isActive?: () => boolean, onDraftWrite?: () => void): ProxyHandler<any>;
|