bireactive 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -7
- package/dist/automerge/doc-cell.d.ts +24 -11
- package/dist/automerge/doc-cell.js +19 -13
- package/dist/automerge/index.d.ts +3 -2
- package/dist/automerge/index.js +6 -5
- package/dist/automerge/reconcile.d.ts +5 -2
- package/dist/automerge/reconcile.js +73 -15
- package/dist/core/_counts.js +5 -12
- package/dist/core/cell.d.ts +3 -3
- package/dist/core/cell.js +6 -7
- package/dist/core/derived-geometry.js +4 -7
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.js +3 -1
- package/dist/core/lenses/aggregates.d.ts +42 -52
- package/dist/core/lenses/aggregates.js +225 -116
- package/dist/core/lenses/geometry.d.ts +22 -4
- package/dist/core/lenses/geometry.js +59 -27
- package/dist/core/lenses/index.d.ts +5 -6
- package/dist/core/lenses/index.js +5 -6
- package/dist/core/lenses/memory.js +4 -17
- package/dist/core/lenses/numerical.d.ts +100 -0
- package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
- package/dist/core/lenses/point-cloud.d.ts +67 -0
- package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +218 -81
- package/dist/core/lenses/snap.d.ts +1 -1
- package/dist/core/lenses/snap.js +3 -10
- package/dist/core/lenses/text.d.ts +40 -0
- package/dist/core/lenses/text.js +202 -0
- package/dist/core/lifecycle.js +3 -6
- package/dist/core/linalg.js +5 -11
- package/dist/core/optic.js +10 -15
- package/dist/core/optics.js +4 -8
- package/dist/core/store.d.ts +1 -2
- package/dist/core/store.js +7 -15
- package/dist/core/traits.d.ts +4 -7
- package/dist/core/traits.js +8 -12
- package/dist/core/values/anchor.js +0 -4
- package/dist/core/values/arr.d.ts +110 -0
- package/dist/core/values/arr.js +336 -0
- package/dist/core/values/audio.d.ts +8 -9
- package/dist/core/values/audio.js +7 -23
- package/dist/core/values/bool.d.ts +11 -11
- package/dist/core/values/bool.js +12 -22
- package/dist/core/values/box.d.ts +15 -20
- package/dist/core/values/box.js +20 -33
- package/dist/core/values/canvas.d.ts +18 -25
- package/dist/core/values/canvas.js +17 -48
- package/dist/core/values/color.d.ts +5 -7
- package/dist/core/values/color.js +5 -11
- package/dist/core/values/field.d.ts +6 -7
- package/dist/core/values/field.js +10 -35
- package/dist/core/values/flags.d.ts +1 -2
- package/dist/core/values/flags.js +1 -17
- package/dist/core/values/gpu.d.ts +6 -10
- package/dist/core/values/gpu.js +8 -22
- package/dist/core/values/matrix.d.ts +2 -4
- package/dist/core/values/matrix.js +2 -12
- package/dist/core/values/num.d.ts +19 -28
- package/dist/core/values/num.js +23 -41
- package/dist/core/values/pose.d.ts +2 -4
- package/dist/core/values/pose.js +3 -12
- package/dist/core/values/range.d.ts +18 -26
- package/dist/core/values/range.js +22 -39
- package/dist/core/values/reg/ambiguity.d.ts +8 -0
- package/dist/core/values/reg/ambiguity.js +131 -0
- package/dist/core/values/reg/engine.d.ts +91 -0
- package/dist/core/values/reg/engine.js +373 -0
- package/dist/core/values/reg/nfa.d.ts +42 -0
- package/dist/core/values/reg/nfa.js +391 -0
- package/dist/core/values/reg/regex.d.ts +7 -0
- package/dist/core/values/reg/regex.js +318 -0
- package/dist/core/values/reg/types.d.ts +60 -0
- package/dist/core/values/reg/types.js +3 -0
- package/dist/core/values/reg.d.ts +250 -0
- package/dist/core/values/reg.js +649 -0
- package/dist/core/values/str.d.ts +16 -60
- package/dist/core/values/str.js +133 -315
- package/dist/core/values/template.js +1 -24
- package/dist/core/values/transform.d.ts +3 -5
- package/dist/core/values/transform.js +3 -12
- package/dist/core/values/tri.d.ts +9 -10
- package/dist/core/values/tri.js +9 -15
- package/dist/core/values/vec.d.ts +9 -24
- package/dist/core/values/vec.js +9 -64
- package/dist/index.d.ts +0 -11
- package/dist/index.js +1 -11
- package/package.json +17 -10
- package/dist/coll.d.ts +0 -74
- package/dist/coll.js +0 -210
- package/dist/core/lenses/closed-form-policies.d.ts +0 -57
- package/dist/core/lenses/decompositions.d.ts +0 -14
- package/dist/core/lenses/decompositions.js +0 -224
- package/dist/core/lenses/domain-aggregates.d.ts +0 -42
- package/dist/core/lenses/domain-aggregates.js +0 -245
- package/dist/core/lenses/typed-factor.d.ts +0 -40
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# bi-reactive
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
[NPM](https://www.npmjs.com/package/bireactive) · [GitHub](https://github.com/OrionReed/bireactive) · [Demos](https://orionreed.github.io/bireactive/) · [API](https://orionreed.github.io/bireactive/api/)
|
|
4
4
|
|
|
5
5
|
A signals-like bidirectional reactive programming system where edges can go both ways. Forward and backward propagation are handled by the engine, with the same set of caveats as regular reactive programming.
|
|
6
6
|
|
|
@@ -10,9 +10,9 @@ A signals-like bidirectional reactive programming system where edges can go both
|
|
|
10
10
|
npm install bireactive
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Runtime dependencies [`temml`](https://temml.org) (for `tex`) and
|
|
13
|
+
Runtime dependencies [`temml`](https://temml.org) (for `tex`), [`Automerge`](https://automerge.org) and
|
|
14
14
|
[`prism-esm`](https://github.com/orionhealthotago/prism-esm) (for `code`) are
|
|
15
|
-
installed automatically.
|
|
15
|
+
installed automatically. These will be split into separate packages later so the
|
|
16
16
|
core stays dependency-free.
|
|
17
17
|
|
|
18
18
|
## Sketch
|
|
@@ -61,20 +61,27 @@ inside.value = true; // assert membership…
|
|
|
61
61
|
q.value; // { x: 100, y: 50 } — q snaps to the nearest in-box point
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
- **Guide** — start with the [Overview](guide/overview.md) (a map of every
|
|
67
|
+
capability) and [Getting Started](guide/getting-started.md).
|
|
68
|
+
- **[API reference](https://orionreed.github.io/bireactive/api/)** — every
|
|
69
|
+
export, grouped by domain; the guide lives in its sidebar too.
|
|
70
|
+
- **[Demos](https://orionreed.github.io/bireactive/)** — live, interactive examples.
|
|
71
|
+
|
|
64
72
|
## Develop
|
|
65
73
|
|
|
66
74
|
```sh
|
|
67
75
|
npm run dev # serve the landing page at :5555
|
|
68
|
-
npm run site # build the static site into dist-web/
|
|
76
|
+
npm run site # build the static site + API docs into dist-web/
|
|
77
|
+
npm run docs # build just the API reference + guide into dist-web/api/
|
|
69
78
|
npm run build # compile the library into dist/
|
|
70
79
|
npm test # run the test suite
|
|
71
80
|
```
|
|
72
81
|
|
|
73
82
|
## Status
|
|
74
83
|
|
|
75
|
-
`0.x` — APIs are still moving. The package is a single bundle today;
|
|
76
|
-
sub-packages (`@bireactive/core`, `@bireactive/animation`, `@bireactive/shapes`, …) are used
|
|
77
|
-
internally as path aliases and will be split out once the surface settles.
|
|
84
|
+
`0.x` — APIs are still moving frequently. The package is a single bundle today; sub-packages will be split out once the surface settles.
|
|
78
85
|
|
|
79
86
|
## License
|
|
80
87
|
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import type { DocHandle } from "@automerge/automerge-repo";
|
|
2
2
|
import { type Cell, type Writable } from "../core/cell.js";
|
|
3
3
|
import { type Store } from "../core/store.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { type By } from "./reconcile.js";
|
|
5
|
+
/** Bridge options shared by every `connect*` entry. */
|
|
6
|
+
export interface DocOptions {
|
|
7
|
+
/** Identity key for list elements, enabling keyed reconciliation (see `reconcile`). */
|
|
8
|
+
by?: By;
|
|
9
|
+
}
|
|
10
|
+
/** Lifecycle shared by every bridge: retarget the doc, detach both directions. */
|
|
11
|
+
export interface DocLifecycle<T> {
|
|
10
12
|
/** Point the same cell at a different doc, keeping every bound lens/view alive. */
|
|
11
13
|
retarget: (handle: DocHandle<T>) => void;
|
|
12
14
|
/** Detach both directions (call from `disconnectedCallback`). */
|
|
13
15
|
dispose: () => void;
|
|
14
16
|
}
|
|
17
|
+
/** Doc bridged to a writable cell; writes (direct or via a lens) flow to the CRDT. */
|
|
18
|
+
export interface CellBridge<T> extends DocLifecycle<T> {
|
|
19
|
+
cell: Writable<Cell<T>>;
|
|
20
|
+
}
|
|
21
|
+
/** Doc bridged to a deep `store` — `bridge.store.a.b.value = x` commits to the doc. */
|
|
22
|
+
export interface StoreBridge<T> extends DocLifecycle<T> {
|
|
23
|
+
store: Store<T>;
|
|
24
|
+
}
|
|
25
|
+
/** Doc bridged to both a cell and a deep store. */
|
|
26
|
+
export interface DocBridge<T> extends CellBridge<T>, StoreBridge<T> {
|
|
27
|
+
}
|
|
28
|
+
/** Connect a `DocHandle` to a reactive cell, syncing both ways. */
|
|
29
|
+
export declare function connectCell<T extends object>(handle: DocHandle<T>, opts?: DocOptions): CellBridge<T>;
|
|
30
|
+
/** Connect a `DocHandle` to a deep `store`, syncing both ways. */
|
|
31
|
+
export declare function connectStore<T extends object>(handle: DocHandle<T>, opts?: DocOptions): StoreBridge<T>;
|
|
15
32
|
/** Connect a `DocHandle` to a reactive cell + store, syncing both ways. */
|
|
16
|
-
export declare function connectDoc<T extends object>(handle: DocHandle<T
|
|
17
|
-
/** Doc as a writable cell (page-lifetime; use {@link connectDoc} when you need disposal). */
|
|
18
|
-
export declare function docCell<T extends object>(handle: DocHandle<T>): Writable<Cell<T>>;
|
|
19
|
-
/** Doc as a deep store (page-lifetime; use {@link connectDoc} when you need disposal). */
|
|
20
|
-
export declare function docStore<T extends object>(handle: DocHandle<T>): Store<T>;
|
|
33
|
+
export declare function connectDoc<T extends object>(handle: DocHandle<T>, opts?: DocOptions): DocBridge<T>;
|
|
@@ -39,42 +39,48 @@ function deepEqual(a, b) {
|
|
|
39
39
|
return true;
|
|
40
40
|
}
|
|
41
41
|
/** Wire an existing cell to a handle in both directions; returns an unbind. */
|
|
42
|
-
function bind(c, handle) {
|
|
42
|
+
function bind(c, handle, by) {
|
|
43
43
|
const onChange = () => {
|
|
44
44
|
c.value = structuredClone(handle.doc());
|
|
45
45
|
};
|
|
46
46
|
handle.on("change", onChange);
|
|
47
47
|
const stop = effect(() => {
|
|
48
48
|
const next = c.value;
|
|
49
|
-
handle.change((d) => reconcile(d, next));
|
|
49
|
+
handle.change((d) => reconcile(d, next, by));
|
|
50
50
|
});
|
|
51
51
|
return () => {
|
|
52
52
|
stop();
|
|
53
53
|
handle.off("change", onChange);
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
|
-
/**
|
|
57
|
-
|
|
56
|
+
/** Core: a doc-backed cell plus lifecycle. The cell projections layer on top. */
|
|
57
|
+
function connect(handle, opts) {
|
|
58
|
+
const by = opts?.by;
|
|
58
59
|
const c = cell(structuredClone(handle.doc()), { equals: deepEqual, name: "doc" });
|
|
59
|
-
let unbind = bind(c, handle);
|
|
60
|
+
let unbind = bind(c, handle, by);
|
|
60
61
|
return {
|
|
61
62
|
cell: c,
|
|
62
|
-
store: store(c),
|
|
63
63
|
retarget: next => {
|
|
64
64
|
unbind();
|
|
65
65
|
// Seed the cell from the new doc *before* re-binding, so the cell→doc
|
|
66
66
|
// effect doesn't push the old value into the freshly targeted doc.
|
|
67
67
|
c.value = structuredClone(next.doc());
|
|
68
|
-
unbind = bind(c, next);
|
|
68
|
+
unbind = bind(c, next, by);
|
|
69
69
|
},
|
|
70
70
|
dispose: () => unbind(),
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
74
|
-
export function
|
|
75
|
-
return
|
|
73
|
+
/** Connect a `DocHandle` to a reactive cell, syncing both ways. */
|
|
74
|
+
export function connectCell(handle, opts) {
|
|
75
|
+
return connect(handle, opts);
|
|
76
|
+
}
|
|
77
|
+
/** Connect a `DocHandle` to a deep `store`, syncing both ways. */
|
|
78
|
+
export function connectStore(handle, opts) {
|
|
79
|
+
const { cell: c, retarget, dispose } = connect(handle, opts);
|
|
80
|
+
return { store: store(c), retarget, dispose };
|
|
76
81
|
}
|
|
77
|
-
/**
|
|
78
|
-
export function
|
|
79
|
-
|
|
82
|
+
/** Connect a `DocHandle` to a reactive cell + store, syncing both ways. */
|
|
83
|
+
export function connectDoc(handle, opts) {
|
|
84
|
+
const b = connect(handle, opts);
|
|
85
|
+
return { ...b, store: store(b.cell) };
|
|
80
86
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export type { DocBridge } from "./doc-cell.js";
|
|
2
|
-
export {
|
|
1
|
+
export type { CellBridge, DocBridge, DocLifecycle, DocOptions, StoreBridge } from "./doc-cell.js";
|
|
2
|
+
export { connectCell, connectDoc, connectStore } from "./doc-cell.js";
|
|
3
|
+
export type { By } from "./reconcile.js";
|
|
3
4
|
export { reconcile } from "./reconcile.js";
|
package/dist/automerge/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// @bireactive/automerge — view an Automerge CRDT through the reactive graph.
|
|
2
2
|
//
|
|
3
3
|
// `connectDoc(handle)` turns a `DocHandle` into a `Writable<Cell<T>>` plus a deep
|
|
4
|
-
// `store`, synced both ways
|
|
5
|
-
//
|
|
6
|
-
// is the apex, every view is a leg.
|
|
7
|
-
// writes merge-friendly; it's exported
|
|
4
|
+
// `store`, synced both ways; `connectCell`/`connectStore` give just one projection.
|
|
5
|
+
// Lens/`store` views off that one cell give you many schemas over a single shared
|
|
6
|
+
// doc with no privileged "primary" — the CRDT is the apex, every view is a leg.
|
|
7
|
+
// `reconcile` is the doc-side diff that keeps writes merge-friendly; it's exported
|
|
8
|
+
// for custom bridges.
|
|
8
9
|
//
|
|
9
10
|
// Automerge is an optional peer dependency: import this entry only when you've
|
|
10
11
|
// installed `@automerge/automerge-repo`.
|
|
11
|
-
export {
|
|
12
|
+
export { connectCell, connectDoc, connectStore } from "./doc-cell.js";
|
|
12
13
|
export { reconcile } from "./reconcile.js";
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
type Any = any;
|
|
2
|
+
/** Stable identity key for a list element; return a primitive. `undefined` (or a
|
|
3
|
+
* collision) on any element makes that list fall back to positional. */
|
|
4
|
+
export type By = (element: unknown) => unknown;
|
|
2
5
|
/** Minimally mutate the Automerge node `target` (inside `handle.change`) to equal
|
|
3
|
-
* the plain value `next`. */
|
|
4
|
-
export declare function reconcile(target: Any, next: Any): void;
|
|
6
|
+
* the plain value `next`. Pass `by` for identity-keyed list reconciliation. */
|
|
7
|
+
export declare function reconcile(target: Any, next: Any, by?: By): void;
|
|
5
8
|
export {};
|
|
@@ -9,38 +9,96 @@
|
|
|
9
9
|
// `updateText` for strings (char-level), in-place splices for lists, recursive
|
|
10
10
|
// descent for objects, scalar sets for the rest.
|
|
11
11
|
//
|
|
12
|
-
// List handling is
|
|
13
|
-
//
|
|
14
|
-
// insert
|
|
15
|
-
//
|
|
12
|
+
// List handling is positional by default: element-wise in place, with a tail
|
|
13
|
+
// push/truncate. Correct for edits/appends/truncations, but a reorder or mid
|
|
14
|
+
// insert rewrites every shifted slot's scalars — merge-hostile. Pass `by` for
|
|
15
|
+
// identity-keyed reconciliation (mirrors the `eachBy` lens's `by`): a longest
|
|
16
|
+
// common subsequence keeps shared elements in place and emits minimal keyed
|
|
17
|
+
// splices/inserts for the rest, so reorders and mid-inserts merge cleanly.
|
|
16
18
|
import { updateText } from "@automerge/automerge-repo";
|
|
17
19
|
const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
18
20
|
/** Minimally mutate the Automerge node `target` (inside `handle.change`) to equal
|
|
19
|
-
* the plain value `next`. */
|
|
20
|
-
export function reconcile(target, next) {
|
|
21
|
+
* the plain value `next`. Pass `by` for identity-keyed list reconciliation. */
|
|
22
|
+
export function reconcile(target, next, by) {
|
|
21
23
|
if (Array.isArray(next) && Array.isArray(target))
|
|
22
|
-
reconcileList(target, next);
|
|
24
|
+
reconcileList(target, next, by);
|
|
23
25
|
else
|
|
24
|
-
reconcileObject(target, next);
|
|
26
|
+
reconcileObject(target, next, by);
|
|
25
27
|
}
|
|
26
|
-
function reconcileObject(target, next) {
|
|
28
|
+
function reconcileObject(target, next, by) {
|
|
27
29
|
for (const k of Object.keys(target))
|
|
28
30
|
if (!(k in next))
|
|
29
31
|
delete target[k];
|
|
30
32
|
for (const k of Object.keys(next))
|
|
31
|
-
setKey(target, k, target[k], next[k], false);
|
|
33
|
+
setKey(target, k, target[k], next[k], false, by);
|
|
32
34
|
}
|
|
33
|
-
function reconcileList(target, next) {
|
|
35
|
+
function reconcileList(target, next, by) {
|
|
36
|
+
if (by !== undefined && reconcileKeyed(target, next, by))
|
|
37
|
+
return;
|
|
34
38
|
const shared = Math.min(target.length, next.length);
|
|
35
39
|
for (let i = 0; i < shared; i++)
|
|
36
|
-
setKey(target, i, target[i], next[i], true);
|
|
40
|
+
setKey(target, i, target[i], next[i], true, by);
|
|
37
41
|
if (next.length < target.length)
|
|
38
42
|
target.splice(next.length);
|
|
39
43
|
else
|
|
40
44
|
for (let i = target.length; i < next.length; i++)
|
|
41
45
|
target.push(next[i]);
|
|
42
46
|
}
|
|
43
|
-
|
|
47
|
+
/** Keyed list reconcile via LCS. Returns false (→ positional fallback) when keys
|
|
48
|
+
* aren't total + unique on either side. */
|
|
49
|
+
function reconcileKeyed(target, next, by) {
|
|
50
|
+
const tKeys = target.map(by);
|
|
51
|
+
const nKeys = next.map(by);
|
|
52
|
+
if (!totalUnique(tKeys) || !totalUnique(nKeys))
|
|
53
|
+
return false;
|
|
54
|
+
const keep = lcs(tKeys, nKeys);
|
|
55
|
+
let i = 0; // cursor into `target`, which mutates as we splice
|
|
56
|
+
for (let n = 0; n < next.length; n++) {
|
|
57
|
+
if (keep.has(nKeys[n])) {
|
|
58
|
+
while (i < target.length && !keep.has(by(target[i])))
|
|
59
|
+
target.splice(i, 1);
|
|
60
|
+
setKey(target, i, target[i], next[n], true, by); // same identity → merge edits
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
target.splice(i, 0, next[n]); // insert (new key, or a moved element re-placed)
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (i < target.length)
|
|
69
|
+
target.splice(i);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
function totalUnique(keys) {
|
|
73
|
+
if (keys.some(k => k === undefined))
|
|
74
|
+
return false;
|
|
75
|
+
return new Set(keys).size === keys.length;
|
|
76
|
+
}
|
|
77
|
+
/** Keys of the longest common subsequence of `a` and `b` (`===` on keys). */
|
|
78
|
+
function lcs(a, b) {
|
|
79
|
+
const n = a.length;
|
|
80
|
+
const m = b.length;
|
|
81
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
82
|
+
for (let i = n - 1; i >= 0; i--)
|
|
83
|
+
for (let j = m - 1; j >= 0; j--)
|
|
84
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
85
|
+
const keep = new Set();
|
|
86
|
+
let i = 0;
|
|
87
|
+
let j = 0;
|
|
88
|
+
while (i < n && j < m) {
|
|
89
|
+
if (a[i] === b[j]) {
|
|
90
|
+
keep.add(a[i]);
|
|
91
|
+
i++;
|
|
92
|
+
j++;
|
|
93
|
+
}
|
|
94
|
+
else if (dp[i + 1][j] >= dp[i][j + 1])
|
|
95
|
+
i++;
|
|
96
|
+
else
|
|
97
|
+
j++;
|
|
98
|
+
}
|
|
99
|
+
return keep;
|
|
100
|
+
}
|
|
101
|
+
function setKey(parent, key, a, b, inList, by) {
|
|
44
102
|
if (typeof b === "string" && typeof a === "string") {
|
|
45
103
|
// Char-level merge for object text fields; list string elements just assign
|
|
46
104
|
// (path-relative updateText targets a keyed field, not an array slot).
|
|
@@ -52,10 +110,10 @@ function setKey(parent, key, a, b, inList) {
|
|
|
52
110
|
}
|
|
53
111
|
}
|
|
54
112
|
else if (Array.isArray(b) && Array.isArray(a)) {
|
|
55
|
-
reconcileList(a, b);
|
|
113
|
+
reconcileList(a, b, by);
|
|
56
114
|
}
|
|
57
115
|
else if (isPlainObject(b) && isPlainObject(a)) {
|
|
58
|
-
reconcileObject(a, b);
|
|
116
|
+
reconcileObject(a, b, by);
|
|
59
117
|
}
|
|
60
118
|
else if (a !== b) {
|
|
61
119
|
parent[key] = b;
|
package/dist/core/_counts.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
// Counts-first instrumentation
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// correct minimal engine has a *calculable* target and any work above it is
|
|
7
|
-
// provably wasted. This is the gate for the unification: a change must not raise
|
|
8
|
-
// the forward counts and must hold the backward counts at their minimum.
|
|
9
|
-
//
|
|
10
|
-
// Off by default. `COUNTS` is one module-level gate; each instrumented site reads
|
|
11
|
-
// `if (COUNTS) counts.x++`, so a downstream minifier sees `if (false)` and drops
|
|
12
|
-
// it, and when on the cost is a single predictable branch. Flip via `withCounts`.
|
|
1
|
+
// Counts-first instrumentation: judge engine work by discrete event *counts*
|
|
2
|
+
// (callbacks invoked, codepaths entered, nodes visited, edges spliced), not
|
|
3
|
+
// timings, so a minimal engine has a calculable target. Off by default — each
|
|
4
|
+
// site reads `if (COUNTS) counts.x++`, so a minifier drops it when off and it
|
|
5
|
+
// costs one branch when on. Flip via `withCounts`.
|
|
13
6
|
function fresh() {
|
|
14
7
|
return {
|
|
15
8
|
recompute: 0,
|
package/dist/core/cell.d.ts
CHANGED
|
@@ -209,8 +209,8 @@ export declare class Cell<T = unknown> implements ReactiveNode {
|
|
|
209
209
|
through<B>(this: Cell<T>, o: Optic<T, B>): Writable<Cell<B>>;
|
|
210
210
|
through<B, C>(this: Cell<T>, o1: Optic<T, B>, o2: Optic<B, C>): Writable<Cell<C>>;
|
|
211
211
|
through<B, C, D>(this: Cell<T>, o1: Optic<T, B>, o2: Optic<B, C>, o3: Optic<C, D>): Writable<Cell<D>>;
|
|
212
|
-
/** Backward fan-in:
|
|
213
|
-
*
|
|
212
|
+
/** Backward fan-in: forwards its parent's value unchanged; on write, folds N
|
|
213
|
+
* contributors into one value. `fold` defaults to last-writer-wins. */
|
|
214
214
|
merge(this: Cell<T>, fold?: MergeFold<T>): Cell<T>;
|
|
215
215
|
/** Read-only typed view. `Cls.derive(parent, fn)` (1-input),
|
|
216
216
|
* `Cls.derive(parents, fn)` (N-input), or `Cls.derive(fn)` (closure).
|
|
@@ -324,7 +324,7 @@ export declare function network(deps: readonly Cell<any>[], body: NetworkBody, o
|
|
|
324
324
|
* get x() { return fieldLens(this, "x", Num); } */
|
|
325
325
|
export declare function fieldLens<S extends Cell<any>, K extends keyof Inner<S>, C extends new (...args: never[]) => Cell<Inner<S>[K]>>(parent: S, key: K, Cls: C): S extends WritableBrand ? Writable<InstanceType<C>> : InstanceType<C>;
|
|
326
326
|
/** Read-only derived view via `Cls.derive(parent, fn)`, memoized per
|
|
327
|
-
* (instance, key).
|
|
327
|
+
* (instance, key).
|
|
328
328
|
*
|
|
329
329
|
* get magnitude() {
|
|
330
330
|
* return cachedDerive(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
package/dist/core/cell.js
CHANGED
|
@@ -754,8 +754,8 @@ export class Cell {
|
|
|
754
754
|
: (target) => o.put(target, undefined);
|
|
755
755
|
return lens(this, o.get, bwd);
|
|
756
756
|
}
|
|
757
|
-
/** Backward fan-in:
|
|
758
|
-
*
|
|
757
|
+
/** Backward fan-in: forwards its parent's value unchanged; on write, folds N
|
|
758
|
+
* contributors into one value. `fold` defaults to last-writer-wins. */
|
|
759
759
|
merge(fold) {
|
|
760
760
|
if (this.getter !== undefined && this._bwd === undefined) {
|
|
761
761
|
throw new TypeError("merge: receiver is read-only");
|
|
@@ -1159,8 +1159,8 @@ function markDown(start) {
|
|
|
1159
1159
|
* `writeBack`ing each. Source-centric — a source reflects all its writers, so a
|
|
1160
1160
|
* call on it resolves every co-writer together and commits once.
|
|
1161
1161
|
*
|
|
1162
|
-
* Iterative post-order over the back-cone
|
|
1163
|
-
*
|
|
1162
|
+
* Iterative post-order over the back-cone, via an explicit frame stack of
|
|
1163
|
+
* {node, next-child cursor}. On entering a
|
|
1164
1164
|
* node (pre): clear a merge's contributions, then `writeBack` if it holds an armed
|
|
1165
1165
|
* target. After its children drain (post): clear `BF.Pending`, then fold a merge.
|
|
1166
1166
|
* Children are walked in forward `childEdges` order (so a co-writer's last write
|
|
@@ -1295,8 +1295,7 @@ function resolveBackDeps(node) {
|
|
|
1295
1295
|
*
|
|
1296
1296
|
* Iterative depth-first, left-to-right (children pushed in reverse onto the
|
|
1297
1297
|
* pooled `wbNode`/`wbTarget` stack), so a sibling read sees a prior sibling's
|
|
1298
|
-
* staged write
|
|
1299
|
-
* the call stack. */
|
|
1298
|
+
* staged write — bounded by pooled stack memory, not the call stack. */
|
|
1300
1299
|
function writeBack(node, target) {
|
|
1301
1300
|
wbNode[0] = node;
|
|
1302
1301
|
wbTarget[0] = target;
|
|
@@ -1815,7 +1814,7 @@ export function fieldLens(parent, key, Cls) {
|
|
|
1815
1814
|
return lazy(parent, key, () => fieldOf(parent, key, Cls));
|
|
1816
1815
|
}
|
|
1817
1816
|
/** Read-only derived view via `Cls.derive(parent, fn)`, memoized per
|
|
1818
|
-
* (instance, key).
|
|
1817
|
+
* (instance, key).
|
|
1819
1818
|
*
|
|
1820
1819
|
* get magnitude() {
|
|
1821
1820
|
* return cachedDerive(this, "magnitude", Num, v => Math.hypot(v.x, v.y));
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// control points have many DOF). They live here, off the lens surface,
|
|
6
|
-
// to keep `lenses/` exclusively bidirectional. For the writable bezier
|
|
7
|
-
// shape decomposition see `bezierGestalt` in `lenses/domain-aggregates`.
|
|
1
|
+
// Read-only geometric readouts over `Cls.derive`: one-way derives whose inverse
|
|
2
|
+
// is under-determined (a point at parameter `t` pins the curve at one point, but
|
|
3
|
+
// the control points keep many DOF). For the writable bezier decomposition see
|
|
4
|
+
// `bezierGestalt` in `lenses/geometry`.
|
|
8
5
|
import { Vec } from "./values/vec.js";
|
|
9
6
|
/** Quadratic Bézier point at parameter `t`. RO. */
|
|
10
7
|
export function bezier2(p0, p1, p2, t) {
|
package/dist/core/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { at, fields } from "./optics.js";
|
|
|
9
9
|
export { type Store, store } from "./store.js";
|
|
10
10
|
export { type Equals, type Lerp, type Linear, type Metric, type Pack, type Pivotal, requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, type TraitDict, type Traits, } from "./traits.js";
|
|
11
11
|
export { Anchor, Dir } from "./values/anchor.js";
|
|
12
|
+
export { Arr, allPass, arr, type CellPred, type Group, GroupArr, is, } from "./values/arr.js";
|
|
12
13
|
export { Audio, type AudioClip, audio, stamp as audioStamp } from "./values/audio.js";
|
|
13
14
|
export { Bool, bool } from "./values/bool.js";
|
|
14
15
|
export { Box, box, edgeFrom as boxEdgeFrom, expand as boxExpand, union as boxUnion, } from "./values/box.js";
|
|
@@ -21,8 +22,9 @@ export { compose as matrixCompose, Matrix, matrix, toMatrixString, transformBox,
|
|
|
21
22
|
export { Num, num } from "./values/num.js";
|
|
22
23
|
export { Pose, pose } from "./values/pose.js";
|
|
23
24
|
export { Range, range, span } from "./values/range.js";
|
|
25
|
+
export { type AltVal, type BindOpts, type Handle, type HandleKind, type HandleOf, Reg, type RegVal, type Silent, type StarVal, } from "./values/reg.js";
|
|
24
26
|
export { Str, str } from "./values/str.js";
|
|
25
27
|
export { type Codec, enumCodec, numCodec, route, type Slot, slot, strCodec, template, tpl, } from "./values/template.js";
|
|
26
28
|
export { Transform, type TransformInit, transform } from "./values/transform.js";
|
|
27
29
|
export { Tri, tri } from "./values/tri.js";
|
|
28
|
-
export {
|
|
30
|
+
export { tangentPoint, Vec, vec } from "./values/vec.js";
|
package/dist/core/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { at, fields } from "./optics.js";
|
|
|
9
9
|
export { store } from "./store.js";
|
|
10
10
|
export { requireEquals, requireLerp, requireLinear, requireMetric, requirePack, requirePivotal, } from "./traits.js";
|
|
11
11
|
export { Anchor, Dir } from "./values/anchor.js";
|
|
12
|
+
export { Arr, allPass, arr, GroupArr, is, } from "./values/arr.js";
|
|
12
13
|
export { Audio, audio, stamp as audioStamp } from "./values/audio.js";
|
|
13
14
|
export { Bool, bool } from "./values/bool.js";
|
|
14
15
|
export { Box, box, edgeFrom as boxEdgeFrom, expand as boxExpand, union as boxUnion, } from "./values/box.js";
|
|
@@ -21,8 +22,9 @@ export { compose as matrixCompose, Matrix, matrix, toMatrixString, transformBox,
|
|
|
21
22
|
export { Num, num } from "./values/num.js";
|
|
22
23
|
export { Pose, pose } from "./values/pose.js";
|
|
23
24
|
export { Range, range, span } from "./values/range.js";
|
|
25
|
+
export { Reg, } from "./values/reg.js";
|
|
24
26
|
export { Str, str } from "./values/str.js";
|
|
25
27
|
export { enumCodec, numCodec, route, slot, strCodec, template, tpl, } from "./values/template.js";
|
|
26
28
|
export { Transform, transform } from "./values/transform.js";
|
|
27
29
|
export { Tri, tri } from "./values/tri.js";
|
|
28
|
-
export {
|
|
30
|
+
export { tangentPoint, Vec, vec } from "./values/vec.js";
|
|
@@ -1,53 +1,43 @@
|
|
|
1
|
-
import { type Writable } from "../
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
1
|
+
import { type Cell, Num, type Read, type Traits, type Val, type Writable } from "../index.js";
|
|
2
|
+
/** Equal-weight mean (writable of `inputs[0]`'s class); writes distribute
|
|
3
|
+
* the delta evenly. Class inferred from the first input; needs `linear`. */
|
|
4
|
+
export declare function mean<S extends Traits<any, "linear">>(inputs: readonly Writable<S>[]): Writable<S>;
|
|
5
|
+
/** Weighted blend of K branches over any `Linear` type: reads the normalized
|
|
6
|
+
* weighted sum `Σ wᵢ·aᵢ`, writes the minimum-norm delta back into the
|
|
7
|
+
* branches (zero-weight branches untouched). Weights are read-only reactive
|
|
8
|
+
* controls — a one-hot is `select`, a `(1−t, t)` edge is `crossfade`. */
|
|
9
|
+
export declare function mix<S extends Traits<any, "linear">>(weights: readonly Val<number>[], branches: readonly Writable<S>[]): Writable<S>;
|
|
10
|
+
/** Two-branch router (mix simplex *vertex*): reads the live branch, writes
|
|
11
|
+
* flow entirely to it, the other is left put. Flipping `cond` snaps the
|
|
12
|
+
* output to the other branch's stored value. */
|
|
13
|
+
export declare function select<S extends Traits<any, "linear">>(cond: Read<boolean>, whenFalse: Writable<S>, whenTrue: Writable<S>): Writable<S>;
|
|
14
|
+
/** Two-branch crossfade (mix simplex *edge*): `lerp(a, b, t)`. Writing
|
|
15
|
+
* keeps `t` fixed and splits the delta by influence. */
|
|
16
|
+
export declare function crossfade<S extends Traits<any, "linear">>(t: Read<number>, a: Writable<S>, b: Writable<S>): Writable<S>;
|
|
17
|
+
/** Mean distance from the centroid (needs `Linear` + `Metric`); write scales
|
|
18
|
+
* the cluster's deviations so the new mean matches. The complement carries
|
|
19
|
+
* normalized deviations, so a collapse (spread → 0) reinflates the shape. */
|
|
20
|
+
export declare function spread<T extends NonNullable<unknown>, S extends Cell<T> & Traits<T, "linear" | "metric">>(inputs: readonly Writable<S>[]): Writable<Num>;
|
|
21
|
+
/** Mean/spread decomposition: K values → {mean, spread}, i.e. centroid +
|
|
22
|
+
* uniform scale about it. `mean` ∘ `spread`; works for any
|
|
23
|
+
* Linear + Metric class (palettes, point clouds, poses, …). */
|
|
24
|
+
export declare function meanSpread<T extends NonNullable<unknown>, S extends Cell<T> & Traits<T, "linear" | "metric">>(colors: readonly Writable<S>[]): {
|
|
25
|
+
mean: Writable<S>;
|
|
26
|
+
spread: Writable<Num>;
|
|
27
|
+
};
|
|
28
|
+
/** (a, b) → {mean: (a+b)/2, diff: a−b}. Square linear iso; each write is the
|
|
29
|
+
* inverse change of basis, so mean and diff are cross-channel invariant. */
|
|
30
|
+
export declare function meanDiff(a: Num, b: Num): {
|
|
31
|
+
mean: Writable<Num>;
|
|
32
|
+
diff: Writable<Num>;
|
|
33
|
+
};
|
|
34
|
+
/** Mean of N nums, clamped to `[lo, hi]` on read and write (writes are
|
|
35
|
+
* clamped before the delta is distributed). */
|
|
36
|
+
export declare function clampedMean(parents: readonly Num[], lo: number, hi: number): Writable<Num>;
|
|
37
|
+
/** Num values as (i, valueᵢ) samples → {mean, slope}. Writing `mean` shifts
|
|
38
|
+
* all values; writing `slope` (least-squares) tilts them about the mean.
|
|
39
|
+
* Each preserves the other's reading. */
|
|
40
|
+
export declare function timeSeries(values: readonly Writable<Num>[]): {
|
|
41
|
+
mean: Writable<Num>;
|
|
42
|
+
slope: Writable<Num>;
|
|
41
43
|
};
|
|
42
|
-
/** Scalar-output argmin lens: write does one Newton step against the FD
|
|
43
|
-
* Jacobian, distributing the residual by `weights`. For typed/multi-
|
|
44
|
-
* output cases use `factor()`; this M=1 path is kept for its hand-rolled
|
|
45
|
-
* inner loop. */
|
|
46
|
-
export declare function argminNum(inputs: readonly Num[], forward: (xs: readonly number[]) => number, weights: readonly number[], opts?: ArgminOpts): Writable<Num>;
|
|
47
|
-
/** 2D-output argmin lens (scalar Num inputs, `{x, y}` forward). For IK
|
|
48
|
-
* arms, draggable points, handle projection. Kept for its hand-rolled
|
|
49
|
-
* 2×2 inverse + `clampTarget` hook; see `factor()` for other M. */
|
|
50
|
-
export declare function argminVec(inputs: readonly Num[], forward: (xs: readonly number[]) => {
|
|
51
|
-
x: number;
|
|
52
|
-
y: number;
|
|
53
|
-
}, weights: readonly number[], opts?: ArgminVecOpts): Writable<Vec>;
|