@graphrefly/graphrefly 0.18.0 → 0.20.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.
Files changed (74) hide show
  1. package/dist/{chunk-J7S54G7I.js → chunk-2L5J6RPM.js} +7 -2
  2. package/dist/chunk-2L5J6RPM.js.map +1 -0
  3. package/dist/{chunk-LB3RYLSC.js → chunk-3N2Y6PCR.js} +197 -42
  4. package/dist/chunk-3N2Y6PCR.js.map +1 -0
  5. package/dist/{chunk-KJGUP35I.js → chunk-5PSVTDNZ.js} +22 -9
  6. package/dist/chunk-5PSVTDNZ.js.map +1 -0
  7. package/dist/{chunk-76YPZQTW.js → chunk-BJAOEU4D.js} +34 -29
  8. package/dist/chunk-BJAOEU4D.js.map +1 -0
  9. package/dist/{chunk-UVWEKTYC.js → chunk-IAPLC4NR.js} +3 -3
  10. package/dist/{chunk-F6ORUNO7.js → chunk-OOA2UTXF.js} +58 -2
  11. package/dist/chunk-OOA2UTXF.js.map +1 -0
  12. package/dist/{chunk-TNKODJ6E.js → chunk-PGEU5MEH.js} +7 -3
  13. package/dist/{chunk-TNKODJ6E.js.map → chunk-PGEU5MEH.js.map} +1 -1
  14. package/dist/chunk-R2LPZIY2.js +111 -0
  15. package/dist/chunk-R2LPZIY2.js.map +1 -0
  16. package/dist/{chunk-BV3TPSBK.js → chunk-XYL3GLB3.js} +742 -757
  17. package/dist/chunk-XYL3GLB3.js.map +1 -0
  18. package/dist/compat/nestjs/index.cjs +967 -811
  19. package/dist/compat/nestjs/index.cjs.map +1 -1
  20. package/dist/compat/nestjs/index.d.cts +4 -4
  21. package/dist/compat/nestjs/index.d.ts +4 -4
  22. package/dist/compat/nestjs/index.js +7 -7
  23. package/dist/core/index.cjs +653 -666
  24. package/dist/core/index.cjs.map +1 -1
  25. package/dist/core/index.d.cts +2 -2
  26. package/dist/core/index.d.ts +2 -2
  27. package/dist/core/index.js +7 -3
  28. package/dist/extra/index.cjs +728 -688
  29. package/dist/extra/index.cjs.map +1 -1
  30. package/dist/extra/index.d.cts +4 -4
  31. package/dist/extra/index.d.ts +4 -4
  32. package/dist/extra/index.js +9 -5
  33. package/dist/graph/index.cjs +836 -808
  34. package/dist/graph/index.cjs.map +1 -1
  35. package/dist/graph/index.d.cts +3 -3
  36. package/dist/graph/index.d.ts +3 -3
  37. package/dist/graph/index.js +8 -8
  38. package/dist/{graph-gISB9n3n.d.ts → graph-KsTe57nI.d.cts} +82 -8
  39. package/dist/{graph-BYFlyNpX.d.cts → graph-mILUUqW8.d.ts} +82 -8
  40. package/dist/{index-CgKPpiu8.d.ts → index-8a605sg9.d.ts} +2 -2
  41. package/dist/{index-DKaB2x0T.d.ts → index-B2SvPEbc.d.ts} +6 -65
  42. package/dist/{index-EmzYk-TG.d.cts → index-BHfg_Ez3.d.ts} +123 -77
  43. package/dist/{index-B80mMeuf.d.ts → index-Bc_diYYJ.d.cts} +123 -77
  44. package/dist/{index-B43mC7uY.d.cts → index-BjtlNirP.d.cts} +3 -3
  45. package/dist/{index-CEDaJaYE.d.ts → index-CgSiUouz.d.ts} +3 -3
  46. package/dist/{index-7WnwgjMu.d.ts → index-DuN3bhtm.d.ts} +82 -32
  47. package/dist/{index-D_tUMcpz.d.cts → index-SFzE_KTa.d.cts} +82 -32
  48. package/dist/{index-Ci_vPaVm.d.cts → index-UudxGnzc.d.cts} +6 -65
  49. package/dist/{index-BqOWSFhr.d.cts → index-VHA43cGP.d.cts} +2 -2
  50. package/dist/index.cjs +5936 -5522
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +644 -379
  53. package/dist/index.d.ts +644 -379
  54. package/dist/index.js +4388 -4058
  55. package/dist/index.js.map +1 -1
  56. package/dist/{meta-npl5b97j.d.cts → meta-BnG7XAaE.d.cts} +394 -236
  57. package/dist/{meta-npl5b97j.d.ts → meta-BnG7XAaE.d.ts} +394 -236
  58. package/dist/{observable-DFBCBELR.d.cts → observable-C8Kx_O6k.d.cts} +1 -1
  59. package/dist/{observable-oAGygKvc.d.ts → observable-DcBwQY7t.d.ts} +1 -1
  60. package/dist/patterns/reactive-layout/index.cjs +865 -718
  61. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  62. package/dist/patterns/reactive-layout/index.d.cts +3 -3
  63. package/dist/patterns/reactive-layout/index.d.ts +3 -3
  64. package/dist/patterns/reactive-layout/index.js +4 -4
  65. package/package.json +2 -2
  66. package/dist/chunk-76YPZQTW.js.map +0 -1
  67. package/dist/chunk-BV3TPSBK.js.map +0 -1
  68. package/dist/chunk-F6ORUNO7.js.map +0 -1
  69. package/dist/chunk-FCLROC4Q.js +0 -231
  70. package/dist/chunk-FCLROC4Q.js.map +0 -1
  71. package/dist/chunk-J7S54G7I.js.map +0 -1
  72. package/dist/chunk-KJGUP35I.js.map +0 -1
  73. package/dist/chunk-LB3RYLSC.js.map +0 -1
  74. /package/dist/{chunk-UVWEKTYC.js.map → chunk-IAPLC4NR.js.map} +0 -0
