@graphrefly/graphrefly 0.20.0 → 0.22.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 (97) hide show
  1. package/README.md +27 -8
  2. package/dist/chunk-44HD4BTA.js +47 -0
  3. package/dist/chunk-44HD4BTA.js.map +1 -0
  4. package/dist/chunk-7TAQJHQV.js +103 -0
  5. package/dist/chunk-7TAQJHQV.js.map +1 -0
  6. package/dist/chunk-BLD3IFYF.js +6827 -0
  7. package/dist/chunk-BLD3IFYF.js.map +1 -0
  8. package/dist/{chunk-IAPLC4NR.js → chunk-EQUZ5NLD.js} +34 -45
  9. package/dist/chunk-EQUZ5NLD.js.map +1 -0
  10. package/dist/{chunk-OOA2UTXF.js → chunk-IR3KMOLX.js} +358 -128
  11. package/dist/chunk-IR3KMOLX.js.map +1 -0
  12. package/dist/{chunk-5PSVTDNZ.js → chunk-MQBQOFDS.js} +20 -11
  13. package/dist/chunk-MQBQOFDS.js.map +1 -0
  14. package/dist/chunk-NXC35KC5.js +2417 -0
  15. package/dist/chunk-NXC35KC5.js.map +1 -0
  16. package/dist/chunk-QA3RP5NH.js +2234 -0
  17. package/dist/chunk-QA3RP5NH.js.map +1 -0
  18. package/dist/chunk-RHI3GHZW.js +115 -0
  19. package/dist/chunk-RHI3GHZW.js.map +1 -0
  20. package/dist/{chunk-2L5J6RPM.js → chunk-TH6COGOP.js} +15 -26
  21. package/dist/chunk-TH6COGOP.js.map +1 -0
  22. package/dist/compat/nestjs/index.cjs +3366 -2259
  23. package/dist/compat/nestjs/index.cjs.map +1 -1
  24. package/dist/compat/nestjs/index.d.cts +6 -4
  25. package/dist/compat/nestjs/index.d.ts +6 -4
  26. package/dist/compat/nestjs/index.js +8 -8
  27. package/dist/core/index.cjs +1611 -1218
  28. package/dist/core/index.cjs.map +1 -1
  29. package/dist/core/index.d.cts +3 -2
  30. package/dist/core/index.d.ts +3 -2
  31. package/dist/core/index.js +37 -34
  32. package/dist/extra/index.cjs +7726 -6470
  33. package/dist/extra/index.cjs.map +1 -1
  34. package/dist/extra/index.d.cts +4 -4
  35. package/dist/extra/index.d.ts +4 -4
  36. package/dist/extra/index.js +57 -30
  37. package/dist/graph/index.cjs +3107 -2216
  38. package/dist/graph/index.cjs.map +1 -1
  39. package/dist/graph/index.d.cts +5 -3
  40. package/dist/graph/index.d.ts +5 -3
  41. package/dist/graph/index.js +24 -11
  42. package/dist/graph-DFr0diXB.d.ts +1128 -0
  43. package/dist/graph-ab1yPwIB.d.cts +1128 -0
  44. package/dist/{index-8a605sg9.d.ts → index-BHm3Ba5q.d.ts} +2 -2
  45. package/dist/{index-SFzE_KTa.d.cts → index-BbYZma8G.d.ts} +1697 -586
  46. package/dist/{index-DuN3bhtm.d.ts → index-BvWfZCTt.d.cts} +1697 -586
  47. package/dist/index-C9z6rU9P.d.cts +388 -0
  48. package/dist/{index-BjtlNirP.d.cts → index-D36MAQ3f.d.ts} +4 -4
  49. package/dist/{index-VHA43cGP.d.cts → index-DLE1Sp-L.d.cts} +2 -2
  50. package/dist/{index-CgSiUouz.d.ts → index-DrJq9B1T.d.cts} +4 -4
  51. package/dist/index-DsGxLfwL.d.ts +315 -0
  52. package/dist/index-Dy04P4W3.d.cts +315 -0
  53. package/dist/index-HdJx_BjO.d.ts +388 -0
  54. package/dist/index.cjs +9919 -7900
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +415 -42
  57. package/dist/index.d.ts +415 -42
  58. package/dist/index.js +1064 -639
  59. package/dist/index.js.map +1 -1
  60. package/dist/meta--fr9sxRM.d.cts +41 -0
  61. package/dist/meta-n3FoVWML.d.ts +41 -0
  62. package/dist/node-C5UD5MGq.d.cts +1146 -0
  63. package/dist/node-C5UD5MGq.d.ts +1146 -0
  64. package/dist/{observable-DcBwQY7t.d.ts → observable-CQRBtEbq.d.ts} +1 -1
  65. package/dist/{observable-C8Kx_O6k.d.cts → observable-DWydVy5b.d.cts} +1 -1
  66. package/dist/patterns/reactive-layout/index.cjs +3102 -2132
  67. package/dist/patterns/reactive-layout/index.cjs.map +1 -1
  68. package/dist/patterns/reactive-layout/index.d.cts +5 -3
  69. package/dist/patterns/reactive-layout/index.d.ts +5 -3
  70. package/dist/patterns/reactive-layout/index.js +5 -4
  71. package/dist/storage-Bew05Xy6.d.cts +182 -0
  72. package/dist/storage-C9fZfMfM.d.ts +182 -0
  73. package/package.json +2 -1
  74. package/dist/chunk-2L5J6RPM.js.map +0 -1
  75. package/dist/chunk-3N2Y6PCR.js +0 -2117
  76. package/dist/chunk-3N2Y6PCR.js.map +0 -1
  77. package/dist/chunk-5PSVTDNZ.js.map +0 -1
  78. package/dist/chunk-BJAOEU4D.js +0 -6269
  79. package/dist/chunk-BJAOEU4D.js.map +0 -1
  80. package/dist/chunk-IAPLC4NR.js.map +0 -1
  81. package/dist/chunk-OOA2UTXF.js.map +0 -1
  82. package/dist/chunk-PGEU5MEH.js +0 -162
  83. package/dist/chunk-PGEU5MEH.js.map +0 -1
  84. package/dist/chunk-R2LPZIY2.js +0 -111
  85. package/dist/chunk-R2LPZIY2.js.map +0 -1
  86. package/dist/chunk-WZ2Z2CRV.js +0 -32
  87. package/dist/chunk-WZ2Z2CRV.js.map +0 -1
  88. package/dist/chunk-XYL3GLB3.js +0 -1631
  89. package/dist/chunk-XYL3GLB3.js.map +0 -1
  90. package/dist/graph-KsTe57nI.d.cts +0 -750
  91. package/dist/graph-mILUUqW8.d.ts +0 -750
  92. package/dist/index-B2SvPEbc.d.ts +0 -257
  93. package/dist/index-BHfg_Ez3.d.ts +0 -629
  94. package/dist/index-Bc_diYYJ.d.cts +0 -629
  95. package/dist/index-UudxGnzc.d.cts +0 -257
  96. package/dist/meta-BnG7XAaE.d.cts +0 -778
  97. package/dist/meta-BnG7XAaE.d.ts +0 -778
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/patterns/reactive-layout/index.ts","../src/patterns/reactive-layout/measurement-adapters.ts","../src/patterns/reactive-layout/reactive-layout.ts","../src/patterns/reactive-layout/reactive-block-layout.ts"],"sourcesContent":["/**\n * Reactive layout pattern — standalone subpath export.\n *\n * ```ts\n * import { reactiveLayout, CliMeasureAdapter } from \"@graphrefly/graphrefly-ts/reactive-layout\";\n * ```\n */\n\nexport * from \"./measurement-adapters.js\";\nexport * from \"./reactive-block-layout.js\";\nexport * from \"./reactive-layout.js\";\n","/**\n * MeasurementAdapter implementations (roadmap §7.1 — pluggable backends).\n *\n * All adapters implement {@link MeasurementAdapter} from `reactive-layout.ts`.\n * Sync constructors, sync `measureSegment()` — no async, no polling.\n */\n\nimport type { MeasurementAdapter } from \"./reactive-layout.js\";\n\n// ---------------------------------------------------------------------------\n// Shared: East Asian Width detection for CLI adapter\n// ---------------------------------------------------------------------------\n\n/**\n * Return the display-cell width of a single codepoint in a monospace terminal.\n * Fullwidth / wide CJK → 2 cells; combining marks → 0 cells; everything else → 1 cell.\n *\n * Approximates UAX #11 East_Asian_Width (W/F → 2). Not a full EAW table —\n * covers CJK, Hangul, fullwidth forms, common emoji, and Extensions B-G.\n * Python's `unicodedata.east_asian_width()` is authoritative; this tracks it\n * closely but may diverge on rare/ambiguous codepoints.\n */\nfunction cellWidth(code: number): 0 | 1 | 2 {\n\t// Combining marks (Mn, Mc, Me) → 0 cells\n\tif (\n\t\t(code >= 0x0300 && code <= 0x036f) || // Combining Diacritical Marks\n\t\t(code >= 0x0483 && code <= 0x0489) || // Cyrillic combining marks\n\t\t(code >= 0x0591 && code <= 0x05bd) || // Hebrew combining marks\n\t\t(code >= 0x0610 && code <= 0x061a) || // Arabic combining marks\n\t\t(code >= 0x064b && code <= 0x065f) || // Arabic combining marks\n\t\t(code >= 0x0670 && code === 0x0670) || // Arabic superscript alef\n\t\t(code >= 0x06d6 && code <= 0x06dc) || // Arabic combining marks\n\t\t(code >= 0x06df && code <= 0x06e4) || // Arabic combining marks\n\t\t(code >= 0x06e7 && code <= 0x06e8) || // Arabic combining marks\n\t\t(code >= 0x06ea && code <= 0x06ed) || // Arabic combining marks\n\t\t(code >= 0x0730 && code <= 0x074a) || // Syriac combining marks\n\t\t(code >= 0x07a6 && code <= 0x07b0) || // Thaana combining marks\n\t\t(code >= 0x0900 && code <= 0x0903) || // Devanagari combining marks\n\t\t(code >= 0x093a && code <= 0x094f) || // Devanagari combining marks\n\t\t(code >= 0x0951 && code <= 0x0957) || // Devanagari combining marks\n\t\t(code >= 0x0962 && code <= 0x0963) || // Devanagari combining marks\n\t\t(code >= 0x0981 && code <= 0x0983) || // Bengali combining marks\n\t\t(code >= 0x09bc && code <= 0x09cd) || // Bengali combining marks\n\t\t(code >= 0x0a01 && code <= 0x0a03) || // Gurmukhi combining marks\n\t\t(code >= 0x0a3c && code <= 0x0a51) || // Gurmukhi combining marks\n\t\t(code >= 0x0a70 && code <= 0x0a71) || // Gurmukhi combining marks\n\t\t(code >= 0x0a75 && code === 0x0a75) || // Gurmukhi combining mark\n\t\t(code >= 0x0e31 && code === 0x0e31) || // Thai combining mark\n\t\t(code >= 0x0e34 && code <= 0x0e3a) || // Thai combining marks\n\t\t(code >= 0x0e47 && code <= 0x0e4e) || // Thai combining marks\n\t\t(code >= 0x0eb1 && code === 0x0eb1) || // Lao combining mark\n\t\t(code >= 0x0eb4 && code <= 0x0ebc) || // Lao combining marks\n\t\t(code >= 0x0ec8 && code <= 0x0ece) || // Lao combining marks\n\t\t(code >= 0x1dc0 && code <= 0x1dff) || // Combining Diacritical Marks Supplement\n\t\t(code >= 0x20d0 && code <= 0x20ff) || // Combining Diacritical Marks for Symbols\n\t\t(code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors\n\t\t(code >= 0xfe20 && code <= 0xfe2f) || // Combining Half Marks\n\t\tcode === 0x200d // Zero Width Joiner\n\t) {\n\t\treturn 0;\n\t}\n\t// Wide / fullwidth → 2 cells\n\tif (\n\t\t(code >= 0x1100 && code <= 0x115f) || // Hangul Jamo\n\t\t(code >= 0x231a && code <= 0x231b) || // Watch, Hourglass\n\t\t(code >= 0x2329 && code <= 0x232a) || // Angle brackets\n\t\t(code >= 0x23e9 && code <= 0x23f3) || // Media control symbols\n\t\t(code >= 0x23f8 && code <= 0x23fa) || // Media control symbols\n\t\t(code >= 0x25fd && code <= 0x25fe) || // Medium squares\n\t\t(code >= 0x2614 && code <= 0x2615) || // Umbrella, Hot Beverage\n\t\t(code >= 0x2648 && code <= 0x2653) || // Zodiac symbols\n\t\tcode === 0x267f || // Wheelchair\n\t\tcode === 0x2693 || // Anchor\n\t\tcode === 0x26a1 || // High Voltage\n\t\t(code >= 0x26aa && code <= 0x26ab) || // Medium circles\n\t\t(code >= 0x26bd && code <= 0x26be) || // Soccer, Baseball\n\t\t(code >= 0x26c4 && code <= 0x26c5) || // Snowman, Sun behind cloud\n\t\tcode === 0x26ce || // Ophiuchus\n\t\tcode === 0x26d4 || // No Entry\n\t\tcode === 0x26ea || // Church\n\t\t(code >= 0x26f2 && code <= 0x26f3) || // Fountain, Golf\n\t\tcode === 0x26f5 || // Sailboat\n\t\tcode === 0x26fa || // Tent\n\t\tcode === 0x26fd || // Fuel Pump\n\t\tcode === 0x2702 || // Scissors\n\t\tcode === 0x2705 || // Check Mark\n\t\t(code >= 0x2708 && code <= 0x270d) || // Airplane...Writing Hand\n\t\tcode === 0x270f || // Pencil\n\t\t(code >= 0x2753 && code <= 0x2755) || // Question marks\n\t\tcode === 0x2757 || // Exclamation\n\t\t(code >= 0x2795 && code <= 0x2797) || // Plus, Minus, Divide\n\t\tcode === 0x27b0 || // Curly Loop\n\t\tcode === 0x27bf || // Double Curly Loop\n\t\t(code >= 0x2934 && code <= 0x2935) || // Arrows\n\t\t(code >= 0x2b05 && code <= 0x2b07) || // Arrows\n\t\t(code >= 0x2b1b && code <= 0x2b1c) || // Squares\n\t\tcode === 0x2b50 || // Star\n\t\tcode === 0x2b55 || // Circle\n\t\t(code >= 0x2e80 && code <= 0x303e) || // CJK Radicals, Symbols, Punctuation\n\t\t(code >= 0x3040 && code <= 0x309f) || // Hiragana\n\t\t(code >= 0x30a0 && code <= 0x30ff) || // Katakana\n\t\t(code >= 0x3105 && code <= 0x312f) || // Bopomofo\n\t\t(code >= 0x3131 && code <= 0x318e) || // Hangul Compatibility Jamo\n\t\t(code >= 0x3190 && code <= 0x31e3) || // Kanbun, CJK Strokes\n\t\t(code >= 0x31f0 && code <= 0x321e) || // Katakana Phonetic Extensions\n\t\t(code >= 0x3220 && code <= 0x3247) || // Enclosed CJK\n\t\t(code >= 0x3250 && code <= 0x4dbf) || // CJK Extensions + Unified block\n\t\t(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified Ideographs\n\t\t(code >= 0xa960 && code <= 0xa97c) || // Hangul Jamo Extended-A\n\t\t(code >= 0xac00 && code <= 0xd7a3) || // Hangul Syllables\n\t\t(code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs\n\t\t(code >= 0xfe10 && code <= 0xfe19) || // Vertical forms\n\t\t(code >= 0xfe30 && code <= 0xfe6b) || // CJK Compatibility Forms\n\t\t(code >= 0xff01 && code <= 0xff60) || // Fullwidth Forms (excl. halfwidth)\n\t\t(code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Signs\n\t\t(code >= 0x1f004 && code === 0x1f004) || // Mahjong Red Dragon\n\t\tcode === 0x1f0cf || // Joker\n\t\t(code >= 0x1f170 && code <= 0x1f171) || // A/B buttons\n\t\tcode === 0x1f17e || // O button\n\t\tcode === 0x1f17f || // P button\n\t\tcode === 0x1f18e || // AB button\n\t\t(code >= 0x1f191 && code <= 0x1f19a) || // Squared symbols\n\t\t(code >= 0x1f1e0 && code <= 0x1f1ff) || // Regional Indicator Symbols\n\t\t(code >= 0x1f200 && code <= 0x1f202) || // Enclosed ideographic\n\t\tcode === 0x1f21a || // Squared CJK\n\t\tcode === 0x1f22f || // Squared CJK\n\t\t(code >= 0x1f232 && code <= 0x1f23a) || // Squared CJK\n\t\t(code >= 0x1f250 && code <= 0x1f251) || // Circled ideographic\n\t\t(code >= 0x1f300 && code <= 0x1f9ff) || // Misc Symbols / Emoticons / Emoji\n\t\t(code >= 0x1fa00 && code <= 0x1faff) || // Chess, Symbols Extended-A\n\t\t(code >= 0x1fb00 && code <= 0x1fbff) || // Symbols for Legacy Computing\n\t\t(code >= 0x20000 && code <= 0x2fffd) || // CJK Extension B-F (excl. nonchars)\n\t\t(code >= 0x30000 && code <= 0x3fffd) // CJK Extension G+ (excl. nonchars)\n\t) {\n\t\treturn 2;\n\t}\n\treturn 1;\n}\n\n/**\n * Count total display cells for a string in a monospace terminal.\n *\n * Combining marks contribute 0 cells; CJK/fullwidth contribute 2.\n * Does not handle ZWJ emoji sequences (multi-codepoint clusters that\n * render as a single glyph) — terminal support for these varies widely.\n */\nfunction countCells(text: string): number {\n\tlet cells = 0;\n\tfor (const ch of text) {\n\t\tcells += cellWidth(ch.codePointAt(0)!);\n\t}\n\treturn cells;\n}\n\n// ---------------------------------------------------------------------------\n// CliMeasureAdapter\n// ---------------------------------------------------------------------------\n\nexport type CliMeasureAdapterOptions = {\n\t/** Pixel width per terminal cell (default: 8). */\n\tcellPx?: number;\n};\n\n/**\n * Monospace terminal measurement adapter.\n *\n * Width = cell count × `cellPx`. CJK / fullwidth characters count as 2 cells.\n * No external dependencies. Works in any JS environment.\n */\nexport class CliMeasureAdapter implements MeasurementAdapter {\n\tprivate readonly cellPx: number;\n\n\tconstructor(opts?: CliMeasureAdapterOptions) {\n\t\tthis.cellPx = opts?.cellPx ?? 8;\n\t}\n\n\tmeasureSegment(text: string, _font: string): { width: number } {\n\t\treturn { width: countCells(text) * this.cellPx };\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// PrecomputedAdapter\n// ---------------------------------------------------------------------------\n\nexport type PrecomputedAdapterOptions = {\n\t/**\n\t * Pre-computed metrics: `{ font: { segment: widthPx } }`.\n\t * Outer key is the CSS font string; inner key is the text segment.\n\t */\n\tmetrics: Record<string, Record<string, number>>;\n\t/**\n\t * Fallback when a segment is not found in the metrics map.\n\t * - `\"per-char\"`: sum individual character widths from the same font map (default)\n\t * - `\"error\"`: throw an error for unknown segments\n\t */\n\tfallback?: \"per-char\" | \"error\";\n};\n\nclass PrecomputedAdapterKeyError extends Error {\n\tname = \"KeyError\";\n}\n\n/**\n * Pre-computed measurement adapter for SSR / snapshot replay.\n *\n * Reads from a static metrics object — zero measurement at runtime.\n * Ideal for server-side rendering or replaying snapshotted layouts.\n */\nexport class PrecomputedAdapter implements MeasurementAdapter {\n\tprivate readonly metrics: Record<string, Record<string, number>>;\n\tprivate readonly fallback: \"per-char\" | \"error\";\n\n\tconstructor(opts: PrecomputedAdapterOptions) {\n\t\tthis.metrics = opts.metrics;\n\t\tconst fb = opts.fallback ?? \"per-char\";\n\t\tif (fb !== \"per-char\" && fb !== \"error\") {\n\t\t\t// Keep parity with Python: validate at runtime.\n\t\t\tthrow new Error(\n\t\t\t\t`fallback must be 'per-char' or 'error', got ${JSON.stringify(opts.fallback)}`,\n\t\t\t);\n\t\t}\n\t\tthis.fallback = fb;\n\t}\n\n\tmeasureSegment(text: string, font: string): { width: number } {\n\t\tconst fontMap = this.metrics[font];\n\t\tif (fontMap) {\n\t\t\tconst w = fontMap[text];\n\t\t\tif (w !== undefined) return { width: w };\n\t\t}\n\n\t\tif (this.fallback === \"error\") {\n\t\t\tthrow new PrecomputedAdapterKeyError(\n\t\t\t\t`PrecomputedAdapter: no metrics for segment ${JSON.stringify(text)} in font ${JSON.stringify(font)}`,\n\t\t\t);\n\t\t}\n\n\t\t// per-char fallback: sum individual character widths\n\t\tlet total = 0;\n\t\tif (fontMap) {\n\t\t\tfor (const ch of text) {\n\t\t\t\tconst cw = fontMap[ch];\n\t\t\t\tif (cw !== undefined) {\n\t\t\t\t\ttotal += cw;\n\t\t\t\t}\n\t\t\t\t// unknown char contributes 0 (best-effort)\n\t\t\t}\n\t\t}\n\t\treturn { width: total };\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// CanvasMeasureAdapter (browser)\n// ---------------------------------------------------------------------------\n\nexport type CanvasMeasureAdapterOptions = {\n\t/** Emoji width correction factor (default: 1, no correction). */\n\temojiCorrection?: number;\n};\n\n/**\n * Browser measurement adapter using `OffscreenCanvas.measureText()`.\n *\n * Lazily creates an OffscreenCanvas and 2D context on first call.\n * Requires a browser environment with OffscreenCanvas support.\n */\nexport class CanvasMeasureAdapter implements MeasurementAdapter {\n\tprivate ctx: OffscreenCanvasRenderingContext2D | null = null;\n\tprivate currentFont = \"\";\n\tprivate readonly emojiCorrection: number;\n\n\tconstructor(opts?: CanvasMeasureAdapterOptions) {\n\t\tthis.emojiCorrection = opts?.emojiCorrection ?? 1;\n\t}\n\n\tprivate getContext(): OffscreenCanvasRenderingContext2D {\n\t\tif (!this.ctx) {\n\t\t\tif (typeof OffscreenCanvas === \"undefined\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"CanvasMeasureAdapter requires a browser environment with OffscreenCanvas support. \" +\n\t\t\t\t\t\t\"Use CliMeasureAdapter or NodeCanvasMeasureAdapter for Node.js.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst canvas = new OffscreenCanvas(0, 0);\n\t\t\tconst ctx = canvas.getContext(\"2d\");\n\t\t\tif (!ctx) throw new Error(\"CanvasMeasureAdapter: failed to get 2d context\");\n\t\t\tthis.ctx = ctx;\n\t\t}\n\t\treturn this.ctx;\n\t}\n\n\tmeasureSegment(text: string, font: string): { width: number } {\n\t\tconst ctx = this.getContext();\n\t\tif (font !== this.currentFont) {\n\t\t\tctx.font = font;\n\t\t\tthis.currentFont = font;\n\t\t}\n\t\tlet width = ctx.measureText(text).width;\n\t\t// Apply emoji correction if configured\n\t\tif (this.emojiCorrection !== 1 && /\\p{Emoji_Presentation}/u.test(text)) {\n\t\t\twidth *= this.emojiCorrection;\n\t\t}\n\t\treturn { width };\n\t}\n\n\tclearCache(): void {\n\t\t// No segment cache; context font is the only state.\n\t\tthis.currentFont = \"\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// NodeCanvasMeasureAdapter (Node.js / CLI with canvas package)\n// ---------------------------------------------------------------------------\n\n/**\n * Canvas API subset expected from `@napi-rs/canvas` or `skia-canvas`.\n * Passed via dependency injection — no dynamic import, no polling.\n */\nexport type CanvasModule = {\n\tcreateCanvas(\n\t\twidth: number,\n\t\theight: number,\n\t): {\n\t\tgetContext(type: \"2d\"): {\n\t\t\tfont: string;\n\t\t\tmeasureText(text: string): { width: number };\n\t\t};\n\t};\n};\n\n/**\n * Node.js measurement adapter using an injected canvas module.\n *\n * ```ts\n * import * as canvas from \"@napi-rs/canvas\";\n * const adapter = new NodeCanvasMeasureAdapter(canvas);\n * ```\n *\n * Works with `@napi-rs/canvas`, `skia-canvas`, or any module exposing\n * `createCanvas(w, h).getContext(\"2d\").measureText(text)`.\n */\nexport class NodeCanvasMeasureAdapter implements MeasurementAdapter {\n\tprivate ctx: { font: string; measureText(text: string): { width: number } } | null = null;\n\tprivate currentFont = \"\";\n\tprivate readonly canvasModule: CanvasModule;\n\n\tconstructor(canvasModule: CanvasModule) {\n\t\tthis.canvasModule = canvasModule;\n\t}\n\n\tprivate getContext(): { font: string; measureText(text: string): { width: number } } {\n\t\tif (!this.ctx) {\n\t\t\tconst canvas = this.canvasModule.createCanvas(0, 0);\n\t\t\tconst ctx = canvas.getContext(\"2d\");\n\t\t\tif (!ctx) throw new Error(\"NodeCanvasMeasureAdapter: failed to get 2d context\");\n\t\t\tthis.ctx = ctx;\n\t\t}\n\t\treturn this.ctx;\n\t}\n\n\tmeasureSegment(text: string, font: string): { width: number } {\n\t\tconst ctx = this.getContext();\n\t\tif (font !== this.currentFont) {\n\t\t\tctx.font = font;\n\t\t\tthis.currentFont = font;\n\t\t}\n\t\treturn { width: ctx.measureText(text).width };\n\t}\n\n\tclearCache(): void {\n\t\tthis.currentFont = \"\";\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// SvgBoundsAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * SVG measurement adapter — extracts dimensions from `viewBox` attribute\n * or explicit `width`/`height` attributes in the SVG string.\n *\n * Pure arithmetic: parses the SVG string for dimension attributes.\n * No DOM required. Works in any JS environment.\n *\n * Browser users who need `getBBox()` should pre-measure and pass explicit\n * `viewBox` on the content block instead.\n */\nexport class SvgBoundsAdapter {\n\tmeasureSvg(content: string): { width: number; height: number } {\n\t\t// Try viewBox first: viewBox=\"minX minY width height\"\n\t\tconst viewBoxMatch = content.match(/viewBox\\s*=\\s*[\"']([^\"']+)[\"']/);\n\t\tif (viewBoxMatch) {\n\t\t\tconst parts = viewBoxMatch[1]!.trim().split(/[\\s,]+/);\n\t\t\tif (parts.length >= 4) {\n\t\t\t\tconst w = Number.parseFloat(parts[2]!);\n\t\t\t\tconst h = Number.parseFloat(parts[3]!);\n\t\t\t\tif (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {\n\t\t\t\t\treturn { width: w, height: h };\n\t\t\t\t}\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"SvgBoundsAdapter: viewBox width/height are missing, non-finite, or not positive\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to explicit width/height attributes\n\t\tconst widthMatch = content.match(/<svg[^>]*\\bwidth\\s*=\\s*[\"']?([\\d.]+)/);\n\t\tconst heightMatch = content.match(/<svg[^>]*\\bheight\\s*=\\s*[\"']?([\\d.]+)/);\n\t\tif (widthMatch && heightMatch) {\n\t\t\tconst w = Number.parseFloat(widthMatch[1]!);\n\t\t\tconst h = Number.parseFloat(heightMatch[1]!);\n\t\t\tif (Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0) {\n\t\t\t\treturn { width: w, height: h };\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t\"SvgBoundsAdapter: svg width/height attributes are non-finite or not positive\",\n\t\t\t);\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t\"SvgBoundsAdapter: cannot determine dimensions — SVG has no viewBox or width/height attributes\",\n\t\t);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// ImageSizeAdapter\n// ---------------------------------------------------------------------------\n\n/**\n * Image measurement adapter — returns pre-registered dimensions by src key.\n *\n * Sync-only: dimensions must be provided upfront via the `sizes` map.\n * No I/O, no polling, no async. For browser use, pre-measure via\n * `Image.onload` and pass natural dimensions on the content block directly,\n * or register them here.\n *\n * ```ts\n * const adapter = new ImageSizeAdapter({\n * \"hero.png\": { width: 1200, height: 630 },\n * \"logo.svg\": { width: 120, height: 40 },\n * });\n * ```\n */\nexport class ImageSizeAdapter {\n\tprivate readonly sizes: Map<string, { width: number; height: number }>;\n\n\tconstructor(sizes: Record<string, { width: number; height: number }>) {\n\t\tthis.sizes = new Map(Object.entries(sizes));\n\t}\n\n\tmeasureImage(src: string): { width: number; height: number } {\n\t\tconst dims = this.sizes.get(src);\n\t\tif (!dims) {\n\t\t\tthrow new Error(`ImageSizeAdapter: no dimensions registered for ${JSON.stringify(src)}`);\n\t\t}\n\t\treturn { width: dims.width, height: dims.height };\n\t}\n}\n","/**\n * Reactive text layout engine (roadmap §7.1 — Pretext parity).\n *\n * Pure-arithmetic text measurement and line breaking without DOM thrashing.\n * Inspired by [Pretext](https://github.com/chenglou/pretext), rebuilt as a\n * GraphReFly graph — inspectable via `describe()`, snapshotable, debuggable.\n *\n * Two-tier DX:\n * - `reactiveLayout({ adapter, text?, font?, lineHeight?, maxWidth?, name? })` — convenience factory\n * - `MeasurementAdapter` — pluggable backends (`measureSegment`; optional `clearCache`)\n */\nimport { downWithBatch } from \"../../core/batch.js\";\nimport { monotonicNs } from \"../../core/clock.js\";\nimport { DATA, INVALIDATE, TEARDOWN } from \"../../core/messages.js\";\nimport type { Node } from \"../../core/node.js\";\nimport { derived, state } from \"../../core/sugar.js\";\nimport { Graph } from \"../../graph/graph.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Pluggable measurement backend. */\nexport interface MeasurementAdapter {\n\tmeasureSegment(text: string, font: string): { width: number };\n\t/** Optional; adapters may omit for read-only / stateless measurement. */\n\tclearCache?(): void;\n}\n\n/** Mutable counters for `analyzeAndMeasure` cache hit ratio (hits / (hits + misses)). */\nexport type SegmentMeasureStats = { hits: number; misses: number };\n\n/** Break kind for each segment (ported from Pretext analysis.ts). */\nexport type SegmentBreakKind = \"text\" | \"space\" | \"zero-width-break\" | \"soft-hyphen\" | \"hard-break\";\n\n/** A measured text segment ready for line breaking. */\nexport type PreparedSegment = {\n\ttext: string;\n\twidth: number;\n\tkind: SegmentBreakKind;\n\t/** Grapheme widths for overflow-wrap: break-word (null if single grapheme). */\n\tgraphemeWidths: number[] | null;\n};\n\n/** A laid-out line with start/end cursors. */\nexport type LayoutLine = {\n\ttext: string;\n\twidth: number;\n\tstartSegment: number;\n\tstartGrapheme: number;\n\tendSegment: number;\n\tendGrapheme: number;\n};\n\n/** Per-character position for hit testing. */\nexport type CharPosition = {\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\tline: number;\n};\n\n/** Full layout result from the line-breaks derived node. */\nexport type LineBreaksResult = {\n\tlines: LayoutLine[];\n\tlineCount: number;\n};\n\n/** Result of the reactive layout graph's describe-accessible state. */\nexport type ReactiveLayoutBundle = {\n\tgraph: Graph;\n\t/** Set input text. */\n\tsetText: (text: string) => void;\n\t/** Set CSS font string. */\n\tsetFont: (font: string) => void;\n\t/** Set line height (px). */\n\tsetLineHeight: (lineHeight: number) => void;\n\t/** Set max width constraint (px). */\n\tsetMaxWidth: (maxWidth: number) => void;\n\t/** Segments node. */\n\tsegments: Node<PreparedSegment[]>;\n\t/** Line breaks node. */\n\tlineBreaks: Node<LineBreaksResult>;\n\t/** Total height node. */\n\theight: Node<number>;\n\t/** Per-character positions node. */\n\tcharPositions: Node<CharPosition[]>;\n};\n\n// ---------------------------------------------------------------------------\n// Text analysis (ported from Pretext analysis.ts — core subset)\n// ---------------------------------------------------------------------------\n\n// CJK detection (Unicode CJK Unified Ideographs + common ranges)\nfunction isCJK(s: string): boolean {\n\tfor (const ch of s) {\n\t\tconst c = ch.codePointAt(0)!;\n\t\tif (\n\t\t\t(c >= 0x4e00 && c <= 0x9fff) || // CJK Unified Ideographs\n\t\t\t(c >= 0x3400 && c <= 0x4dbf) || // CJK Extension A\n\t\t\t(c >= 0x3000 && c <= 0x303f) || // CJK Symbols and Punctuation\n\t\t\t(c >= 0x3040 && c <= 0x309f) || // Hiragana\n\t\t\t(c >= 0x30a0 && c <= 0x30ff) || // Katakana\n\t\t\t(c >= 0xac00 && c <= 0xd7af) || // Hangul\n\t\t\t(c >= 0xff00 && c <= 0xffef) // Fullwidth Forms\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n// Kinsoku: characters that cannot start a line (CJK punctuation)\nconst kinsokuStart = new Set([\n\t\"\\uff0c\",\n\t\"\\uff0e\",\n\t\"\\uff01\",\n\t\"\\uff1a\",\n\t\"\\uff1b\",\n\t\"\\uff1f\",\n\t\"\\u3001\",\n\t\"\\u3002\",\n\t\"\\u30fb\",\n\t\"\\uff09\",\n\t\"\\u3015\",\n\t\"\\u3009\",\n\t\"\\u300b\",\n\t\"\\u300d\",\n\t\"\\u300f\",\n\t\"\\u3011\",\n]);\n\n// Left-sticky punctuation (merges into preceding segment)\nconst leftStickyPunctuation = new Set([\n\t\".\",\n\t\",\",\n\t\"!\",\n\t\"?\",\n\t\":\",\n\t\";\",\n\t\")\",\n\t\"]\",\n\t\"}\",\n\t\"%\",\n\t'\"',\n\t\"\\u201d\",\n\t\"\\u2019\",\n\t\"\\u00bb\",\n\t\"\\u203a\",\n\t\"\\u2026\",\n]);\n\n/** Normalize collapsible whitespace (CSS white-space: normal). */\nfunction normalizeWhitespace(text: string): string {\n\treturn text.replace(/[\\t\\n\\r\\f ]+/g, \" \").replace(/^ | $/g, \"\");\n}\n\n/**\n * Segment text using Intl.Segmenter (word granularity) and classify break kinds.\n * Returns raw segmentation pieces before merging.\n */\nfunction segmentText(normalized: string): {\n\ttexts: string[];\n\tisWordLike: boolean[];\n\tkinds: SegmentBreakKind[];\n}[] {\n\tconst wordSegmenter = new Intl.Segmenter(undefined, { granularity: \"word\" });\n\tconst pieces: {\n\t\ttexts: string[];\n\t\tisWordLike: boolean[];\n\t\tkinds: SegmentBreakKind[];\n\t}[] = [];\n\n\tfor (const s of wordSegmenter.segment(normalized)) {\n\t\tconst text = s.segment;\n\t\tconst isWordLike = s.isWordLike ?? false;\n\n\t\t// Split segment by break-relevant characters\n\t\tconst texts: string[] = [];\n\t\tconst wordLikes: boolean[] = [];\n\t\tconst kinds: SegmentBreakKind[] = [];\n\n\t\tlet currentText = \"\";\n\t\tlet currentKind: SegmentBreakKind | null = null;\n\n\t\tfor (const ch of text) {\n\t\t\tlet kind: SegmentBreakKind;\n\t\t\tif (ch === \" \") kind = \"space\";\n\t\t\telse if (ch === \"\\u200b\") kind = \"zero-width-break\";\n\t\t\telse if (ch === \"\\u00ad\") kind = \"soft-hyphen\";\n\t\t\telse if (ch === \"\\n\") kind = \"hard-break\";\n\t\t\telse kind = \"text\";\n\n\t\t\tif (currentKind !== null && kind === currentKind) {\n\t\t\t\tcurrentText += ch;\n\t\t\t} else {\n\t\t\t\tif (currentKind !== null) {\n\t\t\t\t\ttexts.push(currentText);\n\t\t\t\t\twordLikes.push(currentKind === \"text\" && isWordLike);\n\t\t\t\t\tkinds.push(currentKind);\n\t\t\t\t}\n\t\t\t\tcurrentText = ch;\n\t\t\t\tcurrentKind = kind;\n\t\t\t}\n\t\t}\n\n\t\tif (currentKind !== null) {\n\t\t\ttexts.push(currentText);\n\t\t\twordLikes.push(currentKind === \"text\" && isWordLike);\n\t\t\tkinds.push(currentKind);\n\t\t}\n\n\t\tpieces.push({ texts, isWordLike: wordLikes, kinds });\n\t}\n\treturn pieces;\n}\n\n/**\n * Merge segmentation pieces: sticky punctuation, CJK per-grapheme splitting,\n * and produce the final measured segment list.\n */\nexport function analyzeAndMeasure(\n\ttext: string,\n\tfont: string,\n\tadapter: MeasurementAdapter,\n\tcache: Map<string, Map<string, number>>,\n\tstats?: SegmentMeasureStats,\n): PreparedSegment[] {\n\tconst normalized = normalizeWhitespace(text);\n\tif (normalized.length === 0) return [];\n\n\tconst pieces = segmentText(normalized);\n\tconst graphemeSegmenter = new Intl.Segmenter(undefined, {\n\t\tgranularity: \"grapheme\",\n\t});\n\n\t// Flatten pieces into a single segment list with merging\n\tconst rawTexts: string[] = [];\n\tconst rawKinds: SegmentBreakKind[] = [];\n\tconst rawWordLike: boolean[] = [];\n\n\tfor (const piece of pieces) {\n\t\tfor (let i = 0; i < piece.texts.length; i++) {\n\t\t\trawTexts.push(piece.texts[i]!);\n\t\t\trawKinds.push(piece.kinds[i]!);\n\t\t\trawWordLike.push(piece.isWordLike[i]!);\n\t\t}\n\t}\n\n\t// Merge: left-sticky punctuation and kinsoku-start into preceding text segment\n\tconst mergedTexts: string[] = [];\n\tconst mergedKinds: SegmentBreakKind[] = [];\n\tconst mergedWordLike: boolean[] = [];\n\n\tfor (let i = 0; i < rawTexts.length; i++) {\n\t\tconst t = rawTexts[i]!;\n\t\tconst k = rawKinds[i]!;\n\t\tconst wl = rawWordLike[i]!;\n\n\t\t// Merge left-sticky punctuation into preceding text\n\t\tif (\n\t\t\tk === \"text\" &&\n\t\t\t!wl &&\n\t\t\tmergedTexts.length > 0 &&\n\t\t\tmergedKinds[mergedKinds.length - 1] === \"text\"\n\t\t) {\n\t\t\tconst isSticky = t.length === 1 && (leftStickyPunctuation.has(t) || kinsokuStart.has(t));\n\t\t\tif (isSticky) {\n\t\t\t\tmergedTexts[mergedTexts.length - 1] += t;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\t// Merge hyphen after word into preceding text (\"well-known\" stays together)\n\t\tif (\n\t\t\tt === \"-\" &&\n\t\t\tmergedTexts.length > 0 &&\n\t\t\tmergedKinds[mergedKinds.length - 1] === \"text\" &&\n\t\t\tmergedWordLike[mergedWordLike.length - 1]\n\t\t) {\n\t\t\tmergedTexts[mergedTexts.length - 1] += t;\n\t\t\tcontinue;\n\t\t}\n\n\t\tmergedTexts.push(t);\n\t\tmergedKinds.push(k);\n\t\tmergedWordLike.push(wl);\n\t}\n\n\t// Get or create font-specific cache\n\tlet fontCache = cache.get(font);\n\tif (!fontCache) {\n\t\tfontCache = new Map<string, number>();\n\t\tcache.set(font, fontCache);\n\t}\n\n\tfunction measureCached(seg: string): number {\n\t\tlet w = fontCache!.get(seg);\n\t\tif (w === undefined) {\n\t\t\tif (stats) stats.misses += 1;\n\t\t\tw = adapter.measureSegment(seg, font).width;\n\t\t\tfontCache!.set(seg, w);\n\t\t} else if (stats) {\n\t\t\tstats.hits += 1;\n\t\t}\n\t\treturn w;\n\t}\n\n\t// Build final prepared segments, splitting CJK into per-grapheme\n\tconst segments: PreparedSegment[] = [];\n\n\tfor (let i = 0; i < mergedTexts.length; i++) {\n\t\tconst t = mergedTexts[i]!;\n\t\tconst k = mergedKinds[i]!;\n\n\t\tif (k !== \"text\") {\n\t\t\t// Non-text segments: space, hard-break, soft-hyphen, zero-width-break\n\t\t\tsegments.push({\n\t\t\t\ttext: t,\n\t\t\t\twidth: k === \"space\" ? measureCached(\" \") * t.length : 0,\n\t\t\t\tkind: k,\n\t\t\t\tgraphemeWidths: null,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// CJK text: split into per-grapheme segments for line breaking\n\t\tif (isCJK(t)) {\n\t\t\tlet unitText = \"\";\n\t\t\tfor (const gs of graphemeSegmenter.segment(t)) {\n\t\t\t\tconst grapheme = gs.segment;\n\n\t\t\t\t// Kinsoku: line-start-prohibited chars stick to preceding unit\n\t\t\t\tif (unitText.length > 0 && kinsokuStart.has(grapheme)) {\n\t\t\t\t\tunitText += grapheme;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (unitText.length > 0) {\n\t\t\t\t\tconst w = measureCached(unitText);\n\t\t\t\t\tsegments.push({\n\t\t\t\t\t\ttext: unitText,\n\t\t\t\t\t\twidth: w,\n\t\t\t\t\t\tkind: \"text\",\n\t\t\t\t\t\tgraphemeWidths: null,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tunitText = grapheme;\n\t\t\t}\n\t\t\tif (unitText.length > 0) {\n\t\t\t\tconst w = measureCached(unitText);\n\t\t\t\tsegments.push({\n\t\t\t\t\ttext: unitText,\n\t\t\t\t\twidth: w,\n\t\t\t\t\tkind: \"text\",\n\t\t\t\t\tgraphemeWidths: null,\n\t\t\t\t});\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Non-CJK text: measure whole segment, pre-compute grapheme widths for break-word\n\t\tconst w = measureCached(t);\n\t\tlet graphemeWidths: number[] | null = null;\n\n\t\tif (mergedWordLike[i] && t.length > 1) {\n\t\t\tconst gWidths: number[] = [];\n\t\t\tfor (const gs of graphemeSegmenter.segment(t)) {\n\t\t\t\tgWidths.push(measureCached(gs.segment));\n\t\t\t}\n\t\t\tif (gWidths.length > 1) {\n\t\t\t\tgraphemeWidths = gWidths;\n\t\t\t}\n\t\t}\n\n\t\tsegments.push({ text: t, width: w, kind: \"text\", graphemeWidths });\n\t}\n\n\treturn segments;\n}\n\n// ---------------------------------------------------------------------------\n// Line breaking (greedy, ported from Pretext line-break.ts — core subset)\n// ---------------------------------------------------------------------------\n\n/**\n * Greedy line-breaking algorithm.\n *\n * Walks segments left to right, accumulating width. Breaks when a segment would\n * overflow maxWidth. Supports:\n * - Trailing space hang (spaces don't trigger breaks)\n * - overflow-wrap: break-word via grapheme widths\n * - Soft hyphens (break opportunity, adds visible hyphen width)\n * - Hard breaks (forced newline)\n */\nexport function computeLineBreaks(\n\tsegments: PreparedSegment[],\n\tmaxWidth: number,\n\tadapter: MeasurementAdapter,\n\tfont: string,\n\tcache: Map<string, Map<string, number>>,\n): LineBreaksResult {\n\tif (segments.length === 0) {\n\t\treturn { lines: [], lineCount: 0 };\n\t}\n\n\tconst lines: LayoutLine[] = [];\n\tlet lineW = 0;\n\tlet hasContent = false;\n\tlet lineStartSeg = 0;\n\tlet lineStartGrapheme = 0;\n\tlet lineEndSeg = 0;\n\tlet lineEndGrapheme = 0;\n\tlet pendingBreakSeg = -1;\n\tlet pendingBreakWidth = 0;\n\n\t// Measure hyphen for soft-hyphen support\n\tlet fontCache = cache.get(font);\n\tif (!fontCache) {\n\t\tfontCache = new Map<string, number>();\n\t\tcache.set(font, fontCache);\n\t}\n\tlet hyphenWidth = fontCache.get(\"-\");\n\tif (hyphenWidth === undefined) {\n\t\thyphenWidth = adapter.measureSegment(\"-\", font).width;\n\t\tfontCache.set(\"-\", hyphenWidth);\n\t}\n\n\tfunction flushLine(endSeg = lineEndSeg, endGrapheme = lineEndGrapheme, width = lineW) {\n\t\t// Build line text\n\t\tlet text = \"\";\n\t\tfor (let i = lineStartSeg; i < endSeg; i++) {\n\t\t\tconst seg = segments[i]!;\n\t\t\tif (seg.kind === \"soft-hyphen\" || seg.kind === \"hard-break\") continue;\n\t\t\tif (i === lineStartSeg && lineStartGrapheme > 0 && seg.graphemeWidths) {\n\t\t\t\t// Partial segment from grapheme break\n\t\t\t\tconst graphemeSegmenter = new Intl.Segmenter(undefined, {\n\t\t\t\t\tgranularity: \"grapheme\",\n\t\t\t\t});\n\t\t\t\tconst graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);\n\t\t\t\ttext += graphemes.slice(lineStartGrapheme).join(\"\");\n\t\t\t} else {\n\t\t\t\ttext += seg.text;\n\t\t\t}\n\t\t}\n\t\t// Handle partial end segment\n\t\tif (endGrapheme > 0 && endSeg < segments.length) {\n\t\t\tconst seg = segments[endSeg]!;\n\t\t\tconst graphemeSegmenter = new Intl.Segmenter(undefined, {\n\t\t\t\tgranularity: \"grapheme\",\n\t\t\t});\n\t\t\tconst graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);\n\t\t\tconst startG = lineStartSeg === endSeg ? lineStartGrapheme : 0;\n\t\t\ttext += graphemes.slice(startG, endGrapheme).join(\"\");\n\t\t}\n\t\t// Add visible hyphen if line ends at soft-hyphen\n\t\tif (\n\t\t\tendSeg > 0 &&\n\t\t\tsegments[endSeg - 1]?.kind === \"soft-hyphen\" &&\n\t\t\t!(lineStartSeg === endSeg && lineStartGrapheme > 0)\n\t\t) {\n\t\t\ttext += \"-\";\n\t\t}\n\n\t\tlines.push({\n\t\t\ttext,\n\t\t\twidth,\n\t\t\tstartSegment: lineStartSeg,\n\t\t\tstartGrapheme: lineStartGrapheme,\n\t\t\tendSegment: endSeg,\n\t\t\tendGrapheme,\n\t\t});\n\t\tlineW = 0;\n\t\thasContent = false;\n\t\tpendingBreakSeg = -1;\n\t\tpendingBreakWidth = 0;\n\t}\n\n\tfunction canBreakAfter(kind: SegmentBreakKind): boolean {\n\t\treturn kind === \"space\" || kind === \"zero-width-break\" || kind === \"soft-hyphen\";\n\t}\n\n\tfunction startLine(segIdx: number, graphemeIdx: number, width: number) {\n\t\thasContent = true;\n\t\tlineStartSeg = segIdx;\n\t\tlineStartGrapheme = graphemeIdx;\n\t\tlineEndSeg = segIdx + 1;\n\t\tlineEndGrapheme = 0;\n\t\tlineW = width;\n\t}\n\n\tfunction startLineAtGrapheme(segIdx: number, graphemeIdx: number, width: number) {\n\t\thasContent = true;\n\t\tlineStartSeg = segIdx;\n\t\tlineStartGrapheme = graphemeIdx;\n\t\tlineEndSeg = segIdx;\n\t\tlineEndGrapheme = graphemeIdx + 1;\n\t\tlineW = width;\n\t}\n\n\tfor (let i = 0; i < segments.length; i++) {\n\t\tconst seg = segments[i]!;\n\n\t\t// Hard break: emit current line, start fresh\n\t\tif (seg.kind === \"hard-break\") {\n\t\t\tif (hasContent) {\n\t\t\t\tflushLine();\n\t\t\t} else {\n\t\t\t\t// Empty line\n\t\t\t\tlines.push({\n\t\t\t\t\ttext: \"\",\n\t\t\t\t\twidth: 0,\n\t\t\t\t\tstartSegment: i,\n\t\t\t\t\tstartGrapheme: 0,\n\t\t\t\t\tendSegment: i,\n\t\t\t\t\tendGrapheme: 0,\n\t\t\t\t});\n\t\t\t}\n\t\t\tlineStartSeg = i + 1;\n\t\t\tlineStartGrapheme = 0;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst w = seg.width;\n\n\t\tif (!hasContent) {\n\t\t\t// First content on a new line\n\t\t\tif (w > maxWidth && seg.graphemeWidths) {\n\t\t\t\t// Word wider than maxWidth: break at grapheme level\n\t\t\t\tappendBreakableSegment(i, 0, seg.graphemeWidths);\n\t\t\t} else {\n\t\t\t\tstartLine(i, 0, w);\n\t\t\t}\n\t\t\tif (canBreakAfter(seg.kind)) {\n\t\t\t\tpendingBreakSeg = i + 1;\n\t\t\t\tpendingBreakWidth = seg.kind === \"space\" ? lineW - w : lineW;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst newW = lineW + w;\n\n\t\tif (newW > maxWidth + 0.005) {\n\t\t\t// Overflow\n\t\t\tif (canBreakAfter(seg.kind)) {\n\t\t\t\t// Trailing space: hang past edge, then break\n\t\t\t\tlineW += w;\n\t\t\t\tlineEndSeg = i + 1;\n\t\t\t\tlineEndGrapheme = 0;\n\t\t\t\tflushLine(i + 1, 0, seg.kind === \"space\" ? lineW - w : lineW);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (pendingBreakSeg >= 0) {\n\t\t\t\t// Break at last break opportunity\n\t\t\t\tflushLine(pendingBreakSeg, 0, pendingBreakWidth);\n\t\t\t\t// Don't advance i — re-process current segment on new line\n\t\t\t\ti--;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (w > maxWidth && seg.graphemeWidths) {\n\t\t\t\t// Break-word: split at grapheme level\n\t\t\t\tflushLine();\n\t\t\t\tappendBreakableSegment(i, 0, seg.graphemeWidths);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// No break opportunity: force break before this segment\n\t\t\tflushLine();\n\t\t\ti--;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Fits on current line\n\t\tlineW = newW;\n\t\tlineEndSeg = i + 1;\n\t\tlineEndGrapheme = 0;\n\n\t\tif (canBreakAfter(seg.kind)) {\n\t\t\tpendingBreakSeg = i + 1;\n\t\t\tpendingBreakWidth = seg.kind === \"space\" ? lineW - w : lineW;\n\t\t}\n\t}\n\n\tif (hasContent) {\n\t\tflushLine();\n\t}\n\n\treturn { lines, lineCount: lines.length };\n\n\tfunction appendBreakableSegment(segIdx: number, startG: number, gWidths: number[]) {\n\t\tfor (let g = startG; g < gWidths.length; g++) {\n\t\t\tconst gw = gWidths[g]!;\n\t\t\tif (!hasContent) {\n\t\t\t\tstartLineAtGrapheme(segIdx, g, gw);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (lineW + gw > maxWidth + 0.005) {\n\t\t\t\tflushLine();\n\t\t\t\tstartLineAtGrapheme(segIdx, g, gw);\n\t\t\t} else {\n\t\t\t\tlineW += gw;\n\t\t\t\tlineEndSeg = segIdx;\n\t\t\t\tlineEndGrapheme = g + 1;\n\t\t\t}\n\t\t}\n\t\t// If we consumed the whole segment, advance end past it\n\t\tif (hasContent && lineEndSeg === segIdx && lineEndGrapheme === gWidths.length) {\n\t\t\tlineEndSeg = segIdx + 1;\n\t\t\tlineEndGrapheme = 0;\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Character positions\n// ---------------------------------------------------------------------------\n\n/** Compute per-character x,y positions from line breaks and segments. */\nexport function computeCharPositions(\n\tlineBreaks: LineBreaksResult,\n\tsegments: PreparedSegment[],\n\tlineHeight: number,\n): CharPosition[] {\n\tconst positions: CharPosition[] = [];\n\tconst graphemeSegmenter = new Intl.Segmenter(undefined, {\n\t\tgranularity: \"grapheme\",\n\t});\n\n\tfor (let lineIdx = 0; lineIdx < lineBreaks.lines.length; lineIdx++) {\n\t\tconst line = lineBreaks.lines[lineIdx]!;\n\t\tconst y = lineIdx * lineHeight;\n\t\tlet x = 0;\n\n\t\tfor (let si = line.startSegment; si < segments.length; si++) {\n\t\t\tconst seg = segments[si]!;\n\t\t\tif (seg.kind === \"soft-hyphen\" || seg.kind === \"hard-break\") {\n\t\t\t\t// Skip non-visual segments but stop if past the line\n\t\t\t\tif (si >= line.endSegment && line.endGrapheme === 0) break;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst graphemes = [...graphemeSegmenter.segment(seg.text)].map((g) => g.segment);\n\t\t\tif (graphemes.length === 0) continue;\n\t\t\tconst startG = si === line.startSegment ? line.startGrapheme : 0;\n\n\t\t\t// Determine how many graphemes of this segment belong to this line\n\t\t\tlet endG: number;\n\t\t\tif (si < line.endSegment) {\n\t\t\t\t// Full segment is on this line\n\t\t\t\tendG = graphemes.length;\n\t\t\t} else if (si === line.endSegment && line.endGrapheme > 0) {\n\t\t\t\t// Partial segment (grapheme-level break)\n\t\t\t\tendG = line.endGrapheme;\n\t\t\t} else {\n\t\t\t\t// Past the line's content\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tfor (let g = startG; g < endG; g++) {\n\t\t\t\tconst gWidth = seg.graphemeWidths ? seg.graphemeWidths[g]! : seg.width / graphemes.length;\n\t\t\t\tpositions.push({ x, y, width: gWidth, height: lineHeight, line: lineIdx });\n\t\t\t\tx += gWidth;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn positions;\n}\n\n// ---------------------------------------------------------------------------\n// Reactive graph factory\n// ---------------------------------------------------------------------------\n\nexport type ReactiveLayoutOptions = {\n\t/** Measurement backend (required). */\n\tadapter: MeasurementAdapter;\n\t/** Graph name (default: \"reactive-layout\"). */\n\tname?: string;\n\t/** Initial text. */\n\ttext?: string;\n\t/** Initial CSS font string. */\n\tfont?: string;\n\t/** Initial line height in px. */\n\tlineHeight?: number;\n\t/** Initial max width in px (clamped to ≥ 0). */\n\tmaxWidth?: number;\n};\n\n/**\n * Create a reactive text layout graph.\n *\n * ```\n * Graph(\"reactive-layout\")\n * ├── state(\"text\")\n * ├── state(\"font\")\n * ├── state(\"line-height\")\n * ├── state(\"max-width\")\n * ├── derived(\"segments\") — text + font → PreparedSegment[]\n * ├── derived(\"line-breaks\") — segments + max-width → LineBreaksResult\n * ├── derived(\"height\") — line-breaks → number\n * └── derived(\"char-positions\") — line-breaks + segments → CharPosition[]\n * ```\n */\nexport function reactiveLayout(opts: ReactiveLayoutOptions): ReactiveLayoutBundle {\n\tconst { adapter, name = \"reactive-layout\" } = opts;\n\tconst g = new Graph(name);\n\n\t// Shared measurement cache: Map<font, Map<segment, width>>\n\tconst measureCache = new Map<string, Map<string, number>>();\n\n\t// --- State nodes ---\n\tconst textNode = state<string>(opts.text ?? \"\", { name: \"text\" });\n\tconst fontNode = state<string>(opts.font ?? \"16px sans-serif\", {\n\t\tname: \"font\",\n\t});\n\tconst lineHeightNode = state<number>(opts.lineHeight ?? 20, {\n\t\tname: \"line-height\",\n\t});\n\tconst maxWidthNode = state<number>(Math.max(0, opts.maxWidth ?? 800), {\n\t\tname: \"max-width\",\n\t});\n\n\t// --- Derived: segments (text + font → PreparedSegment[]) ---\n\tfunction graphemeWidthsEqual(a: number[] | null, b: number[] | null): boolean {\n\t\tif (a === null || b === null) return a === b;\n\t\tif (a.length !== b.length) return false;\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (a[i] !== b[i]!) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\tconst segmentsNode = derived<PreparedSegment[]>(\n\t\t[textNode, fontNode],\n\t\t([textVal, fontVal]) => {\n\t\t\tconst t0 = monotonicNs();\n\t\t\tconst measureStats: SegmentMeasureStats = { hits: 0, misses: 0 };\n\t\t\tconst result = analyzeAndMeasure(\n\t\t\t\ttextVal as string,\n\t\t\t\tfontVal as string,\n\t\t\t\tadapter,\n\t\t\t\tmeasureCache,\n\t\t\t\tmeasureStats,\n\t\t\t);\n\t\t\tconst elapsed = monotonicNs() - t0;\n\n\t\t\tconst lookups = measureStats.hits + measureStats.misses;\n\t\t\tconst hitRate = lookups === 0 ? 1 : measureStats.hits / lookups;\n\n\t\t\t// After parent `segments` auto-emits DATA/RESOLVED, deliver metrics\n\t\t\t// via phase-3 deferral so observers see the parent value first.\n\t\t\t// Phase-3 drains after all phase-2 work (parent settlements) completes\n\t\t\t// (parity with Python `defer_down` → `down_with_batch` phase 3).\n\t\t\tconst meta = segmentsNode.meta;\n\t\t\tif (meta) {\n\t\t\t\tconst hr = hitRate;\n\t\t\t\tconst len = result.length;\n\t\t\t\tconst el = elapsed;\n\t\t\t\tdownWithBatch((msgs) => meta[\"cache-hit-rate\"]?.down(msgs), [[DATA, hr]], 3);\n\t\t\t\tdownWithBatch((msgs) => meta[\"segment-count\"]?.down(msgs), [[DATA, len]], 3);\n\t\t\t\tdownWithBatch((msgs) => meta[\"layout-time-ns\"]?.down(msgs), [[DATA, el]], 3);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\t\t{\n\t\t\tname: \"segments\",\n\t\t\tmeta: {\n\t\t\t\t\"cache-hit-rate\": 0,\n\t\t\t\t\"segment-count\": 0,\n\t\t\t\t\"layout-time-ns\": 0,\n\t\t\t},\n\t\t\tonMessage(msg) {\n\t\t\t\tif (msg[0] === INVALIDATE || msg[0] === TEARDOWN) {\n\t\t\t\t\t// Local side-effect: clear closure-held cache that the node\n\t\t\t\t\t// cannot reach via its normal lifecycle. Return false so the\n\t\t\t\t\t// message still propagates via default dispatch (TEARDOWN\n\t\t\t\t\t// forwarding to meta/downstream, INVALIDATE clearing _cached).\n\t\t\t\t\tmeasureCache.clear();\n\t\t\t\t\tadapter.clearCache?.();\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst sa = a as PreparedSegment[] | null;\n\t\t\t\tconst sb = b as PreparedSegment[] | null;\n\t\t\t\tif (sa == null || sb == null) return sa === sb;\n\t\t\t\tif (sa.length !== sb.length) return false;\n\t\t\t\tfor (let i = 0; i < sa.length; i++) {\n\t\t\t\t\tconst pa = sa[i]!;\n\t\t\t\t\tconst pb = sb[i]!;\n\t\t\t\t\tif (\n\t\t\t\t\t\tpa.text !== pb.text ||\n\t\t\t\t\t\tpa.width !== pb.width ||\n\t\t\t\t\t\tpa.kind !== pb.kind ||\n\t\t\t\t\t\t!graphemeWidthsEqual(pa.graphemeWidths ?? null, pb.graphemeWidths ?? null)\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: line-breaks (segments + max-width + font → LineBreaksResult) ---\n\tconst lineBreaksNode = derived<LineBreaksResult>(\n\t\t[segmentsNode, maxWidthNode, fontNode],\n\t\t([segs, mw, font]) => {\n\t\t\treturn computeLineBreaks(\n\t\t\t\tsegs as PreparedSegment[],\n\t\t\t\tmw as number,\n\t\t\t\tadapter,\n\t\t\t\tfont as string,\n\t\t\t\tmeasureCache,\n\t\t\t);\n\t\t},\n\t\t{\n\t\t\tname: \"line-breaks\",\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst la = a as LineBreaksResult | null;\n\t\t\t\tconst lb = b as LineBreaksResult | null;\n\t\t\t\tif (la == null || lb == null) return la === lb;\n\t\t\t\tif (la.lineCount !== lb.lineCount) return false;\n\t\t\t\tfor (let i = 0; i < la.lines.length; i++) {\n\t\t\t\t\tconst lineA = la.lines[i]!;\n\t\t\t\t\tconst lineB = lb.lines[i]!;\n\t\t\t\t\tif (\n\t\t\t\t\t\tlineA.text !== lineB.text ||\n\t\t\t\t\t\tlineA.width !== lineB.width ||\n\t\t\t\t\t\tlineA.startSegment !== lineB.startSegment ||\n\t\t\t\t\t\tlineA.startGrapheme !== lineB.startGrapheme ||\n\t\t\t\t\t\tlineA.endSegment !== lineB.endSegment ||\n\t\t\t\t\t\tlineA.endGrapheme !== lineB.endGrapheme\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: height ---\n\tconst heightNode = derived<number>(\n\t\t[lineBreaksNode, lineHeightNode],\n\t\t([lb, lh]) => (lb as LineBreaksResult).lineCount * (lh as number),\n\t\t{ name: \"height\" },\n\t);\n\n\t// --- Derived: char-positions ---\n\tconst charPositionsNode = derived<CharPosition[]>(\n\t\t[lineBreaksNode, segmentsNode, lineHeightNode],\n\t\t([lb, segs, lh]) => {\n\t\t\treturn computeCharPositions(lb as LineBreaksResult, segs as PreparedSegment[], lh as number);\n\t\t},\n\t\t{\n\t\t\tname: \"char-positions\",\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst ca = a as CharPosition[] | null;\n\t\t\t\tconst cb = b as CharPosition[] | null;\n\t\t\t\tif (ca == null || cb == null) return ca === cb;\n\t\t\t\tif (ca.length !== cb.length) return false;\n\t\t\t\tfor (let i = 0; i < ca.length; i++) {\n\t\t\t\t\tif (ca[i]!.x !== cb[i]!.x || ca[i]!.y !== cb[i]!.y || ca[i]!.width !== cb[i]!.width)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Register in graph ---\n\tg.add(\"text\", textNode);\n\tg.add(\"font\", fontNode);\n\tg.add(\"line-height\", lineHeightNode);\n\tg.add(\"max-width\", maxWidthNode);\n\tg.add(\"segments\", segmentsNode);\n\tg.add(\"line-breaks\", lineBreaksNode);\n\tg.add(\"height\", heightNode);\n\tg.add(\"char-positions\", charPositionsNode);\n\n\t// --- Edges (for describe() visibility) ---\n\tg.connect(\"text\", \"segments\");\n\tg.connect(\"font\", \"segments\");\n\tg.connect(\"segments\", \"line-breaks\");\n\tg.connect(\"max-width\", \"line-breaks\");\n\tg.connect(\"font\", \"line-breaks\");\n\tg.connect(\"line-breaks\", \"height\");\n\tg.connect(\"line-height\", \"height\");\n\tg.connect(\"line-breaks\", \"char-positions\");\n\tg.connect(\"segments\", \"char-positions\");\n\tg.connect(\"line-height\", \"char-positions\");\n\n\treturn {\n\t\tgraph: g,\n\t\tsetText: (text: string) => g.set(\"text\", text),\n\t\tsetFont: (font: string) => g.set(\"font\", font),\n\t\tsetLineHeight: (lh: number) => g.set(\"line-height\", lh),\n\t\tsetMaxWidth: (mw: number) => g.set(\"max-width\", Math.max(0, mw)),\n\t\tsegments: segmentsNode,\n\t\tlineBreaks: lineBreaksNode,\n\t\theight: heightNode,\n\t\tcharPositions: charPositionsNode,\n\t};\n}\n","/**\n * Reactive multi-content block layout engine (roadmap §7.1 — mixed content).\n *\n * Extends the text-only `reactiveLayout` with support for image and SVG blocks.\n * Pure-arithmetic layout over measured child sizes — no DOM, no async.\n *\n * Graph shape:\n * ```\n * Graph(\"reactive-block-layout\")\n * ├── state(\"blocks\") — ContentBlock[] input\n * ├── state(\"max-width\") — container constraint\n * ├── state(\"gap\") — vertical gap between blocks (px)\n * ├── derived(\"measured-blocks\") — blocks → MeasuredBlock[] (per-type measurement)\n * ├── derived(\"block-flow\") — measured-blocks + max-width + gap → PositionedBlock[]\n * ├── derived(\"total-height\") — block-flow → total height\n * └── meta: { block-count, layout-time-ns }\n * ```\n */\nimport { downWithBatch } from \"../../core/batch.js\";\nimport { monotonicNs } from \"../../core/clock.js\";\nimport { DATA, INVALIDATE, TEARDOWN } from \"../../core/messages.js\";\nimport type { Node } from \"../../core/node.js\";\nimport { derived, state } from \"../../core/sugar.js\";\nimport { Graph } from \"../../graph/graph.js\";\nimport {\n\tanalyzeAndMeasure,\n\ttype CharPosition,\n\tcomputeCharPositions,\n\tcomputeLineBreaks,\n\ttype LineBreaksResult,\n\ttype MeasurementAdapter,\n\ttype PreparedSegment,\n} from \"./reactive-layout.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Pluggable measurement backend for SVG content. */\nexport interface SvgMeasurer {\n\tmeasureSvg(content: string): { width: number; height: number };\n}\n\n/** Pluggable measurement backend for image content. */\nexport interface ImageMeasurer {\n\tmeasureImage(src: string): { width: number; height: number };\n}\n\n/** Adapters map for `reactiveBlockLayout`. */\nexport type BlockAdapters = {\n\t/** Text measurement adapter (required — delegates to `reactiveLayout` internals). */\n\ttext: MeasurementAdapter;\n\t/** SVG measurement (optional — required only if SVG blocks are present). */\n\tsvg?: SvgMeasurer;\n\t/** Image measurement (optional — required only if image blocks without explicit dimensions are present). */\n\timage?: ImageMeasurer;\n};\n\n/** A content block — text, image, or SVG. */\nexport type ContentBlock =\n\t| {\n\t\t\ttype: \"text\";\n\t\t\ttext: string;\n\t\t\tfont?: string;\n\t\t\tlineHeight?: number;\n\t }\n\t| {\n\t\t\ttype: \"image\";\n\t\t\tsrc: string;\n\t\t\t/** Natural width in px. Required if no ImageMeasurer adapter is provided. */\n\t\t\tnaturalWidth?: number;\n\t\t\t/** Natural height in px. Required if no ImageMeasurer adapter is provided. */\n\t\t\tnaturalHeight?: number;\n\t }\n\t| {\n\t\t\ttype: \"svg\";\n\t\t\tcontent: string;\n\t\t\t/** Explicit viewBox dimensions. Required if no SvgMeasurer adapter is provided. */\n\t\t\tviewBox?: { width: number; height: number };\n\t };\n\n/**\n * A block after measurement — knows its natural dimensions.\n *\n * **Equality note:** The reactive `measured-blocks` node uses dimension-only equality\n * (`type`, `width`, `height`, `index`). Inner text layout data (`textSegments`,\n * `textLineBreaks`, `textCharPositions`) is NOT compared for change detection.\n * If you need text-level reactivity, use `reactiveLayout()` directly per text block.\n */\nexport type MeasuredBlock = {\n\tindex: number;\n\ttype: \"text\" | \"image\" | \"svg\";\n\twidth: number;\n\theight: number;\n\t/** For text blocks: the inner layout results. */\n\ttextSegments?: PreparedSegment[];\n\ttextLineBreaks?: LineBreaksResult;\n\ttextCharPositions?: CharPosition[];\n};\n\n/** A block after flow — positioned in the container. */\nexport type PositionedBlock = MeasuredBlock & {\n\tx: number;\n\ty: number;\n};\n\n/** Options for `reactiveBlockLayout`. */\nexport type ReactiveBlockLayoutOptions = {\n\tadapters: BlockAdapters;\n\tname?: string;\n\tblocks?: ContentBlock[];\n\t/** Container max width in px (clamped to ≥ 0 on init and `setMaxWidth`). */\n\tmaxWidth?: number;\n\t/** Vertical gap between blocks in px (default 0). */\n\tgap?: number;\n\t/** Default font for text blocks that don't specify one. */\n\tdefaultFont?: string;\n\t/** Default line height for text blocks that don't specify one. */\n\tdefaultLineHeight?: number;\n};\n\n/** Result bundle from `reactiveBlockLayout`. */\nexport type ReactiveBlockLayoutBundle = {\n\tgraph: Graph;\n\tsetBlocks: (blocks: ContentBlock[]) => void;\n\tsetMaxWidth: (maxWidth: number) => void;\n\tsetGap: (gap: number) => void;\n\tmeasuredBlocks: Node<MeasuredBlock[]>;\n\tblockFlow: Node<PositionedBlock[]>;\n\ttotalHeight: Node<number>;\n};\n\n// ---------------------------------------------------------------------------\n// Block measurement (pure functions)\n// ---------------------------------------------------------------------------\n\n/**\n * Measure a single content block, returning natural (unconstrained) dimensions.\n * Text blocks use the text layout pipeline; image/SVG use adapters or explicit dims.\n */\nexport function measureBlock(\n\tblock: ContentBlock,\n\tmaxWidth: number,\n\tadapters: BlockAdapters,\n\tmeasureCache: Map<string, Map<string, number>>,\n\tdefaultFont: string,\n\tdefaultLineHeight: number,\n\tindex: number,\n): MeasuredBlock {\n\tswitch (block.type) {\n\t\tcase \"text\": {\n\t\t\tconst font = block.font ?? defaultFont;\n\t\t\tconst lineHeight = block.lineHeight ?? defaultLineHeight;\n\t\t\tconst segments = analyzeAndMeasure(block.text, font, adapters.text, measureCache);\n\t\t\tconst lineBreaks = computeLineBreaks(segments, maxWidth, adapters.text, font, measureCache);\n\t\t\tconst charPositions = computeCharPositions(lineBreaks, segments, lineHeight);\n\t\t\tconst height = lineBreaks.lineCount * lineHeight;\n\t\t\t// Width is the max line width (clamped to maxWidth)\n\t\t\tlet width = 0;\n\t\t\tfor (const line of lineBreaks.lines) {\n\t\t\t\tif (line.width > width) width = line.width;\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tindex,\n\t\t\t\ttype: \"text\",\n\t\t\t\twidth: Math.min(width, maxWidth),\n\t\t\t\theight,\n\t\t\t\ttextSegments: segments,\n\t\t\t\ttextLineBreaks: lineBreaks,\n\t\t\t\ttextCharPositions: charPositions,\n\t\t\t};\n\t\t}\n\t\tcase \"image\": {\n\t\t\tlet w: number;\n\t\t\tlet h: number;\n\t\t\tif (block.naturalWidth != null && block.naturalHeight != null) {\n\t\t\t\tw = block.naturalWidth;\n\t\t\t\th = block.naturalHeight;\n\t\t\t} else if (adapters.image) {\n\t\t\t\tconst dims = adapters.image.measureImage(block.src);\n\t\t\t\tw = dims.width;\n\t\t\t\th = dims.height;\n\t\t\t} else {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Image block at index ${index} has no naturalWidth/naturalHeight and no ImageMeasurer adapter`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Scale proportionally to fit maxWidth\n\t\t\tif (w > maxWidth) {\n\t\t\t\th = (h * maxWidth) / w;\n\t\t\t\tw = maxWidth;\n\t\t\t}\n\t\t\treturn { index, type: \"image\", width: w, height: h };\n\t\t}\n\t\tcase \"svg\": {\n\t\t\tlet w: number;\n\t\t\tlet h: number;\n\t\t\tif (block.viewBox) {\n\t\t\t\tw = block.viewBox.width;\n\t\t\t\th = block.viewBox.height;\n\t\t\t} else if (adapters.svg) {\n\t\t\t\tconst dims = adapters.svg.measureSvg(block.content);\n\t\t\t\tw = dims.width;\n\t\t\t\th = dims.height;\n\t\t\t} else {\n\t\t\t\tthrow new Error(`SVG block at index ${index} has no viewBox and no SvgMeasurer adapter`);\n\t\t\t}\n\t\t\t// Scale proportionally to fit maxWidth\n\t\t\tif (w > maxWidth) {\n\t\t\t\th = (h * maxWidth) / w;\n\t\t\t\tw = maxWidth;\n\t\t\t}\n\t\t\treturn { index, type: \"svg\", width: w, height: h };\n\t\t}\n\t}\n}\n\n/**\n * Measure all blocks in a content array.\n */\nexport function measureBlocks(\n\tblocks: ContentBlock[],\n\tmaxWidth: number,\n\tadapters: BlockAdapters,\n\tmeasureCache: Map<string, Map<string, number>>,\n\tdefaultFont: string,\n\tdefaultLineHeight: number,\n): MeasuredBlock[] {\n\treturn blocks.map((block, i) =>\n\t\tmeasureBlock(block, maxWidth, adapters, measureCache, defaultFont, defaultLineHeight, i),\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Block flow (pure function)\n// ---------------------------------------------------------------------------\n\n/**\n * Vertical stacking flow: blocks are placed top-to-bottom, left-aligned,\n * separated by `gap` pixels. Pure arithmetic over measured sizes.\n */\nexport function computeBlockFlow(measured: MeasuredBlock[], gap: number): PositionedBlock[] {\n\tconst result: PositionedBlock[] = [];\n\tlet y = 0;\n\tfor (let i = 0; i < measured.length; i++) {\n\t\tconst m = measured[i]!;\n\t\tresult.push({ ...m, x: 0, y });\n\t\ty += m.height + (i < measured.length - 1 ? gap : 0);\n\t}\n\treturn result;\n}\n\n/**\n * Compute total height from positioned blocks.\n */\nexport function computeTotalHeight(flow: PositionedBlock[]): number {\n\tif (flow.length === 0) return 0;\n\tconst last = flow[flow.length - 1]!;\n\treturn last.y + last.height;\n}\n\n// ---------------------------------------------------------------------------\n// Reactive graph factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create a reactive block layout graph for mixed content (text + image + SVG).\n *\n * ```\n * Graph(\"reactive-block-layout\")\n * ├── state(\"blocks\") — ContentBlock[] input\n * ├── state(\"max-width\") — container constraint\n * ├── state(\"gap\") — vertical gap (px)\n * ├── derived(\"measured-blocks\") — blocks + max-width → MeasuredBlock[]\n * ├── derived(\"block-flow\") — measured-blocks + gap → PositionedBlock[]\n * ├── derived(\"total-height\") — block-flow → number\n * └── meta: { block-count, layout-time-ns }\n * ```\n */\nexport function reactiveBlockLayout(opts: ReactiveBlockLayoutOptions): ReactiveBlockLayoutBundle {\n\tconst {\n\t\tadapters,\n\t\tname = \"reactive-block-layout\",\n\t\tdefaultFont = \"16px sans-serif\",\n\t\tdefaultLineHeight = 20,\n\t} = opts;\n\tconst g = new Graph(name);\n\n\t// Shared text measurement cache (same structure as reactiveLayout)\n\tconst measureCache = new Map<string, Map<string, number>>();\n\n\t// --- State nodes ---\n\tconst blocksNode = state<ContentBlock[]>(opts.blocks ?? [], { name: \"blocks\" });\n\tconst maxWidthNode = state<number>(Math.max(0, opts.maxWidth ?? 800), { name: \"max-width\" });\n\tconst gapNode = state<number>(opts.gap ?? 0, { name: \"gap\" });\n\n\t// --- Derived: measured-blocks ---\n\tconst measuredBlocksNode = derived<MeasuredBlock[]>(\n\t\t[blocksNode, maxWidthNode],\n\t\t([blocksVal, mwVal]) => {\n\t\t\tconst t0 = monotonicNs();\n\t\t\tconst result = measureBlocks(\n\t\t\t\tblocksVal as ContentBlock[],\n\t\t\t\tmwVal as number,\n\t\t\t\tadapters,\n\t\t\t\tmeasureCache,\n\t\t\t\tdefaultFont,\n\t\t\t\tdefaultLineHeight,\n\t\t\t);\n\t\t\tconst elapsed = monotonicNs() - t0;\n\n\t\t\t// Phase-3 meta deferral (parity with reactiveLayout)\n\t\t\tconst meta = measuredBlocksNode.meta;\n\t\t\tif (meta) {\n\t\t\t\tdownWithBatch((msgs) => meta[\"block-count\"]?.down(msgs), [[DATA, result.length]], 3);\n\t\t\t\tdownWithBatch((msgs) => meta[\"layout-time-ns\"]?.down(msgs), [[DATA, elapsed]], 3);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\t\t{\n\t\t\tname: \"measured-blocks\",\n\t\t\tmeta: { \"block-count\": 0, \"layout-time-ns\": 0 },\n\t\t\tonMessage(msg, _depIndex, _actions) {\n\t\t\t\tif (msg[0] === INVALIDATE || msg[0] === TEARDOWN) {\n\t\t\t\t\t// Local side-effect only; return false so default dispatch\n\t\t\t\t\t// still propagates the message (TEARDOWN → meta/downstream).\n\t\t\t\t\tmeasureCache.clear();\n\t\t\t\t\tadapters.text.clearCache?.();\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t},\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst ma = a as MeasuredBlock[] | null;\n\t\t\t\tconst mb = b as MeasuredBlock[] | null;\n\t\t\t\tif (ma == null || mb == null) return ma === mb;\n\t\t\t\tif (ma.length !== mb.length) return false;\n\t\t\t\tfor (let i = 0; i < ma.length; i++) {\n\t\t\t\t\tconst ba = ma[i]!;\n\t\t\t\t\tconst bb = mb[i]!;\n\t\t\t\t\tif (\n\t\t\t\t\t\tba.type !== bb.type ||\n\t\t\t\t\t\tba.width !== bb.width ||\n\t\t\t\t\t\tba.height !== bb.height ||\n\t\t\t\t\t\tba.index !== bb.index\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: block-flow ---\n\tconst blockFlowNode = derived<PositionedBlock[]>(\n\t\t[measuredBlocksNode, gapNode],\n\t\t([measured, gapVal]) => {\n\t\t\treturn computeBlockFlow(measured as MeasuredBlock[], gapVal as number);\n\t\t},\n\t\t{\n\t\t\tname: \"block-flow\",\n\t\t\tequals: (a, b) => {\n\t\t\t\tconst fa = a as PositionedBlock[] | null;\n\t\t\t\tconst fb = b as PositionedBlock[] | null;\n\t\t\t\tif (fa == null || fb == null) return fa === fb;\n\t\t\t\tif (fa.length !== fb.length) return false;\n\t\t\t\tfor (let i = 0; i < fa.length; i++) {\n\t\t\t\t\tconst pa = fa[i]!;\n\t\t\t\t\tconst pb = fb[i]!;\n\t\t\t\t\tif (pa.x !== pb.x || pa.y !== pb.y || pa.width !== pb.width || pa.height !== pb.height)\n\t\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t},\n\t\t},\n\t);\n\n\t// --- Derived: total-height ---\n\tconst totalHeightNode = derived<number>(\n\t\t[blockFlowNode],\n\t\t([flow]) => computeTotalHeight(flow as PositionedBlock[]),\n\t\t{ name: \"total-height\" },\n\t);\n\n\t// --- Register in graph ---\n\tg.add(\"blocks\", blocksNode);\n\tg.add(\"max-width\", maxWidthNode);\n\tg.add(\"gap\", gapNode);\n\tg.add(\"measured-blocks\", measuredBlocksNode);\n\tg.add(\"block-flow\", blockFlowNode);\n\tg.add(\"total-height\", totalHeightNode);\n\n\t// --- Edges (for describe() visibility) ---\n\tg.connect(\"blocks\", \"measured-blocks\");\n\tg.connect(\"max-width\", \"measured-blocks\");\n\tg.connect(\"measured-blocks\", \"block-flow\");\n\tg.connect(\"gap\", \"block-flow\");\n\tg.connect(\"block-flow\", \"total-height\");\n\n\treturn {\n\t\tgraph: g,\n\t\tsetBlocks: (blocks: ContentBlock[]) => g.set(\"blocks\", blocks),\n\t\tsetMaxWidth: (mw: number) => g.set(\"max-width\", Math.max(0, mw)),\n\t\tsetGap: (gap: number) => g.set(\"gap\", gap),\n\t\tmeasuredBlocks: measuredBlocksNode,\n\t\tblockFlow: blockFlowNode,\n\t\ttotalHeight: totalHeightNode,\n\t};\n}\n"],"mappings":"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","names":["w"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/extra/observable.ts","../src/extra/cron.ts","../src/extra/sources.ts","../src/extra/backpressure.ts","../src/extra/reactive-log.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// RxJS bridge — reactive interop between GraphReFly nodes and RxJS Observables.\n// ---------------------------------------------------------------------------\n// Usage:\n// import { toObservable } from '@graphrefly/graphrefly-ts/extra';\n// const values$ = toObservable(myNode); // Observable<T>\n// const msgs$ = toObservable(myNode, { raw: true }); // Observable<Messages>\n// ---------------------------------------------------------------------------\n\nimport { Observable } from \"rxjs\";\nimport { COMPLETE, DATA, ERROR, type Messages } from \"../core/messages.js\";\nimport type { Node } from \"../core/node.js\";\n\n/** Options for {@link toObservable}. */\nexport type ToObservableOptions = {\n\t/**\n\t * When `true`, emit raw `Messages` batches instead of extracted `DATA` values.\n\t * Terminal batches are still emitted as the final `next()` before the\n\t * Observable signal (error/complete).\n\t */\n\traw?: boolean;\n};\n\n/**\n * Bridge a `Node<T>` to an RxJS `Observable`.\n *\n * Default mode emits the node's value on each `DATA` message. Maps `ERROR` to\n * `subscriber.error()` and `COMPLETE` to `subscriber.complete()`.\n * Protocol-internal signals (DIRTY, RESOLVED, PAUSE, etc.) are skipped.\n *\n * With `{ raw: true }`, emits full `[[Type, Data?], ...]` message batches.\n * The Observable terminates on ERROR or COMPLETE (the terminal batch is still\n * emitted as the final `next()` before the Observable signal).\n *\n * For graph-level observation, use `toObservable(graph.resolve(path))` or\n * subscribe to `graph.observe()` directly.\n *\n * Unsubscribing the Observable unsubscribes the node.\n */\nexport function toObservable<T>(\n\tnode: Node<T>,\n\toptions?: ToObservableOptions & { raw?: false },\n): Observable<T>;\nexport function toObservable<T>(\n\tnode: Node<T>,\n\toptions: ToObservableOptions & { raw: true },\n): Observable<Messages>;\nexport function toObservable<T>(\n\tnode: Node<T>,\n\toptions?: ToObservableOptions,\n): Observable<T | Messages> {\n\tif (options?.raw) {\n\t\treturn new Observable<Messages>((subscriber) => {\n\t\t\tconst unsub = node.subscribe((msgs) => {\n\t\t\t\tif (subscriber.closed) return;\n\t\t\t\tsubscriber.next(msgs);\n\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\tif (m[0] === ERROR) {\n\t\t\t\t\t\tsubscriber.error(m[1]);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (m[0] === COMPLETE) {\n\t\t\t\t\t\tsubscriber.complete();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn unsub;\n\t\t});\n\t}\n\n\treturn new Observable<T>((subscriber) => {\n\t\tconst unsub = node.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (subscriber.closed) return;\n\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\tsubscriber.next(m[1] as T);\n\t\t\t\t} else if (m[0] === ERROR) {\n\t\t\t\t\tsubscriber.error(m[1]);\n\t\t\t\t\treturn;\n\t\t\t\t} else if (m[0] === COMPLETE) {\n\t\t\t\t\tsubscriber.complete();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\treturn unsub;\n\t});\n}\n","/**\n * Minimal 5-field cron parser and matcher (minute hour day-of-month month day-of-week).\n * Ported from callbag-recharge `extra/cron.ts` for `fromCron` (roadmap §2.3).\n */\nexport interface CronSchedule {\n\tminutes: Set<number>;\n\thours: Set<number>;\n\tdaysOfMonth: Set<number>;\n\tmonths: Set<number>;\n\tdaysOfWeek: Set<number>;\n}\n\nfunction parseField(field: string, min: number, max: number): Set<number> {\n\tconst result = new Set<number>();\n\tfor (const part of field.split(\",\")) {\n\t\tconst [range, stepStr] = part.split(\"/\");\n\t\tconst step = stepStr ? Number.parseInt(stepStr, 10) : 1;\n\t\tif (Number.isNaN(step) || step < 1) throw new Error(`Invalid cron step: ${part}`);\n\t\tlet start: number;\n\t\tlet end: number;\n\t\tif (range === \"*\") {\n\t\t\tstart = min;\n\t\t\tend = max;\n\t\t} else if (range.includes(\"-\")) {\n\t\t\tconst [a, b] = range.split(\"-\");\n\t\t\tstart = Number.parseInt(a, 10);\n\t\t\tend = Number.parseInt(b, 10);\n\t\t} else {\n\t\t\tstart = Number.parseInt(range, 10);\n\t\t\tend = start;\n\t\t}\n\t\tif (Number.isNaN(start) || Number.isNaN(end)) throw new Error(`Invalid cron field: ${field}`);\n\t\tif (start < min || end > max)\n\t\t\tthrow new Error(`Cron field out of range: ${field} (${min}-${max})`);\n\t\tif (start > end) throw new Error(`Invalid cron range: ${start}-${end} in ${field}`);\n\t\tfor (let i = start; i <= end; i += step) result.add(i);\n\t}\n\treturn result;\n}\n\n/**\n * Parses a standard 5-field cron expression into a {@link CronSchedule}.\n *\n * Supports `*`, ranges (`1-5`), steps (`*\\/5`, `0-30/10`), and comma-separated\n * lists. Fields are: minute (0–59), hour (0–23), day-of-month (1–31),\n * month (1–12), day-of-week (0–6, Sunday = 0).\n *\n * @param expr - Five-field whitespace-separated cron string (e.g. `\"0 9 * * 1-5\"`).\n * @returns Parsed {@link CronSchedule} with one `Set<number>` per field.\n * @throws Error when the expression does not have exactly 5 fields, contains\n * out-of-range values, or uses an invalid step.\n *\n * @example\n * ```ts\n * import { parseCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"0 9 * * 1-5\"); // weekdays at 09:00\n * sched.hours; // Set { 9 }\n * sched.daysOfWeek; // Set { 1, 2, 3, 4, 5 }\n * ```\n */\nexport function parseCron(expr: string): CronSchedule {\n\tconst parts = expr.trim().split(/\\s+/);\n\tif (parts.length !== 5) throw new Error(`Invalid cron: expected 5 fields, got ${parts.length}`);\n\treturn {\n\t\tminutes: parseField(parts[0], 0, 59),\n\t\thours: parseField(parts[1], 0, 23),\n\t\tdaysOfMonth: parseField(parts[2], 1, 31),\n\t\tmonths: parseField(parts[3], 1, 12),\n\t\tdaysOfWeek: parseField(parts[4], 0, 6),\n\t};\n}\n\n/**\n * Returns `true` if `date` satisfies every field of `schedule`.\n *\n * @param schedule - Parsed schedule from {@link parseCron}.\n * @param date - Moment to test (local time via `getMinutes`, `getHours`, etc.).\n * @returns `true` when all five cron fields match the given date.\n *\n * @example\n * ```ts\n * import { parseCron, matchesCron } from \"@graphrefly/graphrefly-ts\";\n *\n * const sched = parseCron(\"30 8 * * 1\"); // Mondays at 08:30\n * const monday = new Date(\"2026-03-30T08:30:00\"); // a Monday\n * matchesCron(sched, monday); // true\n * ```\n */\nexport function matchesCron(schedule: CronSchedule, date: Date): boolean {\n\treturn (\n\t\tschedule.minutes.has(date.getMinutes()) &&\n\t\tschedule.hours.has(date.getHours()) &&\n\t\tschedule.daysOfMonth.has(date.getDate()) &&\n\t\tschedule.months.has(date.getMonth() + 1) &&\n\t\tschedule.daysOfWeek.has(date.getDay())\n\t);\n}\n","/**\n * Core reactive sources, sinks, and utilities (roadmap §2.3).\n *\n * Each API returns a {@link Node} built with {@link node}, {@link producer},\n * {@link derived}, or {@link effect} — no second protocol.\n *\n * Protocol/system/ingest adapters (fromHTTP, fromWebSocket, fromKafka, etc.)\n * live in {@link ./adapters.ts}.\n */\n\nimport { existsSync, watch } from \"node:fs\";\nimport { resolve as resolvePath } from \"node:path\";\nimport { wallClockNs } from \"../core/clock.js\";\nimport { COMPLETE, DATA, DIRTY, ERROR, type Message } from \"../core/messages.js\";\nimport { type Node, type NodeOptions, type NodeSink, node } from \"../core/node.js\";\nimport { producer, state } from \"../core/sugar.js\";\nimport { type CronSchedule, matchesCron, parseCron } from \"./cron.js\";\n\ntype ExtraOpts = Omit<NodeOptions, \"describeKind\">;\n\nfunction sourceOpts(opts?: ExtraOpts): NodeOptions {\n\treturn { describeKind: \"producer\", ...opts };\n}\n\n/** @internal kept for toArray which is an operator, not a producer */\nfunction operatorOpts(opts?: ExtraOpts): NodeOptions {\n\treturn { describeKind: \"operator\", ...opts };\n}\n\n/** Options for {@link fromTimer} / {@link fromPromise} / {@link fromAsyncIter}. */\nexport type AsyncSourceOpts = ExtraOpts & { signal?: AbortSignal };\n\n/**\n * Values accepted by {@link fromAny}.\n *\n * @category extra\n */\nexport type NodeInput<T> = Node<T> | PromiseLike<T> | AsyncIterable<T> | Iterable<T> | T;\n\n/** Options for {@link fromCron}. */\nexport type FromCronOptions = ExtraOpts & {\n\t/** Polling interval in ms. Default `60_000`. */\n\ttickMs?: number;\n\t/** Output format: `\"timestamp_ns\"` (default) emits wall-clock nanoseconds; `\"date\"` emits a `Date` object. */\n\toutput?: \"timestamp_ns\" | \"date\";\n};\n\n/** DOM-style event target (browser or `node:events`). */\nexport type EventTargetLike = {\n\taddEventListener(\n\t\ttype: string,\n\t\tlistener: (ev: unknown) => void,\n\t\toptions?: boolean | { capture?: boolean; passive?: boolean; once?: boolean },\n\t): void;\n\tremoveEventListener(\n\t\ttype: string,\n\t\tlistener: (ev: unknown) => void,\n\t\toptions?: boolean | { capture?: boolean; passive?: boolean; once?: boolean },\n\t): void;\n};\n\nexport type FSEventType = \"change\" | \"rename\" | \"create\" | \"delete\";\nexport type FSEvent = {\n\ttype: FSEventType;\n\tpath: string;\n\troot: string;\n\trelative_path: string;\n\tsrc_path?: string;\n\tdest_path?: string;\n\ttimestamp_ns: number;\n};\n\nexport type FromFSWatchOptions = ExtraOpts & {\n\trecursive?: boolean;\n\tdebounce?: number;\n\tinclude?: string[];\n\texclude?: string[];\n};\n\n/** @internal Shared with adapters.ts for glob matching in fromFSWatch / fromGitHook. */\nexport function escapeRegexChar(ch: string): string {\n\treturn /[\\\\^$+?.()|[\\]{}]/.test(ch) ? `\\\\${ch}` : ch;\n}\n\n/** @internal */\nexport function globToRegExp(glob: string): RegExp {\n\tlet out = \"^\";\n\tfor (let i = 0; i < glob.length; i += 1) {\n\t\tconst ch = glob[i];\n\t\tif (ch === \"*\") {\n\t\t\tconst next = glob[i + 1];\n\t\t\tif (next === \"*\") {\n\t\t\t\tout += \".*\";\n\t\t\t\ti += 1;\n\t\t\t} else {\n\t\t\t\tout += \"[^/]*\";\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tout += escapeRegexChar(ch);\n\t}\n\tout += \"$\";\n\treturn new RegExp(out);\n}\n\n/** @internal */\nexport function matchesAnyPattern(path: string, patterns: RegExp[]): boolean {\n\tfor (const pattern of patterns) {\n\t\tif (pattern.test(path)) return true;\n\t}\n\treturn false;\n}\n\nfunction wrapSubscribeHook<T>(inner: Node<T>, before: (sink: NodeSink) => void): Node<T> {\n\tconst wrapper = node<T>([inner], ([val]) => val as T, {\n\t\tdescribeKind: \"operator\",\n\t\tinitial: inner.get(),\n\t});\n\tconst origSubscribe = wrapper.subscribe.bind(wrapper);\n\t(wrapper as { subscribe: typeof wrapper.subscribe }).subscribe = (sink, hints) => {\n\t\tbefore(sink);\n\t\treturn origSubscribe(sink, hints);\n\t};\n\treturn wrapper;\n}\n\n/**\n * Builds a timer-driven source: one-shot (first tick then `COMPLETE`) or periodic (`0`, `1`, `2`, …).\n *\n * @param ms - Milliseconds before the first emission.\n * @param opts - Producer options plus optional `period` for repeating ticks and optional `signal` (`AbortSignal`) to cancel with `ERROR`.\n * @returns `Node<number>` — tick counter from `0`; teardown clears timers.\n *\n * @example\n * ```ts\n * import { fromTimer } from \"@graphrefly/graphrefly-ts\";\n *\n * fromTimer(250, { period: 1_000 });\n * ```\n *\n * @category extra\n */\nexport function fromTimer(ms: number, opts?: AsyncSourceOpts & { period?: number }): Node<number> {\n\tconst { signal, period, ...rest } = opts ?? {};\n\treturn producer<number>((_d, a) => {\n\t\tlet done = false;\n\t\tlet count = 0;\n\t\tlet t: ReturnType<typeof setTimeout> | undefined;\n\t\tlet iv: ReturnType<typeof setInterval> | undefined;\n\t\tconst cleanup = () => {\n\t\t\tdone = true;\n\t\t\tif (t !== undefined) clearTimeout(t);\n\t\t\tif (iv !== undefined) clearInterval(iv);\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t\tconst finish = () => {\n\t\t\tif (done) return;\n\t\t\ta.emit(count++);\n\t\t\tif (period != null) {\n\t\t\t\tiv = setInterval(() => {\n\t\t\t\t\tif (done) return;\n\t\t\t\t\ta.emit(count++);\n\t\t\t\t}, period);\n\t\t\t} else {\n\t\t\t\tdone = true;\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\tqueueMicrotask(() => a.down([[COMPLETE]]));\n\t\t\t}\n\t\t};\n\t\tconst onAbort = () => {\n\t\t\tif (done) return;\n\t\t\tcleanup();\n\t\t\ta.down([[ERROR, signal!.reason]]);\n\t\t};\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t\treturn;\n\t\t}\n\t\tt = setTimeout(finish, ms);\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\treturn cleanup;\n\t}, sourceOpts(rest));\n}\n\n/**\n * Polls on an interval; when the current minute matches a 5-field cron expression, emits once (see {@link parseCron}).\n *\n * @param expr - Cron string (`min hour dom month dow`).\n * @param opts - Producer options plus `tickMs` (default `60_000`) and `output` (`timestamp_ns` default, or `date` for `Date` values).\n * @returns `Node<number>` (nanosecond timestamp) or `Node<Date>` when `output: \"date\"`.\n *\n * @example\n * ```ts\n * import { fromCron } from \"@graphrefly/graphrefly-ts\";\n *\n * fromCron(\"0 9 * * 1\");\n * ```\n *\n * @category extra\n */\nexport function fromCron(expr: string, opts?: FromCronOptions & { output: \"date\" }): Node<Date>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number>;\nexport function fromCron(expr: string, opts?: FromCronOptions): Node<number | Date> {\n\tconst schedule: CronSchedule = parseCron(expr);\n\tconst { tickMs: tickOpt, output, ...rest } = opts ?? {};\n\tconst tickMs = tickOpt ?? 60_000;\n\tconst emitDate = output === \"date\";\n\treturn producer<number | Date>(\n\t\t(_d, a) => {\n\t\t\tlet lastFiredKey = -1;\n\t\t\tconst check = () => {\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst key =\n\t\t\t\t\tnow.getFullYear() * 100_000_000 +\n\t\t\t\t\t(now.getMonth() + 1) * 1_000_000 +\n\t\t\t\t\tnow.getDate() * 10_000 +\n\t\t\t\t\tnow.getHours() * 100 +\n\t\t\t\t\tnow.getMinutes();\n\t\t\t\tif (key !== lastFiredKey && matchesCron(schedule, now)) {\n\t\t\t\t\tlastFiredKey = key;\n\t\t\t\t\ta.emit(emitDate ? now : wallClockNs());\n\t\t\t\t}\n\t\t\t};\n\t\t\tcheck();\n\t\t\tconst id = setInterval(check, tickMs);\n\t\t\treturn () => clearInterval(id);\n\t\t},\n\t\t{ ...sourceOpts(rest), name: rest.name ?? `cron:${expr}` },\n\t);\n}\n\n/**\n * Wraps a DOM-style `addEventListener` target; each event becomes a `DATA` emission.\n *\n * @param target - Object with `addEventListener` / `removeEventListener`.\n * @param type - Event name (e.g. `\"click\"`).\n * @param opts - Producer options plus listener options (`capture`, `passive`, `once`).\n * @returns `Node<T>` — event payloads; teardown removes the listener.\n *\n * @example\n * ```ts\n * import { fromEvent } from \"@graphrefly/graphrefly-ts\";\n *\n * fromEvent(document.body, \"click\");\n * ```\n *\n * @category extra\n */\nexport function fromEvent<T = unknown>(\n\ttarget: EventTargetLike,\n\ttype: string,\n\topts?: ExtraOpts & { capture?: boolean; passive?: boolean; once?: boolean },\n): Node<T> {\n\tconst { capture, passive, once, ...rest } = opts ?? {};\n\treturn producer<T>((_d, a) => {\n\t\tconst handler = (e: unknown) => {\n\t\t\ta.emit(e as T);\n\t\t};\n\t\tconst options = { capture, passive, once };\n\t\ttarget.addEventListener(type, handler, options);\n\t\treturn () => target.removeEventListener(type, handler, options);\n\t}, sourceOpts(rest));\n}\n\n/**\n * Watches filesystem paths and emits debounced change events.\n *\n * Uses `fs.watch` only (no polling fallback). Teardown closes all watchers.\n *\n * @category extra\n */\nexport function fromFSWatch(paths: string | string[], opts?: FromFSWatchOptions): Node<FSEvent> {\n\tconst list = Array.isArray(paths) ? paths : [paths];\n\tif (list.length === 0) {\n\t\tthrow new RangeError(\"fromFSWatch expects at least one path\");\n\t}\n\tconst { recursive = true, debounce = 100, include, exclude, ...rest } = opts ?? {};\n\tconst includePatterns = include?.map(globToRegExp) ?? [];\n\tconst excludePatterns = (exclude ?? [\"**/node_modules/**\", \"**/.git/**\", \"**/dist/**\"]).map(\n\t\tglobToRegExp,\n\t);\n\treturn producer<FSEvent>((_d, a) => {\n\t\tconst pending = new Map<string, FSEvent>();\n\t\tconst watchers: ReturnType<typeof watch>[] = [];\n\t\tlet stopped = false;\n\t\tlet terminalEmitted = false;\n\t\tlet generation = 0;\n\t\tconst closeWatchers = () => {\n\t\t\tfor (const watcher of watchers.splice(0)) watcher.close();\n\t\t};\n\t\tconst emitError = (err: unknown) => {\n\t\t\tif (terminalEmitted) return;\n\t\t\tterminalEmitted = true;\n\t\t\tstopped = true;\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t\tpending.clear();\n\t\t\tcloseWatchers();\n\t\t\ta.down([[ERROR, err]]);\n\t\t};\n\t\tlet timer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst flush = (token: number) => {\n\t\t\ttimer = undefined;\n\t\t\tif (stopped || terminalEmitted) return;\n\t\t\tif (pending.size === 0) return;\n\t\t\tconst batchMessages: Message[] = [];\n\t\t\tfor (const evt of pending.values()) batchMessages.push([DATA, evt]);\n\t\t\tpending.clear();\n\t\t\tif (stopped || terminalEmitted || token !== generation) return;\n\t\t\ta.down(batchMessages);\n\t\t};\n\t\ttry {\n\t\t\tfor (const basePath of list) {\n\t\t\t\tconst watcher = watch(\n\t\t\t\t\tbasePath,\n\t\t\t\t\t{ recursive },\n\t\t\t\t\t(eventType: \"rename\" | \"change\", fileName: string | Buffer | null) => {\n\t\t\t\t\t\tif (stopped || terminalEmitted) return;\n\t\t\t\t\t\tif (fileName == null) return;\n\t\t\t\t\t\tconst rel = String(fileName).replaceAll(\"\\\\\", \"/\");\n\t\t\t\t\t\tconst abs = resolvePath(basePath, String(fileName));\n\t\t\t\t\t\tconst normalized = abs.replaceAll(\"\\\\\", \"/\");\n\t\t\t\t\t\tconst root = resolvePath(basePath).replaceAll(\"\\\\\", \"/\");\n\t\t\t\t\t\tconst relForMatch = rel.startsWith(\"./\") ? rel.slice(2) : rel;\n\t\t\t\t\t\tconst included =\n\t\t\t\t\t\t\tincludePatterns.length === 0 ||\n\t\t\t\t\t\t\tmatchesAnyPattern(normalized, includePatterns) ||\n\t\t\t\t\t\t\tmatchesAnyPattern(relForMatch, includePatterns);\n\t\t\t\t\t\tif (!included) return;\n\t\t\t\t\t\tconst excluded =\n\t\t\t\t\t\t\tmatchesAnyPattern(normalized, excludePatterns) ||\n\t\t\t\t\t\t\tmatchesAnyPattern(relForMatch, excludePatterns);\n\t\t\t\t\t\tif (excluded) return;\n\t\t\t\t\t\tlet kind: FSEventType = \"change\";\n\t\t\t\t\t\tif (eventType === \"rename\") {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tkind = existsSync(normalized) ? \"create\" : \"delete\";\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tkind = \"rename\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpending.set(normalized, {\n\t\t\t\t\t\t\ttype: kind,\n\t\t\t\t\t\t\tpath: normalized,\n\t\t\t\t\t\t\troot,\n\t\t\t\t\t\t\trelative_path: relForMatch,\n\t\t\t\t\t\t\ttimestamp_ns: wallClockNs(),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\t\t\t\tconst token = generation;\n\t\t\t\t\t\ttimer = setTimeout(() => flush(token), debounce);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\twatcher.on(\"error\", (err) => emitError(err));\n\t\t\t\twatchers.push(watcher);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\temitError(err);\n\t\t}\n\t\treturn () => {\n\t\t\tstopped = true;\n\t\t\tgeneration += 1;\n\t\t\tif (timer !== undefined) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t\tcloseWatchers();\n\t\t\tpending.clear();\n\t\t};\n\t}, sourceOpts(rest));\n}\n\n/**\n * Drains a synchronous iterable; each item is `DATA`, then `COMPLETE`, or `ERROR` if iteration throws.\n *\n * @param iterable - Values to emit in order.\n * @param opts - Optional producer options.\n * @returns `Node<T>` — one emission per element.\n *\n * @example\n * ```ts\n * import { fromIter } from \"@graphrefly/graphrefly-ts\";\n *\n * fromIter([1, 2, 3]);\n * ```\n *\n * @category extra\n */\nexport function fromIter<T>(iterable: Iterable<T>, opts?: ExtraOpts): Node<T> {\n\treturn producer<T>((_d, a) => {\n\t\tlet cancelled = false;\n\t\ttry {\n\t\t\tfor (const x of iterable) {\n\t\t\t\tif (cancelled) return;\n\t\t\t\ta.emit(x);\n\t\t\t}\n\t\t\tif (!cancelled) a.down([[COMPLETE]]);\n\t\t} catch (e) {\n\t\t\tif (!cancelled) a.down([[ERROR, e]]);\n\t\t}\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t};\n\t}, sourceOpts(opts));\n}\n\nfunction isThenable(x: unknown): x is PromiseLike<unknown> {\n\treturn x != null && typeof (x as PromiseLike<unknown>).then === \"function\";\n}\n\n/**\n * Lifts a Promise (or thenable) to a single-value stream: one `DATA` then `COMPLETE`, or `ERROR` on rejection.\n *\n * @param p - Promise to await.\n * @param opts - Producer options plus optional `signal` for abort → `ERROR` with reason.\n * @returns `Node<T>` — settles once.\n *\n * @example\n * ```ts\n * import { fromPromise } from \"@graphrefly/graphrefly-ts\";\n *\n * fromPromise(Promise.resolve(42));\n * ```\n *\n * @category extra\n */\nexport function fromPromise<T>(p: Promise<T> | PromiseLike<T>, opts?: AsyncSourceOpts): Node<T> {\n\tconst { signal, ...rest } = opts ?? {};\n\treturn producer<T>((_d, a) => {\n\t\tlet settled = false;\n\t\tconst onAbort = () => {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\ta.down([[ERROR, signal!.reason]]);\n\t\t};\n\t\tif (signal?.aborted) {\n\t\t\tonAbort();\n\t\t\treturn;\n\t\t}\n\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\tvoid Promise.resolve(p).then(\n\t\t\t(v) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\ta.emit(v as T);\n\t\t\t\ta.down([[COMPLETE]]);\n\t\t\t},\n\t\t\t(e) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\ta.down([[ERROR, e]]);\n\t\t\t},\n\t\t);\n\t\treturn () => {\n\t\t\tsettled = true;\n\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t};\n\t}, sourceOpts(rest));\n}\n\n/**\n * Reads an async iterable; each `next()` value becomes `DATA`; `COMPLETE` when done; `ERROR` on failure.\n *\n * @param iterable - Async source (`for await` shape).\n * @param opts - Producer options plus optional `signal` to abort the pump.\n * @returns `Node<T>` — async pull stream.\n *\n * @example\n * ```ts\n * import { fromAsyncIter } from \"@graphrefly/graphrefly-ts\";\n *\n * async function* gen() {\n * yield 1;\n * }\n * fromAsyncIter(gen());\n * ```\n *\n * @category extra\n */\nexport function fromAsyncIter<T>(iterable: AsyncIterable<T>, opts?: AsyncSourceOpts): Node<T> {\n\tconst { signal: outerSignal, ...rest } = opts ?? {};\n\treturn producer<T>((_d, a) => {\n\t\tconst ac = new AbortController();\n\t\tconst onOuterAbort = () => ac.abort(outerSignal?.reason);\n\t\tif (outerSignal?.aborted) {\n\t\t\tac.abort(outerSignal.reason);\n\t\t} else {\n\t\t\touterSignal?.addEventListener(\"abort\", onOuterAbort, { once: true });\n\t\t}\n\t\tconst signal = outerSignal ?? ac.signal;\n\t\tlet cancelled = false;\n\t\tconst it = iterable[Symbol.asyncIterator]();\n\t\tconst pump = (): void => {\n\t\t\tif (cancelled || signal.aborted) return;\n\t\t\tvoid Promise.resolve(it.next()).then(\n\t\t\t\t(step) => {\n\t\t\t\t\tif (cancelled || signal.aborted) return;\n\t\t\t\t\tif (step.done) {\n\t\t\t\t\t\tqueueMicrotask(() => a.down([[COMPLETE]]));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\ta.emit(step.value as T);\n\t\t\t\t\tqueueMicrotask(pump);\n\t\t\t\t},\n\t\t\t\t(e) => {\n\t\t\t\t\tif (!cancelled && !signal.aborted) a.down([[ERROR, e]]);\n\t\t\t\t},\n\t\t\t);\n\t\t};\n\t\tqueueMicrotask(pump);\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\touterSignal?.removeEventListener(\"abort\", onOuterAbort);\n\t\t\tac.abort();\n\t\t\tvoid Promise.resolve(it.return?.()).catch(() => undefined);\n\t\t};\n\t}, sourceOpts(rest));\n}\n\nfunction isNode(x: unknown): x is Node {\n\treturn (\n\t\tx != null &&\n\t\ttypeof (x as Node).subscribe === \"function\" &&\n\t\ttypeof (x as Node).get === \"function\"\n\t);\n}\n\n/**\n * Coerces a value to a `Node` by shape: existing `Node` passthrough, thenable → {@link fromPromise},\n * async iterable → {@link fromAsyncIter}, sync iterable → {@link fromIter}, else scalar → {@link of}.\n *\n * @param input - Any value to wrap.\n * @param opts - Passed through when a Promise/async path is chosen.\n * @returns `Node` of the inferred element type.\n *\n * @example\n * ```ts\n * import { fromAny, state } from \"@graphrefly/graphrefly-ts\";\n *\n * fromAny(state(1));\n * fromAny(Promise.resolve(2));\n * ```\n *\n * @category extra\n */\nexport function fromAny<T>(input: NodeInput<T>, opts?: AsyncSourceOpts): Node<T> {\n\tif (isNode(input)) {\n\t\treturn input as Node<T>;\n\t}\n\tif (isThenable(input)) {\n\t\treturn fromPromise(input as PromiseLike<T>, opts);\n\t}\n\tif (input !== null && input !== undefined) {\n\t\tconst candidate = input as { [Symbol.asyncIterator]?: unknown; [Symbol.iterator]?: unknown };\n\t\tif (typeof candidate[Symbol.asyncIterator] === \"function\") {\n\t\t\treturn fromAsyncIter(input as AsyncIterable<T>, opts);\n\t\t}\n\t\tif (typeof candidate[Symbol.iterator] === \"function\") {\n\t\t\treturn fromIter(input as Iterable<T>, opts);\n\t\t}\n\t}\n\t// scalar fallback\n\treturn of(input as T);\n}\n\n/**\n * Emits each argument as `DATA` in order, then `COMPLETE` (implemented via {@link fromIter}).\n *\n * @param values - Values to emit.\n * @returns `Node<T>` — finite sequence.\n *\n * @example\n * ```ts\n * import { of } from \"@graphrefly/graphrefly-ts\";\n *\n * of(1, 2, 3);\n * ```\n *\n * @category extra\n */\nexport function of<T>(...values: T[]): Node<T> {\n\treturn fromIter(values, undefined);\n}\n\n/**\n * Completes immediately with no `DATA` (cold `EMPTY` analogue).\n *\n * @param opts - Optional producer options.\n * @returns `Node<T>` — terminal `COMPLETE` only.\n *\n * @example\n * ```ts\n * import { empty } from \"@graphrefly/graphrefly-ts\";\n *\n * empty();\n * ```\n *\n * @category extra\n */\nexport function empty<T = never>(opts?: ExtraOpts): Node<T> {\n\treturn producer<T>((_d, a) => {\n\t\ta.down([[COMPLETE]]);\n\t\treturn undefined;\n\t}, sourceOpts(opts));\n}\n\n/**\n * Never emits and never completes until teardown (cold `NEVER` analogue).\n *\n * @param opts - Optional producer options.\n * @returns `Node<T>` — silent until unsubscribed.\n *\n * @example\n * ```ts\n * import { never } from \"@graphrefly/graphrefly-ts\";\n *\n * never();\n * ```\n *\n * @category extra\n */\nexport function never<T = never>(opts?: ExtraOpts): Node<T> {\n\treturn producer<T>(() => undefined, sourceOpts(opts));\n}\n\n/**\n * Emits `ERROR` as soon as the producer starts (cold error source).\n *\n * @param err - Error payload forwarded as `ERROR` data.\n * @param opts - Optional producer options.\n * @returns `Node<never>` — terminates with `ERROR`.\n *\n * @example\n * ```ts\n * import { throwError } from \"@graphrefly/graphrefly-ts\";\n *\n * throwError(new Error(\"fail\"));\n * ```\n *\n * @category extra\n */\nexport function throwError(err: unknown, opts?: ExtraOpts): Node<never> {\n\treturn producer<never>((_d, a) => {\n\t\ta.down([[ERROR, err]]);\n\t\treturn undefined;\n\t}, sourceOpts(opts));\n}\n\n/**\n * Subscribes immediately and runs `fn` for each upstream `DATA`; returns unsubscribe.\n *\n * @param source - Upstream node.\n * @param fn - Side effect per value.\n * @param opts - Effect node options.\n * @returns Unsubscribe function (idempotent).\n *\n * @example\n * ```ts\n * import { forEach, state } from \"@graphrefly/graphrefly-ts\";\n *\n * const u = forEach(state(1), (v) => console.log(v));\n * u();\n * ```\n *\n * @category extra\n */\nexport function forEach<T>(source: Node<T>, fn: (value: T) => void, opts?: ExtraOpts): () => void {\n\tconst inner = node([source as Node], () => undefined, {\n\t\tdescribeKind: \"effect\",\n\t\t...opts,\n\t\tonMessage(msg: Message, _i, _a) {\n\t\t\tif (msg[0] === DATA) {\n\t\t\t\tfn(msg[1] as T);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\treturn inner.subscribe(() => {});\n}\n\n/**\n * Buffers every `DATA`; on upstream `COMPLETE` emits one `DATA` with the full array then `COMPLETE`.\n *\n * @param source - Upstream node.\n * @param opts - Optional node options (operator describe kind).\n * @returns `Node<T[]>` — single array emission before completion.\n *\n * @example\n * ```ts\n * import { of, toArray } from \"@graphrefly/graphrefly-ts\";\n *\n * toArray(of(1, 2, 3));\n * ```\n *\n * @category extra\n */\nexport function toArray<T>(source: Node<T>, opts?: ExtraOpts): Node<T[]> {\n\tconst acc: T[] = [];\n\treturn node<T[]>([source as Node], () => undefined, {\n\t\t...operatorOpts(opts),\n\t\tcompleteWhenDepsComplete: false,\n\t\tonMessage(msg: Message, _i, a) {\n\t\t\tif (msg[0] === DATA) {\n\t\t\t\tacc.push(msg[1] as T);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tif (msg[0] === COMPLETE) {\n\t\t\t\ta.emit([...acc]);\n\t\t\t\ta.down([[COMPLETE]]);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n}\n\n/**\n * Multicasts upstream: one subscription to `source` while this wrapper has subscribers (via {@link producer}).\n *\n * @param source - Upstream node to share.\n * @param opts - Producer options; `initial` seeds from `source.get()` when set by factory.\n * @returns `Node<T>` — hot ref-counted bridge.\n *\n * @example\n * ```ts\n * import { share, state } from \"@graphrefly/graphrefly-ts\";\n *\n * share(state(0));\n * ```\n *\n * @category extra\n */\nexport function share<T>(source: Node<T>, opts?: ExtraOpts): Node<T> {\n\treturn producer<T>(\n\t\t(_d, a) =>\n\t\t\tsource.subscribe((msgs) => {\n\t\t\t\ta.down(msgs);\n\t\t\t}),\n\t\t{ ...sourceOpts(opts), initial: source.get() },\n\t);\n}\n\n/**\n * Like {@link share} with a bounded replay buffer: new subscribers receive the last `bufferSize`\n * `DATA` payloads (as separate batches) before live updates.\n *\n * @param source - Upstream node.\n * @param bufferSize - Maximum past values to replay (≥ 1).\n * @param opts - Producer options.\n * @returns `Node<T>` — multicast with replay on subscribe.\n *\n * @example\n * ```ts\n * import { replay, state } from \"@graphrefly/graphrefly-ts\";\n *\n * replay(state(0), 3);\n * ```\n *\n * @category extra\n */\nexport function replay<T>(source: Node<T>, bufferSize: number, opts?: ExtraOpts): Node<T> {\n\tif (bufferSize < 1) throw new RangeError(\"replay expects bufferSize >= 1\");\n\tconst buf: T[] = [];\n\tconst inner = producer<T>(\n\t\t(_d, a) =>\n\t\t\tsource.subscribe((msgs) => {\n\t\t\t\tfor (const m of msgs) {\n\t\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\t\tbuf.push(m[1] as T);\n\t\t\t\t\t\tif (buf.length > bufferSize) buf.shift();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ta.down(msgs);\n\t\t\t}),\n\t\t{ ...sourceOpts(opts), initial: source.get() },\n\t);\n\treturn wrapSubscribeHook(inner, (sink) => {\n\t\tfor (const v of buf) {\n\t\t\tsink([[DATA, v]]);\n\t\t}\n\t});\n}\n\n/**\n * {@link replay} with `bufferSize === 1` — replays the latest `DATA` to new subscribers.\n *\n * @param source - Upstream node.\n * @param opts - Producer options.\n * @returns `Node<T>` — share + last-value replay.\n *\n * @example\n * ```ts\n * import { cached, state } from \"@graphrefly/graphrefly-ts\";\n *\n * cached(state(0));\n * ```\n *\n * @category extra\n */\nexport function cached<T>(source: Node<T>, opts?: ExtraOpts): Node<T> {\n\treturn replay(source, 1, opts);\n}\n\n/**\n * Converts the first `DATA` on `source` into a Promise; rejects on `ERROR` or `COMPLETE` without data.\n *\n * **Important:** This subscribes and waits for a **future** emission. Data that\n * has already flowed is gone and will not be seen. Call this *before* the upstream\n * emits, or use `source.get()` / `source.status` for already-cached state.\n * See COMPOSITION-GUIDE §2 (subscription ordering).\n *\n * @param source - Node to read once.\n * @returns Promise of the first value.\n *\n * @example\n * ```ts\n * import { firstValueFrom, of } from \"@graphrefly/graphrefly-ts\";\n *\n * await firstValueFrom(of(42));\n * ```\n *\n * @category extra\n */\nexport function firstValueFrom<T>(source: Node<T>): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst unsub = source.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (settled) return;\n\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tresolve(m[1] as T);\n\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (m[0] === ERROR) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\treject(m[1]);\n\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (m[0] === COMPLETE) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\treject(new Error(\"completed without DATA\"));\n\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Wait for the first DATA value from `source` that satisfies `predicate`.\n *\n * Subscribes directly and resolves on the first DATA value where\n * `predicate` returns true. Reactive, no polling. Use in tests and\n * bridging code where you need a single matching value as a Promise.\n *\n * **Important:** This only captures **future** emissions — data that has\n * already flowed through the node is gone. Call this *before* the upstream\n * emits. For already-cached values, use `source.get()` / `source.status`.\n * See COMPOSITION-GUIDE §2 (subscription ordering).\n *\n * ```ts\n * const val = await firstWhere(strategy.node, snap => snap.size > 0);\n * ```\n *\n * @category extra\n */\nexport function firstWhere<T>(source: Node<T>, predicate: (value: T) => boolean): Promise<T> {\n\treturn new Promise<T>((resolve, reject) => {\n\t\tlet settled = false;\n\t\tconst unsub = source.subscribe((msgs) => {\n\t\t\tfor (const m of msgs) {\n\t\t\t\tif (settled) return;\n\t\t\t\tif (m[0] === DATA) {\n\t\t\t\t\tconst v = m[1] as T;\n\t\t\t\t\tif (predicate(v)) {\n\t\t\t\t\t\tsettled = true;\n\t\t\t\t\t\tresolve(v);\n\t\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (m[0] === ERROR) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\treject(m[1]);\n\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (m[0] === COMPLETE) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\treject(new Error(\"completed without matching value\"));\n\t\t\t\t\tqueueMicrotask(() => unsub());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n// ——————————————————————————————————————————————————————————————\n// RxJS-compatible aliases\n// ——————————————————————————————————————————————————————————————\n\n/**\n * RxJS-named alias for {@link replay} — multicast with a replay buffer of size `bufferSize`.\n *\n * @param source - Upstream node.\n * @param bufferSize - Replay depth (≥ 1).\n * @param opts - Producer options.\n * @returns Same behavior as `replay`.\n *\n * @example\n * ```ts\n * import { shareReplay, state } from \"@graphrefly/graphrefly-ts\";\n *\n * shareReplay(state(0), 5);\n * ```\n *\n * @category extra\n */\nexport const shareReplay = replay;\n\n// ---------------------------------------------------------------------------\n// keepalive\n// ---------------------------------------------------------------------------\n\n/**\n * Activate a compute node's upstream wiring without a real sink.\n *\n * Derived/effect nodes are lazy — they don't compute until at least one\n * subscriber exists (COMPOSITION-GUIDE §5). `keepalive` subscribes with an\n * empty sink so the node stays wired for `.get()` and upstream propagation.\n *\n * Returns the unsubscribe handle. Common usage:\n * `graph.addDisposer(keepalive(node))`.\n *\n * @category extra\n */\nexport function keepalive(n: Node<unknown>): () => void {\n\treturn n.subscribe(() => {});\n}\n\n// ---------------------------------------------------------------------------\n// reactiveCounter\n// ---------------------------------------------------------------------------\n\n/** Bundle returned by {@link reactiveCounter}. */\nexport type ReactiveCounterBundle = {\n\t/** Reactive node holding the current count. */\n\treadonly node: Node<number>;\n\t/** Increment by 1. Returns `false` if cap would be exceeded. */\n\tincrement(): boolean;\n\t/** Current count (synchronous read). */\n\tget(): number;\n\t/** Whether the counter has reached its cap. */\n\tatCap(): boolean;\n};\n\n/**\n * Reactive counter with a cap — the building block for circuit breakers.\n *\n * Wraps a `state(0)` node with `increment()` that respects a maximum.\n * The `node` is subscribable and composable like any reactive node. When\n * the cap is reached, `increment()` returns `false`.\n *\n * ```ts\n * const retries = reactiveCounter(10);\n * retries.increment(); // true — count is now 1\n * retries.node.subscribe(...); // reactive updates\n * retries.atCap(); // false\n * ```\n *\n * @param cap - Maximum value (inclusive). 0 = no increments allowed.\n * @category extra\n */\nexport function reactiveCounter(cap: number): ReactiveCounterBundle {\n\tconst counter = state(0);\n\treturn {\n\t\tnode: counter,\n\t\tincrement() {\n\t\t\tconst current = counter.get() ?? 0;\n\t\t\tif (current >= cap) return false;\n\t\t\tcounter.down([[DIRTY], [DATA, current + 1]]);\n\t\t\treturn true;\n\t\t},\n\t\tget() {\n\t\t\treturn counter.get() ?? 0;\n\t\t},\n\t\tatCap() {\n\t\t\treturn (counter.get() ?? 0) >= cap;\n\t\t},\n\t};\n}\n","/**\n * Watermark-based backpressure controller — reactive PAUSE/RESUME flow control.\n *\n * Purely synchronous, event-driven. No timers, no polling, no Promises.\n * Each controller instance uses a unique lockId so multiple controllers\n * on the same upstream node do not collide.\n *\n * @module\n */\n\nimport { type Messages, PAUSE, RESUME } from \"../core/messages.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type WatermarkOptions = {\n\t/** Pending count at which PAUSE is sent upstream. */\n\thighWaterMark: number;\n\t/** Pending count at which RESUME is sent upstream (after being paused). */\n\tlowWaterMark: number;\n};\n\nexport type WatermarkController = {\n\t/** Call when a DATA message is buffered/enqueued. Returns `true` if PAUSE was just sent. */\n\tonEnqueue(): boolean;\n\t/** Call when a buffered item is consumed. Returns `true` if RESUME was just sent. */\n\tonDequeue(): boolean;\n\t/** Current un-consumed item count. */\n\treadonly pending: number;\n\t/** Whether upstream is currently paused by this controller. */\n\treadonly paused: boolean;\n\t/** Dispose: if paused, sends RESUME to unblock upstream. */\n\tdispose(): void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nlet nextLockId = 0;\n\n/**\n * Creates a watermark-based backpressure controller.\n *\n * @param sendUp - Callback that delivers messages upstream (typically `handle.up`).\n * @param opts - High/low watermark thresholds (item counts).\n * @returns A {@link WatermarkController}.\n *\n * @example\n * ```ts\n * const handle = graph.observe(\"fast-source\");\n * const wm = createWatermarkController(\n * (msgs) => handle.up(msgs),\n * { highWaterMark: 64, lowWaterMark: 16 },\n * );\n *\n * // In sink callback:\n * handle.subscribe((msgs) => {\n * for (const msg of msgs) {\n * if (msg[0] === DATA) {\n * buffer.push(msg[1]);\n * wm.onEnqueue();\n * }\n * }\n * });\n *\n * // When consumer drains:\n * const item = buffer.shift();\n * wm.onDequeue();\n * ```\n *\n * @category extra\n */\nexport function createWatermarkController(\n\tsendUp: (messages: Messages) => void,\n\topts: WatermarkOptions,\n): WatermarkController {\n\tif (opts.highWaterMark < 1) throw new RangeError(\"highWaterMark must be >= 1\");\n\tif (opts.lowWaterMark < 0) throw new RangeError(\"lowWaterMark must be >= 0\");\n\tif (opts.lowWaterMark >= opts.highWaterMark)\n\t\tthrow new RangeError(\"lowWaterMark must be < highWaterMark\");\n\tconst lockId = Symbol(`bp-${++nextLockId}`);\n\tlet pending = 0;\n\tlet paused = false;\n\n\treturn {\n\t\tonEnqueue(): boolean {\n\t\t\tpending += 1;\n\t\t\tif (!paused && pending >= opts.highWaterMark) {\n\t\t\t\tpaused = true;\n\t\t\t\tsendUp([[PAUSE, lockId]]);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tonDequeue(): boolean {\n\t\t\tif (pending > 0) pending -= 1;\n\t\t\tif (paused && pending <= opts.lowWaterMark) {\n\t\t\t\tpaused = false;\n\t\t\t\tsendUp([[RESUME, lockId]]);\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t\tget pending() {\n\t\t\treturn pending;\n\t\t},\n\t\tget paused() {\n\t\t\treturn paused;\n\t\t},\n\t\tdispose() {\n\t\t\tif (paused) {\n\t\t\t\tpaused = false;\n\t\t\t\tsendUp([[RESUME, lockId]]);\n\t\t\t}\n\t\t},\n\t};\n}\n","/**\n * Reactive append-only log (roadmap §3.2) — emits `readonly T[]` snapshots directly.\n *\n * Internal version counter drives efficient equality without leaking `Versioned`\n * into the public API (spec §5.12).\n */\nimport { batch } from \"../core/batch.js\";\nimport { DATA, DIRTY } from \"../core/messages.js\";\nimport type { Node } from \"../core/node.js\";\nimport { derived, state } from \"../core/sugar.js\";\n\nexport type ReactiveLogOptions = {\n\tname?: string;\n\tmaxSize?: number;\n};\n\nexport type ReactiveLogBundle<T> = {\n\t/** Emits `readonly T[]` on each append/clear (two-phase). */\n\treadonly entries: Node<readonly T[]>;\n\tappend: (value: T) => void;\n\t/** Push all values, trim once, emit one snapshot. */\n\tappendMany: (values: readonly T[]) => void;\n\tclear: () => void;\n\t/** Remove the first `n` entries; emits snapshot. */\n\ttrimHead: (n: number) => void;\n\t/** Last `n` entries (or fewer); updates when the log changes. */\n\ttail: (n: number) => Node<readonly T[]>;\n};\n\n/**\n * Keep a derived node's dep wiring alive for `get()` without a user sink.\n * Returns the unsubscribe handle so callers can clean up.\n *\n * @remarks Derived views (`tail`, `logSlice`) install this so `get()` stays\n * wired without an external sink. The returned disposer is currently not\n * exposed on the bundle — subscriptions are released when the log bundle\n * becomes unreachable and the GC collects the closure.\n */\nfunction keepaliveDerived(n: Node<unknown>): () => void {\n\treturn n.subscribe(() => {});\n}\n\n/**\n * Creates an append-only reactive log with immutable array snapshots.\n *\n * @param initial - Optional seed entries (copied).\n * @param options - Optional `name` for `describe()` / debugging.\n * @returns Bundle with `entries` (state node), `append`, `clear`, and {@link ReactiveLogBundle.tail}.\n *\n * @remarks\n * **Derived views:** {@link tail} and {@link logSlice} install an internal noop subscription so\n * `get()` stays wired without an external sink; creating very many disposable derived nodes can\n * retain subscriptions until the log bundle is unreachable.\n *\n * @example\n * ```ts\n * import { reactiveLog } from \"@graphrefly/graphrefly-ts\";\n *\n * const lg = reactiveLog<number>([1, 2], { name: \"audit\" });\n * lg.append(3);\n * lg.entries.subscribe((msgs) => console.log(msgs));\n * ```\n *\n * @category extra\n */\nexport function reactiveLog<T>(\n\tinitial?: readonly T[],\n\toptions: ReactiveLogOptions = {},\n): ReactiveLogBundle<T> {\n\tconst { name, maxSize } = options;\n\tif (maxSize !== undefined && maxSize < 1) {\n\t\tthrow new RangeError(\"maxSize must be >= 1\");\n\t}\n\tconst buf: T[] = initial ? [...initial] : [];\n\tif (maxSize !== undefined && buf.length > maxSize) {\n\t\tbuf.splice(0, buf.length - maxSize);\n\t}\n\n\tconst entries = state<readonly T[]>(buf.length > 0 ? [...buf] : [], {\n\t\tname,\n\t\tdescribeKind: \"state\",\n\t\tequals: (a, b) => a === b,\n\t});\n\n\tfunction pushSnapshot(): void {\n\t\tconst snapshot: readonly T[] = [...buf];\n\t\tbatch(() => {\n\t\t\tentries.down([[DIRTY]]);\n\t\t\tentries.down([[DATA, snapshot]]);\n\t\t});\n\t}\n\n\tfunction trimBuf(): void {\n\t\tif (maxSize !== undefined && buf.length > maxSize) {\n\t\t\tbuf.splice(0, buf.length - maxSize);\n\t\t}\n\t}\n\n\tconst bundle: ReactiveLogBundle<T> = {\n\t\tentries,\n\n\t\tappend(value: T): void {\n\t\t\tbuf.push(value);\n\t\t\ttrimBuf();\n\t\t\tpushSnapshot();\n\t\t},\n\n\t\tappendMany(values: readonly T[]): void {\n\t\t\tif (values.length === 0) return;\n\t\t\tbuf.push(...values);\n\t\t\ttrimBuf();\n\t\t\tpushSnapshot();\n\t\t},\n\n\t\tclear(): void {\n\t\t\tif (buf.length === 0) return;\n\t\t\tbuf.length = 0;\n\t\t\tpushSnapshot();\n\t\t},\n\n\t\ttrimHead(n: number): void {\n\t\t\tif (n < 0) {\n\t\t\t\tthrow new RangeError(\"n must be >= 0\");\n\t\t\t}\n\t\t\tif (n === 0) return;\n\t\t\tif (n >= buf.length) {\n\t\t\t\tif (buf.length === 0) return;\n\t\t\t\tbuf.length = 0;\n\t\t\t} else {\n\t\t\t\tbuf.splice(0, n);\n\t\t\t}\n\t\t\tpushSnapshot();\n\t\t},\n\n\t\ttail(n: number): Node<readonly T[]> {\n\t\t\tif (n < 0) {\n\t\t\t\tthrow new RangeError(\"n must be >= 0\");\n\t\t\t}\n\t\t\tconst e = entries.get() as readonly T[];\n\t\t\tconst init = n === 0 ? [] : e.slice(Math.max(0, e.length - n));\n\t\t\tconst out = derived(\n\t\t\t\t[entries],\n\t\t\t\t([s]) => {\n\t\t\t\t\tconst list = s as readonly T[];\n\t\t\t\t\treturn n === 0 ? [] : list.slice(Math.max(0, list.length - n));\n\t\t\t\t},\n\t\t\t\t{ initial: init, describeKind: \"derived\" },\n\t\t\t);\n\t\t\tkeepaliveDerived(out);\n\t\t\treturn out;\n\t\t},\n\t};\n\n\treturn bundle;\n}\n\n/**\n * Builds a derived node for `entries.slice(start, stop)` (same semantics as `Array.prototype.slice`; `stop` exclusive).\n *\n * @param log - Log from {@link reactiveLog}.\n * @param start - Start index (must be `>= 0`).\n * @param stop - End index (exclusive); omit to slice to the end.\n * @returns Derived node emitting the sliced readonly array.\n *\n * @example\n * ```ts\n * import { reactiveLog, logSlice } from \"@graphrefly/graphrefly-ts\";\n *\n * const lg = reactiveLog<number>([10, 20, 30, 40, 50]);\n * const slice$ = logSlice(lg, 1, 4); // reactive view of [20, 30, 40]\n * slice$.subscribe((msgs) => console.log(msgs));\n *\n * lg.append(60); // slice$ now reflects [20, 30, 40] (indices 1–3 of updated log)\n * ```\n *\n * @category extra\n */\nexport function logSlice<T>(\n\tlog: ReactiveLogBundle<T>,\n\tstart: number,\n\tstop?: number,\n): Node<readonly T[]> {\n\tif (start < 0) {\n\t\tthrow new RangeError(\"start must be >= 0\");\n\t}\n\tconst e = log.entries.get() as readonly T[];\n\tconst init = stop === undefined ? e.slice(start) : e.slice(start, stop);\n\tconst out = derived(\n\t\t[log.entries],\n\t\t([s]) => {\n\t\t\tconst list = s as readonly T[];\n\t\t\treturn stop === undefined ? list.slice(start) : list.slice(start, stop);\n\t\t},\n\t\t{ initial: init, describeKind: \"derived\" },\n\t);\n\tkeepaliveDerived(out);\n\treturn out;\n}\n"],"mappings":"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","names":["node"]}
@@ -1,162 +0,0 @@
1
- import {
2
- ResettableTimer
3
- } from "./chunk-WZ2Z2CRV.js";
4
- import {
5
- resolveDescribeFields
6
- } from "./chunk-2L5J6RPM.js";
7
- import {
8
- CLEANUP_RESULT,
9
- COMPLETE,
10
- DATA,
11
- DEFAULT_ACTOR,
12
- DIRTY,
13
- DynamicNodeImpl,
14
- ERROR,
15
- GuardDenied,
16
- INVALIDATE,
17
- PAUSE,
18
- RESOLVED,
19
- RESUME,
20
- START,
21
- TEARDOWN,
22
- __export,
23
- accessHintForGuard,
24
- advanceVersion,
25
- batch,
26
- cleanupResult,
27
- createVersioning,
28
- defaultHash,
29
- derived,
30
- downWithBatch,
31
- dynamicNode,
32
- effect,
33
- isBatching,
34
- isKnownMessageType,
35
- isLocalOnly,
36
- isPhase2Message,
37
- isTerminalMessage,
38
- isV1,
39
- knownMessageTypes,
40
- messageTier,
41
- monotonicNs,
42
- node,
43
- normalizeActor,
44
- partitionForBatch,
45
- pipe,
46
- policy,
47
- policyFromRules,
48
- producer,
49
- propagatesToMeta,
50
- state,
51
- wallClockNs
52
- } from "./chunk-XYL3GLB3.js";
53
-
54
- // src/core/index.ts
55
- var core_exports = {};
56
- __export(core_exports, {
57
- CLEANUP_RESULT: () => CLEANUP_RESULT,
58
- COMPLETE: () => COMPLETE,
59
- DATA: () => DATA,
60
- DEFAULT_ACTOR: () => DEFAULT_ACTOR,
61
- DEFAULT_DOWN: () => DEFAULT_DOWN,
62
- DIRTY: () => DIRTY,
63
- DynamicNodeImpl: () => DynamicNodeImpl,
64
- ERROR: () => ERROR,
65
- GuardDenied: () => GuardDenied,
66
- INVALIDATE: () => INVALIDATE,
67
- PAUSE: () => PAUSE,
68
- RESOLVED: () => RESOLVED,
69
- RESUME: () => RESUME,
70
- ResettableTimer: () => ResettableTimer,
71
- START: () => START,
72
- TEARDOWN: () => TEARDOWN,
73
- accessHintForGuard: () => accessHintForGuard,
74
- advanceVersion: () => advanceVersion,
75
- batch: () => batch,
76
- bridge: () => bridge,
77
- cleanupResult: () => cleanupResult,
78
- createVersioning: () => createVersioning,
79
- defaultHash: () => defaultHash,
80
- derived: () => derived,
81
- downWithBatch: () => downWithBatch,
82
- dynamicNode: () => dynamicNode,
83
- effect: () => effect,
84
- isBatching: () => isBatching,
85
- isKnownMessageType: () => isKnownMessageType,
86
- isLocalOnly: () => isLocalOnly,
87
- isPhase2Message: () => isPhase2Message,
88
- isTerminalMessage: () => isTerminalMessage,
89
- isV1: () => isV1,
90
- knownMessageTypes: () => knownMessageTypes,
91
- messageTier: () => messageTier,
92
- monotonicNs: () => monotonicNs,
93
- node: () => node,
94
- normalizeActor: () => normalizeActor,
95
- partitionForBatch: () => partitionForBatch,
96
- pipe: () => pipe,
97
- policy: () => policy,
98
- policyFromRules: () => policyFromRules,
99
- producer: () => producer,
100
- propagatesToMeta: () => propagatesToMeta,
101
- resolveDescribeFields: () => resolveDescribeFields,
102
- state: () => state,
103
- wallClockNs: () => wallClockNs
104
- });
105
-
106
- // src/core/bridge.ts
107
- var DEFAULT_DOWN = [
108
- DATA,
109
- DIRTY,
110
- RESOLVED,
111
- COMPLETE,
112
- ERROR,
113
- TEARDOWN,
114
- PAUSE,
115
- RESUME,
116
- INVALIDATE
117
- ];
118
- var STANDARD_TYPES = /* @__PURE__ */ new Set([
119
- DATA,
120
- DIRTY,
121
- RESOLVED,
122
- COMPLETE,
123
- ERROR,
124
- TEARDOWN,
125
- PAUSE,
126
- RESUME,
127
- INVALIDATE
128
- ]);
129
- function bridge(from, to, opts) {
130
- const allowedDown = new Set(opts?.down ?? DEFAULT_DOWN);
131
- const onMessage = (msg, _depIndex, _actions) => {
132
- const type = msg[0];
133
- if (!STANDARD_TYPES.has(type)) {
134
- to.down([msg]);
135
- return true;
136
- }
137
- if (type === COMPLETE || type === ERROR) {
138
- if (allowedDown.has(type)) {
139
- to.down([msg]);
140
- }
141
- return false;
142
- }
143
- if (!allowedDown.has(type)) {
144
- return true;
145
- }
146
- to.down([msg]);
147
- return true;
148
- };
149
- return node([from], void 0, {
150
- name: opts?.name,
151
- describeKind: "effect",
152
- onMessage,
153
- meta: { _internal: true }
154
- });
155
- }
156
-
157
- export {
158
- DEFAULT_DOWN,
159
- bridge,
160
- core_exports
161
- };
162
- //# sourceMappingURL=chunk-PGEU5MEH.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/index.ts","../src/core/bridge.ts"],"sourcesContent":["/**\n * Core layer: message protocol, node primitive, lifecycle (Phase 0).\n */\nexport * from \"./actor.js\";\nexport * from \"./batch.js\";\nexport { type BridgeOptions, bridge, DEFAULT_DOWN } from \"./bridge.js\";\nexport { monotonicNs, wallClockNs } from \"./clock.js\";\nexport * from \"./dynamic-node.js\";\nexport * from \"./guard.js\";\nexport * from \"./messages.js\";\nexport {\n\ttype DescribeDetail,\n\ttype DescribeField,\n\ttype DescribeNodeOutput,\n\tresolveDescribeFields,\n} from \"./meta.js\";\nexport {\n\tCLEANUP_RESULT,\n\ttype CleanupResult,\n\tcleanupResult,\n\ttype Node,\n\ttype NodeActions,\n\ttype NodeDescribeKind,\n\ttype NodeFn,\n\ttype NodeOptions,\n\ttype NodeSink,\n\ttype NodeStatus,\n\ttype NodeTransportOptions,\n\tnode,\n\ttype OnMessageHandler,\n\ttype SubscribeHints,\n} from \"./node.js\";\nexport * from \"./sugar.js\";\nexport { ResettableTimer } from \"./timer.js\";\nexport {\n\tadvanceVersion,\n\tcreateVersioning,\n\tdefaultHash,\n\ttype HashFn,\n\tisV1,\n\ttype NodeVersionInfo,\n\ttype V0,\n\ttype V1,\n\ttype VersioningLevel,\n\ttype VersioningOptions,\n} from \"./versioning.js\";\n","/**\n * bridge — graph-visible message forwarding between two nodes.\n *\n * Replaces ad-hoc `subscribe()` bridges that bypass graph topology.\n * The returned node is an effect that intercepts messages from `from`\n * and forwards them to `to.down()`. Register it with `graph.add()` to\n * make the bridge visible in `describe()` and `snapshot()`.\n *\n * **Upstream path:** The bridge node has `from` as its dep, so anything\n * downstream of the bridge that calls `up()` naturally reaches `from`.\n * If `to` is used as a dep by other nodes and those nodes send `up()`,\n * the messages reach `to`'s deps (not `from`). For full upstream relay\n * across the bridge boundary, wire the bridge as a dep of `to`'s\n * consumers or use `graph.connect()`.\n *\n * **ABAC / guards:** `to.down()` is called without `NodeTransportOptions`,\n * so any ABAC guard on `to` receives `actor = undefined`. Upstream (`up()`)\n * messages propagate through the dep chain the same way — no actor is\n * injected on either path. Both paths are intentionally unguarded; if `to`\n * requires a specific actor, provide a guarded wrapper node and bridge to\n * that instead.\n *\n * **Default forwarding:** All standard message types are forwarded by\n * default, including TEARDOWN, PAUSE, RESUME, and INVALIDATE. Use the\n * `down` option to restrict which types are forwarded. Callers that need\n * to exclude TEARDOWN (e.g. inter-stage wiring in `funnel()`) pass an\n * explicit `down` array without TEARDOWN.\n *\n * @module\n */\n\nimport {\n\tCOMPLETE,\n\tDATA,\n\tDIRTY,\n\tERROR,\n\tINVALIDATE,\n\ttype Message,\n\tPAUSE,\n\tRESOLVED,\n\tRESUME,\n\tTEARDOWN,\n} from \"./messages.js\";\nimport { type Node, type NodeActions, node, type OnMessageHandler } from \"./node.js\";\n\n/** Options for {@link bridge}. */\nexport type BridgeOptions = {\n\t/** Node name (for graph registration / describe). */\n\tname?: string;\n\t/**\n\t * Standard message types to forward downstream. Default: all standard\n\t * types. Unknown (non-standard) types always forward per spec §1.3.6\n\t * regardless of this option.\n\t */\n\tdown?: readonly symbol[];\n};\n\n/** All standard types forwarded by default. Export for callers that\n * need to customize (e.g. exclude TEARDOWN). */\nexport const DEFAULT_DOWN: readonly symbol[] = [\n\tDATA,\n\tDIRTY,\n\tRESOLVED,\n\tCOMPLETE,\n\tERROR,\n\tTEARDOWN,\n\tPAUSE,\n\tRESUME,\n\tINVALIDATE,\n];\n\n/**\n * All standard message types the bridge understands. Types outside this set\n * are \"unknown\" and must always be forwarded (spec §1.3.6).\n */\nconst STANDARD_TYPES = new Set<symbol>([\n\tDATA,\n\tDIRTY,\n\tRESOLVED,\n\tCOMPLETE,\n\tERROR,\n\tTEARDOWN,\n\tPAUSE,\n\tRESUME,\n\tINVALIDATE,\n]);\n\n/**\n * Create a graph-visible bridge node that forwards messages from `from` to `to`.\n *\n * The bridge is a real node (effect) — it shows up in `describe()`, participates\n * in two-phase push, and cleans up on TEARDOWN. Register it via `graph.add()`\n * to make it part of the graph topology.\n *\n * **Unknown message types** (custom domain signals not in the standard protocol\n * set) are always forwarded to `to`, regardless of the `down` option. This\n * satisfies spec §1.3.6 (\"unknown types forward unchanged\").\n *\n * **COMPLETE / ERROR**: when forwarded, the bridge also transitions to terminal\n * state so graph-wide completion detection works correctly.\n *\n * @param from - Source node to observe.\n * @param to - Target node to forward messages to via `to.down()`.\n * @param opts - Optional configuration.\n * @returns A bridge effect node. Add it to a graph with `graph.add(name, bridge(...))`.\n *\n * @example\n * ```ts\n * import { bridge, state } from \"@graphrefly/graphrefly-ts\";\n *\n * const a = state(0);\n * const b = state(0);\n * const br = bridge(a, b, { name: \"__bridge_a_b\" });\n * graph.add(\"__bridge_a_b\", br);\n * // Now a's messages flow to b, visible in describe()\n * ```\n *\n * @category core\n */\nexport function bridge<T = unknown>(from: Node<T>, to: Node, opts?: BridgeOptions): Node<unknown> {\n\tconst allowedDown = new Set(opts?.down ?? DEFAULT_DOWN);\n\n\tconst onMessage: OnMessageHandler = (\n\t\tmsg: Message,\n\t\t_depIndex: number,\n\t\t_actions: NodeActions,\n\t): boolean => {\n\t\tconst type = msg[0];\n\n\t\t// Unknown types (custom domain signals) always forward — spec §1.3.6.\n\t\tif (!STANDARD_TYPES.has(type)) {\n\t\t\tto.down([msg]);\n\t\t\treturn true;\n\t\t}\n\n\t\t// Terminal types: always transition the bridge to terminal state\n\t\t// (return false → default dispatch). Only forward to `to` if allowed.\n\t\tif (type === COMPLETE || type === ERROR) {\n\t\t\tif (allowedDown.has(type)) {\n\t\t\t\tto.down([msg]);\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\t// Known type, not in allowedDown — consume without forwarding.\n\t\tif (!allowedDown.has(type)) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Forward the message to the target.\n\t\tto.down([msg]);\n\t\treturn true;\n\t};\n\n\treturn node([from as Node], undefined, {\n\t\tname: opts?.name,\n\t\tdescribeKind: \"effect\",\n\t\tonMessage,\n\t\tmeta: { _internal: true },\n\t});\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;AC2DO,IAAM,eAAkC;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAMA,IAAM,iBAAiB,oBAAI,IAAY;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAkCM,SAAS,OAAoB,MAAe,IAAU,MAAqC;AACjG,QAAM,cAAc,IAAI,IAAI,MAAM,QAAQ,YAAY;AAEtD,QAAM,YAA8B,CACnC,KACA,WACA,aACa;AACb,UAAM,OAAO,IAAI,CAAC;AAGlB,QAAI,CAAC,eAAe,IAAI,IAAI,GAAG;AAC9B,SAAG,KAAK,CAAC,GAAG,CAAC;AACb,aAAO;AAAA,IACR;AAIA,QAAI,SAAS,YAAY,SAAS,OAAO;AACxC,UAAI,YAAY,IAAI,IAAI,GAAG;AAC1B,WAAG,KAAK,CAAC,GAAG,CAAC;AAAA,MACd;AACA,aAAO;AAAA,IACR;AAGA,QAAI,CAAC,YAAY,IAAI,IAAI,GAAG;AAC3B,aAAO;AAAA,IACR;AAGA,OAAG,KAAK,CAAC,GAAG,CAAC;AACb,WAAO;AAAA,EACR;AAEA,SAAO,KAAK,CAAC,IAAY,GAAG,QAAW;AAAA,IACtC,MAAM,MAAM;AAAA,IACZ,cAAc;AAAA,IACd;AAAA,IACA,MAAM,EAAE,WAAW,KAAK;AAAA,EACzB,CAAC;AACF;","names":[]}