@lazily-hub/lazily-js 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,18 +39,20 @@ notes and platform carve-outs lives in
39
39
  | Feature | Rust | Python | Kotlin | JS | Dart | Zig | Go | C++ |
40
40
  | --------- | :----: | :------: | :------: | :--: | :----: | :---: | :--: | :---: |
41
41
  | Reactive graph — `Cell` / `Slot` / `Signal` / `Effect` / memo / batch | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
42
- | Reactive family (`ReactiveFamily`) — keyed cell/slot family + materialization mode (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
43
- | Thread-safe reactive family (`ThreadSafeReactiveFamily`) — `Send + Sync` keyed family + materialization confluence (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
44
- | Async reactive family (`AsyncReactiveFamily`) — keyed family + eventual transparency (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
45
- | Reactive family sync — membership propagation + materialize-on-ingest + derived-aggregate transparency (`#lzfamilysync`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
42
+ | Keyed-map materialization (`SlotMap`) — mint-on-access derived slots: transparency + deferral (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
43
+ | Thread-safe keyed map (`ThreadSafeSlotMap`) — `Send + Sync` + materialization confluence (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
44
+ | Async keyed map (`AsyncSlotMap`) — eventual transparency (`#lzmatmode`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
45
+ | Keyed-map sync — membership propagation + materialize-on-ingest + derived-aggregate transparency (`#lzfamilysync`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
46
46
  | Thread-safe context (lock-backed) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
47
47
  | Async reactive context | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
48
48
  | Flat state machine | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
49
49
  | Harel state charts | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
50
- | Keyed cell collections (`CellMap` / `CellTree`) + reconcile | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
50
+ | Keyed reactive maps (`ReactiveMap`: `CellMap` / `SlotMap`) + `CellTree` + reconcile | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
51
51
  | Memoized semantic tree (`SemTree`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
52
52
  | Stable-id alignment (manufactured identity) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
53
53
  | Reactive queue (`QueueCell` SPSC/MPSC + `QueueStorage` adapter) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
54
+ | Merge algebra + `MergeCell` — associative `MergePolicy` (`KeepLatest`/`Sum`/`Max`/`SetUnion`/`RawFifo`), `Cell ≡ MergeCell<KeepLatest>`, `Reactive`/`Source` split (`#relaycell`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
55
+ | RelayCell — conflating relay + `BackpressurePolicy` + `SpillStore` + `Transport` + Inbox/Outbox + Rate/Window/Expiry/Priority/keyed policies (`#relaycell`) | ✅ | — | — | ✅ | — | — | — | — |
54
56
  | Free-text character CRDT (`TextCrdt`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
55
57
  | `TextCrdt` delta sync (`version_vector` / `delta_since` / `apply_delta`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
56
58
  | Move-aware sequence CRDT (`SeqCrdt`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
@@ -62,8 +64,8 @@ notes and platform carve-outs lives in
62
64
  | Shared-memory blob path (`ShmBlobArena`) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
63
65
  | Cross-process zero-copy transport (`BlobBackend` / shm / arrow) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
64
66
  | Distributed CRDT plane (`CrdtPlaneRuntime` / anti-entropy) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
65
- | Reliable sync — resync coordinator + at-least-once durable outbox + OR-set/LWW liveness (`#lzsync`) | ✅ | | ✅ | ✅ | | | | |
66
- | Reliable-sync transport seam + full-duplex `SyncDriver` loop (`IpcSink`/`IpcSource`, `#sync-driver`) | ✅ | | ✅ | ✅ | | | | |
67
+ | Reliable sync — resync coordinator + at-least-once durable outbox + OR-set/LWW liveness (`#lzsync`) | ✅ | | ✅ | ✅ | | | | |
68
+ | Reliable-sync transport seam + full-duplex `SyncDriver` loop (`IpcSink`/`IpcSource`, `#sync-driver`) | ✅ | | ✅ | ✅ | | | | |
67
69
  | Distributed plane — WebRTC transport + signaling | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
68
70
  | State projection / mirror | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
69
71
  | Causal receipts (`CausalReceipts` outcome projection) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
@@ -76,7 +78,7 @@ notes and platform carve-outs lives in
76
78
 
77
79
  Two JS ✅ marks are backed by runtime-specific mechanisms while keeping the core isomorphic:
78
80
 
79
- - **Thread-safe** (`ThreadSafeReactiveFamily`, `Thread-safe context`) — cross-realm mutual exclusion via a `SharedArrayBuffer` + `Atomics` reentrant mutex shared across Web Workers / `worker_threads`; degrades to a single-realm guard where shared memory is unavailable (e.g. a browser without cross-origin isolation), which is sound because no shared memory means no cross-realm concurrency.
81
+ - **Thread-safe** (`ThreadSafeReactiveMap`, `Thread-safe context`) — cross-realm mutual exclusion via a `SharedArrayBuffer` + `Atomics` reentrant mutex shared across Web Workers / `worker_threads`; degrades to a single-realm guard where shared memory is unavailable (e.g. a browser without cross-origin isolation), which is sound because no shared memory means no cross-realm concurrency.
80
82
  - **C-ABI FFI** — the normative codec + in-process `FfiChannel` are pure-JS and run unchanged in the browser; the Node build additionally binds the real `lazily` shared library (`lazily_ffi_channel_*`) via koffi. Both speak the identical byte contract, so browser and native are drop-in interchangeable.
81
83
 
82
84
  CRDT convergence and the wire protocol are pinned by the shared conformance fixtures
@@ -89,10 +91,10 @@ and JSON Schemas in `lazily-spec` and the Lean models in `lazily-formal`.
89
91
  | `@lazily-hub/lazily-js/transport` | Cross-process zero-copy transport (`#lzzcpy`): `ShmBlobArena`, `InProcessBackend` / `ArrowBackend`, `BlobRouter`, `spillMessage` / `resolveValue`, and the FFI-gated `createShmBackend` (Node/Bun/Deno). Isomorphic — no FFI import; browser-safe |
90
92
  | `@lazily-hub/lazily-js/reactive` | Reactive dependency graph: `Context`, `Cell`, `Slot`, `Signal`, `Effect` |
91
93
  | `@lazily-hub/lazily-js/reactive-async` | Async reactive graph: `AsyncContext` — Promise-driven slots/effects with revision-guarded stale-completion discard, in-flight dedup, and cancellation |
92
- | `@lazily-hub/lazily-js/reactive-family` | Unified keyed reactive family: `ReactiveFamily` (`EntryKind` cell/slot × `MaterializationMode` eager/lazy) + `cellFamily` input-cell specialization (`#lzmatmode`) |
93
- | `@lazily-hub/lazily-js/async-reactive-family` | Async keyed reactive family: `AsyncReactiveFamily` + `asyncCellFamily` over `AsyncContext` — eventual transparency (a pending slot observes `undefined` and resolves to the canonical value; eager ≡ lazy once resolved) (`#lzmatmode`) |
94
+ | `@lazily-hub/lazily-js/reactive-family` | Unified keyed reactive map: `ReactiveMap<K,V,H>` (reactive membership/order, `getOrInsertWith` mint-on-access, `remove`, `move`) + `CellMap` (adds cell-only `set` + eager `entry`/`entryWith`) and `SlotMap` (lazy `getOrInsertWith` + eager `materializeAll`; no `set`) specializations. No eager/lazy mode flag (`#reactivemap`) |
95
+ | `@lazily-hub/lazily-js/async-reactive-family` | Async keyed reactive map: `AsyncReactiveMap` + `AsyncCellMap` / `AsyncSlotMap` over `AsyncContext` — eventual transparency (a pending slot observes `undefined` and resolves to the canonical value; eager ≡ lazy once resolved) (`#reactivemap`) |
94
96
  | `@lazily-hub/lazily-js/thread-safe` | Lock-backed reactive context: `ThreadSafeContext` (`Send + Sync` flavor of `Context`) + `AtomicMutex` — a real `SharedArrayBuffer` + `Atomics` reentrant mutex giving cross-worker mutual exclusion; degrades to a single-realm guard where shared memory is unavailable |
95
- | `@lazily-hub/lazily-js/thread-safe-reactive-family` | Thread-safe keyed reactive family: `ThreadSafeReactiveFamily` + `threadSafeCellFamily` — mutex-guarded present set with first-writer-wins materialization confluence (`#lzmatmode`) |
97
+ | `@lazily-hub/lazily-js/thread-safe-reactive-family` | Thread-safe keyed reactive map: `ThreadSafeReactiveMap` + `ThreadSafeCellMap` / `ThreadSafeSlotMap` — mutex-guarded present set with first-writer-wins materialization confluence (`#reactivemap`) |
96
98
  | `@lazily-hub/lazily-js/ffi` | C-ABI FFI boundary (`schemas/ffi.json`): message codec (`encodeMessage` / `decodeMessage` / `validateMessage` / `kindOf`, `LazilyFfiStatus` / `LazilyFfiMessageKind`) + `FfiChannel` FIFO. Isomorphic core (browser shim); the Node build additionally exposes `NativeFfiChannel` / `loadNativeChannel` over the real `lazily_ffi_channel_*` C ABI via koffi |
97
99
  | `@lazily-hub/lazily-js/instrumentation` | In-library instrumentation/benchmark API: `benchmark`, `runBenchmarkSuite`, `BenchmarkResult`, `withInstrumentation` — plus opt-in reactive-core counters via `new Context({ instrument: true })` / `instrumentationSnapshot()` |
98
100
  | `@lazily-hub/lazily-js/state-machine` | Flat finite-state-machine kernel backed by a reactive `Cell` |
@@ -174,42 +176,46 @@ ctx.setCell(userId, 2); // supersedes any in-flight compute; slot re-resolves
174
176
  await ctx.getAsync(profile); // the profile for user 2
175
177
  ```
176
178
 
177
- ## Reactive family and materialization mode
179
+ ## Keyed reactive maps (`ReactiveMap` / `CellMap` / `SlotMap`)
178
180
 
179
- `ReactiveFamily` (from `@lazily-hub/lazily-js/reactive-family`) is the unified
180
- **keyed reactive family** (`#lzmatmode`): it maps keys to per-entry reactive
181
- nodes and abstracts over the entry's handle kind. Two orthogonal axes:
181
+ `ReactiveMap<K, V, H>` (from `@lazily-hub/lazily-js/reactive-family`) is the ONE
182
+ unified **keyed reactive collection** (`#reactivemap`): reactive membership +
183
+ order, `getOrInsertWith` mint-on-access, `remove`, and atomic `move`, generic
184
+ over the entry's handle kind. Its two specializations are the concrete types you
185
+ use:
182
186
 
183
- - **Entry kind**`EntryKind.Cell` entries are input cells (**always
184
- materialized**, any mode); `EntryKind.Slot` entries are derived slots (what
185
- materialization governs). `cellFamily(...)` is the input-cell specialization.
186
- - **Materialization mode** `MaterializationMode.Eager` (**default**) allocates
187
- every derived node up front; `MaterializationMode.Lazy` (opt-in) allocates a
188
- derived node on its **first read** ("materialize on pull") and caches it.
187
+ - **`CellMap<K, V>`**input-cell entries. Adds cell-only `set(key, value)` (an
188
+ input is settable) and eager value-minting (`entry` / `entryWith`).
189
+ - **`SlotMap<K, V>`** — derived-slot entries. `getOrInsertWith(key, factory)`
190
+ mints a slot on **first access** ("materialize on pull", **lazy**);
191
+ `materializeAll(keys, factory)` pre-mints the keyset up front (**eager**). A
192
+ slot's value is derived, so `SlotMap` has **no `set`**.
189
193
 
190
- Mode is **observationally transparent**: a read returns the same value under
191
- either mode it changes allocation timing and memory, never results. Lazy pays
192
- off only for sparsely-touched large keyed address spaces.
194
+ There is **no eager/lazy mode flag** eager is the pre-mint loop, lazy is
195
+ mint-on-access, and they are **observationally transparent**: a read returns the
196
+ same value either way; only allocation timing and memory change. Lazy pays off
197
+ only for sparsely-touched large keyed address spaces.
193
198
 
194
199
  ```js
195
200
  import { Context } from "@lazily-hub/lazily-js/reactive";
196
- import { ReactiveFamily } from "@lazily-hub/lazily-js/reactive-family";
201
+ import { SlotMap } from "@lazily-hub/lazily-js/reactive-family";
197
202
 
198
203
  const ctx = new Context();
199
204
 
200
- // A derived (slot) family of key*3 over a large address space, built lazily:
205
+ // A derived (slot) map of key*3 over a large address space, built lazily:
201
206
  // nothing is allocated until a key is read.
202
- const fam = ReactiveFamily.lazy(ctx, range(0, 1_000_000), (k) => k * 3);
203
- fam.presentCount(); // 0
204
-
205
- fam.observe(5); // 15 — first read materializes just this entry
206
- fam.presentCount(); // 1
207
- fam.isPresent(5); // true
208
- fam.isPresent(6); // false
209
-
210
- // Eager builds the same values up front — observationally identical.
211
- const eager = ReactiveFamily.eager(ctx, [0, 1, 2, 3], (k) => k * 3);
212
- eager.observe(2) === fam.observe(2); // true
207
+ const map = new SlotMap(ctx);
208
+ map.presentCount(); // 0
209
+
210
+ map.getOrInsertWith(5, (k) => k * 3); // 15 — first read materializes just this entry
211
+ map.presentCount(); // 1
212
+ map.isPresent(5); // true
213
+ map.isPresent(6); // false
214
+
215
+ // Eager pre-mints the same values up front — observationally identical.
216
+ const eager = new SlotMap(ctx);
217
+ eager.materializeAll([0, 1, 2, 3], (k) => k * 3);
218
+ eager.get(2) === map.getOrInsertWith(2, (k) => k * 3); // true
213
219
  ```
214
220
 
215
221
  ## State machine and state charts
@@ -504,11 +510,11 @@ lazily-js replays the shared `lazily-spec` fixtures for IPC, agent-doc state,
504
510
  keyed collections (`CellMap`, `CellTree`, LIS reconciliation), semantic tree,
505
511
  sequence and text CRDTs (incl. `TextCrdt` delta sync, `#lztextsync`:
506
512
  `textcrdt_convergence.json` + `textcrdt_delta_sync.json`), manufactured text
507
- identity, the reactive family / materialization mode (`#lzmatmode`:
513
+ identity, the keyed reactive maps / materialization (`#reactivemap`:
508
514
  `materialization/observational_transparency.json`,
509
515
  `materialization/deferral_not_deallocation.json`,
510
516
  `materialization/entry_kind_orthogonal_to_mode.json` — replayed through the
511
- single-threaded, async, and thread-safe families), the C-ABI FFI boundary
517
+ single-threaded, async, and thread-safe maps), the C-ABI FFI boundary
512
518
  (`schemas/ffi.json`: message codec + channel round-trip over `snapshot_*` /
513
519
  `delta_*` wire), Harel state charts, the
514
520
  signaling protocol (`signaling/frames.json`,
@@ -532,8 +538,8 @@ names the Lean theorems it mirrors:
532
538
  | `Collection` | `collection-properties.test.js` | `setEntryValue_preserves_{membership,order,siblings}`, `moveKey_preserves_{membership,values}`, `moveKey_advances_order`, `addKey_advances_membership_and_order`, `Family.get_idempotent_after_first` |
533
539
  | `Tree` | `tree-properties.test.js` | `setNodeValue_preserves_{other_nodes,node_signals}`, `moveChild_preserves_{non_parent,parent_value}`, `moveChild_advances_order_signal_only` |
534
540
  | `Materialization` | `reactive-family.test.js` | `observe_canonical`, `eager_lazy_observationally_equivalent`, `eager_materializes_all`, `lazy_defers_slots`, `materialize_present_monotone`, `lazy_present_subset_eager`, `materialize_preserves_observe`, `cell_entries_materialized_in_every_mode`, `slot_entries_deferred_under_lazy` |
535
- | `Materialization` (thread-safe) | `thread-safe-reactive-family.test.js` | `materialize_present_comm`, `materialize_observe_comm` (materialization confluence) + the base materialization laws replayed through `ThreadSafeReactiveFamily` |
536
- | `AsyncMaterialization` | `async-reactive-family.test.js` | eventual transparency (a driven async slot resolves to the canonical value; eager ≡ lazy) + present-set monotonicity through `AsyncReactiveFamily` |
541
+ | `Materialization` (thread-safe) | `thread-safe-reactive-family.test.js` | `materialize_present_comm`, `materialize_observe_comm` (materialization confluence) + the base materialization laws replayed through `ThreadSafeSlotMap` |
542
+ | `AsyncMaterialization` | `async-reactive-family.test.js` | eventual transparency (a driven async slot resolves to the canonical value; eager ≡ lazy) + present-set monotonicity through `AsyncSlotMap` |
537
543
  | `ThreadSafe` | `thread-safe.test.js` | `flushBatch_empty`, `flushBatch_singleton_eq_setCell` (thread-safe batch refines `setCell`), `flushBatch_dependent_dirty`, `flushBatch_preserves_nondependent_dirty` |
538
544
  | `Reconciliation` | `reconciliation-properties.test.js` | `lisBy_longest`, `reconcile_move_minimized`, `reconcile_stable_not_invalidated` |
539
545
  | `AsyncSlotState` | `reactive-async.test.js` | `stale_completeOk_discarded`, `current_completeOk_publishes`, `current_completeErr_to_error` |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lazily-hub/lazily-js",
3
- "version": "0.14.0",
4
- "description": "Native JavaScript port of the lazily reactive core: a full reactive graph (Cell/Slot/Signal/Effect), sync/async/thread-safe keyed reactive families with materialization mode, the lazily-spec IPC wire types + C-ABI FFI boundary (isomorphic channel + Node native binding), keyed cell collections + LIS reconciliation, the memoized semantic tree, the move-aware sequence CRDT, the Fugue/RGA text CRDT, manufactured text identity, full-Harel state charts, an FFI state-projection consumer, and an in-library instrumentation/benchmark API.",
3
+ "version": "0.16.0",
4
+ "description": "Native JavaScript port of the lazily reactive core: a full reactive graph (Cell/Slot/Signal/Effect), sync/async/thread-safe keyed reactive maps (ReactiveMap/CellMap/SlotMap), the lazily-spec IPC wire types + C-ABI FFI boundary (isomorphic channel + Node native binding), keyed cell collections + LIS reconciliation, the memoized semantic tree, the move-aware sequence CRDT, the Fugue/RGA text CRDT, manufactured text identity, full-Harel state charts, an FFI state-projection consumer, and an in-library instrumentation/benchmark API.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -69,6 +69,14 @@
69
69
  "types": "./src/queue.d.ts",
70
70
  "default": "./src/queue.js"
71
71
  },
72
+ "./merge": {
73
+ "types": "./src/merge.d.ts",
74
+ "default": "./src/merge.js"
75
+ },
76
+ "./relay": {
77
+ "types": "./src/relay.d.ts",
78
+ "default": "./src/relay.js"
79
+ },
72
80
  "./sem-tree": {
73
81
  "types": "./src/sem-tree.d.ts",
74
82
  "default": "./src/sem-tree.js"
@@ -93,10 +101,6 @@
93
101
  "types": "./src/state-projection.d.ts",
94
102
  "default": "./src/state-projection.js"
95
103
  },
96
- "./state-graph-mirror": {
97
- "types": "./src/state-graph-mirror.d.ts",
98
- "default": "./src/state-graph-mirror.js"
99
- },
100
104
  "./signaling": {
101
105
  "types": "./src/signaling.d.ts",
102
106
  "default": "./src/signaling.js"
@@ -107,7 +111,7 @@
107
111
  }
108
112
  },
109
113
  "scripts": {
110
- "build": "node --check src/index.js && node --check src/transport.js && node --check src/shm-backend.js && node --check src/reactive.js && node --check src/reactive-async.js && node --check src/reactive-family.js && node --check src/async-reactive-family.js && node --check src/thread-safe.js && node --check src/thread-safe-reactive-family.js && node --check src/ffi.js && node --check src/ffi-node.js && node --check src/instrumentation.js && node --check src/state-machine.js && node --check src/statechart.js && node --check src/collections.js && node --check src/queue.js && node --check src/sem-tree.js && node --check src/stable-id.js && node --check src/seq-crdt.js && node --check src/text-crdt.js && node --check src/utf8-offsets.js && node --check src/lossless-tree-crdt.js && node --check src/state-projection.js && node --check src/state-graph-mirror.js && node --check src/signaling.js && node --check src/distributed.js",
114
+ "build": "node --check src/index.js && node --check src/transport.js && node --check src/shm-backend.js && node --check src/reactive.js && node --check src/reactive-async.js && node --check src/reactive-family.js && node --check src/async-reactive-family.js && node --check src/thread-safe.js && node --check src/thread-safe-reactive-family.js && node --check src/ffi.js && node --check src/ffi-node.js && node --check src/instrumentation.js && node --check src/state-machine.js && node --check src/statechart.js && node --check src/collections.js && node --check src/queue.js && node --check src/merge.js && node --check src/relay.js && node --check src/sem-tree.js && node --check src/stable-id.js && node --check src/seq-crdt.js && node --check src/text-crdt.js && node --check src/utf8-offsets.js && node --check src/lossless-tree-crdt.js && node --check src/state-projection.js && node --check src/graph-view.js && node --check src/signaling.js && node --check src/distributed.js",
111
115
  "test:formal": "node scripts/formal-check.mjs",
112
116
  "bench": "node bench/context.bench.mjs",
113
117
  "bench:scale": "node --max-old-space-size=8192 bench/scale.bench.mjs",
@@ -1,78 +1,55 @@
1
1
  import type { AsyncCellHandle, AsyncContext, AsyncSlotHandle } from "./reactive-async.js";
2
- import type { EntryKind, MaterializationMode } from "./reactive-family.js";
2
+ import type { EntryKind } from "./reactive-family.js";
3
3
 
4
- export { EntryKind, MaterializationMode, DEFAULT_MATERIALIZATION_MODE } from "./reactive-family.js";
4
+ export { EntryKind } from "./reactive-family.js";
5
5
 
6
- export type EntryKindResolver<K> = EntryKind | ((key: K) => EntryKind);
7
- export type AsyncFamilyHandle = AsyncCellHandle | AsyncSlotHandle;
6
+ /** The entry handle an {@link AsyncReactiveMap} holds. */
7
+ export type AsyncMapHandle = AsyncCellHandle | AsyncSlotHandle;
8
8
 
9
9
  /**
10
- * The async keyed reactive family (`#lzmatmode`, async flavor): keys map to
10
+ * The async keyed reactive collection (`#reactivemap`, async flavor): keys map to
11
11
  * per-entry async reactive nodes (input cells resolved synchronously, or derived
12
- * slots resolved asynchronously), allocated per the family's
13
- * {@link MaterializationMode}. The transparency law is EVENTUAL a pending slot
14
- * observes as `undefined` and resolves to the canonical value.
12
+ * slots resolved asynchronously). The transparency law is EVENTUAL — a pending
13
+ * slot observes as `undefined` and resolves to the canonical value. Its two
14
+ * specializations are {@link AsyncCellMap} and {@link AsyncSlotMap}.
15
15
  */
16
- export class AsyncReactiveFamily<K = unknown, V = unknown> {
17
- constructor(
18
- ctx: AsyncContext,
19
- mode: MaterializationMode,
20
- keys: Iterable<K>,
21
- factory: (key: K) => V,
22
- entryKind?: EntryKindResolver<K>,
23
- );
16
+ export class AsyncReactiveMap<K = unknown, V = unknown> {
17
+ constructor(ctx: AsyncContext, kind?: EntryKind);
24
18
 
25
- /** Build an eager async family: every declared key's node is allocated now. */
26
- static eager<K, V>(
27
- ctx: AsyncContext,
28
- keys: Iterable<K>,
29
- factory: (key: K) => V,
30
- entryKind?: EntryKindResolver<K>,
31
- ): AsyncReactiveFamily<K, V>;
32
-
33
- /** Build a lazy async family: derived (slot) entries deferred to first read. */
34
- static lazy<K, V>(
35
- ctx: AsyncContext,
36
- keys: Iterable<K>,
37
- factory: (key: K) => V,
38
- entryKind?: EntryKindResolver<K>,
39
- ): AsyncReactiveFamily<K, V>;
40
-
41
- /** Build an async family in the default (eager) mode. */
42
- static create<K, V>(
43
- ctx: AsyncContext,
44
- keys: Iterable<K>,
45
- factory: (key: K) => V,
46
- entryKind?: EntryKindResolver<K>,
47
- ): AsyncReactiveFamily<K, V>;
48
-
49
- /** Materialize (lazy pull) and return the entry handle for `key`. */
50
- get(key: K): AsyncFamilyHandle;
19
+ /** Get the entry handle for `key`, minting via `factory(key)` if absent. */
20
+ getOrInsertHandle(key: K, factory: (key: K) => V): AsyncMapHandle;
51
21
  /** Non-blocking observe: value for a cell/resolved slot, `undefined` if pending. */
52
22
  observe(key: K): V | undefined;
53
- /** Drive `key` to resolution and return its canonical value. */
54
- resolve(key: K): Promise<V>;
55
- /** Set a cell entry's value (input entries only). */
56
- setCell(key: K, value: V): void;
23
+ /** Drive `key` to resolution; mint via `factory(key)` if absent. */
24
+ resolve(key: K, factory?: (key: K) => V): Promise<V>;
25
+ /** The existing entry handle for `key`, or `undefined`. Non-minting. */
26
+ handle(key: K): AsyncMapHandle | undefined;
57
27
  /** Whether `key` is currently materialized. Non-reactive. */
58
28
  isPresent(key: K): boolean;
59
29
  /** Currently-materialized keys, in first-materialization order. */
60
30
  presentKeys(): K[];
61
31
  /** Number of currently-materialized entries. */
62
32
  presentCount(): number;
63
- /** This family's entry kind for `key`. */
64
- entryKind(key: K): EntryKind;
65
- /** This family's materialization mode. */
66
- readonly mode: MaterializationMode;
33
+ /** This map's entry kind. */
34
+ entryKind(): EntryKind;
35
+ }
36
+
37
+ /**
38
+ * The async input-cell map: every entry is an always-resolved input cell. Adds
39
+ * cell-only `set`.
40
+ */
41
+ export class AsyncCellMap<K = unknown, V = unknown> extends AsyncReactiveMap<K, V> {
42
+ constructor(ctx: AsyncContext);
43
+ /** Set the value at `key`, inserting a new input cell if absent. Cell-only. */
44
+ set(key: K, value: V): void;
67
45
  }
68
46
 
69
47
  /**
70
- * The input-cell specialization of {@link AsyncReactiveFamily}: a keyed async
71
- * family whose entries are all input cells (always materialized, always resolved).
48
+ * The async derived-slot map: entries are derived slots minted lazily on access
49
+ * or eagerly via `materializeAll`, resolved via `ctx.getAsync`. NO `set`.
72
50
  */
73
- export function asyncCellFamily<K, V>(
74
- ctx: AsyncContext,
75
- keys: Iterable<K>,
76
- factory: (key: K) => V,
77
- mode?: MaterializationMode,
78
- ): AsyncReactiveFamily<K, V>;
51
+ export class AsyncSlotMap<K = unknown, V = unknown> extends AsyncReactiveMap<K, V> {
52
+ constructor(ctx: AsyncContext);
53
+ /** Eager materialization: pre-mint a derived slot for every key in `keys`. */
54
+ materializeAll(keys: Iterable<K>, factory: (key: K) => V): void;
55
+ }