@@ -1,3 +1,206 @@
1
+ /**
2
+ * GraphReFly message protocol — §1 `~/src/graphrefly/GRAPHREFLY-SPEC.md`.
3
+ * Emissions are always `[[Type, Data?], ...]` (no single-tuple shorthand).
4
+ *
5
+ * ## Canonical message ordering (within a composite batch)
6
+ *
7
+ * When multiple message types appear in a single `down()` call, the canonical
8
+ * delivery order is determined by **signal tier**:
9
+ *
10
+ * | Tier | Signals | Role | Batch behavior |
11
+ * |------|------------------------|-------------------|-------------------------------------|
12
+ * | 0 | START | Subscribe handshake | Immediate (never deferred) |
13
+ * | 1 | DIRTY, INVALIDATE | Notification | Immediate (never deferred) |
14
+ * | 2 | PAUSE, RESUME | Flow control | Immediate (never deferred) |
15
+ * | 3 | DATA, RESOLVED | Value settlement | Deferred inside `batch()` |
16
+ * | 4 | COMPLETE, ERROR | Terminal lifecycle | Deferred to after phase-2 |
17
+ * | 5 | TEARDOWN | Destruction | Immediate (usually sent alone) |
18
+ *
19
+ * **Rule:** Within `downWithBatch`, messages are partitioned by tier and delivered
20
+ * in tier order. This ensures phase-2 values (DATA/RESOLVED) reach sinks before
21
+ * terminal signals (COMPLETE/ERROR) mark the node as done, preventing the
22
+ * "COMPLETE-before-DATA" class of bugs. Sources that emit in canonical order
23
+ * naturally partition correctly with zero overhead.
24
+ *
25
+ * Unknown message types (forward-compat) are tier 1 (immediate).
26
+ *
27
+ * ## Meta node bypass rules (centralized — GRAPHREFLY-SPEC §2.3)
28
+ *
29
+ * - **INVALIDATE** via `graph.signal()` — no-op on meta nodes (cached values preserved).
30
+ * - **COMPLETE / ERROR** — not propagated from parent to meta (meta outlives terminal
31
+ * state for post-mortem writes like setting `meta.error` after ERROR).
32
+ * - **TEARDOWN** — propagated from parent to meta, releasing meta resources.
33
+ */
34
+ /**
35
+ * Subscribe-time handshake (`START`). Delivered to each new sink at the top of
36
+ * `subscribe()` — `[[START]]` for a SENTINEL (no cached value) node or
37
+ * `[[START], [DATA, cached]]` for a node with a cached value.
38
+ *
39
+ * Semantics: "upstream connected and ready to flow." A new sink receiving
40
+ * `[[START]]` alone knows the upstream is alive but has no current value; a
41
+ * sink receiving `[[START], [DATA, v]]` knows the upstream is ready
42
+ * with value `v`. Absence of `START` before any other message means the
43
+ * subscription was either never established or the node is terminal.
44
+ *
45
+ * Tier 0 — immediate, delivered before any DIRTY/DATA in the same batch.
46
+ * Not forwarded through nodes — each node emits its own `START` to its own
47
+ * new sinks.
48
+ */
49
+ declare const START: unique symbol;
50
+ /** Value delivery (`DATA`, value). Tier 3 — deferred inside `batch()`. */
51
+ declare const DATA: unique symbol;
52
+ /** Phase 1: value about to change. Tier 1 — immediate. */
53
+ declare const DIRTY: unique symbol;
54
+ /** Phase 2: dirty pass completed, value unchanged. Tier 3 — deferred inside `batch()`. */
55
+ declare const RESOLVED: unique symbol;
56
+ /** Clear cached state; do not auto-emit. Tier 1 — immediate. */
57
+ declare const INVALIDATE: unique symbol;
58
+ /** Suspend activity. Tier 2 — immediate. */
59
+ declare const PAUSE: unique symbol;
60
+ /** Resume after pause. Tier 2 — immediate. */
61
+ declare const RESUME: unique symbol;
62
+ /** Permanent cleanup. Tier 5 — immediate (usually sent alone). */
63
+ declare const TEARDOWN: unique symbol;
64
+ /** Clean termination. Tier 4 — delivered after phase-2 in the same batch. */
65
+ declare const COMPLETE: unique symbol;
66
+ /** Error termination. Tier 4 — delivered after phase-2 in the same batch. */
67
+ declare const ERROR: unique symbol;
68
+ /** Known protocol type symbols (open set — other symbols are valid and forward). */
69
+ declare const knownMessageTypes: readonly symbol[];
70
+ /** One protocol tuple: `[Type, optional payload]`. */
71
+ type Message = readonly [symbol, unknown?];
72
+ /**
73
+ * A batch of tuples — the wire shape for `node.down()` / `node.up()`.
74
+ */
75
+ type Messages = readonly Message[];
76
+ /**
77
+ * Whether `t` is one of the built-in protocol symbols in this module.
78
+ *
79
+ * @param t — Message type symbol (unknown types are still valid; they must forward).
80
+ * @returns `true` for `DATA`, `DIRTY`, `RESOLVED`, etc.
81
+ *
82
+ * @example
83
+ * ```ts
84
+ * import { DATA, DIRTY, isKnownMessageType } from "@graphrefly/graphrefly-ts";
85
+ *
86
+ * isKnownMessageType(DATA); // true
87
+ * isKnownMessageType(Symbol("custom")); // false
88
+ * ```
89
+ */
90
+ declare function isKnownMessageType(t: symbol): boolean;
91
+ /**
92
+ * Returns the signal tier for a message type (see module-level ordering table).
93
+ *
94
+ * - 0: subscribe handshake (START) — immediate, first in canonical order
95
+ * - 1: notification (DIRTY, INVALIDATE) — immediate
96
+ * - 2: flow control (PAUSE, RESUME) — immediate
97
+ * - 3: value (DATA, RESOLVED) — deferred inside `batch()`
98
+ * - 4: terminal (COMPLETE, ERROR) — delivered after phase-3
99
+ * - 5: destruction (TEARDOWN) — immediate, usually alone
100
+ * - 1 for unknown types (forward-compat: immediate)
101
+ *
102
+ * @param t — Message type symbol.
103
+ * @returns Tier number (0–5).
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * import { DATA, DIRTY, COMPLETE, TEARDOWN, messageTier, START } from "@graphrefly/graphrefly-ts";
108
+ *
109
+ * messageTier(START); // 0
110
+ * messageTier(DIRTY); // 1
111
+ * messageTier(DATA); // 3
112
+ * messageTier(COMPLETE); // 4
113
+ * messageTier(TEARDOWN); // 5
114
+ * ```
115
+ */
116
+ declare function messageTier(t: symbol): number;
117
+ /**
118
+ * Returns whether this tuple is deferred by `batch()` (phase 2: `DATA` or `RESOLVED`).
119
+ *
120
+ * @param msg — Single message tuple.
121
+ * @returns `true` if `msg` is `DATA` or `RESOLVED`.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * import { DATA, RESOLVED, DIRTY, isPhase2Message } from "@graphrefly/graphrefly-ts";
126
+ *
127
+ * isPhase2Message([DATA, 42]); // true
128
+ * isPhase2Message([RESOLVED]); // true
129
+ * isPhase2Message([DIRTY]); // false
130
+ * ```
131
+ */
132
+ declare function isPhase2Message(msg: Message): boolean;
133
+ /**
134
+ * Returns whether this message type is terminal (COMPLETE or ERROR).
135
+ * Terminal messages are delivered after phase-2 in the same batch to prevent
136
+ * the node from becoming terminal before value messages reach sinks.
137
+ *
138
+ * @param t — Message type symbol.
139
+ * @returns `true` for `COMPLETE` or `ERROR`.
140
+ *
141
+ * @example
142
+ * ```ts
143
+ * import { COMPLETE, ERROR, DATA, isTerminalMessage } from "@graphrefly/graphrefly-ts";
144
+ *
145
+ * isTerminalMessage(COMPLETE); // true
146
+ * isTerminalMessage(ERROR); // true
147
+ * isTerminalMessage(DATA); // false
148
+ * ```
149
+ */
150
+ declare function isTerminalMessage(t: symbol): boolean;
151
+ /**
152
+ * Whether `t` is a graph-local signal that should NOT cross a wire/transport
153
+ * boundary (SSE, WebSocket, worker bridge, persistence sinks).
154
+ *
155
+ * Local-only signals (tier 0–2): START, DIRTY, INVALIDATE, PAUSE, RESUME.
156
+ * These are internal to the reactive graph — subscribe handshakes,
157
+ * notification phases, and flow control have no semantics for remote consumers.
158
+ *
159
+ * Wire-crossing signals (tier 3+): DATA, RESOLVED, COMPLETE, ERROR, TEARDOWN.
160
+ * Unknown message types (spec §1.3.6 forward-compat) also cross the wire.
161
+ *
162
+ * Individual adapters may further opt-in to local signals for observability
163
+ * (e.g. SSE `includeDirty`, `includeResolved` options). Storage/persistence
164
+ * adapters that need INVALIDATE for remote cache-clear should explicitly
165
+ * forward it despite `isLocalOnly(INVALIDATE) === true`. The default is to
166
+ * skip all local-only signals at the boundary.
167
+ *
168
+ * @param t — Message type symbol.
169
+ * @returns `true` if the message should be kept local (not sent over wire).
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * import { START, DIRTY, DATA, COMPLETE, isLocalOnly } from "@graphrefly/graphrefly-ts";
174
+ *
175
+ * isLocalOnly(START); // true — subscribe handshake
176
+ * isLocalOnly(DIRTY); // true — notification phase
177
+ * isLocalOnly(RESOLVED); // false — value settlement crosses wire
178
+ * isLocalOnly(DATA); // false — value crosses wire
179
+ * isLocalOnly(COMPLETE); // false — terminal crosses wire
180
+ * ```
181
+ *
182
+ * @category core
183
+ */
184
+ declare function isLocalOnly(t: symbol): boolean;
185
+ /**
186
+ * Whether `t` should be propagated from a parent node to its companion meta nodes.
187
+ * Only TEARDOWN propagates; COMPLETE/ERROR/INVALIDATE do not (meta outlives parent
188
+ * terminal state for post-mortem writes).
189
+ *
190
+ * @param t — Message type symbol.
191
+ * @returns `true` if the signal should reach meta nodes.
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * import { TEARDOWN, COMPLETE, ERROR, propagatesToMeta } from "@graphrefly/graphrefly-ts";
196
+ *
197
+ * propagatesToMeta(TEARDOWN); // true
198
+ * propagatesToMeta(COMPLETE); // false
199
+ * propagatesToMeta(ERROR); // false
200
+ * ```
201
+ */
202
+ declare function propagatesToMeta(t: symbol): boolean;
203
+
1
204
  /**
2
205
  * Who is performing an operation (attribution + ABAC input).
3
206
  *
@@ -131,156 +334,6 @@ declare function policyFromRules(rules: readonly PolicyRuleData[]): NodeGuard;
131
334
  */
