bireactive 0.3.0 → 0.3.2

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.
Files changed (117) hide show
  1. package/README.md +14 -7
  2. package/dist/automerge/doc-cell.d.ts +20 -0
  3. package/dist/automerge/doc-cell.js +80 -0
  4. package/dist/automerge/index.d.ts +3 -0
  5. package/dist/automerge/index.js +12 -0
  6. package/dist/automerge/reconcile.d.ts +5 -0
  7. package/dist/automerge/reconcile.js +63 -0
  8. package/dist/core/_counts.d.ts +48 -0
  9. package/dist/core/_counts.js +51 -0
  10. package/dist/core/cell.d.ts +148 -112
  11. package/dist/core/cell.js +945 -768
  12. package/dist/core/debug.d.ts +25 -0
  13. package/dist/core/debug.js +121 -0
  14. package/dist/core/derived-geometry.js +4 -7
  15. package/dist/core/index.d.ts +9 -2
  16. package/dist/core/index.js +8 -1
  17. package/dist/core/lenses/aggregates.d.ts +42 -52
  18. package/dist/core/lenses/aggregates.js +225 -116
  19. package/dist/core/lenses/geometry.d.ts +22 -4
  20. package/dist/core/lenses/geometry.js +59 -27
  21. package/dist/core/lenses/index.d.ts +6 -6
  22. package/dist/core/lenses/index.js +6 -6
  23. package/dist/core/lenses/memory.js +4 -17
  24. package/dist/core/lenses/numerical.d.ts +100 -0
  25. package/dist/core/lenses/{typed-factor.js → numerical.js} +136 -34
  26. package/dist/core/lenses/point-cloud.d.ts +67 -0
  27. package/dist/core/lenses/{closed-form-policies.js → point-cloud.js} +226 -84
  28. package/dist/core/lenses/snap.d.ts +18 -0
  29. package/dist/core/lenses/snap.js +138 -0
  30. package/dist/core/lenses/text.d.ts +40 -0
  31. package/dist/core/lenses/text.js +202 -0
  32. package/dist/core/lifecycle.js +3 -6
  33. package/dist/core/linalg.js +5 -11
  34. package/dist/core/optic.d.ts +13 -0
  35. package/dist/core/optic.js +39 -0
  36. package/dist/core/optics.d.ts +10 -0
  37. package/dist/core/optics.js +26 -0
  38. package/dist/core/store.d.ts +9 -0
  39. package/dist/core/store.js +77 -0
  40. package/dist/core/traits.d.ts +4 -7
  41. package/dist/core/traits.js +8 -12
  42. package/dist/core/values/anchor.js +0 -4
  43. package/dist/core/values/arr.d.ts +110 -0
  44. package/dist/core/values/arr.js +336 -0
  45. package/dist/core/values/audio.d.ts +8 -9
  46. package/dist/core/values/audio.js +11 -28
  47. package/dist/core/values/bool.d.ts +11 -11
  48. package/dist/core/values/bool.js +12 -22
  49. package/dist/core/values/box.d.ts +15 -20
  50. package/dist/core/values/box.js +20 -33
  51. package/dist/core/values/canvas.d.ts +18 -25
  52. package/dist/core/values/canvas.js +32 -66
  53. package/dist/core/values/color.d.ts +5 -7
  54. package/dist/core/values/color.js +5 -11
  55. package/dist/core/values/field.d.ts +6 -7
  56. package/dist/core/values/field.js +10 -35
  57. package/dist/core/values/flags.d.ts +1 -2
  58. package/dist/core/values/flags.js +1 -17
  59. package/dist/core/values/gpu.d.ts +6 -10
  60. package/dist/core/values/gpu.js +8 -22
  61. package/dist/core/values/matrix.d.ts +2 -4
  62. package/dist/core/values/matrix.js +2 -12
  63. package/dist/core/values/num.d.ts +19 -28
  64. package/dist/core/values/num.js +23 -41
  65. package/dist/core/values/pose.d.ts +2 -4
  66. package/dist/core/values/pose.js +3 -12
  67. package/dist/core/values/range.d.ts +18 -26
  68. package/dist/core/values/range.js +22 -39
  69. package/dist/core/values/reg/ambiguity.d.ts +8 -0
  70. package/dist/core/values/reg/ambiguity.js +131 -0
  71. package/dist/core/values/reg/engine.d.ts +91 -0
  72. package/dist/core/values/reg/engine.js +373 -0
  73. package/dist/core/values/reg/nfa.d.ts +42 -0
  74. package/dist/core/values/reg/nfa.js +391 -0
  75. package/dist/core/values/reg/regex.d.ts +7 -0
  76. package/dist/core/values/reg/regex.js +318 -0
  77. package/dist/core/values/reg/types.d.ts +60 -0
  78. package/dist/core/values/reg/types.js +3 -0
  79. package/dist/core/values/reg.d.ts +250 -0
  80. package/dist/core/values/reg.js +649 -0
  81. package/dist/core/values/str.d.ts +16 -60
  82. package/dist/core/values/str.js +133 -315
  83. package/dist/core/values/template.js +1 -24
  84. package/dist/core/values/transform.d.ts +3 -5
  85. package/dist/core/values/transform.js +3 -12
  86. package/dist/core/values/tri.d.ts +9 -10
  87. package/dist/core/values/tri.js +9 -15
  88. package/dist/core/values/vec.d.ts +9 -24
  89. package/dist/core/values/vec.js +9 -64
  90. package/dist/formats/lens.js +6 -9
  91. package/dist/index.d.ts +0 -11
  92. package/dist/index.js +1 -11
  93. package/dist/jsx-dev-runtime.d.ts +2 -0
  94. package/dist/jsx-dev-runtime.js +5 -0
  95. package/dist/jsx-runtime.d.ts +54 -0
  96. package/dist/jsx-runtime.js +219 -0
  97. package/dist/schema/lens.js +5 -5
  98. package/dist/shapes/drag-behaviors.d.ts +56 -0
  99. package/dist/shapes/drag-behaviors.js +102 -0
  100. package/dist/shapes/drag-spec.d.ts +52 -0
  101. package/dist/shapes/drag-spec.js +112 -0
  102. package/dist/shapes/index.d.ts +3 -1
  103. package/dist/shapes/index.js +3 -1
  104. package/dist/shapes/interaction.d.ts +2 -3
  105. package/dist/shapes/interaction.js +77 -56
  106. package/dist/shapes/label.js +6 -0
  107. package/dist/shapes/layout.d.ts +47 -1
  108. package/dist/shapes/layout.js +59 -1
  109. package/package.json +22 -1
  110. package/dist/coll.d.ts +0 -74
  111. package/dist/coll.js +0 -210
  112. package/dist/core/lenses/closed-form-policies.d.ts +0 -57
  113. package/dist/core/lenses/decompositions.d.ts +0 -14
  114. package/dist/core/lenses/decompositions.js +0 -224
  115. package/dist/core/lenses/domain-aggregates.d.ts +0 -42
  116. package/dist/core/lenses/domain-aggregates.js +0 -245
  117. 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
