bireactive 0.3.4 → 0.3.5
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/automerge/apply-patches.d.ts +4 -0
- package/dist/automerge/apply-patches.js +62 -0
- package/dist/automerge/doc-cell.js +8 -2
- package/dist/automerge/reconcile.d.ts +9 -8
- package/dist/automerge/reconcile.js +38 -25
- package/dist/core/_counts.d.ts +0 -2
- package/dist/core/_counts.js +0 -1
- package/dist/core/cell.d.ts +44 -87
- package/dist/core/cell.js +195 -301
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/lenses/memory.js +25 -21
- package/dist/core/lenses/point-cloud.js +66 -47
- package/dist/core/lenses/snap.js +8 -7
- package/dist/core/lenses/text.js +8 -6
- package/dist/core/optic.d.ts +0 -5
- package/dist/core/optic.js +16 -28
- package/dist/core/values/audio.js +6 -5
- package/dist/core/values/canvas.js +20 -12
- package/dist/core/values/field.d.ts +2 -0
- package/dist/core/values/field.js +25 -0
- package/dist/core/values/reg.d.ts +2 -3
- package/dist/core/values/reg.js +3 -4
- package/dist/core/values/str.js +6 -6
- package/dist/formats/lens.js +21 -13
- package/dist/jsx-runtime.js +88 -22
- package/dist/learn/lens-net.js +3 -5
- package/dist/schema/lens.d.ts +4 -1
- package/dist/schema/lens.js +14 -7
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Patch } from "@automerge/automerge-repo";
|
|
2
|
+
/** Rebuild `prev` to match the post-change doc `after`, cloning only the spine to
|
|
3
|
+
* each patched value and sharing every untouched subtree by reference. */
|
|
4
|
+
export declare function applyPatches<T extends object>(prev: T, patches: Patch[], after: T): T;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// apply-patches.ts — doc → cell incremental invalidation.
|
|
2
|
+
//
|
|
3
|
+
// The naive bridge re-snapshots the whole doc on every change
|
|
4
|
+
// (`structuredClone(handle.doc())`), giving every sub-object a fresh identity, so
|
|
5
|
+
// every field lens recomputes even where nothing changed. `applyPatches` instead
|
|
6
|
+
// walks the Automerge `change` patches and rebuilds only the spine to each changed
|
|
7
|
+
// value: ancestors are shallow-cloned (new identity), the changed value is taken
|
|
8
|
+
// from the post-change doc, and every untouched sibling keeps its prior reference.
|
|
9
|
+
// Unchanged slices stay `Object.is`-equal, so their lenses never fire.
|
|
10
|
+
const shallow = (v) => (Array.isArray(v) ? v.slice() : { ...v });
|
|
11
|
+
function getIn(root, path) {
|
|
12
|
+
let cur = root;
|
|
13
|
+
for (const k of path)
|
|
14
|
+
cur = cur[k];
|
|
15
|
+
return cur;
|
|
16
|
+
}
|
|
17
|
+
/** Rebuild `prev` to match the post-change doc `after`, cloning only the spine to
|
|
18
|
+
* each patched value and sharing every untouched subtree by reference. */
|
|
19
|
+
export function applyPatches(prev, patches, after) {
|
|
20
|
+
if (patches.length === 0)
|
|
21
|
+
return prev;
|
|
22
|
+
const root = shallow(prev);
|
|
23
|
+
const owned = new Set([root]);
|
|
24
|
+
// Descend `path`, shallow-cloning each container the first time we enter it so a
|
|
25
|
+
// shared (prev) object is never mutated; returns the owned container at `path`.
|
|
26
|
+
const spine = (path) => {
|
|
27
|
+
let cur = root;
|
|
28
|
+
for (const k of path) {
|
|
29
|
+
let child = cur[k];
|
|
30
|
+
if (!owned.has(child)) {
|
|
31
|
+
child = shallow(child);
|
|
32
|
+
owned.add(child);
|
|
33
|
+
cur[k] = child;
|
|
34
|
+
}
|
|
35
|
+
cur = child;
|
|
36
|
+
}
|
|
37
|
+
return cur;
|
|
38
|
+
};
|
|
39
|
+
for (const p of patches) {
|
|
40
|
+
const path = p.path;
|
|
41
|
+
if (path.length === 0)
|
|
42
|
+
return structuredClone(after);
|
|
43
|
+
const last = path[path.length - 1];
|
|
44
|
+
// Object-key deletion: rebuild the container minus the key, surviving siblings shared.
|
|
45
|
+
if (p.action === "del" && typeof last === "string") {
|
|
46
|
+
delete spine(path.slice(0, -1))[last];
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Sequence ops (list splice/insert/del, text splice, range marks) point *into*
|
|
50
|
+
// their container; drop the trailing index to replace the whole list/string.
|
|
51
|
+
const seqOp = p.action === "del" ||
|
|
52
|
+
p.action === "insert" ||
|
|
53
|
+
p.action === "splice" ||
|
|
54
|
+
p.action === "mark" ||
|
|
55
|
+
p.action === "unmark";
|
|
56
|
+
const vp = seqOp && typeof last === "number" ? path.slice(0, -1) : path;
|
|
57
|
+
if (vp.length === 0)
|
|
58
|
+
return structuredClone(after);
|
|
59
|
+
spine(vp.slice(0, -1))[vp[vp.length - 1]] = structuredClone(getIn(after, vp));
|
|
60
|
+
}
|
|
61
|
+
return root;
|
|
62
|
+
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// CRDT is the shared core, every schema is just a projection.
|
|
18
18
|
import { cell, effect } from "../core/cell.js";
|
|
19
19
|
import { store } from "../core/store.js";
|
|
20
|
+
import { applyPatches } from "./apply-patches.js";
|
|
20
21
|
import { reconcile } from "./reconcile.js";
|
|
21
22
|
function deepEqual(a, b) {
|
|
22
23
|
if (Object.is(a, b))
|
|
@@ -40,8 +41,13 @@ function deepEqual(a, b) {
|
|
|
40
41
|
}
|
|
41
42
|
/** Wire an existing cell to a handle in both directions; returns an unbind. */
|
|
42
43
|
function bind(c, handle, by, replace) {
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// Patch-driven invalidation: rebuild only the spine to each changed value so
|
|
45
|
+
// untouched slices keep their identity and their lenses don't recompute. Fall
|
|
46
|
+
// back to a full snapshot when the change replaced this handle's whole scope.
|
|
47
|
+
const onChange = (p) => {
|
|
48
|
+
c.value = p.scopeReplaced
|
|
49
|
+
? structuredClone(handle.doc())
|
|
50
|
+
: applyPatches(c.peek(), p.patches, p.patchInfo.after);
|
|
45
51
|
};
|
|
46
52
|
handle.on("change", onChange);
|
|
47
53
|
const stop = effect(() => {
|
|
@@ -2,14 +2,15 @@ type Any = any;
|
|
|
2
2
|
/** Stable identity key for a list element; return a primitive. `undefined` (or a
|
|
3
3
|
* collision) on any element makes that list fall back to positional. */
|
|
4
4
|
export type By = (element: unknown) => unknown;
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
5
|
+
/** Selects fields whose value is *replaced* wholesale (a scalar assignment → an
|
|
6
|
+
* Automerge `put`) instead of recursively merged. Use this for opaque JSON blobs
|
|
7
|
+
* a downstream bridge can only consume as whole-object puts — e.g. tldraw's
|
|
8
|
+
* `richText`, whose patch applier rejects nested text `splice`s and mis-reads
|
|
9
|
+
* nested `del`s. A predicate receives the value's full path from the doc root
|
|
10
|
+
* (e.g. `["store", "shape:1", "props", "richText"]`); an array or Set of names
|
|
11
|
+
* matches any path whose last segment is one of them (key-name sugar). The value
|
|
12
|
+
* is still only written when it differs, so unrelated commits don't churn it. */
|
|
13
|
+
export type Replace = ((path: (string | number)[]) => boolean) | readonly string[] | ReadonlySet<string>;
|
|
13
14
|
/** Minimally mutate the Automerge node `target` (inside `handle.change`) to equal
|
|
14
15
|
* the plain value `next`. Pass `by` for identity-keyed list reconciliation, and
|
|
15
16
|
* `replace` to assign chosen keys wholesale instead of merging into them. */
|
|
@@ -16,6 +16,13 @@
|
|
|
16
16
|
// common subsequence keeps shared elements in place and emits minimal keyed
|
|
17
17
|
// splices/inserts for the rest, so reorders and mid-inserts merge cleanly.
|
|
18
18
|
import { updateText } from "@automerge/automerge-repo";
|
|
19
|
+
/** Normalize the `Replace` sugar (array/Set of key names) into a path predicate. */
|
|
20
|
+
function toReplacePred(r) {
|
|
21
|
+
if (typeof r === "function")
|
|
22
|
+
return r;
|
|
23
|
+
const names = r instanceof Set ? r : new Set(r);
|
|
24
|
+
return path => names.has(String(path[path.length - 1]));
|
|
25
|
+
}
|
|
19
26
|
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
20
27
|
/** Structural equality, used to skip a wholesale `replace` write when the field
|
|
21
28
|
* is already deep-equal (so reconciling an unrelated change doesn't rewrite it).
|
|
@@ -55,7 +62,7 @@ function deepEq(a, b) {
|
|
|
55
62
|
* the plain value `next`. Pass `by` for identity-keyed list reconciliation, and
|
|
56
63
|
* `replace` to assign chosen keys wholesale instead of merging into them. */
|
|
57
64
|
export function reconcile(target, next, by, replace) {
|
|
58
|
-
const ctx = { by, replace };
|
|
65
|
+
const ctx = { by, replace: replace && toReplacePred(replace), path: [] };
|
|
59
66
|
if (Array.isArray(next) && Array.isArray(target))
|
|
60
67
|
reconcileList(target, next, ctx);
|
|
61
68
|
else
|
|
@@ -136,32 +143,38 @@ function lcs(a, b) {
|
|
|
136
143
|
return keep;
|
|
137
144
|
}
|
|
138
145
|
function setKey(parent, key, a, b, inList, ctx) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
if (typeof b === "string" && typeof a === "string") {
|
|
149
|
-
// Char-level merge for object text fields; list string elements just assign
|
|
150
|
-
// (path-relative updateText targets a keyed field, not an array slot).
|
|
151
|
-
if (a !== b) {
|
|
152
|
-
if (inList)
|
|
146
|
+
ctx.path.push(key);
|
|
147
|
+
try {
|
|
148
|
+
// Wholesale-replace paths bypass the merge entirely: assign the value so
|
|
149
|
+
// Automerge emits a single `put` of the (re)built subtree — no nested text
|
|
150
|
+
// `splice`s, no `del`s — which is all some bridges can apply. Guard on
|
|
151
|
+
// deep-equality so reconciling an unrelated change doesn't rewrite it.
|
|
152
|
+
if (ctx.replace?.(ctx.path)) {
|
|
153
|
+
if (!deepEq(a, b))
|
|
153
154
|
parent[key] = b;
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (typeof b === "string" && typeof a === "string") {
|
|
158
|
+
// Char-level merge for object text fields; list string elements just assign
|
|
159
|
+
// (path-relative updateText targets a keyed field, not an array slot).
|
|
160
|
+
if (a !== b) {
|
|
161
|
+
if (inList)
|
|
162
|
+
parent[key] = b;
|
|
163
|
+
else
|
|
164
|
+
updateText(parent, [key], b);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
else if (Array.isArray(b) && Array.isArray(a)) {
|
|
168
|
+
reconcileList(a, b, ctx);
|
|
169
|
+
}
|
|
170
|
+
else if (isPlainObject(b) && isPlainObject(a)) {
|
|
171
|
+
reconcileObject(a, b, ctx);
|
|
172
|
+
}
|
|
173
|
+
else if (a !== b) {
|
|
174
|
+
parent[key] = b;
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
else if (isPlainObject(b) && isPlainObject(a)) {
|
|
162
|
-
reconcileObject(a, b, ctx);
|
|
163
|
-
}
|
|
164
|
-
else if (a !== b) {
|
|
165
|
-
parent[key] = b;
|
|
177
|
+
finally {
|
|
178
|
+
ctx.path.pop();
|
|
166
179
|
}
|
|
167
180
|
}
|
package/dist/core/_counts.d.ts
CHANGED
|
@@ -29,8 +29,6 @@ export interface Counts {
|
|
|
29
29
|
put: number;
|
|
30
30
|
/** A merge `fold` was invoked. */
|
|
31
31
|
fold: number;
|
|
32
|
-
/** A stateful `step` was invoked (backward commit path). */
|
|
33
|
-
step: number;
|
|
34
32
|
}
|
|
35
33
|
/** Live counter record. Mutated in place so importers hold a stable reference. */
|
|
36
34
|
export declare const counts: Counts;
|
package/dist/core/_counts.js
CHANGED
package/dist/core/cell.d.ts
CHANGED
|
@@ -21,9 +21,8 @@ interface LensLink {
|
|
|
21
21
|
index: number;
|
|
22
22
|
parent: Cell<unknown>;
|
|
23
23
|
child: Cell<unknown>;
|
|
24
|
-
/** Spliced into `parent.childEdges` yet? The
|
|
25
|
-
*
|
|
26
|
-
* parent's child order is arm-order (co-writer resolution is last-write-wins). */
|
|
24
|
+
/** Spliced into `parent.childEdges` yet? The up-list is lazy on first back-mark,
|
|
25
|
+
* so a parent's child order is arm-order (co-writer resolution is last-wins). */
|
|
27
26
|
linked: boolean;
|
|
28
27
|
nextParent: LensLink | undefined;
|
|
29
28
|
prevChild: LensLink | undefined;
|
|
@@ -39,35 +38,19 @@ declare class MergeNode<T> {
|
|
|
39
38
|
constructor(fold: MergeFold<T> | undefined);
|
|
40
39
|
}
|
|
41
40
|
declare class BwdSpec {
|
|
42
|
-
/** Lens `put` (dual of `getter`): `put(target)` for 1→1 / multi-out
|
|
43
|
-
*
|
|
44
|
-
* for stateful. `undefined` for a merge (folds) or pin (absorbs). */
|
|
41
|
+
/** Lens `put` (dual of `getter`): `put(target)` for 1→1 / multi-out,
|
|
42
|
+
* `put(target, sources, c)` for stateful. `undefined` for a merge or pin. */
|
|
45
43
|
put: ((target: any, current?: any) => any) | undefined;
|
|
46
44
|
/** Fold payload; present ⇒ a fan-in merge. */
|
|
47
45
|
merge: MergeNode<unknown> | undefined;
|
|
48
|
-
/**
|
|
49
|
-
|
|
46
|
+
/** The mutable complement object a stateful optic's `get`/`put` thread (and may
|
|
47
|
+
* mutate in place); present ⇒ a complement-carrying (stateful) lens. Seeded once
|
|
48
|
+
* per bind from `optic.complement`, never reassigned. */
|
|
49
|
+
stateful: object | undefined;
|
|
50
50
|
/** `put` yields a per-parent tuple (split / stateful) vs a scalar (1→1). The
|
|
51
51
|
* only discriminant not derivable from topology (a 1-parent split is a tuple). */
|
|
52
52
|
scatter: boolean;
|
|
53
53
|
}
|
|
54
|
-
/** Runtime state of a symmetric-lens complement, kept off `BwdSpec` so plain
|
|
55
|
-
* lenses don't carry its slots. See the stateful-lens header for the theory
|
|
56
|
-
* (symmetric/edit lenses) and the version-stamp provenance. */
|
|
57
|
-
declare class StatefulCore {
|
|
58
|
-
/** Engine-owned memory the view discards. */
|
|
59
|
-
complement: unknown;
|
|
60
|
-
/** Advance the complement: `step(sources, complement)`. Run only when the
|
|
61
|
-
* sources actually moved (the engine gates it; see the stateful header). */
|
|
62
|
-
step: (sources: any, complement: any) => any;
|
|
63
|
-
/** Sum of the parents' `version`s as of the last sync. Sources moved iff the
|
|
64
|
-
* live sum differs — the lazy own-vs-external provenance that replaces a value
|
|
65
|
-
* witness. A read syncs it after stepping; a back-write re-stamps it post-order
|
|
66
|
-
* (own writes don't re-step, so `bwd` must leave the complement consistent).
|
|
67
|
-
* Seeded to `-1` (sums are ≥ 0) so the first use always folds the sources in. */
|
|
68
|
-
stamp: number;
|
|
69
|
-
constructor(complement: unknown, step: (sources: any, complement: any) => any);
|
|
70
|
-
}
|
|
71
54
|
/** Multi-out / stateful back-write sentinel: "leave this parent untouched."
|
|
72
55
|
* Every non-`SKIP` slot is written verbatim, `undefined` included; a short array
|
|
73
56
|
* skips the trailing parents. (1→1 `put` always writes its one parent.) */
|
|
@@ -129,17 +112,24 @@ export interface CellOptions<T = unknown> {
|
|
|
129
112
|
/** Debug label; surfaces in cyclic-read errors and graph dumps (see debug.ts). */
|
|
130
113
|
name?: string;
|
|
131
114
|
}
|
|
132
|
-
/**
|
|
133
|
-
*
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
115
|
+
/** Per-source back-write shape: a partial SKIP-able tuple for N parents (an
|
|
116
|
+
* array source), the whole new source (or `SKIP`) for one. */
|
|
117
|
+
export type Updates<S> = S extends readonly unknown[] ? BackUpdates<{
|
|
118
|
+
[K in keyof S]: S[K] | Skip;
|
|
119
|
+
}> : S | Skip;
|
|
120
|
+
/** A lens as a first-class value, unbound from any source. `get` projects the
|
|
121
|
+
* source(s) `S` to a view `V`; `put` writes the view back as per-source
|
|
122
|
+
* `Updates<S>`. A 1-arg `put` is an `iso` and ignores the source. `complement`
|
|
123
|
+
* present ⇒ stateful (see the stateful-optic header). Apply with `cell.lens` /
|
|
124
|
+
* `lens`; build with `optic` / `iso` / `atKey`. */
|
|
125
|
+
export interface Optic<S, V, C extends object = never> {
|
|
126
|
+
get(s: S, c: C): V;
|
|
127
|
+
put(target: V, s: S, c: C): Updates<S>;
|
|
128
|
+
/** Present ⇒ stateful; seeds the per-bind complement. */
|
|
129
|
+
complement?: (s: S) => C;
|
|
142
130
|
}
|
|
131
|
+
/** An optic bound to a source, with its complement type hidden. */
|
|
132
|
+
export type AppliedOptic<S, V> = Optic<S, V, any>;
|
|
143
133
|
export declare class Cell<T = unknown> implements ReactiveNode {
|
|
144
134
|
/** @internal */
|
|
145
135
|
flags: number;
|
|
@@ -180,10 +170,6 @@ export declare class Cell<T = unknown> implements ReactiveNode {
|
|
|
180
170
|
/** @internal Visit epoch for `backResolve`'s collect phase (dedups diamonds
|
|
181
171
|
* without a Set; compared against the global `backCycle`). */
|
|
182
172
|
bEpoch: number;
|
|
183
|
-
/** @internal Monotone committed-change counter. A stateful lens sums its
|
|
184
|
-
* parents' versions to detect "did my sources move since I last synced?" —
|
|
185
|
-
* the lazy provenance that replaces a value witness (see the stateful header). */
|
|
186
|
-
version: number;
|
|
187
173
|
/** Optional debug label (`cell(0, { name })`); used by errors and graph dumps. */
|
|
188
174
|
name: string | undefined;
|
|
189
175
|
constructor(initial: T, opts?: CellOptions<T>);
|
|
@@ -197,18 +183,16 @@ export declare class Cell<T = unknown> implements ReactiveNode {
|
|
|
197
183
|
/** @internal */
|
|
198
184
|
_unwatched(): void;
|
|
199
185
|
peek(): T;
|
|
200
|
-
/** Endomorphic lens
|
|
201
|
-
*
|
|
186
|
+
/** Endomorphic lens from a `fwd`/`bwd` pair (2-arg `bwd` reads the source, 1-arg
|
|
187
|
+
* reconstructs it), or apply optic value(s), chaining left-to-right. The function
|
|
188
|
+
* form is same-type (returns `this`); optics are cross-type. */
|
|
202
189
|
lens(this: Cell<T>, fwd: (v: T) => T, bwd: (target: T, current: T) => T): this;
|
|
190
|
+
lens<V1>(this: Cell<T>, o: AppliedOptic<T, V1>): Writable<Cell<V1>>;
|
|
191
|
+
lens<V1, V2>(this: Cell<T>, o1: AppliedOptic<T, V1>, o2: AppliedOptic<V1, V2>): Writable<Cell<V2>>;
|
|
192
|
+
lens<V1, V2, V3>(this: Cell<T>, o1: AppliedOptic<T, V1>, o2: AppliedOptic<V1, V2>, o3: AppliedOptic<V2, V3>): Writable<Cell<V3>>;
|
|
203
193
|
/** Read-only same-type view: the RO dual of the endo `.lens`. For a cross-type view use the typed static
|
|
204
194
|
* `Target.derive(src, fn)`. */
|
|
205
195
|
derive(this: Cell<T>, fn: (v: T) => T): this;
|
|
206
|
-
/** Apply optic value(s) as a writable lens: `c.through(o)` ≡ `lens(c, o.get,
|
|
207
|
-
* o.put)`; multiple optics compose left-to-right (`c.through(a, b)` = `a`
|
|
208
|
-
* then `b`). Cross-type, unlike the endomorphic instance `.lens`. */
|
|
209
|
-
through<B>(this: Cell<T>, o: Optic<T, B>): Writable<Cell<B>>;
|
|
210
|
-
through<B, C>(this: Cell<T>, o1: Optic<T, B>, o2: Optic<B, C>): Writable<Cell<C>>;
|
|
211
|
-
through<B, C, D>(this: Cell<T>, o1: Optic<T, B>, o2: Optic<B, C>, o3: Optic<C, D>): Writable<Cell<D>>;
|
|
212
196
|
/** Backward fan-in: forwards its parent's value unchanged; on write, folds N
|
|
213
197
|
* contributors into one value. `fold` defaults to last-writer-wins. */
|
|
214
198
|
merge(this: Cell<T>, fold?: MergeFold<T>): Cell<T>;
|
|
@@ -218,14 +202,14 @@ export declare class Cell<T = unknown> implements ReactiveNode {
|
|
|
218
202
|
static derive<C extends AnyCellCtor, P>(this: C, parent: Read<P>, fn: (v: P) => Inner<InstanceType<C>>): InstanceType<C>;
|
|
219
203
|
static derive<C extends AnyCellCtor, P extends readonly Read<unknown>[]>(this: C, parents: P, fn: (vals: ReadValues<P>) => Inner<InstanceType<C>>): InstanceType<C>;
|
|
220
204
|
static derive<C extends AnyCellCtor>(this: C, fn: () => Inner<InstanceType<C>>): InstanceType<C>;
|
|
221
|
-
/** Writable lens. `Cls.lens(parent, fwd, bwd)` for one
|
|
222
|
-
* `Cls.lens(parents, fwd, bwd)` for N
|
|
223
|
-
*
|
|
224
|
-
* complement-carrying
|
|
205
|
+
/** Writable lens, typed to this class. `Cls.lens(parent, fwd, bwd)` for one
|
|
206
|
+
* input, `Cls.lens(parents, fwd, bwd)` for N (a 2-arg `bwd` reads the source, a
|
|
207
|
+
* 1-arg `bwd` reconstructs it); `Cls.lens(parent(s), optic)` applies an optic
|
|
208
|
+
* value (pure or complement-carrying), chaining if several are given. */
|
|
225
209
|
static lens<C extends AnyCellCtor, P>(this: C, parent: Read<P>, fwd: (v: P) => Inner<InstanceType<C>>, bwd: (target: Inner<InstanceType<C>>, v: P) => P): Writable<InstanceType<C>>;
|
|
226
210
|
static lens<C extends AnyCellCtor, P extends readonly Read<unknown>[]>(this: C, parents: P, fwd: (vals: ReadValues<P>) => Inner<InstanceType<C>>, bwd: (target: Inner<InstanceType<C>>, vals: ReadValues<P>) => BackUpdates<ReadValuesOrSkip<P>>): Writable<InstanceType<C>>;
|
|
227
|
-
static lens<C extends AnyCellCtor, P
|
|
228
|
-
static lens<C extends AnyCellCtor, P extends readonly Read<unknown>[]
|
|
211
|
+
static lens<C extends AnyCellCtor, P>(this: C, parent: Read<P>, optic: AppliedOptic<P, Inner<InstanceType<C>>>): Writable<InstanceType<C>>;
|
|
212
|
+
static lens<C extends AnyCellCtor, P extends readonly Read<unknown>[]>(this: C, parents: P, optic: AppliedOptic<ReadValues<P>, Inner<InstanceType<C>>>): Writable<InstanceType<C>>;
|
|
229
213
|
/** Type predicate against this class: `Vec.is(x)` narrows `x` to `Vec`.
|
|
230
214
|
* Inherited static; works for any subclass via polymorphic `this`. */
|
|
231
215
|
static is<C extends AnyCellCtor>(this: C, v: unknown): v is InstanceType<C>;
|
|
@@ -239,35 +223,6 @@ export declare class Cell<T = unknown> implements ReactiveNode {
|
|
|
239
223
|
/** Typed field lens onto `parent.value[key]`. RO parent → RO derive;
|
|
240
224
|
* writable parent → bidirectional lens with spread-replace `put`. */
|
|
241
225
|
export declare function fieldOf<C extends AnyCellCtor>(parent: Cell<any>, key: string | number | symbol, Cls: C): InstanceType<C>;
|
|
242
|
-
export interface StatefulBwd<S extends readonly unknown[], C> {
|
|
243
|
-
/** Per-parent updates: a value (written verbatim, `undefined` included) or
|
|
244
|
-
* `SKIP` to leave that parent. A short array skips the trailing parents. */
|
|
245
|
-
updates: BackUpdates<{
|
|
246
|
-
[K in keyof S]: S[K] | Skip;
|
|
247
|
-
}>;
|
|
248
|
-
complement: C;
|
|
249
|
-
}
|
|
250
|
-
export interface StatefulLensSpec<S extends readonly unknown[], V, C> {
|
|
251
|
-
init: (sources: S) => C;
|
|
252
|
-
/** Advance the complement on an outside change. Optional — defaults to `init`
|
|
253
|
-
* (the memoryless refresh); the engine runs it only when sources actually move. */
|
|
254
|
-
step?: (sources: S, complement: C) => C;
|
|
255
|
-
fwd: (sources: S, complement: C) => V;
|
|
256
|
-
bwd: (target: V, sources: S, complement: C) => StatefulBwd<S, C>;
|
|
257
|
-
}
|
|
258
|
-
/** Single-source `bwd` result: a scalar `update` (or `SKIP`) plus the complement. */
|
|
259
|
-
export interface StatefulBwd1<S, C> {
|
|
260
|
-
update: S | Skip;
|
|
261
|
-
complement: C;
|
|
262
|
-
}
|
|
263
|
-
/** Single-source stateful spec — the scalar fast-path of `StatefulLensSpec`: one
|
|
264
|
-
* parent, so `init`/`step`/`fwd`/`bwd` take the source value directly, not a tuple. */
|
|
265
|
-
export interface StatefulLensSpec1<S, V, C> {
|
|
266
|
-
init: (source: S) => C;
|
|
267
|
-
step?: (source: S, complement: C) => C;
|
|
268
|
-
fwd: (source: S, complement: C) => V;
|
|
269
|
-
bwd: (target: V, source: S, complement: C) => StatefulBwd1<S, C>;
|
|
270
|
-
}
|
|
271
226
|
/** Writable source; passes an existing `Writable` through (idempotent). */
|
|
272
227
|
export declare function cell<T>(initial: T | Writable<Cell<T>>, opts?: CellOptions<T>): Writable<Cell<T>>;
|
|
273
228
|
/** Untyped read-only view: `derive(parent, fn)`, `derive(parents, fn)`,
|
|
@@ -275,9 +230,9 @@ export declare function cell<T>(initial: T | Writable<Cell<T>>, opts?: CellOptio
|
|
|
275
230
|
export declare function derive<P, R>(parent: Read<P>, fn: (v: P) => R): Cell<R>;
|
|
276
231
|
export declare function derive<P extends readonly Read<unknown>[], R>(parents: P, fn: (vals: ReadValues<P>) => R): Cell<R>;
|
|
277
232
|
export declare function derive<R>(fn: () => R): Cell<R>;
|
|
278
|
-
/** Untyped lens, inferring `R` from the closures. A 2-arg `bwd` reads
|
|
279
|
-
* source, a 1-arg `bwd` reconstructs it; `lens(parent(s),
|
|
280
|
-
* complement-carrying
|
|
233
|
+
/** Untyped lens, inferring `R` from the closures or optic. A 2-arg `bwd` reads
|
|
234
|
+
* the source, a 1-arg `bwd` reconstructs it; `lens(parent(s), optic)` applies an
|
|
235
|
+
* optic value (pure or complement-carrying), chaining if several are given. */
|
|
281
236
|
export declare function lens<P, R>(parent: Read<P>, fwd: (v: P) => R, bwd: (target: R, v: P) => P): Writable<Cell<R>>;
|
|
282
237
|
export declare function lens<P extends readonly Read<unknown>[], R>(parents: P, fwd: (vals: ReadValues<P>) => R, bwd: (target: R, vals: ReadValues<P>) => ReadValuesOrSkip<P>): Writable<Cell<R>>;
|
|
283
238
|
export declare function lens<S extends Record<string, Read<unknown>>, R>(parents: S, fwd: (vals: {
|
|
@@ -287,8 +242,10 @@ export declare function lens<S extends Record<string, Read<unknown>>, R>(parents
|
|
|
287
242
|
}) => Partial<{
|
|
288
243
|
[K in keyof S]: Inner<S[K]> | Skip;
|
|
289
244
|
}>): Writable<Cell<R>>;
|
|
290
|
-
export declare function lens<P, R
|
|
291
|
-
export declare function lens<P
|
|
245
|
+
export declare function lens<P, R>(parent: Read<P>, optic: AppliedOptic<P, R>): Writable<Cell<R>>;
|
|
246
|
+
export declare function lens<P, V1, R>(parent: Read<P>, o1: AppliedOptic<P, V1>, o2: AppliedOptic<V1, R>): Writable<Cell<R>>;
|
|
247
|
+
export declare function lens<P, V1, V2, R>(parent: Read<P>, o1: AppliedOptic<P, V1>, o2: AppliedOptic<V1, V2>, o3: AppliedOptic<V2, R>): Writable<Cell<R>>;
|
|
248
|
+
export declare function lens<P extends readonly Read<unknown>[], R>(parents: P, optic: AppliedOptic<ReadValues<P>, R>): Writable<Cell<R>>;
|
|
292
249
|
export declare function effect(fn: () => (() => void) | void): () => void;
|
|
293
250
|
/** Run all pending effects now, synchronously — the escape hatch for code that
|
|
294
251
|
* must observe effect side-effects before yielding. Reads never need it. */
|