132
335
  declare function accessHintForGuard(guard: NodeGuard): string;
133
336
 
134
- /**
135
- * GraphReFly message protocol — §1 `~/src/graphrefly/GRAPHREFLY-SPEC.md`.
136
- * Emissions are always `[[Type, Data?], ...]` (no single-tuple shorthand).
137
- *
138
- * ## Canonical message ordering (within a composite batch)
139
- *
140
- * When multiple message types appear in a single `down()` call, the canonical
141
- * delivery order is determined by **signal tier**:
142
- *
143
- * | Tier | Signals | Role | Batch behavior |
144
- * |------|------------------------|-------------------|-------------------------------------|
145
- * | 0 | DIRTY, INVALIDATE | Notification | Immediate (never deferred) |
146
- * | 1 | PAUSE, RESUME | Flow control | Immediate (never deferred) |
147
- * | 2 | DATA, RESOLVED | Value settlement | Deferred inside `batch()` |
148
- * | 3 | COMPLETE, ERROR | Terminal lifecycle | Deferred to after phase-2 |
149
- * | 4 | TEARDOWN | Destruction | Immediate (usually sent alone) |
150
- *
151
- * **Rule:** Within `downWithBatch`, messages are partitioned by tier and delivered
152
- * in tier order. This ensures phase-2 values (DATA/RESOLVED) reach sinks before
153
- * terminal signals (COMPLETE/ERROR) mark the node as done, preventing the
154
- * "COMPLETE-before-DATA" class of bugs. Sources that emit in canonical order
155
- * naturally partition correctly with zero overhead.
156
- *
157
- * Unknown message types (forward-compat) are tier 0 (immediate).
158
- *
159
- * ## Meta node bypass rules (centralized — GRAPHREFLY-SPEC §2.3)
160
- *
161
- * - **INVALIDATE** via `graph.signal()` — no-op on meta nodes (cached values preserved).
162
- * - **COMPLETE / ERROR** — not propagated from parent to meta (meta outlives terminal
163
- * state for post-mortem writes like setting `meta.error` after ERROR).
164
- * - **TEARDOWN** — propagated from parent to meta, releasing meta resources.
165
- */
166
- /** Value delivery (`DATA`, value). Tier 2 — deferred inside `batch()`. */
167
- declare const DATA: unique symbol;
168
- /** Phase 1: value about to change. Tier 0 — immediate. */
169
- declare const DIRTY: unique symbol;
170
- /** Phase 2: dirty pass completed, value unchanged. Tier 2 — deferred inside `batch()`. */
171
- declare const RESOLVED: unique symbol;
172
- /** Clear cached state; do not auto-emit. Tier 0 — immediate. */
173
- declare const INVALIDATE: unique symbol;
174
- /** Suspend activity. Tier 1 — immediate. */
175
- declare const PAUSE: unique symbol;
176
- /** Resume after pause. Tier 1 — immediate. */
177
- declare const RESUME: unique symbol;
178
- /** Permanent cleanup. Tier 4 — immediate (usually sent alone). */
179
- declare const TEARDOWN: unique symbol;
180
- /** Clean termination. Tier 3 — delivered after phase-2 in the same batch. */
181
- declare const COMPLETE: unique symbol;
182
- /** Error termination. Tier 3 — delivered after phase-2 in the same batch. */
183
- declare const ERROR: unique symbol;
184
- /** Known protocol type symbols (open set — other symbols are valid and forward). */
185
- declare const knownMessageTypes: readonly symbol[];
186
- /** One protocol tuple: `[Type, optional payload]`. */
187
- type Message = readonly [symbol, unknown?];
188
- /**
189
- * A batch of tuples — the wire shape for `node.down()` / `node.up()`.
190
- */
191
- type Messages = readonly Message[];
192
- /**
193
- * Whether `t` is one of the built-in protocol symbols in this module.
194
- *
195
- * @param t — Message type symbol (unknown types are still valid; they must forward).
196
- * @returns `true` for `DATA`, `DIRTY`, `RESOLVED`, etc.
197
- *
198
- * @example
199
- * ```ts
200
- * import { DATA, DIRTY, isKnownMessageType } from "@graphrefly/graphrefly-ts";
201
- *
202
- * isKnownMessageType(DATA); // true
203
- * isKnownMessageType(Symbol("custom")); // false
204
- * ```
205
- */
206
- declare function isKnownMessageType(t: symbol): boolean;
207
- /**
208
- * Returns the signal tier for a message type (see module-level ordering table).
209
- *
210
- * - 0: notification (DIRTY, INVALIDATE) — immediate
211
- * - 1: flow control (PAUSE, RESUME) — immediate
212
- * - 2: value (DATA, RESOLVED) — deferred inside `batch()`
213
- * - 3: terminal (COMPLETE, ERROR) — delivered after phase-2
214
- * - 4: destruction (TEARDOWN) — immediate, usually alone
215
- * - 0 for unknown types (forward-compat: immediate)
216
- *
217
- * @param t — Message type symbol.
218
- * @returns Tier number (0–4).
219
- *
220
- * @example
221
- * ```ts
222
- * import { DATA, DIRTY, COMPLETE, TEARDOWN, messageTier } from "@graphrefly/graphrefly-ts";
223
- *
224
- * messageTier(DIRTY); // 0
225
- * messageTier(DATA); // 2
226
- * messageTier(COMPLETE); // 3
227
- * messageTier(TEARDOWN); // 4
228
- * ```
229
- */
230
- declare function messageTier(t: symbol): number;
231
- /**
232
- * Returns whether this tuple is deferred by `batch()` (phase 2: `DATA` or `RESOLVED`).
233
- *
234
- * @param msg — Single message tuple.
235
- * @returns `true` if `msg` is `DATA` or `RESOLVED`.
236
- *
237
- * @example
238
- * ```ts
239
- * import { DATA, RESOLVED, DIRTY, isPhase2Message } from "@graphrefly/graphrefly-ts";
240
- *
241
- * isPhase2Message([DATA, 42]); // true
242
- * isPhase2Message([RESOLVED]); // true
243
- * isPhase2Message([DIRTY]); // false
244
- * ```
245
- */
246
- declare function isPhase2Message(msg: Message): boolean;
247
- /**
248
- * Returns whether this message type is terminal (COMPLETE or ERROR).
249
- * Terminal messages are delivered after phase-2 in the same batch to prevent
250
- * the node from becoming terminal before value messages reach sinks.
251
- *
252
- * @param t — Message type symbol.
253
- * @returns `true` for `COMPLETE` or `ERROR`.
254
- *
255
- * @example
256
- * ```ts
257
- * import { COMPLETE, ERROR, DATA, isTerminalMessage } from "@graphrefly/graphrefly-ts";
258
- *
259
- * isTerminalMessage(COMPLETE); // true
260
- * isTerminalMessage(ERROR); // true
261
- * isTerminalMessage(DATA); // false
262
- * ```
263
- */
264
- declare function isTerminalMessage(t: symbol): boolean;
265
- /**
266
- * Whether `t` should be propagated from a parent node to its companion meta nodes.
267
- * Only TEARDOWN propagates; COMPLETE/ERROR/INVALIDATE do not (meta outlives parent
268
- * terminal state for post-mortem writes).
269
- *
270
- * @param t — Message type symbol.
271
- * @returns `true` if the signal should reach meta nodes.
272
- *
273
- * @example
274
- * ```ts
275
- * import { TEARDOWN, COMPLETE, ERROR, propagatesToMeta } from "@graphrefly/graphrefly-ts";
276
- *
277
- * propagatesToMeta(TEARDOWN); // true
278
- * propagatesToMeta(COMPLETE); // false
279
- * propagatesToMeta(ERROR); // false
280
- * ```
281
- */
282
- declare function propagatesToMeta(t: symbol): boolean;
283
-
284
337
  /**
285
338
  * Node versioning — GRAPHREFLY-SPEC §7.
286
339
  *
@@ -346,23 +399,44 @@ declare function advanceVersion(info: NodeVersionInfo, newValue: unknown, hashFn
346
399
  declare function isV1(info: NodeVersionInfo): info is V1;
347
400
 
348
401
  /**
349
- * Branded symbol that marks a {@link CleanupResult} wrapper.
350
- * Used internally by {@link cleanupResult} prevents duck-type collisions
351
- * with domain objects that happen to have a `cleanup` property.
402
+ * `NodeBase` abstract class implementing the {@link Node} protocol with
403
+ * lifecycle machinery shared between {@link NodeImpl} (static deps) and
404
+ * {@link DynamicNodeImpl} (runtime-tracked deps).
405
+ *
406
+ * **Responsibilities (shared):**
407
+ * - Identity (name, describeKind, meta, guard, versioning)
408
+ * - Cache + status lifecycle (`_cached`, `_status`, `_terminal`)
409
+ * - Sink storage (null / single / Set fast paths)
410
+ * - `subscribe()` with START handshake + first-subscriber activation
411
+ * - `_downInternal` → `_downToSinks` delivery pipeline (via `downWithBatch`)
412
+ * - `_downAutoValue` (value → protocol framing with equals)
413
+ * - `_handleLocalLifecycle` (cached/status/terminal updates + meta propagation)
414
+ *
415
+ * **Subclass hooks (abstract):**
416
+ * - `_onActivate()` — called when sinkCount transitions 0 → 1
417
+ * - `_doDeactivate()` — cleanup on deactivation (at-most-once, guarded by `_onDeactivate`)
418
+ * - `up()` / `unsubscribe()` / `_upInternal()` — dep-iteration specifics
419
+ * - `_createMetaNode()` — meta companion factory (avoids circular imports)
420
+ *
421
+ * See GRAPHREFLY-SPEC §2 and COMPOSITION-GUIDE §1 for protocol contracts.
422
+ */
423
+
424
+ /**
425
+ * Internal sentinel value: "no cached value has been set or emitted."
426
+ * Used instead of `undefined` so that `undefined` can be a valid emitted value.
427
+ */
428
+ declare const NO_VALUE: unique symbol;
429
+ /**
430
+ * Branded symbol that marks a {@link CleanupResult} wrapper — prevents
431
+ * duck-type collisions with domain objects that happen to have a `cleanup`
432
+ * property.
352
433
  */