- [npm](https://www.npmjs.com/package/bireactive) · [GitHub](https://github.com/OrionReed/bireactive) · [site](https://orionreed.github.io/bireactive/) · [API](https://orionreed.github.io/bireactive/api/)
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. They may be split into separate packages later so the
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
 
@@ -0,0 +1,20 @@
1
+ import type { DocHandle } from "@automerge/automerge-repo";
2
+ import { type Cell, type Writable } from "../core/cell.js";
3
+ import { type Store } from "../core/store.js";
4
+ /** Two-way bridge between an Automerge doc and the reactive graph. */
5
+ export interface DocBridge<T> {
6
+ /** Source cell mirroring the doc; writes (direct or via a lens) flow back to the CRDT. */
7
+ cell: Writable<Cell<T>>;
8
+ /** Deep `store` view over `cell` — `bridge.store.a.b.value = x` commits to the doc. */
9
+ store: Store<T>;
10
+ /** Point the same cell at a different doc, keeping every bound lens/view alive. */
11
+ retarget: (handle: DocHandle<T>) => void;
12
+ /** Detach both directions (call from `disconnectedCallback`). */
13
+ dispose: () => void;
14
+ }
15
+ /** Connect a `DocHandle` to a reactive cell + store, syncing both ways. */
16
+ export declare function connectDoc<T extends object>(handle: DocHandle<T>): DocBridge<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>;
@@ -0,0 +1,80 @@
1
+ // doc-cell.ts — bridge an Automerge document to the reactive graph.
2
+ //
3
+ // An Automerge `DocHandle` is a writable source of truth that lives outside the
4
+ // cell graph: it emits `change` events and is mutated through `handle.change`.
5
+ // `connectDoc` wires it to a `Writable<Cell<T>>` in both directions so the doc
6
+ // becomes an ordinary cell you can lens, `store`, and bind in JSX.
7
+ //
8
+ // doc → cell : on every `change`, snapshot the doc into the cell.
9
+ // cell → doc : an effect mirrors each commit back via `reconcile` (minimal
10
+ // ops, so concurrent edits merge).
11
+ //
12
+ // The cell uses structural equality, so the two directions converge in one hop:
13
+ // a write reconciles into the doc, the doc echoes a `change`, the snapshot is
14
+ // deep-equal to what we already hold, and the engine stops. No flags, no echo
15
+ // storm. Because the doc is the apex, many independent lens/`store` views can
16
+ // hang off one `connectDoc` — that's the symmetric, no-primary topology: the
17
+ // CRDT is the shared core, every schema is just a projection.
18
+ import { cell, effect } from "../core/cell.js";
19
+ import { store } from "../core/store.js";
20
+ import { reconcile } from "./reconcile.js";
21
+ function deepEqual(a, b) {
22
+ if (Object.is(a, b))
23
+ return true;
24
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object")
25
+ return false;
26
+ const aArr = Array.isArray(a);
27
+ if (aArr !== Array.isArray(b))
28
+ return false;
29
+ const ak = Object.keys(a);
30
+ const bk = Object.keys(b);
31
+ if (ak.length !== bk.length)
32
+ return false;
33
+ for (const k of ak) {
34
+ if (!Object.hasOwn(b, k))
35
+ return false;
36
+ if (!deepEqual(a[k], b[k]))
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ /** Wire an existing cell to a handle in both directions; returns an unbind. */
42
+ function bind(c, handle) {
43
+ const onChange = () => {
44
+ c.value = structuredClone(handle.doc());
45
+ };
46
+ handle.on("change", onChange);
47
+ const stop = effect(() => {
48
+ const next = c.value;
49
+ handle.change((d) => reconcile(d, next));
50
+ });
51
+ return () => {
52
+ stop();
53
+ handle.off("change", onChange);
54
+ };
55
+ }
56
+ /** Connect a `DocHandle` to a reactive cell + store, syncing both ways. */
57
+ export function connectDoc(handle) {
58
+ const c = cell(structuredClone(handle.doc()), { equals: deepEqual, name: "doc" });
59
+ let unbind = bind(c, handle);
60
+ return {
61
+ cell: c,
62
+ store: store(c),
63
+ retarget: next => {
64
+ unbind();
65
+ // Seed the cell from the new doc *before* re-binding, so the cell→doc
66
+ // effect doesn't push the old value into the freshly targeted doc.
67
+ c.value = structuredClone(next.doc());
68
+ unbind = bind(c, next);
69
+ },
70
+ dispose: () => unbind(),
71
+ };
72
+ }
73
+ /** Doc as a writable cell (page-lifetime; use {@link connectDoc} when you need disposal). */
74
+ export function docCell(handle) {
75
+ return connectDoc(handle).cell;
76
+ }
77
+ /** Doc as a deep store (page-lifetime; use {@link connectDoc} when you need disposal). */
78
+ export function docStore(handle) {
79
+ return connectDoc(handle).store;
80
+ }
@@ -0,0 +1,3 @@
1
+ export type { DocBridge } from "./doc-cell.js";
2
+ export { connectDoc, docCell, docStore } from "./doc-cell.js";
3
+ export { reconcile } from "./reconcile.js";
@@ -0,0 +1,12 @@
1
+ // @bireactive/automerge — view an Automerge CRDT through the reactive graph.
2
+ //
3
+ // `connectDoc(handle)` turns a `DocHandle` into a `Writable<Cell<T>>` plus a deep
4
+ // `store`, synced both ways. Lens/`store` projections off that one cell give you
5
+ // many schemas over a single shared doc with no privileged "primary" — the CRDT
6
+ // is the apex, every view is a leg. `reconcile` is the doc-side diff that keeps
7
+ // writes merge-friendly; it's exported for custom bridges.
8
+ //
9
+ // Automerge is an optional peer dependency: import this entry only when you've
10
+ // installed `@automerge/automerge-repo`.
11
+ export { connectDoc, docCell, docStore } from "./doc-cell.js";
12
+ export { reconcile } from "./reconcile.js";
@@ -0,0 +1,5 @@
1
+ type Any = any;
2
+ /** 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;
5
+ export {};
@@ -0,0 +1,63 @@
1
+ // reconcile.ts — bring an Automerge document to equal a plain POJO with the
2
+ // *minimum* mutations, so concurrent edits merge instead of clobbering.
3
+ //
4
+ // Automerge's docs warn that spread-assignment (`d.x = {...d.x, k}`) replaces the
5
+ // whole object and destroys its merge history. The reactive side, by contrast,
6
+ // hands us a fresh immutable snapshot on every write (spread-replace all the way
7
+ // up). `reconcile` bridges the two: called inside `handle.change`, it walks the
8
+ // live doc against the snapshot and emits only the ops that actually differ —
9
+ // `updateText` for strings (char-level), in-place splices for lists, recursive
10
+ // descent for objects, scalar sets for the rest.
11
+ //
12
+ // List handling is intentionally simple for now: element-wise in place, with a
13
+ // tail push/truncate. Correct for edits/appends/truncations; a reorder or mid
14
+ // insert produces more ops than ideal. Identity-keyed list reconciliation is the
15
+ // obvious upgrade (mirror the `eachBy` lens's `by`).
16
+ import { updateText } from "@automerge/automerge-repo";
17
+ const isPlainObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
18
+ /** Minimally mutate the Automerge node `target` (inside `handle.change`) to equal
19
+ * the plain value `next`. */
20
+ export function reconcile(target, next) {
21
+ if (Array.isArray(next) && Array.isArray(target))
22
+ reconcileList(target, next);
23
+ else
24
+ reconcileObject(target, next);
25
+ }
26
+ function reconcileObject(target, next) {
27
+ for (const k of Object.keys(target))
28
+ if (!(k in next))
29
+ delete target[k];
30
+ for (const k of Object.keys(next))
31
+ setKey(target, k, target[k], next[k], false);
32
+ }
33
+ function reconcileList(target, next) {
34
+ const shared = Math.min(target.length, next.length);
35
+ for (let i = 0; i < shared; i++)
36
+ setKey(target, i, target[i], next[i], true);
37
+ if (next.length < target.length)
38
+ target.splice(next.length);
39
+ else
40
+ for (let i = target.length; i < next.length; i++)
41
+ target.push(next[i]);
42
+ }
43
+ function setKey(parent, key, a, b, inList) {
44
+ if (typeof b === "string" && typeof a === "string") {
45
+ // Char-level merge for object text fields; list string elements just assign
46
+ // (path-relative updateText targets a keyed field, not an array slot).
47
+ if (a !== b) {
48
+ if (inList)
49
+ parent[key] = b;
50
+ else
51
+ updateText(parent, [key], b);
52
+ }
53
+ }
54
+ else if (Array.isArray(b) && Array.isArray(a)) {
55
+ reconcileList(a, b);
56
+ }
57
+ else if (isPlainObject(b) && isPlainObject(a)) {
58
+ reconcileObject(a, b);
59
+ }
60
+ else if (a !== b) {
61
+ parent[key] = b;
62
+ }
63
+ }
@@ -0,0 +1,48 @@
1
+ export interface Counts {
2
+ /** A computed/lens/merge getter actually ran (the forward "work" unit). */
3
+ recompute: number;
4
+ /** `propagate` entered (a mark sweep down `subs`). */
5
+ propagate: number;
6
+ /** `checkDirty` entered (a validity pull up `deps`). */
7
+ checkDirty: number;
8
+ /** A dynamic forward edge (`Link`) was created. */
9
+ link: number;
10
+ /** A dynamic forward edge was dropped. */
11
+ unlink: number;
12
+ /** A back-write was armed (legal). */
13
+ arm: number;
14
+ /** A structurally-impossible write was rejected at `arm` (no work done). */
15
+ armBlocked: number;
16
+ /** Nodes visited descending the back-path in `markDown`. */
17
+ markDownVisit: number;
18
+ /** A reverse edge was spliced onto a parent's up-list. */
19
+ linkChild: number;
20
+ /** A reverse edge was released (view unwatched). */
21
+ unlinkChild: number;
22
+ /** Frames entered resolving back-cones (`enterCone`). */
23
+ resolveConeVisit: number;
24
+ /** Nodes popped committing a back-write (`writeBack`). */
25
+ writeBackVisit: number;
26
+ /** Steps of the co-writer re-assert scan (the fan-in re-assert cost). */
27
+ reassertScan: number;
28
+ /** A user `put` (1→1, tuple, or stateful backward) was invoked. */
29
+ put: number;
30
+ /** A merge `fold` was invoked. */
31
+ fold: number;
32
+ /** A stateful `step` was invoked (backward commit path). */
33
+ step: number;
34
+ }
35
+ /** Live counter record. Mutated in place so importers hold a stable reference. */
36
+ export declare const counts: Counts;
37
+ /** The single gate. Read at every instrumented site; flip via `withCounts`. */
38
+ export declare let COUNTS: boolean;
39
+ /** Reset all counters to zero (keeps the same object identity). */
40
+ export declare function resetCounts(): void;
41
+ /** Shallow copy of the current counts. */
42
+ export declare function snapshotCounts(): Counts;
43
+ /** Run `fn` with counting on from a zero baseline; returns the result and the
44
+ * counts it accrued. Restores the prior gate state (counters left as measured). */
45
+ export declare function withCounts<T>(fn: () => T): {
46
+ result: T;
47
+ counts: Counts;
48
+ };
@@ -0,0 +1,51 @@
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`.
6
+ function fresh() {
7
+ return {
8
+ recompute: 0,
9
+ propagate: 0,
10
+ checkDirty: 0,
11
+ link: 0,
12
+ unlink: 0,
13
+ arm: 0,
14
+ armBlocked: 0,
15
+ markDownVisit: 0,
16
+ linkChild: 0,
17
+ unlinkChild: 0,
18
+ resolveConeVisit: 0,
19
+ writeBackVisit: 0,
20
+ reassertScan: 0,
21
+ put: 0,
22
+ fold: 0,
23
+ step: 0,
24
+ };
25
+ }
26
+ /** Live counter record. Mutated in place so importers hold a stable reference. */
27
+ export const counts = fresh();
28
+ /** The single gate. Read at every instrumented site; flip via `withCounts`. */
29
+ export let COUNTS = false;
30
+ /** Reset all counters to zero (keeps the same object identity). */
31
+ export function resetCounts() {
32
+ Object.assign(counts, fresh());
33
+ }
34
+ /** Shallow copy of the current counts. */
35
+ export function snapshotCounts() {
36
+ return { ...counts };
37
+ }
38
+ /** Run `fn` with counting on from a zero baseline; returns the result and the
39
+ * counts it accrued. Restores the prior gate state (counters left as measured). */
40
+ export function withCounts(fn) {
41
+ const prevOn = COUNTS;
42
+ resetCounts();
43
+ COUNTS = true;
44
+ try {
45
+ const result = fn();
46
+ return { result, counts: snapshotCounts() };
47
+ }
48
+ finally {
49
+ COUNTS = prevOn;
50
+ }
51
+ }