@graphrefly/graphrefly 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/ai-CaR_912Q.d.cts +1033 -0
- package/dist/ai-WlRltJV7.d.ts +1033 -0
- package/dist/audit-ClmqGOCx.d.cts +245 -0
- package/dist/audit-DRlSzBu9.d.ts +245 -0
- package/dist/{chunk-QOWVNWOC.js → chunk-3ZWCKRHX.js} +27 -25
- package/dist/{chunk-QOWVNWOC.js.map → chunk-3ZWCKRHX.js.map} +1 -1
- package/dist/chunk-APFNLIRG.js +62 -0
- package/dist/chunk-APFNLIRG.js.map +1 -0
- package/dist/chunk-AT5LKYNL.js +395 -0
- package/dist/chunk-AT5LKYNL.js.map +1 -0
- package/dist/{chunk-IAHGTNOZ.js → chunk-BQ6RQQFF.js} +351 -2095
- package/dist/chunk-BQ6RQQFF.js.map +1 -0
- package/dist/{chunk-L2GLW2U7.js → chunk-BVZYTZ5H.js} +9 -103
- package/dist/chunk-BVZYTZ5H.js.map +1 -0
- package/dist/{chunk-EVR6UFUV.js → chunk-DST5DKZS.js} +19 -15
- package/dist/{chunk-EVR6UFUV.js.map → chunk-DST5DKZS.js.map} +1 -1
- package/dist/{chunk-TKE3JGOH.js → chunk-GTE6PWRZ.js} +5 -692
- package/dist/chunk-GTE6PWRZ.js.map +1 -0
- package/dist/chunk-HXZEYDUR.js +94 -0
- package/dist/chunk-HXZEYDUR.js.map +1 -0
- package/dist/chunk-J22W6HV3.js +107 -0
- package/dist/chunk-J22W6HV3.js.map +1 -0
- package/dist/{chunk-PY4XCDLR.js → chunk-J2VBW3DZ.js} +6 -95
- package/dist/chunk-J2VBW3DZ.js.map +1 -0
- package/dist/{chunk-HWPIFSW2.js → chunk-JSCT3CR4.js} +6 -4
- package/dist/{chunk-HWPIFSW2.js.map → chunk-JSCT3CR4.js.map} +1 -1
- package/dist/chunk-JWBCY4NC.js +330 -0
- package/dist/chunk-JWBCY4NC.js.map +1 -0
- package/dist/chunk-K2AUJHVP.js +2251 -0
- package/dist/chunk-K2AUJHVP.js.map +1 -0
- package/dist/chunk-MJ2NKQQL.js +119 -0
- package/dist/chunk-MJ2NKQQL.js.map +1 -0
- package/dist/chunk-N6UR7YVY.js +198 -0
- package/dist/chunk-N6UR7YVY.js.map +1 -0
- package/dist/chunk-NC6S43JJ.js +456 -0
- package/dist/chunk-NC6S43JJ.js.map +1 -0
- package/dist/chunk-OFVJBJXR.js +98 -0
- package/dist/chunk-OFVJBJXR.js.map +1 -0
- package/dist/chunk-OHISZPOJ.js +97 -0
- package/dist/chunk-OHISZPOJ.js.map +1 -0
- package/dist/chunk-OU5CQKNW.js +102 -0
- package/dist/chunk-OU5CQKNW.js.map +1 -0
- package/dist/{chunk-XOFWRC73.js → chunk-PF7GRZMW.js} +316 -21
- package/dist/chunk-PF7GRZMW.js.map +1 -0
- package/dist/{chunk-5DJTTKX3.js → chunk-PHOUUNK7.js} +74 -111
- package/dist/chunk-PHOUUNK7.js.map +1 -0
- package/dist/chunk-RNHBMHKA.js +1665 -0
- package/dist/chunk-RNHBMHKA.js.map +1 -0
- package/dist/chunk-SX52TAR4.js +110 -0
- package/dist/chunk-SX52TAR4.js.map +1 -0
- package/dist/{chunk-H4RVA4VE.js → chunk-VYPWMZ6H.js} +2 -2
- package/dist/chunk-WBZOVTYK.js +171 -0
- package/dist/chunk-WBZOVTYK.js.map +1 -0
- package/dist/chunk-WKNUIZOY.js +354 -0
- package/dist/chunk-WKNUIZOY.js.map +1 -0
- package/dist/chunk-X3VMZYBT.js +713 -0
- package/dist/chunk-X3VMZYBT.js.map +1 -0
- package/dist/chunk-X5R3GL6H.js +525 -0
- package/dist/chunk-X5R3GL6H.js.map +1 -0
- package/dist/chunk-XGPU467M.js +136 -0
- package/dist/chunk-XGPU467M.js.map +1 -0
- package/dist/compat/index.cjs +7656 -0
- package/dist/compat/index.cjs.map +1 -0
- package/dist/compat/index.d.cts +18 -0
- package/dist/compat/index.d.ts +18 -0
- package/dist/compat/index.js +50 -0
- package/dist/compat/index.js.map +1 -0
- package/dist/compat/jotai/index.cjs +2048 -0
- package/dist/compat/jotai/index.cjs.map +1 -0
- package/dist/compat/jotai/index.d.cts +2 -0
- package/dist/compat/jotai/index.d.ts +2 -0
- package/dist/compat/jotai/index.js +9 -0
- package/dist/compat/jotai/index.js.map +1 -0
- package/dist/compat/nanostores/index.cjs +2175 -0
- package/dist/compat/nanostores/index.cjs.map +1 -0
- package/dist/compat/nanostores/index.d.cts +2 -0
- package/dist/compat/nanostores/index.d.ts +2 -0
- package/dist/compat/nanostores/index.js +23 -0
- package/dist/compat/nanostores/index.js.map +1 -0
- package/dist/compat/nestjs/index.cjs +350 -16
- package/dist/compat/nestjs/index.cjs.map +1 -1
- package/dist/compat/nestjs/index.d.cts +6 -6
- package/dist/compat/nestjs/index.d.ts +6 -6
- package/dist/compat/nestjs/index.js +11 -9
- package/dist/compat/react/index.cjs +141 -0
- package/dist/compat/react/index.cjs.map +1 -0
- package/dist/compat/react/index.d.cts +2 -0
- package/dist/compat/react/index.d.ts +2 -0
- package/dist/compat/react/index.js +12 -0
- package/dist/compat/react/index.js.map +1 -0
- package/dist/compat/solid/index.cjs +128 -0
- package/dist/compat/solid/index.cjs.map +1 -0
- package/dist/compat/solid/index.d.cts +2 -0
- package/dist/compat/solid/index.d.ts +2 -0
- package/dist/compat/solid/index.js +12 -0
- package/dist/compat/solid/index.js.map +1 -0
- package/dist/compat/svelte/index.cjs +131 -0
- package/dist/compat/svelte/index.cjs.map +1 -0
- package/dist/compat/svelte/index.d.cts +2 -0
- package/dist/compat/svelte/index.d.ts +2 -0
- package/dist/compat/svelte/index.js +12 -0
- package/dist/compat/svelte/index.js.map +1 -0
- package/dist/compat/vue/index.cjs +146 -0
- package/dist/compat/vue/index.cjs.map +1 -0
- package/dist/compat/vue/index.d.cts +3 -0
- package/dist/compat/vue/index.d.ts +3 -0
- package/dist/compat/vue/index.js +12 -0
- package/dist/compat/vue/index.js.map +1 -0
- package/dist/compat/zustand/index.cjs +4931 -0
- package/dist/compat/zustand/index.cjs.map +1 -0
- package/dist/compat/zustand/index.d.cts +5 -0
- package/dist/compat/zustand/index.d.ts +5 -0
- package/dist/compat/zustand/index.js +12 -0
- package/dist/compat/zustand/index.js.map +1 -0
- package/dist/composite-C7PcQvcs.d.cts +303 -0
- package/dist/composite-aUCvjZVR.d.ts +303 -0
- package/dist/core/index.cjs +53 -4
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +4 -3
- package/dist/core/index.d.ts +4 -3
- package/dist/core/index.js +26 -24
- package/dist/demo-shell-BDkOptd6.d.ts +102 -0
- package/dist/demo-shell-Crid1WdR.d.cts +102 -0
- package/dist/extra/index.cjs +222 -110
- package/dist/extra/index.cjs.map +1 -1
- package/dist/extra/index.d.cts +6 -4
- package/dist/extra/index.d.ts +6 -4
- package/dist/extra/index.js +72 -65
- package/dist/extra/sources.cjs +2486 -0
- package/dist/extra/sources.cjs.map +1 -0
- package/dist/extra/sources.d.cts +465 -0
- package/dist/extra/sources.d.ts +465 -0
- package/dist/extra/sources.js +57 -0
- package/dist/extra/sources.js.map +1 -0
- package/dist/graph/index.cjs +408 -14
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.d.cts +5 -5
- package/dist/graph/index.d.ts +5 -5
- package/dist/graph/index.js +13 -5
- package/dist/{graph-D-3JIQme.d.cts → graph-CCwGKLCm.d.ts} +195 -4
- package/dist/{graph-B6NFqv3z.d.ts → graph-DNCrvZSn.d.cts} +195 -4
- package/dist/index-3lsddbbS.d.ts +86 -0
- package/dist/index-B1tloyhO.d.cts +34 -0
- package/dist/{index-CYkjxu3s.d.ts → index-B6D3QNSA.d.ts} +33 -4
- package/dist/index-B6EhDnjH.d.cts +37 -0
- package/dist/index-B9B7_HEY.d.ts +37 -0
- package/dist/{index-Ds23Wvou.d.ts → index-BHlKbUwO.d.cts} +131 -883
- package/dist/{index-DiobMNwE.d.ts → index-BPVt8kqc.d.ts} +3 -3
- package/dist/index-BaSM3aYt.d.ts +195 -0
- package/dist/index-BuEoe-Qu.d.ts +121 -0
- package/dist/{index-Ch0IpIO0.d.cts → index-BwfLUNw4.d.ts} +131 -883
- package/dist/index-ByQxazQJ.d.cts +86 -0
- package/dist/index-C0svESO4.d.ts +127 -0
- package/dist/{index-OXImXMq6.d.ts → index-C8oil6M6.d.ts} +18 -196
- package/dist/{index-DKE1EATr.d.cts → index-CI3DprxP.d.cts} +18 -196
- package/dist/{index-AMWewNDe.d.cts → index-CO8uBlUh.d.cts} +33 -4
- package/dist/index-CxFrXH4m.d.ts +45 -0
- package/dist/index-D8wS_PeY.d.cts +121 -0
- package/dist/index-DO_6JN9Z.d.cts +127 -0
- package/dist/index-DVGiGFGT.d.cts +195 -0
- package/dist/index-DYme44FM.d.cts +44 -0
- package/dist/{index-J7Kc0oIQ.d.cts → index-DlLp-2Xn.d.cts} +3 -3
- package/dist/index-Dzk2hrlR.d.ts +44 -0
- package/dist/index-VHqptjhu.d.cts +45 -0
- package/dist/index-VdHQMPy1.d.ts +36 -0
- package/dist/index-Xi3u0HCQ.d.cts +36 -0
- package/dist/index-wEn0eFe8.d.ts +34 -0
- package/dist/index.cjs +1780 -176
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +784 -2082
- package/dist/index.d.ts +784 -2082
- package/dist/index.js +955 -4349
- package/dist/index.js.map +1 -1
- package/dist/memory-C6Z2tGpC.d.cts +139 -0
- package/dist/memory-li6FL5RM.d.ts +139 -0
- package/dist/messaging-Gt4LPbyA.d.cts +269 -0
- package/dist/messaging-XDoYablx.d.ts +269 -0
- package/dist/{meta-DWbkoq1s.d.cts → meta-BxCA7rcr.d.cts} +1 -1
- package/dist/{meta-CnkLA_43.d.ts → meta-CbznRPYJ.d.ts} +1 -1
- package/dist/{node-B-f-Lu-k.d.cts → node-BmerH3kS.d.cts} +26 -1
- package/dist/{node-B-f-Lu-k.d.ts → node-BmerH3kS.d.ts} +26 -1
- package/dist/{observable-uP-wy_uK.d.ts → observable-BgGUwcqp.d.ts} +1 -1
- package/dist/{observable-DBnrwcar.d.cts → observable-DJt_AxzQ.d.cts} +1 -1
- package/dist/patterns/ai.cjs +7930 -0
- package/dist/patterns/ai.cjs.map +1 -0
- package/dist/patterns/ai.d.cts +10 -0
- package/dist/patterns/ai.d.ts +10 -0
- package/dist/patterns/ai.js +71 -0
- package/dist/patterns/ai.js.map +1 -0
- package/dist/patterns/audit.cjs +5805 -0
- package/dist/patterns/audit.cjs.map +1 -0
- package/dist/patterns/audit.d.cts +6 -0
- package/dist/patterns/audit.d.ts +6 -0
- package/dist/patterns/audit.js +29 -0
- package/dist/patterns/audit.js.map +1 -0
- package/dist/patterns/demo-shell.cjs +5604 -0
- package/dist/patterns/demo-shell.cjs.map +1 -0
- package/dist/patterns/demo-shell.d.cts +6 -0
- package/dist/patterns/demo-shell.d.ts +6 -0
- package/dist/patterns/demo-shell.js +15 -0
- package/dist/patterns/demo-shell.js.map +1 -0
- package/dist/patterns/memory.cjs +5283 -0
- package/dist/patterns/memory.cjs.map +1 -0
- package/dist/patterns/memory.d.cts +5 -0
- package/dist/patterns/memory.d.ts +5 -0
- package/dist/patterns/memory.js +20 -0
- package/dist/patterns/memory.js.map +1 -0
- package/dist/patterns/reactive-layout/index.cjs +355 -13
- package/dist/patterns/reactive-layout/index.cjs.map +1 -1
- package/dist/patterns/reactive-layout/index.d.cts +6 -5
- package/dist/patterns/reactive-layout/index.d.ts +6 -5
- package/dist/patterns/reactive-layout/index.js +15 -12
- package/dist/reactive-layout-MQP--J3F.d.cts +183 -0
- package/dist/reactive-layout-u5Ulnqag.d.ts +183 -0
- package/dist/{storage-BuTdpCI1.d.cts → storage-CMjUUuxn.d.ts} +10 -2
- package/dist/{storage-F2X1U1x0.d.ts → storage-DdWlZo6U.d.cts} +10 -2
- package/dist/sugar-CCOxXK1e.d.ts +201 -0
- package/dist/sugar-D02n5JjF.d.cts +201 -0
- package/package.json +63 -3
- package/dist/chunk-5DJTTKX3.js.map +0 -1
- package/dist/chunk-IAHGTNOZ.js.map +0 -1
- package/dist/chunk-L2GLW2U7.js.map +0 -1
- package/dist/chunk-MW4VAKAO.js +0 -47
- package/dist/chunk-MW4VAKAO.js.map +0 -1
- package/dist/chunk-PY4XCDLR.js.map +0 -1
- package/dist/chunk-TKE3JGOH.js.map +0 -1
- package/dist/chunk-XOFWRC73.js.map +0 -1
- package/dist/index-BJB7t9gg.d.cts +0 -392
- package/dist/index-C-TXEa7C.d.ts +0 -392
- /package/dist/{chunk-H4RVA4VE.js.map → chunk-VYPWMZ6H.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/patterns/reactive-layout/reactive-layout.ts"],"sourcesContent":["/**\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 { monotonicNs } from \"../../core/clock.js\";\nimport type { Node } from \"../../core/node.js\";\nimport { node } from \"../../core/node.js\";\nimport { derived, state } from \"../../core/sugar.js\";\nimport { Graph } from \"../../graph/graph.js\";\nimport { emitToMeta } from \"../_internal.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/**\n * A position within `PreparedSegment[]` — segment + grapheme offset.\n * `graphemeIndex: 0` at segment boundaries.\n *\n * Used by {@link layoutNextLine} for cursor-based line walking; needed when\n * lines have varying widths (multi-column flow, text wrapping around obstacles).\n */\nexport type LayoutCursor = {\n\tsegmentIndex: number;\n\tgraphemeIndex: number;\n};\n\n/** A horizontal span `[left, right]` in pixels — used by flow-layout slot carving. */\nexport type Interval = { left: number; right: number };\n\n/** Result of a single `layoutNextLine` call. */\nexport type LayoutNextLineResult = {\n\ttext: string;\n\twidth: number;\n\tstart: LayoutCursor;\n\tend: LayoutCursor;\n};\n\n/** Optional context for `layoutNextLine` — enables soft-hyphen visible-hyphen rendering. */\nexport type LayoutNextLineContext = {\n\tadapter?: MeasurementAdapter;\n\tfont?: string;\n\tcache?: Map<string, Map<string, 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\tconst raw = adapter.measureSegment(seg, font).width;\n\t\t\t// Coerce adapter misbehavior (NaN / Infinity / negative) to 0 — downstream\n\t\t\t// arithmetic would propagate NaN widths, breaking line-break decisions and\n\t\t\t// rendering. Cached so the coercion happens once per (font, segment).\n\t\t\tw = Number.isFinite(raw) && raw >= 0 ? raw : 0;\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// Cursor-based single-line layout (for multi-column flow / shape wrapping)\n// ---------------------------------------------------------------------------\n\nfunction canBreakAfter(kind: SegmentBreakKind): boolean {\n\treturn kind === \"space\" || kind === \"zero-width-break\" || kind === \"soft-hyphen\";\n}\n\n// Module-scoped segmenter — `Intl.Segmenter` construction is non-trivial and\n// its `segment()` method is stateless per-call, so one shared instance is safe\n// and avoids ~N-per-frame allocations when many lines cross segment boundaries.\nlet _graphemeSegmenter: Intl.Segmenter | null = null;\nfunction graphemeSegmenter(): Intl.Segmenter {\n\tif (_graphemeSegmenter === null) {\n\t\t_graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: \"grapheme\" });\n\t}\n\treturn _graphemeSegmenter;\n}\n\nfunction sliceSegmentText(seg: PreparedSegment, startG: number, endG: number): string {\n\tif (startG === 0 && endG < 0) return seg.text;\n\tconst graphemes = [...graphemeSegmenter().segment(seg.text)].map((g) => g.segment);\n\tconst stop = endG < 0 ? graphemes.length : endG;\n\treturn graphemes.slice(startG, stop).join(\"\");\n}\n\nfunction buildLineText(\n\tsegments: PreparedSegment[],\n\tstartSeg: number,\n\tstartG: number,\n\tendSeg: number,\n\tendG: number,\n\tappendHyphen: boolean,\n): string {\n\tlet text = \"\";\n\tfor (let i = startSeg; i < endSeg; i++) {\n\t\tconst seg = segments[i]!;\n\t\tif (seg.kind === \"soft-hyphen\" || seg.kind === \"hard-break\") continue;\n\t\tif (i === startSeg && startG > 0) {\n\t\t\ttext += sliceSegmentText(seg, startG, -1);\n\t\t} else {\n\t\t\ttext += seg.text;\n\t\t}\n\t}\n\tif (endG > 0 && endSeg < segments.length) {\n\t\tconst seg = segments[endSeg]!;\n\t\tconst from = startSeg === endSeg ? startG : 0;\n\t\ttext += sliceSegmentText(seg, from, endG);\n\t}\n\tif (appendHyphen) text += \"-\";\n\treturn text;\n}\n\nfunction resolveHyphenWidth(ctx: LayoutNextLineContext | undefined): number {\n\tif (!ctx || !ctx.adapter || !ctx.font) return 0;\n\tconst cache = ctx.cache;\n\tif (cache) {\n\t\tlet fc = cache.get(ctx.font);\n\t\tif (!fc) {\n\t\t\tfc = new Map<string, number>();\n\t\t\tcache.set(ctx.font, fc);\n\t\t}\n\t\tlet hw = fc.get(\"-\");\n\t\tif (hw === undefined) {\n\t\t\thw = ctx.adapter.measureSegment(\"-\", ctx.font).width;\n\t\t\tfc.set(\"-\", hw);\n\t\t}\n\t\treturn hw;\n\t}\n\treturn ctx.adapter.measureSegment(\"-\", ctx.font).width;\n}\n\n/**\n * Lay out the next single line starting from `cursor`, fitting into `slotWidth`.\n *\n * Unlike `computeLineBreaks`, which consumes whole text with one `maxWidth`,\n * this is the cursor-based primitive needed when successive lines have different\n * widths (multi-column flow, text wrapping around shape obstacles, mixed\n * column+pullquote layouts).\n *\n * Returns `null` when the cursor is past all segments (text exhausted).\n * At a hard-break with no preceding content, returns an empty line and advances\n * the cursor past the break so the caller can continue.\n *\n * ```ts\n * let cursor: LayoutCursor = { segmentIndex: 0, graphemeIndex: 0 };\n * while (true) {\n * const line = layoutNextLine(segments, cursor, availableWidth);\n * if (line === null) break;\n * render(line);\n * cursor = line.end;\n * }\n * ```\n */\nexport function layoutNextLine(\n\tsegments: PreparedSegment[],\n\tcursor: LayoutCursor,\n\tslotWidth: number,\n\tctx?: LayoutNextLineContext,\n): LayoutNextLineResult | null {\n\tlet i = cursor.segmentIndex;\n\tconst initialG = cursor.graphemeIndex;\n\n\tif (i >= segments.length) return null;\n\n\tif (initialG === 0) {\n\t\twhile (i < segments.length) {\n\t\t\tconst seg = segments[i]!;\n\t\t\tif (seg.kind === \"hard-break\") {\n\t\t\t\treturn {\n\t\t\t\t\ttext: \"\",\n\t\t\t\t\twidth: 0,\n\t\t\t\t\tstart: { segmentIndex: cursor.segmentIndex, graphemeIndex: 0 },\n\t\t\t\t\tend: { segmentIndex: i + 1, graphemeIndex: 0 },\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (seg.kind === \"space\" || seg.kind === \"zero-width-break\" || seg.kind === \"soft-hyphen\") {\n\t\t\t\ti += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tif (i >= segments.length) return null;\n\t}\n\n\tconst hyphenWidth = resolveHyphenWidth(ctx);\n\n\tconst startSeg = i;\n\tconst startG = i === cursor.segmentIndex ? initialG : 0;\n\n\tlet lineW = 0;\n\tlet lineEndSeg = startSeg;\n\tlet lineEndG = 0;\n\tlet hasContent = false;\n\tlet pendingBreakSeg = -1;\n\tlet pendingBreakG = 0;\n\tlet pendingBreakWidth = 0;\n\tlet pendingBreakSoftHyphen = false;\n\n\tconst recordPending = (\n\t\tsIdx: number,\n\t\tgIdx: number,\n\t\twidthAtBreak: number,\n\t\tkind: SegmentBreakKind,\n\t): void => {\n\t\tpendingBreakSeg = sIdx;\n\t\tpendingBreakG = gIdx;\n\t\tpendingBreakWidth = widthAtBreak;\n\t\tpendingBreakSoftHyphen = kind === \"soft-hyphen\";\n\t};\n\n\tconst consumeBreakable = (segIdx: number, gStart: number, gWidths: number[]): boolean => {\n\t\tfor (let g = gStart; g < gWidths.length; g++) {\n\t\t\tconst gw = gWidths[g]!;\n\t\t\tif (!hasContent) {\n\t\t\t\tlineW = gw;\n\t\t\t\tlineEndSeg = segIdx;\n\t\t\t\tlineEndG = g + 1;\n\t\t\t\thasContent = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (lineW + gw > slotWidth + 0.005) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\tlineW += gw;\n\t\t\tlineEndSeg = segIdx;\n\t\t\tlineEndG = g + 1;\n\t\t}\n\t\tif (lineEndSeg === segIdx && lineEndG === gWidths.length) {\n\t\t\tlineEndSeg = segIdx + 1;\n\t\t\tlineEndG = 0;\n\t\t}\n\t\treturn false;\n\t};\n\n\tif (startG > 0 && startSeg < segments.length) {\n\t\tconst seg = segments[startSeg]!;\n\t\tif (seg.graphemeWidths) {\n\t\t\tconst overflowed = consumeBreakable(startSeg, startG, seg.graphemeWidths);\n\t\t\tif (overflowed) {\n\t\t\t\tconst text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);\n\t\t\t\treturn {\n\t\t\t\t\ttext,\n\t\t\t\t\twidth: lineW,\n\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t\t};\n\t\t\t}\n\t\t\ti = lineEndSeg;\n\t\t} else {\n\t\t\t// Mid-segment cursor on a non-breakable segment is an invariant\n\t\t\t// violation (cursor should only advance to a grapheme boundary via\n\t\t\t// `consumeBreakable` on a segment that HAS graphemeWidths). Treat as\n\t\t\t// segment-start so the caller gets well-formed output instead of\n\t\t\t// silently re-including the prefix.\n\t\t\t//\n\t\t\t// Not reachable through `computeFlowLines` but possible with\n\t\t\t// externally-constructed cursors.\n\t\t}\n\t}\n\n\tfor (; i < segments.length; ) {\n\t\tconst seg = segments[i]!;\n\n\t\tif (seg.kind === \"hard-break\") {\n\t\t\tif (hasContent) {\n\t\t\t\tconst endsAtSoftHyphen = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === \"soft-hyphen\";\n\t\t\t\tconst text = buildLineText(\n\t\t\t\t\tsegments,\n\t\t\t\t\tstartSeg,\n\t\t\t\t\tstartG,\n\t\t\t\t\tlineEndSeg,\n\t\t\t\t\tlineEndG,\n\t\t\t\t\tendsAtSoftHyphen,\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\ttext,\n\t\t\t\t\twidth: lineW + (endsAtSoftHyphen ? hyphenWidth : 0),\n\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\ttext: \"\",\n\t\t\t\twidth: 0,\n\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\tend: { segmentIndex: i + 1, graphemeIndex: 0 },\n\t\t\t};\n\t\t}\n\n\t\tconst w = seg.width;\n\n\t\tif (!hasContent) {\n\t\t\tif (w > slotWidth && seg.graphemeWidths) {\n\t\t\t\tconst overflowed = consumeBreakable(i, 0, seg.graphemeWidths);\n\t\t\t\tif (overflowed) {\n\t\t\t\t\tconst text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttext,\n\t\t\t\t\t\twidth: lineW,\n\t\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\t// No `recordPending` here: segments with `graphemeWidths` are always\n\t\t\t\t// `kind === \"text\"` (see `analyzeAndMeasure`), which is not a break-\n\t\t\t\t// after kind, so the check would always fail.\n\t\t\t\ti = lineEndSeg;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tlineW = w;\n\t\t\tlineEndSeg = i + 1;\n\t\t\tlineEndG = 0;\n\t\t\thasContent = true;\n\t\t\tif (canBreakAfter(seg.kind)) {\n\t\t\t\trecordPending(i + 1, 0, seg.kind === \"space\" ? lineW - w : lineW, seg.kind);\n\t\t\t}\n\t\t\ti += 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst newW = lineW + w;\n\n\t\tif (newW > slotWidth + 0.005) {\n\t\t\tif (canBreakAfter(seg.kind)) {\n\t\t\t\t// `lineW` is carried from before this segment — no mutation needed;\n\t\t\t\t// trailing space/soft-hyphen width is excluded from the line width.\n\t\t\t\tlineEndSeg = i + 1;\n\t\t\t\tlineEndG = 0;\n\t\t\t\tconst endsAtSoftHyphen = seg.kind === \"soft-hyphen\";\n\t\t\t\tconst finalWidth =\n\t\t\t\t\tseg.kind === \"space\" ? lineW : lineW + (endsAtSoftHyphen ? hyphenWidth : 0);\n\t\t\t\tconst text = buildLineText(\n\t\t\t\t\tsegments,\n\t\t\t\t\tstartSeg,\n\t\t\t\t\tstartG,\n\t\t\t\t\tlineEndSeg,\n\t\t\t\t\tlineEndG,\n\t\t\t\t\tendsAtSoftHyphen,\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\ttext,\n\t\t\t\t\twidth: finalWidth,\n\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (pendingBreakSeg >= 0) {\n\t\t\t\tconst text = buildLineText(\n\t\t\t\t\tsegments,\n\t\t\t\t\tstartSeg,\n\t\t\t\t\tstartG,\n\t\t\t\t\tpendingBreakSeg,\n\t\t\t\t\tpendingBreakG,\n\t\t\t\t\tpendingBreakSoftHyphen,\n\t\t\t\t);\n\t\t\t\treturn {\n\t\t\t\t\ttext,\n\t\t\t\t\twidth: pendingBreakWidth + (pendingBreakSoftHyphen ? hyphenWidth : 0),\n\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\tend: { segmentIndex: pendingBreakSeg, graphemeIndex: pendingBreakG },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (w > slotWidth && seg.graphemeWidths) {\n\t\t\t\tconst text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);\n\t\t\t\treturn {\n\t\t\t\t\ttext,\n\t\t\t\t\twidth: lineW,\n\t\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, false);\n\t\t\treturn {\n\t\t\t\ttext,\n\t\t\t\twidth: lineW,\n\t\t\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\t\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t\t\t};\n\t\t}\n\n\t\tlineW = newW;\n\t\tlineEndSeg = i + 1;\n\t\tlineEndG = 0;\n\t\tif (canBreakAfter(seg.kind)) {\n\t\t\trecordPending(i + 1, 0, seg.kind === \"space\" ? lineW - w : lineW, seg.kind);\n\t\t}\n\t\ti += 1;\n\t}\n\n\tif (!hasContent) return null;\n\n\tconst endsAtSoftHyphen = lineEndSeg > 0 && segments[lineEndSeg - 1]?.kind === \"soft-hyphen\";\n\tconst text = buildLineText(segments, startSeg, startG, lineEndSeg, lineEndG, endsAtSoftHyphen);\n\treturn {\n\t\ttext,\n\t\twidth: lineW + (endsAtSoftHyphen ? hyphenWidth : 0),\n\t\tstart: { segmentIndex: startSeg, graphemeIndex: startG },\n\t\tend: { segmentIndex: lineEndSeg, graphemeIndex: lineEndG },\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Slot carving (flow-layout helper)\n// ---------------------------------------------------------------------------\n\n/**\n * Subtract blocked horizontal intervals from a base interval, producing\n * remaining ordered, non-overlapping slots wide enough to fit text.\n *\n * Pure geometry — no text dependency. Used by flow-layout to turn obstacle\n * intersections into per-line layout slots.\n *\n * ```ts\n * carveTextLineSlots({left: 0, right: 600}, [{left: 200, right: 280}])\n * // → [{left: 0, right: 200}, {left: 280, right: 600}]\n * ```\n */\nexport function carveTextLineSlots(\n\tbase: Interval,\n\tblocked: Interval[],\n\tminSlotWidth = 0,\n): Interval[] {\n\tlet slots: Interval[] = [base];\n\tfor (let bi = 0; bi < blocked.length; bi++) {\n\t\tconst block = blocked[bi]!;\n\t\tconst next: Interval[] = [];\n\t\tfor (let si = 0; si < slots.length; si++) {\n\t\t\tconst slot = slots[si]!;\n\t\t\tif (block.right <= slot.left || block.left >= slot.right) {\n\t\t\t\tnext.push(slot);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (block.left > slot.left) next.push({ left: slot.left, right: block.left });\n\t\t\tif (block.right < slot.right) next.push({ left: block.right, right: slot.right });\n\t\t}\n\t\tslots = next;\n\t}\n\tif (minSlotWidth > 0) {\n\t\treturn slots.filter((s) => s.right - s.left >= minSlotWidth);\n\t}\n\treturn slots;\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\t// Raw `node(...)` instead of `derived(...)` so the fn can return a\n\t// cleanup function. Core fires function-form cleanup on INVALIDATE (see\n\t// `node.ts:_updateState` INVALIDATE branch), which replaces the old v4\n\t// per-node `onMessage` hook that watched for INVALIDATE/TEARDOWN to flush\n\t// measurement caches.\n\tconst segmentsNode: Node<PreparedSegment[]> = node<PreparedSegment[]>(\n\t\t[textNode, fontNode],\n\t\t(data, actions, ctx) => {\n\t\t\tconst b0 = data[0];\n\t\t\tconst textVal = (b0 != null && b0.length > 0 ? b0.at(-1) : ctx.prevData[0]) as string;\n\t\t\tconst b1 = data[1];\n\t\t\tconst fontVal = (b1 != null && b1.length > 0 ? b1.at(-1) : ctx.prevData[1]) as string;\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` emits, deliver metrics via phase-3 deferral\n\t\t\t// so observers see the parent value first.\n\t\t\t// Forwards via `emitToMeta` (see `patterns/_internal.ts`).\n\t\t\tconst meta = segmentsNode.meta;\n\t\t\tif (meta) {\n\t\t\t\temitToMeta(meta[\"cache-hit-rate\"], hitRate);\n\t\t\t\temitToMeta(meta[\"segment-count\"], result.length);\n\t\t\t\temitToMeta(meta[\"layout-time-ns\"], elapsed);\n\t\t\t}\n\n\t\t\tactions.emit(result);\n\n\t\t\t// Function-form cleanup: fires before the next run AND on\n\t\t\t// deactivation AND on INVALIDATE. Flushes the shared measurement\n\t\t\t// cache + the adapter's own cache so stale measurements don't\n\t\t\t// persist across INVALIDATE broadcasts.\n\t\t\treturn () => {\n\t\t\t\tmeasureCache.clear();\n\t\t\t\tadapter.clearCache?.();\n\t\t\t};\n\t\t},\n\t\t{\n\t\t\tname: \"segments\",\n\t\t\tdescribeKind: \"derived\",\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\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\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"],"mappings":"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","names":["graphemeSegmenter","w","canBreakAfter","text","endsAtSoftHyphen"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DATA,
|
|
3
|
+
DIRTY,
|
|
4
|
+
RESOLVED,
|
|
5
|
+
__export
|
|
6
|
+
} from "./chunk-SX52TAR4.js";
|
|
7
|
+
|
|
8
|
+
// src/compat/solid/index.ts
|
|
9
|
+
var solid_exports = {};
|
|
10
|
+
__export(solid_exports, {
|
|
11
|
+
useStore: () => useStore,
|
|
12
|
+
useSubscribe: () => useSubscribe,
|
|
13
|
+
useSubscribeRecord: () => useSubscribeRecord
|
|
14
|
+
});
|
|
15
|
+
import { createSignal, getOwner, onCleanup } from "solid-js";
|
|
16
|
+
function useSubscribe(node) {
|
|
17
|
+
const [value, setValue] = createSignal(node.cache, { equals: false });
|
|
18
|
+
const unsub = node.subscribe(() => {
|
|
19
|
+
setValue(() => node.cache);
|
|
20
|
+
});
|
|
21
|
+
if (getOwner()) {
|
|
22
|
+
onCleanup(() => unsub());
|
|
23
|
+
} else if (typeof console !== "undefined") {
|
|
24
|
+
console.warn(
|
|
25
|
+
"[graphrefly-ts] useSubscribe called outside a Solid reactive owner \u2014 subscription will not be auto-disposed."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function useStore(node) {
|
|
31
|
+
const value = useSubscribe(node);
|
|
32
|
+
const setter = (v) => {
|
|
33
|
+
node.down([[DIRTY], [DATA, v]]);
|
|
34
|
+
};
|
|
35
|
+
return [value, setter];
|
|
36
|
+
}
|
|
37
|
+
function useSubscribeRecord(keysNode, factory) {
|
|
38
|
+
const [value, setValue] = createSignal({}, { equals: false });
|
|
39
|
+
let entrySubs = [];
|
|
40
|
+
const cleanupEntries = () => {
|
|
41
|
+
for (const unsub of entrySubs) unsub();
|
|
42
|
+
entrySubs = [];
|
|
43
|
+
};
|
|
44
|
+
const buildSnapshot = () => {
|
|
45
|
+
const snap = {};
|
|
46
|
+
for (const key of keysNode.cache ?? []) {
|
|
47
|
+
const nodes = factory(key);
|
|
48
|
+
const values = {};
|
|
49
|
+
for (const field of Object.keys(nodes)) {
|
|
50
|
+
values[field] = nodes[field].cache;
|
|
51
|
+
}
|
|
52
|
+
snap[key] = values;
|
|
53
|
+
}
|
|
54
|
+
return snap;
|
|
55
|
+
};
|
|
56
|
+
const sync = (nextKeys) => {
|
|
57
|
+
cleanupEntries();
|
|
58
|
+
for (const key of nextKeys) {
|
|
59
|
+
const nodes = factory(key);
|
|
60
|
+
for (const field of Object.keys(nodes)) {
|
|
61
|
+
const unsub = nodes[field].subscribe(() => {
|
|
62
|
+
setValue(() => buildSnapshot());
|
|
63
|
+
});
|
|
64
|
+
entrySubs.push(unsub);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
setValue(() => buildSnapshot());
|
|
68
|
+
};
|
|
69
|
+
const keysUnsub = keysNode.subscribe((msgs) => {
|
|
70
|
+
if (msgs.some((m) => m[0] === DATA || m[0] === RESOLVED)) {
|
|
71
|
+
sync(keysNode.cache ?? []);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
sync(keysNode.cache ?? []);
|
|
75
|
+
if (getOwner()) {
|
|
76
|
+
onCleanup(() => {
|
|
77
|
+
keysUnsub();
|
|
78
|
+
cleanupEntries();
|
|
79
|
+
});
|
|
80
|
+
} else if (typeof console !== "undefined") {
|
|
81
|
+
console.warn(
|
|
82
|
+
"[graphrefly-ts] useSubscribeRecord called outside a Solid reactive owner \u2014 subscription will not be auto-disposed."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
useSubscribe,
|
|
90
|
+
useStore,
|
|
91
|
+
useSubscribeRecord,
|
|
92
|
+
solid_exports
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=chunk-HXZEYDUR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compat/solid/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Solid bindings — useSubscribe / useStore\n// ---------------------------------------------------------------------------\n// Bridges GraphReFly nodes into Solid reactivity via createSignal.\n// Works with any Node<T>, including companion nodes (node.meta.status).\n//\n// Usage:\n// import { useSubscribe, useStore } from '@graphrefly/graphrefly-ts/compat/solid';\n// // Optional peer install (only for this adapter): pnpm add solid-js\n// const status = useSubscribe(wsStatusNode); // Accessor<string | undefined>\n// const [count, setCount] = useStore(countNode); // [Accessor<number | undefined>, Setter]\n// ---------------------------------------------------------------------------\n\nimport { createSignal, getOwner, onCleanup } from \"solid-js\";\nimport { DATA, DIRTY, type Messages, RESOLVED } from \"../../core/messages.js\";\nimport type { Node } from \"../../core/node.js\";\n\n/** Solid accessor function — returns current value when called. */\nexport type Accessor<T> = () => T;\n\n/**\n * Subscribe to a `Node<T>` as a Solid signal. Auto-cleans up with the owning scope.\n * Subscription lifecycle is tied to Solid scope cleanup (not node terminal messages).\n */\nexport function useSubscribe<T>(node: Node<T>): Accessor<T | undefined | null> {\n\tconst [value, setValue] = createSignal(node.cache, { equals: false });\n\n\tconst unsub = node.subscribe(() => {\n\t\tsetValue(() => node.cache);\n\t});\n\n\tif (getOwner()) {\n\t\tonCleanup(() => unsub());\n\t} else if (typeof console !== \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[graphrefly-ts] useSubscribe called outside a Solid reactive owner — subscription will not be auto-disposed.\",\n\t\t);\n\t}\n\n\treturn value;\n}\n\n/**\n * Bind a writable `Node<T>` as a Solid resource tuple `[accessor, setter]`.\n * Setter always forwards `[[DIRTY], [DATA, value]]`, including `value === undefined`.\n * Subscription lifecycle is tied to Solid scope cleanup (not node terminal messages).\n */\nexport function useStore<T>(node: Node<T>): [Accessor<T | undefined | null>, (v: T) => void] {\n\tconst value = useSubscribe(node);\n\tconst setter = (v: T) => {\n\t\tnode.down([[DIRTY], [DATA, v]]);\n\t};\n\treturn [value, setter];\n}\n\n/** Maps a key to an object of nodes. Used by `useSubscribeRecord`. */\nexport type NodeFactory<K, R extends Record<string, any>> = (key: K) => {\n\t[P in keyof R]: Node<R[P]>;\n};\n\n/**\n * Subscribe to a dynamic set of keyed node records as a Solid accessor.\n * Re-subscribes all per-key fields whenever `keys` changes.\n * Key re-sync is gated to settled batches (`messageTier >= 3`) to avoid DIRTY-phase churn.\n */\nexport function useSubscribeRecord<K extends string, R extends Record<string, any>>(\n\tkeysNode: Node<K[]>,\n\tfactory: NodeFactory<K, R>,\n): Accessor<Record<K, R>> {\n\tconst [value, setValue] = createSignal({} as Record<K, R>, { equals: false });\n\tlet entrySubs: Array<() => void> = [];\n\n\tconst cleanupEntries = () => {\n\t\tfor (const unsub of entrySubs) unsub();\n\t\tentrySubs = [];\n\t};\n\n\tconst buildSnapshot = (): Record<K, R> => {\n\t\tconst snap = {} as Record<K, R>;\n\t\tfor (const key of keysNode.cache ?? []) {\n\t\t\tconst nodes = factory(key);\n\t\t\tconst values = {} as R;\n\t\t\tfor (const field of Object.keys(nodes) as (keyof R)[]) {\n\t\t\t\tvalues[field] = nodes[field].cache as R[keyof R];\n\t\t\t}\n\t\t\tsnap[key] = values;\n\t\t}\n\t\treturn snap;\n\t};\n\n\tconst sync = (nextKeys: K[]) => {\n\t\tcleanupEntries();\n\t\tfor (const key of nextKeys) {\n\t\t\tconst nodes = factory(key);\n\t\t\tfor (const field of Object.keys(nodes) as (keyof R)[]) {\n\t\t\t\tconst unsub = nodes[field].subscribe(() => {\n\t\t\t\t\tsetValue(() => buildSnapshot());\n\t\t\t\t});\n\t\t\t\tentrySubs.push(unsub);\n\t\t\t}\n\t\t}\n\t\tsetValue(() => buildSnapshot());\n\t};\n\n\tconst keysUnsub = keysNode.subscribe((msgs: Messages) => {\n\t\tif (msgs.some((m) => m[0] === DATA || m[0] === RESOLVED)) {\n\t\t\tsync(keysNode.cache ?? []);\n\t\t}\n\t});\n\tsync(keysNode.cache ?? []);\n\n\tif (getOwner()) {\n\t\tonCleanup(() => {\n\t\t\tkeysUnsub();\n\t\t\tcleanupEntries();\n\t\t});\n\t} else if (typeof console !== \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[graphrefly-ts] useSubscribeRecord called outside a Solid reactive owner — subscription will not be auto-disposed.\",\n\t\t);\n\t}\n\n\treturn value;\n}\n"],"mappings":";;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,SAAS,cAAc,UAAU,iBAAiB;AAW3C,SAAS,aAAgB,MAA+C;AAC9E,QAAM,CAAC,OAAO,QAAQ,IAAI,aAAa,KAAK,OAAO,EAAE,QAAQ,MAAM,CAAC;AAEpE,QAAM,QAAQ,KAAK,UAAU,MAAM;AAClC,aAAS,MAAM,KAAK,KAAK;AAAA,EAC1B,CAAC;AAED,MAAI,SAAS,GAAG;AACf,cAAU,MAAM,MAAM,CAAC;AAAA,EACxB,WAAW,OAAO,YAAY,aAAa;AAC1C,YAAQ;AAAA,MACP;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AAOO,SAAS,SAAY,MAAiE;AAC5F,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,SAAS,CAAC,MAAS;AACxB,SAAK,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,EAC/B;AACA,SAAO,CAAC,OAAO,MAAM;AACtB;AAYO,SAAS,mBACf,UACA,SACyB;AACzB,QAAM,CAAC,OAAO,QAAQ,IAAI,aAAa,CAAC,GAAmB,EAAE,QAAQ,MAAM,CAAC;AAC5E,MAAI,YAA+B,CAAC;AAEpC,QAAM,iBAAiB,MAAM;AAC5B,eAAW,SAAS,UAAW,OAAM;AACrC,gBAAY,CAAC;AAAA,EACd;AAEA,QAAM,gBAAgB,MAAoB;AACzC,UAAM,OAAO,CAAC;AACd,eAAW,OAAO,SAAS,SAAS,CAAC,GAAG;AACvC,YAAM,QAAQ,QAAQ,GAAG;AACzB,YAAM,SAAS,CAAC;AAChB,iBAAW,SAAS,OAAO,KAAK,KAAK,GAAkB;AACtD,eAAO,KAAK,IAAI,MAAM,KAAK,EAAE;AAAA,MAC9B;AACA,WAAK,GAAG,IAAI;AAAA,IACb;AACA,WAAO;AAAA,EACR;AAEA,QAAM,OAAO,CAAC,aAAkB;AAC/B,mBAAe;AACf,eAAW,OAAO,UAAU;AAC3B,YAAM,QAAQ,QAAQ,GAAG;AACzB,iBAAW,SAAS,OAAO,KAAK,KAAK,GAAkB;AACtD,cAAM,QAAQ,MAAM,KAAK,EAAE,UAAU,MAAM;AAC1C,mBAAS,MAAM,cAAc,CAAC;AAAA,QAC/B,CAAC;AACD,kBAAU,KAAK,KAAK;AAAA,MACrB;AAAA,IACD;AACA,aAAS,MAAM,cAAc,CAAC;AAAA,EAC/B;AAEA,QAAM,YAAY,SAAS,UAAU,CAAC,SAAmB;AACxD,QAAI,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,QAAQ,EAAE,CAAC,MAAM,QAAQ,GAAG;AACzD,WAAK,SAAS,SAAS,CAAC,CAAC;AAAA,IAC1B;AAAA,EACD,CAAC;AACD,OAAK,SAAS,SAAS,CAAC,CAAC;AAEzB,MAAI,SAAS,GAAG;AACf,cAAU,MAAM;AACf,gBAAU;AACV,qBAAe;AAAA,IAChB,CAAC;AAAA,EACF,WAAW,OAAO,YAAY,aAAa;AAC1C,YAAQ;AAAA,MACP;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;","names":[]}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DATA,
|
|
3
|
+
DIRTY,
|
|
4
|
+
RESOLVED,
|
|
5
|
+
__export
|
|
6
|
+
} from "./chunk-SX52TAR4.js";
|
|
7
|
+
|
|
8
|
+
// src/compat/react/index.ts
|
|
9
|
+
var react_exports = {};
|
|
10
|
+
__export(react_exports, {
|
|
11
|
+
useStore: () => useStore,
|
|
12
|
+
useSubscribe: () => useSubscribe,
|
|
13
|
+
useSubscribeRecord: () => useSubscribeRecord
|
|
14
|
+
});
|
|
15
|
+
import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
|
|
16
|
+
function useSubscribe(node) {
|
|
17
|
+
return useSyncExternalStore(
|
|
18
|
+
(onStoreChange) => {
|
|
19
|
+
let disposed = false;
|
|
20
|
+
const unsub = node.subscribe(() => {
|
|
21
|
+
if (!disposed) onStoreChange();
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
disposed = true;
|
|
25
|
+
unsub();
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
() => node.cache,
|
|
29
|
+
() => node.cache
|
|
30
|
+
// Server snapshot
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
function useStore(node) {
|
|
34
|
+
const value = useSubscribe(node);
|
|
35
|
+
const setter = useCallback(
|
|
36
|
+
(v) => {
|
|
37
|
+
node.down([[DIRTY], [DATA, v]]);
|
|
38
|
+
},
|
|
39
|
+
[node]
|
|
40
|
+
);
|
|
41
|
+
return [value, setter];
|
|
42
|
+
}
|
|
43
|
+
function useSubscribeRecord(keysNode, factory) {
|
|
44
|
+
const factoryRef = useRef(factory);
|
|
45
|
+
factoryRef.current = factory;
|
|
46
|
+
const store = useMemo(() => {
|
|
47
|
+
const computeSnap = () => {
|
|
48
|
+
const snap = {};
|
|
49
|
+
const keys = keysNode.cache ?? [];
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
const nodes = factoryRef.current(key);
|
|
52
|
+
const values = {};
|
|
53
|
+
for (const field of Object.keys(nodes)) {
|
|
54
|
+
values[field] = nodes[field].cache;
|
|
55
|
+
}
|
|
56
|
+
snap[key] = values;
|
|
57
|
+
}
|
|
58
|
+
return snap;
|
|
59
|
+
};
|
|
60
|
+
let currentSnapshot = computeSnap();
|
|
61
|
+
return {
|
|
62
|
+
subscribe: (onStoreChange) => {
|
|
63
|
+
let disposed = false;
|
|
64
|
+
let entrySubs = [];
|
|
65
|
+
const cleanupEntries = () => {
|
|
66
|
+
for (const unsub of entrySubs) unsub();
|
|
67
|
+
entrySubs = [];
|
|
68
|
+
};
|
|
69
|
+
const sync = (nextKeys) => {
|
|
70
|
+
cleanupEntries();
|
|
71
|
+
for (const key of nextKeys) {
|
|
72
|
+
const nodes = factoryRef.current(key);
|
|
73
|
+
for (const field of Object.keys(nodes)) {
|
|
74
|
+
const unsub = nodes[field].subscribe(() => {
|
|
75
|
+
currentSnapshot = computeSnap();
|
|
76
|
+
if (!disposed) onStoreChange();
|
|
77
|
+
});
|
|
78
|
+
entrySubs.push(unsub);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
currentSnapshot = computeSnap();
|
|
82
|
+
if (!disposed) onStoreChange();
|
|
83
|
+
};
|
|
84
|
+
const keysUnsub = keysNode.subscribe((msgs) => {
|
|
85
|
+
const hasSettled = msgs.some((m) => m[0] === DATA || m[0] === RESOLVED);
|
|
86
|
+
if (!disposed && hasSettled) sync(keysNode.cache ?? []);
|
|
87
|
+
});
|
|
88
|
+
sync(keysNode.cache ?? []);
|
|
89
|
+
return () => {
|
|
90
|
+
disposed = true;
|
|
91
|
+
keysUnsub();
|
|
92
|
+
cleanupEntries();
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
getSnapshot: () => currentSnapshot
|
|
96
|
+
};
|
|
97
|
+
}, [keysNode]);
|
|
98
|
+
return useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
useSubscribe,
|
|
103
|
+
useStore,
|
|
104
|
+
useSubscribeRecord,
|
|
105
|
+
react_exports
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=chunk-J22W6HV3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compat/react/index.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// React bindings — useStore / useSubscribe\n// ---------------------------------------------------------------------------\n// Bridges GraphReFly nodes into React via useSyncExternalStore.\n// Works with any Node<T>, including companion nodes (node.meta.status).\n//\n// Usage:\n// import { useStore, useSubscribe } from '@graphrefly/graphrefly-ts/compat/react';\n// // Optional peer install (only for this adapter): pnpm add react react-dom\n// const value = useSubscribe(myNode); // T | undefined (read-only)\n// const [count, setCount] = useStore(counter); // [T | undefined, setter]\n// ---------------------------------------------------------------------------\n\nimport { useCallback, useMemo, useRef, useSyncExternalStore } from \"react\";\nimport { DATA, DIRTY, type Messages, RESOLVED } from \"../../core/messages.js\";\nimport type { Node } from \"../../core/node.js\";\n\n/**\n * Subscribe to a read-only `Node<T>` as a React value. Re-renders on node value settlement.\n * Subscription lifecycle is tied to React mount/unmount (not node terminal messages).\n *\n * @param node - Any `Node<T>`.\n * @returns `T | undefined` — the current node value, kept in sync via `useSyncExternalStore`.\n */\nexport function useSubscribe<T>(node: Node<T>): T | undefined | null {\n\treturn useSyncExternalStore(\n\t\t(onStoreChange) => {\n\t\t\tlet disposed = false;\n\t\t\tconst unsub = node.subscribe(() => {\n\t\t\t\tif (!disposed) onStoreChange();\n\t\t\t});\n\t\t\treturn () => {\n\t\t\t\tdisposed = true;\n\t\t\t\tunsub();\n\t\t\t};\n\t\t},\n\t\t() => node.cache,\n\t\t() => node.cache, // Server snapshot\n\t);\n}\n\n/**\n * Bind a writable `Node<T>` as a React `[value, setter]` tuple.\n * Setting the value always pushes `[[DIRTY], [DATA, value]]`, including `value === undefined`.\n * Subscription lifecycle is tied to React mount/unmount (not node terminal messages).\n *\n * @param node - A `Node<T>` (e.g. state node).\n * @returns `[T | undefined, (value: T) => void]` — current value and setter function.\n */\nexport function useStore<T>(node: Node<T>): [T | undefined | null, (value: T) => void] {\n\tconst value = useSubscribe(node);\n\tconst setter = useCallback(\n\t\t(v: T) => {\n\t\t\tnode.down([[DIRTY], [DATA, v]]);\n\t\t},\n\t\t[node],\n\t);\n\treturn [value, setter];\n}\n\n/** Maps a key to an object of nodes. Used by `useSubscribeRecord`. */\nexport type NodeFactory<K, R extends Record<string, any>> = (key: K) => {\n\t[P in keyof R]: Node<R[P]>;\n};\n\n/**\n * Subscribe to a dynamic set of keyed node records.\n * Re-subscribes all per-key fields whenever `keysNode` changes.\n * Key re-sync is gated to settled batches (`messageTier >= 3`) to avoid DIRTY-phase churn.\n * Guaranteed to clean up strictly with React hook lifecycle, utilizing no global mappings.\n *\n * @param keysNode - Node of current keys (e.g. node IDs)\n * @param factory - Function returning `{ [field]: Node<V> }` for each key.\n * @returns `Record<K, R>` — snapshot of resolved values for all keys.\n */\nexport function useSubscribeRecord<K extends string, R extends Record<string, any>>(\n\tkeysNode: Node<K[]>,\n\tfactory: NodeFactory<K, R>,\n): Record<K, R> {\n\tconst factoryRef = useRef(factory);\n\tfactoryRef.current = factory;\n\n\tconst store = useMemo(() => {\n\t\tconst computeSnap = () => {\n\t\t\tconst snap = {} as Record<K, R>;\n\t\t\tconst keys = keysNode.cache ?? [];\n\t\t\tfor (const key of keys) {\n\t\t\t\tconst nodes = factoryRef.current(key);\n\t\t\t\tconst values = {} as R;\n\t\t\t\tfor (const field of Object.keys(nodes) as (keyof R)[]) {\n\t\t\t\t\tvalues[field] = nodes[field].cache as R[keyof R];\n\t\t\t\t}\n\t\t\t\tsnap[key] = values;\n\t\t\t}\n\t\t\treturn snap;\n\t\t};\n\n\t\tlet currentSnapshot = computeSnap();\n\n\t\treturn {\n\t\t\tsubscribe: (onStoreChange: () => void) => {\n\t\t\t\tlet disposed = false;\n\t\t\t\tlet entrySubs: Array<() => void> = [];\n\n\t\t\t\tconst cleanupEntries = () => {\n\t\t\t\t\tfor (const unsub of entrySubs) unsub();\n\t\t\t\t\tentrySubs = [];\n\t\t\t\t};\n\n\t\t\t\tconst sync = (nextKeys: K[]) => {\n\t\t\t\t\tcleanupEntries();\n\t\t\t\t\tfor (const key of nextKeys) {\n\t\t\t\t\t\tconst nodes = factoryRef.current(key);\n\t\t\t\t\t\tfor (const field of Object.keys(nodes) as (keyof R)[]) {\n\t\t\t\t\t\t\tconst unsub = nodes[field].subscribe(() => {\n\t\t\t\t\t\t\t\tcurrentSnapshot = computeSnap();\n\t\t\t\t\t\t\t\tif (!disposed) onStoreChange();\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tentrySubs.push(unsub);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcurrentSnapshot = computeSnap();\n\t\t\t\t\tif (!disposed) onStoreChange();\n\t\t\t\t};\n\n\t\t\t\tconst keysUnsub = keysNode.subscribe((msgs: Messages) => {\n\t\t\t\t\tconst hasSettled = msgs.some((m) => m[0] === DATA || m[0] === RESOLVED);\n\t\t\t\t\tif (!disposed && hasSettled) sync(keysNode.cache ?? []);\n\t\t\t\t});\n\t\t\t\tsync(keysNode.cache ?? []);\n\n\t\t\t\treturn () => {\n\t\t\t\t\tdisposed = true;\n\t\t\t\t\tkeysUnsub();\n\t\t\t\t\tcleanupEntries();\n\t\t\t\t};\n\t\t\t},\n\t\t\tgetSnapshot: () => currentSnapshot,\n\t\t};\n\t}, [keysNode]);\n\n\treturn useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n}\n"],"mappings":";;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAaA,SAAS,aAAa,SAAS,QAAQ,4BAA4B;AAW5D,SAAS,aAAgB,MAAqC;AACpE,SAAO;AAAA,IACN,CAAC,kBAAkB;AAClB,UAAI,WAAW;AACf,YAAM,QAAQ,KAAK,UAAU,MAAM;AAClC,YAAI,CAAC,SAAU,eAAc;AAAA,MAC9B,CAAC;AACD,aAAO,MAAM;AACZ,mBAAW;AACX,cAAM;AAAA,MACP;AAAA,IACD;AAAA,IACA,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA;AAAA,EACZ;AACD;AAUO,SAAS,SAAY,MAA2D;AACtF,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,SAAS;AAAA,IACd,CAAC,MAAS;AACT,WAAK,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAAA,IAC/B;AAAA,IACA,CAAC,IAAI;AAAA,EACN;AACA,SAAO,CAAC,OAAO,MAAM;AACtB;AAiBO,SAAS,mBACf,UACA,SACe;AACf,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,QAAQ,QAAQ,MAAM;AAC3B,UAAM,cAAc,MAAM;AACzB,YAAM,OAAO,CAAC;AACd,YAAM,OAAO,SAAS,SAAS,CAAC;AAChC,iBAAW,OAAO,MAAM;AACvB,cAAM,QAAQ,WAAW,QAAQ,GAAG;AACpC,cAAM,SAAS,CAAC;AAChB,mBAAW,SAAS,OAAO,KAAK,KAAK,GAAkB;AACtD,iBAAO,KAAK,IAAI,MAAM,KAAK,EAAE;AAAA,QAC9B;AACA,aAAK,GAAG,IAAI;AAAA,MACb;AACA,aAAO;AAAA,IACR;AAEA,QAAI,kBAAkB,YAAY;AAElC,WAAO;AAAA,MACN,WAAW,CAAC,kBAA8B;AACzC,YAAI,WAAW;AACf,YAAI,YAA+B,CAAC;AAEpC,cAAM,iBAAiB,MAAM;AAC5B,qBAAW,SAAS,UAAW,OAAM;AACrC,sBAAY,CAAC;AAAA,QACd;AAEA,cAAM,OAAO,CAAC,aAAkB;AAC/B,yBAAe;AACf,qBAAW,OAAO,UAAU;AAC3B,kBAAM,QAAQ,WAAW,QAAQ,GAAG;AACpC,uBAAW,SAAS,OAAO,KAAK,KAAK,GAAkB;AACtD,oBAAM,QAAQ,MAAM,KAAK,EAAE,UAAU,MAAM;AAC1C,kCAAkB,YAAY;AAC9B,oBAAI,CAAC,SAAU,eAAc;AAAA,cAC9B,CAAC;AACD,wBAAU,KAAK,KAAK;AAAA,YACrB;AAAA,UACD;AACA,4BAAkB,YAAY;AAC9B,cAAI,CAAC,SAAU,eAAc;AAAA,QAC9B;AAEA,cAAM,YAAY,SAAS,UAAU,CAAC,SAAmB;AACxD,gBAAM,aAAa,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC,MAAM,QAAQ,EAAE,CAAC,MAAM,QAAQ;AACtE,cAAI,CAAC,YAAY,WAAY,MAAK,SAAS,SAAS,CAAC,CAAC;AAAA,QACvD,CAAC;AACD,aAAK,SAAS,SAAS,CAAC,CAAC;AAEzB,eAAO,MAAM;AACZ,qBAAW;AACX,oBAAU;AACV,yBAAe;AAAA,QAChB;AAAA,MACD;AAAA,MACA,aAAa,MAAM;AAAA,IACpB;AAAA,EACD,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO,qBAAqB,MAAM,WAAW,MAAM,aAAa,MAAM,WAAW;AAClF;","names":[]}
|
|
@@ -1,99 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
COMPLETE,
|
|
3
|
-
DATA,
|
|
4
|
-
DIRTY,
|
|
5
|
-
ERROR,
|
|
6
|
-
PAUSE,
|
|
7
|
-
RESUME,
|
|
8
2
|
batch,
|
|
9
3
|
derived,
|
|
10
4
|
state
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (options?.raw) {
|
|
17
|
-
return new Observable((subscriber) => {
|
|
18
|
-
const unsub = node.subscribe((msgs) => {
|
|
19
|
-
if (subscriber.closed) return;
|
|
20
|
-
subscriber.next(msgs);
|
|
21
|
-
for (const m of msgs) {
|
|
22
|
-
if (m[0] === ERROR) {
|
|
23
|
-
subscriber.error(m[1]);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (m[0] === COMPLETE) {
|
|
27
|
-
subscriber.complete();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
return unsub;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
return new Observable((subscriber) => {
|
|
36
|
-
const unsub = node.subscribe((msgs) => {
|
|
37
|
-
for (const m of msgs) {
|
|
38
|
-
if (subscriber.closed) return;
|
|
39
|
-
if (m[0] === DATA) {
|
|
40
|
-
subscriber.next(m[1]);
|
|
41
|
-
} else if (m[0] === ERROR) {
|
|
42
|
-
subscriber.error(m[1]);
|
|
43
|
-
return;
|
|
44
|
-
} else if (m[0] === COMPLETE) {
|
|
45
|
-
subscriber.complete();
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
return unsub;
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/extra/backpressure.ts
|
|
55
|
-
var nextLockId = 0;
|
|
56
|
-
function createWatermarkController(sendUp, opts) {
|
|
57
|
-
if (opts.highWaterMark < 1) throw new RangeError("highWaterMark must be >= 1");
|
|
58
|
-
if (opts.lowWaterMark < 0) throw new RangeError("lowWaterMark must be >= 0");
|
|
59
|
-
if (opts.lowWaterMark >= opts.highWaterMark)
|
|
60
|
-
throw new RangeError("lowWaterMark must be < highWaterMark");
|
|
61
|
-
const lockId = /* @__PURE__ */ Symbol(`bp-${++nextLockId}`);
|
|
62
|
-
let pending = 0;
|
|
63
|
-
let paused = false;
|
|
64
|
-
return {
|
|
65
|
-
onEnqueue() {
|
|
66
|
-
pending += 1;
|
|
67
|
-
if (!paused && pending >= opts.highWaterMark) {
|
|
68
|
-
paused = true;
|
|
69
|
-
sendUp([[PAUSE, lockId]]);
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
return false;
|
|
73
|
-
},
|
|
74
|
-
onDequeue() {
|
|
75
|
-
if (pending > 0) pending -= 1;
|
|
76
|
-
if (paused && pending <= opts.lowWaterMark) {
|
|
77
|
-
paused = false;
|
|
78
|
-
sendUp([[RESUME, lockId]]);
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
},
|
|
83
|
-
get pending() {
|
|
84
|
-
return pending;
|
|
85
|
-
},
|
|
86
|
-
get paused() {
|
|
87
|
-
return paused;
|
|
88
|
-
},
|
|
89
|
-
dispose() {
|
|
90
|
-
if (paused) {
|
|
91
|
-
paused = false;
|
|
92
|
-
sendUp([[RESUME, lockId]]);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}
|
|
5
|
+
} from "./chunk-PHOUUNK7.js";
|
|
6
|
+
import {
|
|
7
|
+
DATA,
|
|
8
|
+
DIRTY
|
|
9
|
+
} from "./chunk-SX52TAR4.js";
|
|
97
10
|
|
|
98
11
|
// src/extra/reactive-log.ts
|
|
99
12
|
var NativeLogBackend = class {
|
|
@@ -383,9 +296,7 @@ function reactiveLog(initial, options = {}) {
|
|
|
383
296
|
}
|
|
384
297
|
|
|
385
298
|
export {
|
|
386
|
-
toObservable,
|
|
387
|
-
createWatermarkController,
|
|
388
299
|
NativeLogBackend,
|
|
389
300
|
reactiveLog
|
|
390
301
|
};
|
|
391
|
-
//# sourceMappingURL=chunk-
|
|
302
|
+
//# sourceMappingURL=chunk-J2VBW3DZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extra/reactive-log.ts"],"sourcesContent":["/**\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 *\n * **Wave 4 refactor (2026-04-15):** Introduces the `LogBackend<T>` pluggable-backend\n * interface. The default `NativeLogBackend` uses a ring buffer when `maxSize` is set\n * (O(1) append + trim) and a flat array otherwise. `tail(n)` and `slice(start, stop)`\n * are memoized — repeat calls with identical arguments return the same derived node,\n * bounding the keepalive-subscription footprint. The standalone `logSlice` factory\n * has been removed; use `log.slice(start, stop)` instead.\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\";\nimport type { VersioningLevel } from \"../core/versioning.js\";\n\nexport type ReactiveLogOptions<T> = {\n\tname?: string;\n\tmaxSize?: number;\n\t/**\n\t * Optional versioning level for the underlying `entries` state node. Set\n\t * at construction time; cannot be changed later. Pass `0` for V0 identity\n\t * + monotonic version counter, or `1` for V1 + content-addressed cid.\n\t */\n\tversioning?: VersioningLevel;\n\t/**\n\t * Storage backend. Defaults to `NativeLogBackend` (ring buffer if `maxSize` is set,\n\t * flat array otherwise). Users can plug in persistent / RRB-tree backends via\n\t * the {@link LogBackend} interface.\n\t */\n\tbackend?: LogBackend<T>;\n};\n\nexport type ReactiveLogBundle<T> = {\n\t/** Emits `readonly T[]` on each append/clear/trim (two-phase). */\n\treadonly entries: Node<readonly T[]>;\n\t/** Current entry count (O(1)). */\n\treadonly size: number;\n\t/** Positional access (O(1)); returns `undefined` on out-of-range. Supports negative indices (Python-style). */\n\tat: (index: number) => T | undefined;\n\tappend: (value: T) => void;\n\t/**\n\t * Push all values, emit one snapshot. No-op if `values` is empty.\n\t * **Iterable consumption:** `values` is a `readonly T[]` — safe to pass arrays.\n\t */\n\tappendMany: (values: readonly T[]) => void;\n\tclear: () => void;\n\t/** Remove the first `n` entries (clamped to `size`). Throws on non-integer or negative `n`. */\n\ttrimHead: (n: number) => void;\n\t/**\n\t * Last `n` entries (or fewer) as a derived reactive view. Memoized with\n\t * an LRU cache (default cap 64) — repeat calls with the same `n` return\n\t * the same node. Throws on non-integer or negative `n`.\n\t *\n\t * **LRU eviction contract (D3(b)):** when a 65th distinct `n` is passed,\n\t * the least-recently-used cached view is evicted and its keepalive is\n\t * disposed. External holders of the evicted node will NOT receive further\n\t * updates — re-call `tail(n)` for a fresh node, or dispose proactively\n\t * via {@link disposeTail} / {@link disposeAllViews}. To avoid surprise:\n\t * resolve `tail(n)` at the point of use rather than caching the returned\n\t * node across many distinct `n`s.\n\t */\n\ttail: (n: number) => Node<readonly T[]>;\n\t/**\n\t * Reactive view of `entries.slice(start, stop)` — non-negative integer\n\t * `start`, non-negative integer `stop` (exclusive) or `undefined` (to end).\n\t * Memoized with an LRU cache (default cap 64) — repeat calls with the\n\t * same `(start, stop)` return the same node.\n\t *\n\t * Throws on non-integer `start`, negative `start`, non-integer `stop`, or\n\t * negative `stop` (P4 — the backend cannot cheaply honor JS-style\n\t * negative `stop` without scanning length; disallowed for a consistent\n\t * contract between backend, derived recomputation, and cached initial).\n\t *\n\t * **LRU eviction contract (D3(b)):** same as {@link tail} — past 64\n\t * distinct `(start, stop)` pairs, the oldest cached view is evicted and\n\t * its keepalive disposed. External holders stop receiving updates.\n\t */\n\tslice: (start: number, stop?: number) => Node<readonly T[]>;\n\t/**\n\t * Releases the cached `tail(n)` view if present (disposes its keepalive\n\t * subscription). Subsequent `tail(n)` calls create a fresh node. No-op if\n\t * `n` was not cached. Returns `true` if a view was disposed.\n\t */\n\tdisposeTail: (n: number) => boolean;\n\t/**\n\t * Releases the cached `slice(start, stop?)` view if present. No-op if not cached.\n\t */\n\tdisposeSlice: (start: number, stop?: number) => boolean;\n\t/** Releases all cached tail/slice views and their keepalive subscriptions. */\n\tdisposeAllViews: () => void;\n\t/**\n\t * Releases all internal keepalive subscriptions so the bundle can be\n\t * GC'd — currently equivalent to {@link disposeAllViews}, but exposed as\n\t * a uniform API across all reactive data structures for lifecycle\n\t * symmetry (mirrors `reactiveMap.dispose` / `reactiveList.dispose` /\n\t * `reactiveIndex.dispose`). Idempotent. D6(a).\n\t */\n\tdispose: () => void;\n};\n\n// ── Backend interface ─────────────────────────────────────────────────────\n\n/**\n * Storage contract for {@link reactiveLog}. Implementations own the mutable state and\n * expose a monotonic `version` counter that increments on every structural change.\n *\n * The reactive layer reads `version` to decide when to emit; it does not inspect\n * internal representation. Users can plug in persistent / ring-buffer / skip-list\n * backends without touching the reactive emission logic.\n *\n * @remarks Post-1.0 op-log changesets will extend this interface with a\n * `changesSince(version: number): Iterable<Change>` method. Current consumers\n * should treat all methods here as stable.\n *\n * @category extra\n */\nexport interface LogBackend<T> {\n\t/** Monotonic mutation counter; increments on every append/trim/clear that changes state. */\n\treadonly version: number;\n\t/** Number of entries currently stored. */\n\treadonly size: number;\n\t/** O(1) positional access; returns `undefined` on out-of-range. */\n\tat(index: number): T | undefined;\n\t/** Append a value. Applies `maxSize` head-drop if configured. Advances `version`. */\n\tappend(value: T): void;\n\t/** Append a batch; advances `version` once. No-op if `values.length === 0`. */\n\tappendMany(values: readonly T[]): void;\n\t/** Remove all entries. Returns count removed. Advances `version` only if non-zero. */\n\tclear(): number;\n\t/** Remove the first `n` entries (clamped). Returns count removed. Throws on negative `n`. */\n\ttrimHead(n: number): number;\n\t/** Fresh snapshot array for `[start, stop)`. Throws on negative `start`. */\n\tslice(start: number, stop?: number): readonly T[];\n\t/** Last `n` entries as a fresh array. Throws on negative `n`. */\n\ttail(n: number): readonly T[];\n\t/** Full snapshot as a fresh array. */\n\ttoArray(): readonly T[];\n}\n\n/**\n * Default append-only log backend.\n *\n * - When `maxSize` is set: uses a **ring buffer** with `_head` index and circular\n * modular arithmetic. Append and trim become O(1); snapshot is O(size) unrolling.\n * - When `maxSize` is unset: uses a flat array with standard push/splice.\n *\n * `appendMany` pre-trims oversize input: if `values.length > maxSize`, only the\n * tail of `values` is pushed (the rest would be immediately evicted).\n *\n * @category extra\n */\nexport class NativeLogBackend<T> implements LogBackend<T> {\n\tprivate _version = 0;\n\tprivate readonly _maxSize?: number;\n\tprivate readonly _buf: T[];\n\tprivate _head = 0;\n\tprivate _size = 0;\n\n\tconstructor(initial?: readonly T[], maxSize?: number) {\n\t\tif (maxSize !== undefined && maxSize < 1) {\n\t\t\tthrow new RangeError(\"maxSize must be >= 1\");\n\t\t}\n\t\tthis._maxSize = maxSize;\n\t\tif (maxSize !== undefined) {\n\t\t\t// Ring buffer mode — pre-allocate fixed size\n\t\t\tthis._buf = new Array(maxSize);\n\t\t\tif (initial && initial.length > 0) {\n\t\t\t\tconst take = Math.min(initial.length, maxSize);\n\t\t\t\tconst start = initial.length - take;\n\t\t\t\tfor (let i = 0; i < take; i++) {\n\t\t\t\t\tthis._buf[i] = initial[start + i]!;\n\t\t\t\t}\n\t\t\t\tthis._size = take;\n\t\t\t}\n\t\t} else {\n\t\t\t// Unbounded mode — dynamic array\n\t\t\tthis._buf = initial ? [...initial] : [];\n\t\t\tthis._size = this._buf.length;\n\t\t}\n\t}\n\n\tget version(): number {\n\t\treturn this._version;\n\t}\n\n\tget size(): number {\n\t\treturn this._size;\n\t}\n\n\tat(index: number): T | undefined {\n\t\tif (!Number.isInteger(index)) return undefined;\n\t\t// P5: Python-style negative index — `-1` returns the last entry.\n\t\tconst i = index >= 0 ? index : this._size + index;\n\t\tif (i < 0 || i >= this._size) return undefined;\n\t\tif (this._maxSize !== undefined) {\n\t\t\treturn this._buf[(this._head + i) % this._maxSize];\n\t\t}\n\t\treturn this._buf[i];\n\t}\n\n\tappend(value: T): void {\n\t\tthis._rawAppend(value);\n\t\tthis._version += 1;\n\t}\n\n\tappendMany(values: readonly T[]): void {\n\t\tif (values.length === 0) return;\n\t\t// Pre-trim oversize input in ring mode — skip values that would be\n\t\t// immediately evicted. Iterate with a start index instead of\n\t\t// allocating an intermediate slice. F2.\n\t\tconst start =\n\t\t\tthis._maxSize !== undefined && values.length > this._maxSize\n\t\t\t\t? values.length - this._maxSize\n\t\t\t\t: 0;\n\t\tfor (let i = start; i < values.length; i++) {\n\t\t\tthis._rawAppend(values[i] as T);\n\t\t}\n\t\tthis._version += 1;\n\t}\n\n\tclear(): number {\n\t\tif (this._size === 0) return 0;\n\t\tconst n = this._size;\n\t\tif (this._maxSize === undefined) {\n\t\t\tthis._buf.length = 0;\n\t\t} else {\n\t\t\t// Ring buffer: only null the currently-live window so the GC can\n\t\t\t// reclaim ref-typed `T`. Iterating the full capacity would be O(cap)\n\t\t\t// even when only a few slots are in use (P6). Non-live slots are\n\t\t\t// already `undefined` (pre-allocation state) or whatever a prior\n\t\t\t// trim/clear left — they hold no live refs.\n\t\t\tfor (let i = 0; i < n; i++) {\n\t\t\t\tthis._buf[(this._head + i) % this._maxSize] = undefined as unknown as T;\n\t\t\t}\n\t\t}\n\t\tthis._head = 0;\n\t\tthis._size = 0;\n\t\tthis._version += 1;\n\t\treturn n;\n\t}\n\n\ttrimHead(n: number): number {\n\t\tif (!Number.isInteger(n) || n < 0) {\n\t\t\tthrow new RangeError(`trimHead: n must be a non-negative integer (got ${n})`);\n\t\t}\n\t\tif (n === 0 || this._size === 0) return 0;\n\t\tconst removed = Math.min(n, this._size);\n\t\tif (this._maxSize === undefined) {\n\t\t\tthis._buf.splice(0, removed);\n\t\t} else {\n\t\t\t// Null trimmed slots so the GC can reclaim ref-typed T (P4 extension).\n\t\t\tfor (let i = 0; i < removed; i++) {\n\t\t\t\tthis._buf[(this._head + i) % this._maxSize] = undefined as unknown as T;\n\t\t\t}\n\t\t\tthis._head = (this._head + removed) % this._maxSize;\n\t\t}\n\t\tthis._size -= removed;\n\t\tthis._version += 1;\n\t\treturn removed;\n\t}\n\n\tslice(start: number, stop?: number): readonly T[] {\n\t\tif (!Number.isInteger(start) || start < 0) {\n\t\t\tthrow new RangeError(`slice: start must be a non-negative integer (got ${start})`);\n\t\t}\n\t\t// P4: reject negative `stop` explicitly so the bundle / backend / derived\n\t\t// contract stays consistent. Previously stop was silently clamped to 0,\n\t\t// producing `[]` in the backend but a different value under JS semantics\n\t\t// in the derived recomputation — a latent bug for negative inputs.\n\t\tif (stop !== undefined && (!Number.isInteger(stop) || stop < 0)) {\n\t\t\tthrow new RangeError(`slice: stop must be a non-negative integer or undefined (got ${stop})`);\n\t\t}\n\t\tconst end = stop === undefined ? this._size : Math.min(Math.max(stop, 0), this._size);\n\t\tconst s = Math.min(start, this._size);\n\t\tif (s >= end) return [];\n\t\tconst len = end - s;\n\t\tif (this._maxSize === undefined) {\n\t\t\treturn this._buf.slice(s, end);\n\t\t}\n\t\tconst out: T[] = new Array(len);\n\t\tfor (let i = 0; i < len; i++) {\n\t\t\tout[i] = this._buf[(this._head + s + i) % this._maxSize]!;\n\t\t}\n\t\treturn out;\n\t}\n\n\ttail(n: number): readonly T[] {\n\t\tif (!Number.isInteger(n) || n < 0) {\n\t\t\tthrow new RangeError(`tail: n must be a non-negative integer (got ${n})`);\n\t\t}\n\t\tif (n === 0 || this._size === 0) return [];\n\t\tconst take = Math.min(n, this._size);\n\t\treturn this.slice(this._size - take, this._size);\n\t}\n\n\ttoArray(): readonly T[] {\n\t\tif (this._maxSize === undefined) {\n\t\t\treturn [...this._buf];\n\t\t}\n\t\tconst out: T[] = new Array(this._size);\n\t\tfor (let i = 0; i < this._size; i++) {\n\t\t\tout[i] = this._buf[(this._head + i) % this._maxSize]!;\n\t\t}\n\t\treturn out;\n\t}\n\n\t/** Internal append without version bump — used by `appendMany`. */\n\tprivate _rawAppend(value: T): void {\n\t\tif (this._maxSize === undefined) {\n\t\t\tthis._buf.push(value);\n\t\t\tthis._size = this._buf.length;\n\t\t\treturn;\n\t\t}\n\t\tif (this._size < this._maxSize) {\n\t\t\tthis._buf[(this._head + this._size) % this._maxSize] = value;\n\t\t\tthis._size += 1;\n\t\t} else {\n\t\t\t// Overwrite slot at head, advance head.\n\t\t\tthis._buf[this._head] = value;\n\t\t\tthis._head = (this._head + 1) % this._maxSize;\n\t\t}\n\t}\n}\n\n// ── Reactive wrapper ──────────────────────────────────────────────────────\n\n/** Installs a keepalive subscription; returns the disposer so callers can release it. */\nfunction keepaliveDerived(n: Node<unknown>): () => void {\n\treturn n.subscribe(() => {});\n}\n\n/** Default cap on the LRU view cache for `tail(n)` / `slice(start, stop?)`. D2(c). */\nconst DEFAULT_VIEW_CACHE_MAX = 64;\n\n/**\n * Creates an append-only reactive log with immutable array snapshots.\n *\n * @param initial - Optional seed entries (copied; pre-trimmed to `maxSize` if set).\n * @param options - `name`, `maxSize`, and optional pluggable `backend`.\n * @returns Bundle with `entries` (state node), `append`/`appendMany`/`clear`/`trimHead`,\n * `size` / `at`, and memoized derived views `tail(n)` / `slice(start, stop?)`.\n *\n * @remarks\n * **Backend:** The default {@link NativeLogBackend} uses a ring buffer when `maxSize`\n * is set (O(1) append + trim) and a flat array otherwise. For persistent/structural-\n * sharing semantics plug in a custom {@link LogBackend}.\n *\n * **`initial` + custom `backend` (F5):** When you supply `options.backend`, the\n * `initial` argument is IGNORED — seed the backend yourself before passing it in.\n * The `initial` seed only applies to the default `NativeLogBackend`.\n *\n * **Memoized views:** {@link ReactiveLogBundle.tail} and {@link ReactiveLogBundle.slice}\n * cache derived nodes per-argument. Repeat calls with the same `n` / `(start, stop)`\n * return the same node, bounding keepalive-subscription count to one per unique argument.\n *\n * @example\n * ```ts\n * import { reactiveLog } from \"@graphrefly/graphrefly-ts\";\n *\n * const lg = reactiveLog<number>([1, 2], { name: \"audit\", maxSize: 100 });\n * lg.append(3);\n * lg.entries.subscribe((msgs) => console.log(msgs));\n * const last5 = lg.tail(5); // derived node\n * const window = lg.slice(10, 20); // derived node\n * ```\n *\n * @category extra\n */\nexport function reactiveLog<T>(\n\tinitial?: readonly T[],\n\toptions: ReactiveLogOptions<T> = {},\n): ReactiveLogBundle<T> {\n\tconst { name, maxSize, versioning, backend: userBackend } = options;\n\tconst backend: LogBackend<T> = userBackend ?? new NativeLogBackend<T>(initial, maxSize);\n\n\tconst entries = state<readonly T[]>(backend.toArray(), {\n\t\tname,\n\t\tdescribeKind: \"state\",\n\t\tequals: (a, b) => a === b,\n\t\t...(versioning != null ? { versioning } : {}),\n\t});\n\n\tfunction pushSnapshot(): void {\n\t\tconst snapshot = backend.toArray();\n\t\tbatch(() => {\n\t\t\tentries.down([[DIRTY]]);\n\t\t\tentries.down([[DATA, snapshot]]);\n\t\t});\n\t}\n\n\t// Memoization caches for derived views (D2(c)). Each cache is an LRU keyed by\n\t// the unique view argument, bounded by `DEFAULT_VIEW_CACHE_MAX`. On cache miss\n\t// past the cap, the least-recently-used entry is evicted and its keepalive\n\t// disposer is called so the underlying derived node can be GC'd. Callers can\n\t// also release views proactively via `disposeTail` / `disposeSlice` /\n\t// `disposeAllViews`. Iteration order of `Map` is insertion order, so moving\n\t// an entry to the end on hit is the LRU \"touch\".\n\ttype ViewEntry = { node: Node<readonly T[]>; dispose: () => void };\n\tconst tailCache = new Map<number, ViewEntry>();\n\tconst sliceCache = new Map<string, ViewEntry>();\n\n\tfunction sliceKey(start: number, stop?: number): string {\n\t\treturn `${start}:${stop === undefined ? \"END\" : stop}`;\n\t}\n\n\tfunction evictOldestIfFull<K>(cache: Map<K, ViewEntry>): void {\n\t\tif (cache.size < DEFAULT_VIEW_CACHE_MAX) return;\n\t\tconst first = cache.keys().next();\n\t\tif (first.done) return;\n\t\tconst oldest = cache.get(first.value);\n\t\tif (oldest !== undefined) oldest.dispose();\n\t\tcache.delete(first.value);\n\t}\n\n\t/**\n\t * D4(a): try/finally defense-in-depth — if a custom backend op throws\n\t * mid-mutation, surface the partial state via pushSnapshot so subscribers\n\t * don't see a stale cache. Matches the pattern in reactive-map and\n\t * reactive-index. Native ops are atomic by contract; this only matters\n\t * for user-supplied backends.\n\t */\n\tfunction wrapMutation<R>(op: () => R): R {\n\t\tconst prev = backend.version;\n\t\ttry {\n\t\t\treturn op();\n\t\t} finally {\n\t\t\tif (backend.version !== prev) pushSnapshot();\n\t\t}\n\t}\n\n\treturn {\n\t\tentries,\n\n\t\tget size(): number {\n\t\t\treturn backend.size;\n\t\t},\n\n\t\tat(index: number): T | undefined {\n\t\t\treturn backend.at(index);\n\t\t},\n\n\t\tappend(value: T): void {\n\t\t\twrapMutation(() => backend.append(value));\n\t\t},\n\n\t\tappendMany(values: readonly T[]): void {\n\t\t\tif (values.length === 0) return;\n\t\t\twrapMutation(() => backend.appendMany(values));\n\t\t},\n\n\t\tclear(): void {\n\t\t\twrapMutation(() => backend.clear());\n\t\t\t// NOTE: cached tail/slice derived views are intentionally NOT\n\t\t\t// disposed here. Disposing would kill the keepalive on any node\n\t\t\t// a caller already holds externally, silently stopping their\n\t\t\t// updates. The derived nodes recompute from the new empty\n\t\t\t// snapshot when `entries` emits post-clear, so `.cache` on an\n\t\t\t// outstanding view settles to `[]` without any manual\n\t\t\t// reset. (Initial snapshots, if inspected before the next wave,\n\t\t\t// may be stale — callers who care can `disposeTail` / `slice`\n\t\t\t// explicitly.)\n\t\t},\n\n\t\ttrimHead(n: number): void {\n\t\t\twrapMutation(() => backend.trimHead(n));\n\t\t},\n\n\t\ttail(n: number): Node<readonly T[]> {\n\t\t\tif (!Number.isInteger(n) || n < 0) {\n\t\t\t\tthrow new RangeError(`tail: n must be a non-negative integer (got ${n})`);\n\t\t\t}\n\t\t\tconst hit = tailCache.get(n);\n\t\t\tif (hit !== undefined) {\n\t\t\t\t// LRU touch: move to end of insertion order.\n\t\t\t\ttailCache.delete(n);\n\t\t\t\ttailCache.set(n, hit);\n\t\t\t\treturn hit.node;\n\t\t\t}\n\t\t\tevictOldestIfFull(tailCache);\n\t\t\tconst node_ = 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\tif (n === 0 || list.length === 0) return [];\n\t\t\t\t\treturn list.slice(Math.max(0, list.length - n));\n\t\t\t\t},\n\t\t\t\t{ initial: backend.tail(n), describeKind: \"derived\" },\n\t\t\t);\n\t\t\tconst dispose = keepaliveDerived(node_);\n\t\t\ttailCache.set(n, { node: node_, dispose });\n\t\t\treturn node_;\n\t\t},\n\n\t\tslice(start: number, stop?: number): Node<readonly T[]> {\n\t\t\tif (!Number.isInteger(start) || start < 0) {\n\t\t\t\tthrow new RangeError(`slice: start must be a non-negative integer (got ${start})`);\n\t\t\t}\n\t\t\t// P4: reject negative stop explicitly to keep bundle / backend / derived\n\t\t\t// consistent (JS `Array.prototype.slice` supports negative stop, but the\n\t\t\t// backend can't cheaply honor it without scanning length, so we disallow).\n\t\t\tif (stop !== undefined && (!Number.isInteger(stop) || stop < 0)) {\n\t\t\t\tthrow new RangeError(\n\t\t\t\t\t`slice: stop must be a non-negative integer or undefined (got ${stop})`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst key = sliceKey(start, stop);\n\t\t\tconst hit = sliceCache.get(key);\n\t\t\tif (hit !== undefined) {\n\t\t\t\tsliceCache.delete(key);\n\t\t\t\tsliceCache.set(key, hit);\n\t\t\t\treturn hit.node;\n\t\t\t}\n\t\t\tevictOldestIfFull(sliceCache);\n\t\t\tconst node_ = 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 stop === undefined ? list.slice(start) : list.slice(start, stop);\n\t\t\t\t},\n\t\t\t\t{ initial: backend.slice(start, stop), describeKind: \"derived\" },\n\t\t\t);\n\t\t\tconst dispose = keepaliveDerived(node_);\n\t\t\tsliceCache.set(key, { node: node_, dispose });\n\t\t\treturn node_;\n\t\t},\n\n\t\tdisposeTail(n: number): boolean {\n\t\t\tconst hit = tailCache.get(n);\n\t\t\tif (hit === undefined) return false;\n\t\t\thit.dispose();\n\t\t\ttailCache.delete(n);\n\t\t\treturn true;\n\t\t},\n\n\t\tdisposeSlice(start: number, stop?: number): boolean {\n\t\t\tconst key = sliceKey(start, stop);\n\t\t\tconst hit = sliceCache.get(key);\n\t\t\tif (hit === undefined) return false;\n\t\t\thit.dispose();\n\t\t\tsliceCache.delete(key);\n\t\t\treturn true;\n\t\t},\n\n\t\tdisposeAllViews(): void {\n\t\t\tfor (const entry of tailCache.values()) entry.dispose();\n\t\t\ttailCache.clear();\n\t\t\tfor (const entry of sliceCache.values()) entry.dispose();\n\t\t\tsliceCache.clear();\n\t\t},\n\n\t\tdispose(): void {\n\t\t\t// D6(a): currently identical to disposeAllViews. Exposed as a\n\t\t\t// uniform lifecycle API across all 4 reactive data structures.\n\t\t\tfor (const entry of tailCache.values()) entry.dispose();\n\t\t\ttailCache.clear();\n\t\t\tfor (const entry of sliceCache.values()) entry.dispose();\n\t\t\tsliceCache.clear();\n\t\t},\n\t};\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DATA,
|
|
3
|
-
DIRTY,
|
|
4
2
|
defaultConfig,
|
|
5
3
|
downWithBatch
|
|
6
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PHOUUNK7.js";
|
|
5
|
+
import {
|
|
6
|
+
DATA,
|
|
7
|
+
DIRTY
|
|
8
|
+
} from "./chunk-SX52TAR4.js";
|
|
7
9
|
|
|
8
10
|
// src/patterns/_internal.ts
|
|
9
11
|
function emitToMeta(metaNode, value) {
|
|
@@ -33,4 +35,4 @@ export {
|
|
|
33
35
|
domainMeta,
|
|
34
36
|
trackingKey
|
|
35
37
|
};
|
|
36
|
-
//# sourceMappingURL=chunk-
|
|
38
|
+
//# sourceMappingURL=chunk-JSCT3CR4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/patterns/_internal.ts"],"sourcesContent":["/**\n * Shared internal utilities for the patterns layer.\n *\n * These are private helpers used across multiple pattern modules. They are NOT\n * part of the public API — import from `./patterns/index.js` for public exports.\n *\n * General-purpose reactive utilities (`keepalive`, `reactiveCounter`) live in\n * `extra/sources.ts` and are re-exported here for convenience.\n *\n * @internal\n * @module\n */\n\nimport { downWithBatch } from \"../core/batch.js\";\nimport { DATA, DIRTY } from \"../core/messages.js\";\nimport type { Node } from \"../core/node.js\";\nimport { defaultConfig } from \"../core/node.js\";\n\n// Re-export general-purpose utilities from extra (canonical home).\nexport { keepalive, reactiveCounter } from \"../extra/sources.js\";\n\n// ---------------------------------------------------------------------------\n// emitToMeta\n// ---------------------------------------------------------------------------\n\n/**\n * Forward a single `[DATA, value]` to a meta companion node via tier-3\n * deferral, tolerating absent companions. Used by patterns that publish\n * per-wave statistics alongside their main output (cache-hit-rate,\n * segment-count, layout-time-ns, etc.) — subscribers see the parent's\n * DATA first because phase-2 completes before phase-3 during drain.\n *\n * // Expands to: `if (meta) downWithBatch(meta, [[Type, value]])` with null-guard.\n *\n * @internal\n */\nexport function emitToMeta<T>(metaNode: Node<T> | undefined, value: T): void {\n\tif (metaNode == null) return;\n\tdownWithBatch((msgs) => metaNode.down(msgs), [[DATA, value]], defaultConfig.tierOf);\n}\n\n// ---------------------------------------------------------------------------\n// tryIncrementBounded\n// ---------------------------------------------------------------------------\n\n/**\n * Bounded increment for a self-owned counter state node.\n *\n * Reads `counter.cache`, bumps by 1 if under `cap`, writes back. Returns\n * `false` when the cap is reached. Documented P3 exception: the counter is\n * not a declared dep of the caller — it's a private budget read+written from\n * a single call site. This helper keeps the `.cache` access in one named\n * place.\n *\n * **Safety today:**\n * 1. Single-threaded JS runner never invokes the caller concurrently.\n * 2. `counter.down` writes the cache synchronously before returning, so\n * synchronous re-entry through a downstream publish reads the\n * freshly-incremented value — no double-count.\n *\n * **Future risk:** under a free-threaded runner (PY no-GIL or hypothetical\n * concurrent TS runner), two concurrent firings could still race. Revisit\n * when that surfaces.\n *\n * @internal\n */\nexport function tryIncrementBounded(counter: Node<number>, cap: number): boolean {\n\tconst cur = (counter.cache as number | undefined) ?? 0;\n\tif (cur >= cap) return false;\n\tcounter.down([[DIRTY], [DATA, cur + 1]]);\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// domainMeta\n// ---------------------------------------------------------------------------\n\n/**\n * Build a domain metadata object for pattern-layer nodes.\n *\n * Each domain (orchestration, messaging, reduction, ai, cqrs, domain_template)\n * follows the same shape: `{ [domain]: true, [domain]_type: kind, ...extra }`.\n *\n * @param domain - The domain tag (e.g. `\"orchestration\"`, `\"ai\"`, `\"cqrs\"`).\n * @param kind - The specific type within the domain (e.g. `\"gate\"`, `\"prompt\"`).\n * @param extra - Additional metadata to merge.\n * @returns Metadata object.\n *\n * @internal\n */\nexport function domainMeta(\n\tdomain: string,\n\tkind: string,\n\textra?: Record<string, unknown>,\n): Record<string, unknown> {\n\treturn {\n\t\t[domain]: true,\n\t\t[`${domain}_type`]: kind,\n\t\t...(extra ?? {}),\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// trackingKey\n// ---------------------------------------------------------------------------\n\n/**\n * Stable tracking key for an item with retry/reingestion decoration.\n *\n * Uses `relatedTo[0]` if present (carries the original key forward through\n * retries and reingestions). Falls back to `summary` for first-time items.\n *\n * This avoids deriving keys from mutated summary strings — retries decorate\n * the summary with `[RETRY N/M]` and failure context, so regex-stripping\n * would be fragile and any new decoration pattern would risk infinite loops\n * by generating novel keys.\n *\n * @internal\n */\nexport function trackingKey(item: { summary: string; relatedTo?: string[] }): string {\n\treturn item.relatedTo?.[0] ?? item.summary;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/patterns/_internal.ts"],"sourcesContent":["/**\n * Shared internal utilities for the patterns layer.\n *\n * These are private helpers used across multiple pattern modules. They are NOT\n * part of the public API — import from `./patterns/index.js` for public exports.\n *\n * General-purpose reactive utilities (`keepalive`, `reactiveCounter`) live in\n * `extra/sources.ts` and are re-exported here for convenience.\n *\n * @internal\n * @module\n */\n\nimport { downWithBatch } from \"../core/batch.js\";\nimport { DATA, DIRTY } from \"../core/messages.js\";\nimport type { Node } from \"../core/node.js\";\nimport { defaultConfig } from \"../core/node.js\";\n\n// Re-export general-purpose utilities from extra (canonical home).\nexport { keepalive, reactiveCounter } from \"../extra/sources.js\";\n\n// ---------------------------------------------------------------------------\n// emitToMeta\n// ---------------------------------------------------------------------------\n\n/**\n * Forward a single `[DATA, value]` to a meta companion node via tier-3\n * deferral, tolerating absent companions. Used by patterns that publish\n * per-wave statistics alongside their main output (cache-hit-rate,\n * segment-count, layout-time-ns, etc.) — subscribers see the parent's\n * DATA first because phase-2 completes before phase-3 during drain.\n *\n * // Expands to: `if (meta) downWithBatch(meta, [[Type, value]])` with null-guard.\n *\n * @internal\n */\nexport function emitToMeta<T>(metaNode: Node<T> | undefined, value: T): void {\n\tif (metaNode == null) return;\n\tdownWithBatch((msgs) => metaNode.down(msgs), [[DATA, value]], defaultConfig.tierOf);\n}\n\n// ---------------------------------------------------------------------------\n// tryIncrementBounded\n// ---------------------------------------------------------------------------\n\n/**\n * Bounded increment for a self-owned counter state node.\n *\n * Reads `counter.cache`, bumps by 1 if under `cap`, writes back. Returns\n * `false` when the cap is reached. Documented P3 exception: the counter is\n * not a declared dep of the caller — it's a private budget read+written from\n * a single call site. This helper keeps the `.cache` access in one named\n * place.\n *\n * **Safety today:**\n * 1. Single-threaded JS runner never invokes the caller concurrently.\n * 2. `counter.down` writes the cache synchronously before returning, so\n * synchronous re-entry through a downstream publish reads the\n * freshly-incremented value — no double-count.\n *\n * **Future risk:** under a free-threaded runner (PY no-GIL or hypothetical\n * concurrent TS runner), two concurrent firings could still race. Revisit\n * when that surfaces.\n *\n * @internal\n */\nexport function tryIncrementBounded(counter: Node<number>, cap: number): boolean {\n\tconst cur = (counter.cache as number | undefined) ?? 0;\n\tif (cur >= cap) return false;\n\tcounter.down([[DIRTY], [DATA, cur + 1]]);\n\treturn true;\n}\n\n// ---------------------------------------------------------------------------\n// domainMeta\n// ---------------------------------------------------------------------------\n\n/**\n * Build a domain metadata object for pattern-layer nodes.\n *\n * Each domain (orchestration, messaging, reduction, ai, cqrs, domain_template)\n * follows the same shape: `{ [domain]: true, [domain]_type: kind, ...extra }`.\n *\n * @param domain - The domain tag (e.g. `\"orchestration\"`, `\"ai\"`, `\"cqrs\"`).\n * @param kind - The specific type within the domain (e.g. `\"gate\"`, `\"prompt\"`).\n * @param extra - Additional metadata to merge.\n * @returns Metadata object.\n *\n * @internal\n */\nexport function domainMeta(\n\tdomain: string,\n\tkind: string,\n\textra?: Record<string, unknown>,\n): Record<string, unknown> {\n\treturn {\n\t\t[domain]: true,\n\t\t[`${domain}_type`]: kind,\n\t\t...(extra ?? {}),\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// trackingKey\n// ---------------------------------------------------------------------------\n\n/**\n * Stable tracking key for an item with retry/reingestion decoration.\n *\n * Uses `relatedTo[0]` if present (carries the original key forward through\n * retries and reingestions). Falls back to `summary` for first-time items.\n *\n * This avoids deriving keys from mutated summary strings — retries decorate\n * the summary with `[RETRY N/M]` and failure context, so regex-stripping\n * would be fragile and any new decoration pattern would risk infinite loops\n * by generating novel keys.\n *\n * @internal\n */\nexport function trackingKey(item: { summary: string; relatedTo?: string[] }): string {\n\treturn item.relatedTo?.[0] ?? item.summary;\n}\n"],"mappings":";;;;;;;;;;AAoCO,SAAS,WAAc,UAA+B,OAAgB;AAC5E,MAAI,YAAY,KAAM;AACtB,gBAAc,CAAC,SAAS,SAAS,KAAK,IAAI,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,GAAG,cAAc,MAAM;AACnF;AA2BO,SAAS,oBAAoB,SAAuB,KAAsB;AAChF,QAAM,MAAO,QAAQ,SAAgC;AACrD,MAAI,OAAO,IAAK,QAAO;AACvB,UAAQ,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC;AACvC,SAAO;AACR;AAmBO,SAAS,WACf,QACA,MACA,OAC0B;AAC1B,SAAO;AAAA,IACN,CAAC,MAAM,GAAG;AAAA,IACV,CAAC,GAAG,MAAM,OAAO,GAAG;AAAA,IACpB,GAAI,SAAS,CAAC;AAAA,EACf;AACD;AAmBO,SAAS,YAAY,MAAyD;AACpF,SAAO,KAAK,YAAY,CAAC,KAAK,KAAK;AACpC;","names":[]}
|