353
434
  declare const CLEANUP_RESULT: unique symbol;
354
- /** Lifecycle status of a node. */
355
- type NodeStatus = "disconnected" | "dirty" | "settled" | "resolved" | "completed" | "errored";
435
+ /** Lifecycle status of a node (GRAPHREFLY-SPEC §2.2). */
436
+ type NodeStatus = "disconnected" | "pending" | "dirty" | "settled" | "resolved" | "completed" | "errored";
356
437
  /** Callback that receives downstream message batches. */
357
438
  type NodeSink = (messages: Messages) => void;
358
- /**
359
- * Compute function passed to `node()`.
360
- *
361
- * @returns A value to emit, `undefined` to skip emission, or a cleanup
362
- * function invoked before the next run or on teardown.
363
- */
364
- type NodeFn<T = unknown> = (deps: readonly unknown[], actions: NodeActions) => T | undefined | (() => void);
365
- /** Imperative actions available inside a {@link NodeFn}. */
439
+ /** Imperative actions available inside a node's compute function. */
366
440
  interface NodeActions {
367
441
  /** Emit raw messages downstream. */
368
442
  down(messages: Messages): void;
@@ -372,14 +446,10 @@ interface NodeActions {
372
446
  up(messages: Messages): void;
373
447
  }
374
448
  /**
375
- * Callback for intercepting messages before the default dispatch.
449
+ * Callback for intercepting messages before the default dispatch (§2.6).
376
450
  *
377
- * Called for every message from every dep. Return `true` to consume the
378
- * message (skip default handling), or `false` to let default dispatch run.
379
- *
380
- * @param msg — The message tuple `[Type, Data?]`.
381
- * @param depIndex — Which dep sent it (index into the deps array).
382
- * @param actions — `{ down(), emit(), up() }` — same as `NodeFn` receives.
451
+ * Called for every message from every dep. Return `true` to consume
452
+ * (skip default handling), or `false` to let default dispatch run.
383
453
  */
384
454
  type OnMessageHandler = (msg: Message, depIndex: number, actions: NodeActions) => boolean;
385
455
  /**
@@ -395,9 +465,9 @@ type NodeInspectorHookEvent = {
395
465
  depValues: readonly unknown[];
396
466
  };
397
467
  type NodeInspectorHook = (event: NodeInspectorHookEvent) => void;
398
- /** Explicit describe `type` for {@link Graph.describe} / {@link describeNode} (GRAPHREFLY-SPEC Appendix B). */
468
+ /** Explicit describe `type` for {@link Graph.describe} (GRAPHREFLY-SPEC Appendix B). */
399
469
  type NodeDescribeKind = "state" | "derived" | "producer" | "operator" | "effect";
400
- /** Options for {@link node}. */
470
+ /** Options accepted by every node constructor. */
401
471
  interface NodeOptions {
402
472
  name?: string;
403
473
  /**
@@ -411,7 +481,7 @@ interface NodeOptions {
411
481
  /**
412
482
  * Each key becomes an independently subscribable companion node.
413
483
  * Meta nodes outlive the parent's subscription lifecycle: when all sinks
414
- * unsubscribe the parent disconnects upstream but meta nodes stay alive.
484
+ * unsubscribe the parent deactivates but meta nodes stay alive.
415
485
  * Send `[[TEARDOWN]]` to the parent to release meta node resources.
416
486
  */
417
487
  meta?: Record<string, unknown>;
@@ -442,20 +512,14 @@ interface NodeOptions {
442
512
  * Companion {@link NodeOptions.meta | meta} nodes inherit this guard from the primary.
443
513
  */
444
514
  guard?: NodeGuard;
445
- /**
446
- * Opt-in versioning level (GRAPHREFLY-SPEC §7).
447
- * - `0` (V0): `id` + `version` — identity & change detection.
448
- * - `1` (V1): + `cid` + `prev` — content addressing & linked history.
449
- */
515
+ /** Opt-in versioning level (GRAPHREFLY-SPEC §7). */
450
516
  versioning?: VersioningLevel;
451
517
  /** Override auto-generated versioning id. */
452
518
  versioningId?: string;
453
519
  /** Custom hash function for V1 cid computation. */
454
520
  versioningHash?: HashFn;
455
521
  }
456
- /**
457
- * Options for {@link Node.down} / {@link Node.up} (actor context, graph delivery mode, internal bypass).
458
- */
522
+ /** Actor/delivery context for {@link Node.down} and {@link Node.up}. */
459
523
  type NodeTransportOptions = {
460
524
  actor?: Actor;
461
525
  /**
@@ -464,25 +528,23 @@ type NodeTransportOptions = {
464
528
  */
465
529
  internal?: boolean;
466
530
  /**
467
- * `signal` for {@link Graph.signal} deliveries; default `write` for {@link Graph.set} and direct `down`.
531
+ * `signal` for {@link Graph.signal} deliveries; default `write` for {@link Graph.set}
532
+ * and direct `down`.
468
533
  */
469
534
  delivery?: "write" | "signal";
470
535
  };
471
- /**
472
- * Optional hints passed to {@link Node.subscribe} to enable per-sink
473
- * optimizations.
474
- */
536
+ /** Optional hints passed to {@link Node.subscribe}. */
475
537
  interface SubscribeHints {
476
538
  /**
477
539
  * Subscriber has exactly one dep with `fn` — the source may skip DIRTY
478
540
  * dispatch when this is the sole subscriber. The subscriber synthesizes
479
- * dirty state locally via `onDepSettled`.
541
+ * dirty state locally.
480
542
  */
481
543
  singleDep?: boolean;
482
544
  /**
483
- * Actor to check against the node's `observe` guard.
484
- * When set, `subscribe()` throws {@link GuardDenied} if the actor is not
485
- * permitted to observe this node. Aligned with graphrefly-py `subscribe(actor=)`.
545
+ * Actor to check against the node's `observe` guard. When set,
546
+ * `subscribe()` throws {@link GuardDenied} if the actor is not permitted
547
+ * to observe this node.
486
548
  */
487
549
  actor?: Actor;
488
550
  }
@@ -512,7 +574,7 @@ interface Node<T = unknown> {
512
574
  actor: Actor;
513
575
  timestamp_ns: number;
514
576
  }>;
515
- /** Whether {@link NodeTransportOptions.actor | actor} may {@link Graph.observe | observe} this node. */
577
+ /** Whether `actor` may {@link Graph.observe} this node. */
516
578
  allowsObserve(actor: Actor): boolean;
517
579
  /** Whether a {@link NodeOptions.guard | guard} is installed. */
518
580
  hasGuard(): boolean;
@@ -524,60 +586,154 @@ interface Node<T = unknown> {
524
586
  * `cleanup` is registered as the teardown/recompute cleanup and `value`
525
587
  * (if present) is emitted as data. This avoids the ambiguity where returning
526
588
  * a plain function is silently consumed as cleanup instead of emitted as data.
527
- *
528
- * Use the {@link cleanupResult} factory to create instances — it stamps the
529
- * branded {@link CLEANUP_RESULT} symbol so that domain objects with a `cleanup`
530
- * property are never misinterpreted.
531
- *
532
- * Plain function returns are still treated as cleanup for backward compatibility.
533
589
  */
534
590
  type CleanupResult<T = unknown> = {
535
591
  readonly [CLEANUP_RESULT]: true;
536
592
  cleanup: () => void;
537
593
  value?: T;
538
594
  };
539
- /**
540
- * Create a branded {@link CleanupResult}.
541
- *
542
- * ```ts
543
- * node([dep], () => cleanupResult(() => release(), computedValue))
544
- * ```
545
- */
595
+ /** Create a branded {@link CleanupResult}. */
546
596
  declare function cleanupResult<T>(cleanup: () => void): CleanupResult<T>;
547
597
  declare function cleanupResult<T>(cleanup: () => void, value: T): CleanupResult<T>;
548
598
  /**
549
- * Creates a reactive {@link Node} the single GraphReFly primitive (GRAPHREFLY-SPEC §2).
550
- *
551
- * Typical shapes: `node([])` / `node([], opts)` for a manual source; `node(producerFn, opts)` for a
552
- * producer; `node(deps, computeFn, opts)` for derived nodes and operators.
553
- *
554
- * @param depsOrFn - Dependency nodes, a {@link NodeFn} (producer), or {@link NodeOptions} alone.
555
- * @param fnOrOpts - With deps: compute function or options. Omitted for producer-only form.
556
- * @param optsArg - Options when both `deps` and `fn` are provided.
557
- * @returns `Node<T>` - Configured node instance (lazy until subscribed).
558
- *
559
- * @remarks
560
- * **Protocol:** DIRTY / DATA / RESOLVED ordering, completion, and batch deferral follow `~/src/graphrefly/GRAPHREFLY-SPEC.md`.
561
- *
562
- * **`equals` and mutable values:** The default `Object.is` identity check is
563
- * correct for the common immutable-value case. If your node produces mutable
564
- * objects (e.g. arrays or maps mutated in place), provide a custom `equals`
565
- * function — otherwise `Object.is` will always return `true` for the same
566
- * reference and the node will emit `RESOLVED` instead of `DATA`.
567
- *
568
- * @example
569
- * ```ts
570
- * import { node, state } from "@graphrefly/graphrefly-ts";
571
- *
572
- * const a = state(1);
573
- * const b = node([a], ([x]) => (x as number) + 1);
574
- * ```
575
- *
576
- * @seeAlso [Specification](/spec)
577
- *
578
- * @category core
599
+ * Abstract base class for every node in the graph. Both {@link NodeImpl}
600
+ * (static deps) and {@link DynamicNodeImpl} (runtime-tracked deps) extend
601
+ * this to share subscribe/sink/lifecycle machinery.
602
+ *
603
+ * Invariants (see GRAPHREFLY-SPEC §2.2):
604
+ * - `_sinkCount` always reflects the size of `_sinks`.
605
+ * - `_cached === NO_VALUE` iff the node has never produced a value (SENTINEL).
606
+ * - `_terminal` is set exactly once (per subscription cycle for resubscribable).
607
+ * - `_onActivate` runs exactly once per activation cycle; `_doDeactivate`
608
+ * runs at most once per deactivation (guarded by `_active` flag).
609
+ *
610
+ * ROM/RAM rule (GRAPHREFLY-SPEC §2.2): state nodes (no fn) preserve `_cached`
611
+ * across disconnect — intrinsic, non-volatile. Compute nodes (derived,
612
+ * producer, dynamic) clear `_cached` on disconnect in their subclass
613
+ * `_doDeactivate` their value is a function of live subscriptions.
579
614
  */
580
- declare function node<T = unknown>(depsOrFn?: readonly Node[] | NodeFn<T> | NodeOptions, fnOrOpts?: NodeFn<T> | NodeOptions, optsArg?: NodeOptions): Node<T>;
615
+ declare abstract class NodeBase<T = unknown> implements Node<T> {
616
+ protected readonly _optsName: string | undefined;
617
+ private _registryName;
618
+ /** @internal Read by `describeNode` before inference. */
619
+ readonly _describeKind: NodeDescribeKind | undefined;
620
+ readonly meta: Record<string, Node>;
621
+ protected readonly _equals: (a: unknown, b: unknown) => boolean;
622
+ protected readonly _resubscribable: boolean;
623
+ protected readonly _resetOnTeardown: boolean;
624
+ protected readonly _onResubscribe: (() => void) | undefined;
625
+ protected readonly _onMessage: OnMessageHandler | undefined;
626
+ /** @internal Read by `describeNode` for `accessHintForGuard`. */
627
+ readonly _guard: NodeGuard | undefined;
628
+ /** @internal Subclasses update this through {@link _recordMutation}. */
629
+ protected _lastMutation: {
630
+ actor: Actor;
631
+ timestamp_ns: number;
632
+ } | undefined;
633
+ protected _hashFn: HashFn;
634
+ private _versioning;
635
+ /** @internal Read by `describeNode` and `graph.ts`. */
636
+ _cached: T | typeof NO_VALUE;
637
+ /** @internal Read externally via `get status()`. */
638
+ _status: NodeStatus;
639
+ protected _terminal: boolean;
640
+ private _active;
641
+ /** @internal Read by `graph/profile.ts` for subscriber counts. */
642
+ _sinkCount: number;
643
+ protected _singleDepSinkCount: number;
644
+ protected _singleDepSinks: WeakSet<NodeSink>;
645
+ protected _sinks: NodeSink | Set<NodeSink> | null;
646
+ protected readonly _actions: NodeActions;
647
+ protected readonly _boundDownToSinks: (messages: Messages) => void;
648
+ private _inspectorHook;
649
+ constructor(opts: NodeOptions);
650
+ /**
651
+ * Subclass hook invoked by `actions.down` / `actions.emit`. Default no-op;
652
+ * {@link NodeImpl} overrides to set `_manualEmitUsed`.
653
+ */
654
+ protected _onManualEmit(): void;
655
+ /**
656
+ * Create a companion meta node. Called from the base constructor; must
657
+ * not touch subclass fields that haven't been initialized yet (safe to
658
+ * read from `opts`).
659
+ */
660
+ protected abstract _createMetaNode(key: string, initialValue: unknown, opts: NodeOptions): Node;
661
+ get name(): string | undefined;
662
+ /** @internal Assigned by `Graph.add` when registered without an options `name`. */
663
+ _assignRegistryName(localName: string): void;
664
+ /**
665
+ * @internal Attach/remove inspector hook for graph-level observability.
666
+ * Returns a disposer that restores the previous hook.
667
+ */
668
+ _setInspectorHook(hook?: NodeInspectorHook): () => void;
669
+ /** @internal Used by subclasses to surface inspector events. */
670
+ protected _emitInspectorHook(event: NodeInspectorHookEvent): void;
671
+ get status(): NodeStatus;
672
+ get lastMutation(): Readonly<{
673
+ actor: Actor;
674
+ timestamp_ns: number;
675
+ }> | undefined;
676
+ get v(): Readonly<NodeVersionInfo> | undefined;
677
+ /** @internal Used by `Graph.setVersioning` to retroactively apply versioning. */
678
+ _applyVersioning(level: VersioningLevel, opts?: {
679
+ id?: string;
680
+ hash?: HashFn;
681
+ }): void;
682
+ hasGuard(): boolean;
683
+ allowsObserve(actor: Actor): boolean;
684
+ get(): T | undefined;
685
+ down(messages: Messages, options?: NodeTransportOptions): void;
686
+ /** @internal Record a successful guarded mutation (called by `down` and subclass `up`). */
687
+ protected _recordMutation(actor: Actor): void;
688
+ /** Abstract — subclasses forward messages to dependencies (or no-op for sources). */
689
+ abstract up(messages: Messages, options?: NodeTransportOptions): void;
690
+ /** Abstract — subclasses release upstream subscriptions (or no-op for sources). */
691
+ abstract unsubscribe(): void;
692
+ /** Internal upstream-send used by `actions.up`. */
693
+ protected abstract _upInternal(messages: Messages): void;
694
+ /** Called when `_sinkCount` transitions 0 → 1. */
695
+ protected abstract _onActivate(): void;
696
+ /**
697
+ * At-most-once deactivation guard. Both TEARDOWN (eager) and
698
+ * unsubscribe-body (lazy) call this. The `_active` flag ensures
699
+ * `_doDeactivate` runs exactly once per activation cycle.
700
+ */
701
+ protected _onDeactivate(): void;
702
+ /** Subclass hook: cleanup on deactivation (called at most once). */
703
+ protected abstract _doDeactivate(): void;
704
+ subscribe(sink: NodeSink, hints?: SubscribeHints): () => void;
705
+ /**
706
+ * Core outgoing dispatch. Applies terminal filter + local lifecycle
707
+ * update, then hands messages to `downWithBatch` for tier-aware delivery.
708
+ */
709
+ protected _downInternal(messages: Messages): void;
710
+ protected _canSkipDirty(): boolean;
711
+ protected _downToSinks(messages: Messages): void;
712
+ /**
713
+ * Update `_cached`, `_status`, `_terminal` from message batch before
714
+ * delivery. Subclass hooks `_onInvalidate` / `_onTeardown` let
715
+ * {@link NodeImpl} clear `_lastDepValues` and invoke cleanup fns.
716
+ */
717
+ protected _handleLocalLifecycle(messages: Messages): void;
718
+ /**
719
+ * Subclass hook: invoked when INVALIDATE arrives (before `_cached` is
720
+ * cleared). {@link NodeImpl} uses this to run the fn cleanup fn and
721
+ * drop `_lastDepValues` so the next wave re-runs fn.
722
+ */
723
+ protected _onInvalidate(): void;
724
+ /**
725
+ * Subclass hook: invoked when TEARDOWN arrives, before `_onDeactivate`.
726
+ * {@link NodeImpl} uses this to run any pending cleanup fn.
727
+ */
728
+ protected _onTeardown(): void;
729
+ /** Forward a signal to all companion meta nodes (best-effort). */
730
+ protected _propagateToMeta(t: symbol): void;
731
+ /**
732
+ * Frame a computed value into the right protocol messages and dispatch
733
+ * via `_downInternal`. Used by `_runFn` and `actions.emit`.
734
+ */
735
+ protected _downAutoValue(value: unknown): void;
736
+ }
581
737
 
582
738
  /** JSON-shaped slice of a node for Phase 1 `Graph.describe()` (GRAPHREFLY-SPEC §3.6, Appendix B). */
583
739
  type DescribeNodeOutput = {
@@ -587,6 +743,8 @@ type DescribeNodeOutput = {
587
743
  meta?: Record<string, unknown>;
588
744
  name?: string;
589
745
  value?: unknown;
746
+ /** True when the node has never received or been initialized with a value (cache holds SENTINEL). */
747
+ sentinel?: boolean;
590
748
  /** Node versioning info (GRAPHREFLY-SPEC §7). Present only when versioning is enabled. */
591
749
  v?: {
592
750
  id: string;
@@ -617,4 +775,4 @@ type DescribeField = "type" | "status" | "value" | "deps" | "meta" | "v" | "guar
617
775
  /** Resolve which fields to include based on detail level or explicit field list. */
618
776
  declare function resolveDescribeFields(detail?: DescribeDetail, fields?: readonly DescribeField[]): Set<string> | null;
619
777
 
620
- export { normalizeActor as $, type Actor as A, accessHintForGuard as B, CLEANUP_RESULT as C, DATA as D, ERROR as E, advanceVersion as F, type GuardAction as G, type HashFn as H, INVALIDATE as I, cleanupResult as J, createVersioning as K, defaultHash as L, type Message as M, type Node as N, type OnMessageHandler as O, PAUSE as P, isKnownMessageType as Q, RESOLVED as R, type SubscribeHints as S, TEARDOWN as T, isPhase2Message as U, type V0 as V, isTerminalMessage as W, isV1 as X, knownMessageTypes as Y, messageTier as Z, node as _, type NodeOptions as a, policy as a0, policyFromRules as a1, propagatesToMeta as a2, resolveDescribeFields as a3, type NodeInspectorHook as a4, type NodeActions as b, type NodeFn as c, COMPLETE as d, type CleanupResult as e, DEFAULT_ACTOR as f, DIRTY as g, type DescribeDetail as h, type DescribeField as i, type DescribeNodeOutput as j, GuardDenied as k, type GuardDeniedDetails as l, type Messages as m, type NodeDescribeKind as n, type NodeGuard as o, type NodeSink as p, type NodeStatus as q, type NodeTransportOptions as r, type NodeVersionInfo as s, type PolicyAllow as t, type PolicyDeny as u, type PolicyRuleData as v, RESUME as w, type V1 as x, type VersioningLevel as y, type VersioningOptions as z };
778
+ export { normalizeActor as $, type Actor as A, accessHintForGuard as B, CLEANUP_RESULT as C, DATA as D, ERROR as E, advanceVersion as F, type GuardAction as G, type HashFn as H, INVALIDATE as I, cleanupResult as J, createVersioning as K, defaultHash as L, type Message as M, type Node as N, type OnMessageHandler as O, PAUSE as P, isKnownMessageType as Q, RESOLVED as R, START as S, TEARDOWN as T, isLocalOnly as U, type V0 as V, isPhase2Message as W, isTerminalMessage as X, isV1 as Y, knownMessageTypes as Z, messageTier as _, type NodeOptions as a, policy as a0, policyFromRules as a1, propagatesToMeta as a2, resolveDescribeFields as a3, NodeBase as a4, type NodeActions as b, COMPLETE as c, type CleanupResult as d, DEFAULT_ACTOR as e, DIRTY as f, type DescribeDetail as g, type DescribeField as h, type DescribeNodeOutput as i, GuardDenied as j, type GuardDeniedDetails as k, type Messages as l, type NodeDescribeKind as m, type NodeGuard as n, type NodeSink as o, type NodeStatus as p, type NodeTransportOptions as q, type NodeVersionInfo as r, type PolicyAllow as s, type PolicyDeny as t, type PolicyRuleData as u, RESUME as v, type SubscribeHints as w, type V1 as x, type VersioningLevel as y, type VersioningOptions as z };