@graphrefly/graphrefly 0.27.0 → 0.29.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/dist/backoff-HPZMEZNF.js +1 -0
- package/dist/cascading-Bp99ckMJ.d.ts +180 -0
- package/dist/cascading-CcAgRacD.d.cts +180 -0
- package/dist/chunk-22F4K3G7.js +1 -0
- package/dist/chunk-22SVXUPB.js +64 -0
- package/dist/chunk-2GQREQ6C.js +1 -0
- package/dist/chunk-3JXNEPCD.js +2 -0
- package/dist/chunk-4JJCCD5S.js +2 -0
- package/dist/chunk-4OFIQ66T.js +1 -0
- package/dist/chunk-4V4C7K56.js +1 -0
- package/dist/chunk-4VVTGLXJ.js +1 -0
- package/dist/chunk-567NWZ3T.js +1 -0
- package/dist/chunk-5JDE5JHE.js +1 -0
- package/dist/chunk-5QDBSZBV.js +1 -0
- package/dist/chunk-5Z4HDCO6.js +1 -0
- package/dist/chunk-63FFOHLA.js +1 -0
- package/dist/chunk-6QZNQS5B.js +1 -0
- package/dist/chunk-7JDLFI6N.js +1 -0
- package/dist/chunk-7TDOES3L.js +1 -0
- package/dist/chunk-A7IAQQ63.js +1 -0
- package/dist/chunk-AMG5VBHW.js +1 -0
- package/dist/chunk-AUY2YKCO.js +1 -0
- package/dist/chunk-AV3PIDFQ.js +1 -0
- package/dist/chunk-BA5URFYW.js +1 -0
- package/dist/chunk-BKPLTBL5.js +1 -0
- package/dist/chunk-BZP5T4X6.js +1 -0
- package/dist/chunk-CK2E7BTU.js +1 -0
- package/dist/chunk-CSJE2EKV.js +1 -0
- package/dist/chunk-E3AXATVZ.js +9 -0
- package/dist/chunk-ESMPEKEV.js +1 -0
- package/dist/chunk-GJR3P6JG.js +1 -0
- package/dist/chunk-GNCBXARM.js +1 -0
- package/dist/chunk-GPW2V3RE.js +1 -0
- package/dist/chunk-HSIEYSDY.js +1 -0
- package/dist/chunk-I6VIH3VA.js +1 -0
- package/dist/chunk-ISCENNXS.js +1 -0
- package/dist/chunk-JYOUF6UQ.js +1 -0
- package/dist/chunk-KASHOCF5.js +1 -0
- package/dist/chunk-LGSNR4LU.js +5 -0
- package/dist/chunk-LVGBLZM2.js +1 -0
- package/dist/chunk-MGKAO4EK.js +7 -0
- package/dist/chunk-NSG4C6BF.js +23 -0
- package/dist/chunk-OL33ZI6R.js +1 -0
- package/dist/chunk-PCZ35NXD.js +78 -0
- package/dist/chunk-PGMUCUHG.js +43 -0
- package/dist/chunk-QYADASLV.js +1 -0
- package/dist/chunk-RD52SNH2.js +1 -0
- package/dist/chunk-SLMYTGTU.js +1 -0
- package/dist/chunk-TWMEGG45.js +1 -0
- package/dist/chunk-UVJQ35G2.js +1 -0
- package/dist/chunk-VGTCGNRX.js +18 -0
- package/dist/chunk-VIMF6LGM.js +1 -0
- package/dist/chunk-VJLMUKOI.js +1 -0
- package/dist/chunk-VWPRPPKR.js +1 -0
- package/dist/chunk-W4TSQ6RJ.js +1 -0
- package/dist/chunk-WM7H7WTY.js +3 -0
- package/dist/chunk-Y32RJO24.js +1 -0
- package/dist/chunk-Y53B6NS4.js +1 -0
- package/dist/compat/index.cjs +15 -7656
- package/dist/compat/index.d.cts +15 -14
- package/dist/compat/index.d.ts +15 -14
- package/dist/compat/index.js +1 -50
- package/dist/compat/jotai/index.cjs +1 -2048
- package/dist/compat/jotai/index.d.cts +2 -2
- package/dist/compat/jotai/index.d.ts +2 -2
- package/dist/compat/jotai/index.js +1 -9
- package/dist/compat/nanostores/index.cjs +1 -2175
- package/dist/compat/nanostores/index.d.cts +2 -2
- package/dist/compat/nanostores/index.d.ts +2 -2
- package/dist/compat/nanostores/index.js +1 -23
- package/dist/compat/nestjs/index.cjs +15 -6782
- package/dist/compat/nestjs/index.d.cts +7 -6
- package/dist/compat/nestjs/index.d.ts +7 -6
- package/dist/compat/nestjs/index.js +1 -83
- package/dist/compat/react/index.cjs +1 -141
- package/dist/compat/react/index.d.cts +2 -2
- package/dist/compat/react/index.d.ts +2 -2
- package/dist/compat/react/index.js +1 -12
- package/dist/compat/solid/index.cjs +1 -128
- package/dist/compat/solid/index.d.cts +2 -2
- package/dist/compat/solid/index.d.ts +2 -2
- package/dist/compat/solid/index.js +1 -12
- package/dist/compat/svelte/index.cjs +1 -131
- package/dist/compat/svelte/index.d.cts +2 -2
- package/dist/compat/svelte/index.d.ts +2 -2
- package/dist/compat/svelte/index.js +1 -12
- package/dist/compat/vue/index.cjs +1 -146
- package/dist/compat/vue/index.d.cts +2 -2
- package/dist/compat/vue/index.d.ts +2 -2
- package/dist/compat/vue/index.js +1 -12
- package/dist/compat/zustand/index.cjs +7 -4931
- package/dist/compat/zustand/index.d.cts +5 -5
- package/dist/compat/zustand/index.d.ts +5 -5
- package/dist/compat/zustand/index.js +1 -12
- package/dist/composite-BL-llbnE.d.ts +69 -0
- package/dist/composite-Dze--DaA.d.cts +69 -0
- package/dist/core/index.cjs +1 -2271
- package/dist/core/index.d.cts +4 -4
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +1 -110
- package/dist/extra/browser.cjs +1 -0
- package/dist/extra/browser.d.cts +3 -0
- package/dist/extra/browser.d.ts +3 -0
- package/dist/extra/browser.js +1 -0
- package/dist/extra/index.cjs +24 -9971
- package/dist/extra/index.d.cts +13 -6
- package/dist/extra/index.d.ts +13 -6
- package/dist/extra/index.js +1 -381
- package/dist/extra/node.cjs +3 -0
- package/dist/extra/node.d.cts +81 -0
- package/dist/extra/node.d.ts +81 -0
- package/dist/extra/node.js +2 -0
- package/dist/extra/operators.cjs +1 -0
- package/dist/extra/operators.d.cts +910 -0
- package/dist/extra/operators.d.ts +910 -0
- package/dist/extra/operators.js +1 -0
- package/dist/extra/reactive.cjs +1 -0
- package/dist/extra/reactive.d.cts +352 -0
- package/dist/extra/reactive.d.ts +352 -0
- package/dist/extra/reactive.js +1 -0
- package/dist/extra/sources.cjs +1 -2486
- package/dist/extra/sources.d.cts +6 -2
- package/dist/extra/sources.d.ts +6 -2
- package/dist/extra/sources.js +1 -57
- package/dist/extra/storage-browser.cjs +1 -0
- package/dist/extra/storage-browser.d.cts +71 -0
- package/dist/extra/storage-browser.d.ts +71 -0
- package/dist/extra/storage-browser.js +1 -0
- package/dist/extra/storage-core.cjs +1 -0
- package/dist/extra/storage-core.d.cts +98 -0
- package/dist/extra/storage-core.d.ts +98 -0
- package/dist/extra/storage-core.js +1 -0
- package/dist/extra/storage-node.cjs +2 -0
- package/dist/extra/storage-node.d.cts +60 -0
- package/dist/extra/storage-node.d.ts +60 -0
- package/dist/extra/storage-node.js +1 -0
- package/dist/fallback-BaTS7vVY.d.cts +258 -0
- package/dist/fallback-eOm3LNxP.d.ts +258 -0
- package/dist/graph/index.cjs +7 -5030
- package/dist/graph/index.d.cts +6 -5
- package/dist/graph/index.d.ts +6 -5
- package/dist/graph/index.js +1 -50
- package/dist/{graph-DNCrvZSn.d.cts → graph-DgohqXK-.d.cts} +151 -32
- package/dist/{graph-CCwGKLCm.d.ts → graph-Qjg9gWHI.d.ts} +151 -32
- package/dist/{index-BwfLUNw4.d.ts → index-2BVuRCI4.d.ts} +173 -2040
- package/dist/{index-BPVt8kqc.d.ts → index-2NvguqQA.d.ts} +10 -195
- package/dist/index-A65LZhoM.d.ts +186 -0
- package/dist/{index-DlLp-2Xn.d.cts → index-B-8FCEua.d.cts} +10 -195
- package/dist/index-B-gqvYel.d.ts +135 -0
- package/dist/index-B2PRuolf.d.cts +86 -0
- package/dist/index-BJqt9EwW.d.ts +231 -0
- package/dist/{index-BHlKbUwO.d.cts → index-BKjT5DiZ.d.cts} +173 -2040
- package/dist/{index-C0svESO4.d.ts → index-BM8BU4q6.d.ts} +1 -1
- package/dist/{index-VdHQMPy1.d.ts → index-BQaEnxBf.d.ts} +1 -1
- package/dist/index-BSfwiy5B.d.ts +192 -0
- package/dist/index-BTSkeCZs.d.cts +291 -0
- package/dist/index-BUVtw1Ay.d.cts +186 -0
- package/dist/index-BUi57v_p.d.ts +163 -0
- package/dist/{messaging-Gt4LPbyA.d.cts → index-BanNUILp.d.cts} +31 -93
- package/dist/{audit-DRlSzBu9.d.ts → index-BayHDRx6.d.cts} +27 -21
- package/dist/{index-B6D3QNSA.d.ts → index-BiyjsZ0m.d.ts} +148 -20
- package/dist/index-BqZ6vB2A.d.ts +2057 -0
- package/dist/{memory-li6FL5RM.d.ts → index-BuVidq3D.d.cts} +26 -26
- package/dist/index-Byh-xTyp.d.ts +105 -0
- package/dist/index-C08QPDcV.d.cts +321 -0
- package/dist/{demo-shell-BDkOptd6.d.ts → index-C66RJiX8.d.ts} +14 -14
- package/dist/index-C8RfjffH.d.ts +291 -0
- package/dist/index-CA1Cu7Ud.d.ts +873 -0
- package/dist/{index-ByQxazQJ.d.cts → index-CABbltIu.d.cts} +1 -1
- package/dist/{index-VHqptjhu.d.cts → index-CHEBsnYv.d.cts} +1 -1
- package/dist/{index-BuEoe-Qu.d.ts → index-CI0yDnLp.d.ts} +9 -9
- package/dist/{index-B9B7_HEY.d.ts → index-CJ45TW-h.d.ts} +1 -1
- package/dist/{index-CO8uBlUh.d.cts → index-CJZKZoo4.d.cts} +148 -20
- package/dist/{index-wEn0eFe8.d.ts → index-CX2tFJL1.d.ts} +1 -1
- package/dist/{index-BaSM3aYt.d.ts → index-CcWOJ6F0.d.ts} +3 -3
- package/dist/{audit-ClmqGOCx.d.cts → index-CiaVoZGo.d.ts} +27 -21
- package/dist/{index-Dzk2hrlR.d.ts → index-Ct5CWc4E.d.ts} +1 -1
- package/dist/index-D38duMCv.d.cts +357 -0
- package/dist/{index-C8oil6M6.d.ts → index-D5XJ2tSx.d.ts} +30 -6
- package/dist/{index-DO_6JN9Z.d.cts → index-D80do5jX.d.cts} +1 -1
- package/dist/index-D9HKAH_-.d.cts +231 -0
- package/dist/index-DBe_8XW5.d.cts +143 -0
- package/dist/{memory-C6Z2tGpC.d.cts → index-DCeTyFlB.d.ts} +26 -26
- package/dist/index-DDy8eeXS.d.ts +321 -0
- package/dist/index-DJ5oNBc8.d.ts +143 -0
- package/dist/{index-CI3DprxP.d.cts → index-DP0_O3ls.d.cts} +30 -6
- package/dist/{demo-shell-Crid1WdR.d.cts → index-DX8xS-yB.d.cts} +14 -14
- package/dist/index-DiZejfCI.d.cts +2057 -0
- package/dist/index-DknyJ2Fu.d.cts +163 -0
- package/dist/index-DoWMs-Kk.d.cts +135 -0
- package/dist/{messaging-XDoYablx.d.ts → index-DuB5aO4-.d.ts} +31 -93
- package/dist/index-DwjJGKxV.d.ts +357 -0
- package/dist/{index-B6EhDnjH.d.cts → index-H_oxVec5.d.cts} +1 -1
- package/dist/{index-3lsddbbS.d.ts → index-IMAHq-ia.d.ts} +1 -1
- package/dist/{index-B1tloyhO.d.cts → index-U1nir7MX.d.cts} +1 -1
- package/dist/index-Vg7tORgk.d.ts +86 -0
- package/dist/index-dig-r2tQ.d.cts +873 -0
- package/dist/{index-CxFrXH4m.d.ts → index-gsT79Xu9.d.ts} +1 -1
- package/dist/{index-D8wS_PeY.d.cts → index-i99Ka8s7.d.cts} +9 -9
- package/dist/index-iKkyJosF.d.cts +105 -0
- package/dist/{index-Xi3u0HCQ.d.cts → index-jleeotBT.d.cts} +1 -1
- package/dist/{index-DVGiGFGT.d.cts → index-kykKWwV-.d.cts} +3 -3
- package/dist/index-vgcLF5TH.d.cts +192 -0
- package/dist/{index-DYme44FM.d.cts → index-y1RllPn4.d.cts} +1 -1
- package/dist/index.cjs +151 -24142
- package/dist/index.d.cts +69 -2099
- package/dist/index.d.ts +69 -2099
- package/dist/index.js +1 -3868
- package/dist/{meta-CbznRPYJ.d.ts → meta-CX7YsOzp.d.cts} +5 -5
- package/dist/{meta-BxCA7rcr.d.cts → meta-CckhhFRd.d.ts} +5 -5
- package/dist/{node-BmerH3kS.d.cts → node-Dd6wHSib.d.cts} +71 -11
- package/dist/{node-BmerH3kS.d.ts → node-Dd6wHSib.d.ts} +71 -11
- package/dist/{observable-BgGUwcqp.d.ts → observable-BZJgo616.d.ts} +1 -1
- package/dist/{observable-DJt_AxzQ.d.cts → observable-kwzpLvbi.d.cts} +1 -1
- package/dist/patterns/ai/browser.cjs +25 -0
- package/dist/patterns/ai/browser.d.cts +127 -0
- package/dist/patterns/ai/browser.d.ts +127 -0
- package/dist/patterns/ai/browser.js +3 -0
- package/dist/patterns/ai/index.cjs +92 -0
- package/dist/patterns/ai/index.d.cts +17 -0
- package/dist/patterns/ai/index.d.ts +17 -0
- package/dist/patterns/ai/index.js +1 -0
- package/dist/patterns/ai/node.cjs +2 -0
- package/dist/patterns/ai/node.d.cts +58 -0
- package/dist/patterns/ai/node.d.ts +58 -0
- package/dist/patterns/ai/node.js +1 -0
- package/dist/patterns/audit/index.cjs +7 -0
- package/dist/patterns/audit/index.d.cts +6 -0
- package/dist/patterns/audit/index.d.ts +6 -0
- package/dist/patterns/audit/index.js +1 -0
- package/dist/patterns/cqrs/index.cjs +7 -0
- package/dist/patterns/cqrs/index.d.cts +5 -0
- package/dist/patterns/cqrs/index.d.ts +5 -0
- package/dist/patterns/cqrs/index.js +1 -0
- package/dist/patterns/demo-shell/index.cjs +8 -0
- package/dist/patterns/demo-shell/index.d.cts +6 -0
- package/dist/patterns/demo-shell/index.d.ts +6 -0
- package/dist/patterns/demo-shell/index.js +1 -0
- package/dist/patterns/domain-templates/index.cjs +7 -0
- package/dist/patterns/domain-templates/index.d.cts +5 -0
- package/dist/patterns/domain-templates/index.d.ts +5 -0
- package/dist/patterns/domain-templates/index.js +1 -0
- package/dist/patterns/graphspec/index.cjs +84 -0
- package/dist/patterns/graphspec/index.d.cts +7 -0
- package/dist/patterns/graphspec/index.d.ts +7 -0
- package/dist/patterns/graphspec/index.js +1 -0
- package/dist/patterns/guarded-execution/index.cjs +7 -0
- package/dist/patterns/guarded-execution/index.d.cts +7 -0
- package/dist/patterns/guarded-execution/index.d.ts +7 -0
- package/dist/patterns/guarded-execution/index.js +1 -0
- package/dist/patterns/harness/index.cjs +49 -0
- package/dist/patterns/harness/index.d.cts +11 -0
- package/dist/patterns/harness/index.d.ts +11 -0
- package/dist/patterns/harness/index.js +1 -0
- package/dist/patterns/job-queue/index.cjs +7 -0
- package/dist/patterns/job-queue/index.d.cts +5 -0
- package/dist/patterns/job-queue/index.d.ts +5 -0
- package/dist/patterns/job-queue/index.js +1 -0
- package/dist/patterns/lens/index.cjs +7 -0
- package/dist/patterns/lens/index.d.cts +7 -0
- package/dist/patterns/lens/index.d.ts +7 -0
- package/dist/patterns/lens/index.js +1 -0
- package/dist/patterns/memory/index.cjs +7 -0
- package/dist/patterns/memory/index.d.cts +5 -0
- package/dist/patterns/memory/index.d.ts +5 -0
- package/dist/patterns/memory/index.js +1 -0
- package/dist/patterns/messaging/index.cjs +7 -0
- package/dist/patterns/messaging/index.d.cts +5 -0
- package/dist/patterns/messaging/index.d.ts +5 -0
- package/dist/patterns/messaging/index.js +1 -0
- package/dist/patterns/orchestration/index.cjs +7 -0
- package/dist/patterns/orchestration/index.d.cts +6 -0
- package/dist/patterns/orchestration/index.d.ts +6 -0
- package/dist/patterns/orchestration/index.js +1 -0
- package/dist/patterns/reactive-layout/index.cjs +8 -6444
- package/dist/patterns/reactive-layout/index.d.cts +6 -6
- package/dist/patterns/reactive-layout/index.d.ts +6 -6
- package/dist/patterns/reactive-layout/index.js +1 -56
- package/dist/patterns/reduction/index.cjs +7 -0
- package/dist/patterns/reduction/index.d.cts +5 -0
- package/dist/patterns/reduction/index.d.ts +5 -0
- package/dist/patterns/reduction/index.js +1 -0
- package/dist/patterns/refine-loop/index.cjs +9 -0
- package/dist/patterns/refine-loop/index.d.cts +7 -0
- package/dist/patterns/refine-loop/index.d.ts +7 -0
- package/dist/patterns/refine-loop/index.js +1 -0
- package/dist/patterns/resilient-pipeline/index.cjs +1 -0
- package/dist/patterns/resilient-pipeline/index.d.cts +7 -0
- package/dist/patterns/resilient-pipeline/index.d.ts +7 -0
- package/dist/patterns/resilient-pipeline/index.js +1 -0
- package/dist/patterns/surface/index.cjs +15 -0
- package/dist/patterns/surface/index.d.cts +8 -0
- package/dist/patterns/surface/index.d.ts +8 -0
- package/dist/patterns/surface/index.js +1 -0
- package/dist/{reactive-layout-u5Ulnqag.d.ts → reactive-layout-BkBwVvwm.d.ts} +2 -2
- package/dist/{reactive-layout-MQP--J3F.d.cts → reactive-layout-PiFwVaWS.d.cts} +2 -2
- package/dist/reactive-log-1QTyx10a.d.ts +190 -0
- package/dist/reactive-log-BiVoSxke.d.cts +190 -0
- package/dist/{composite-aUCvjZVR.d.ts → reactive-map-CwO_COHy.d.cts} +2 -67
- package/dist/{composite-C7PcQvcs.d.cts → reactive-map-FeuTVAJb.d.ts} +2 -67
- package/dist/resilience-CBfYJW5C.d.ts +493 -0
- package/dist/resilience-XRUF267O.js +1 -0
- package/dist/resilience-uBz4yvYB.d.cts +493 -0
- package/dist/{sugar-CCOxXK1e.d.ts → sugar-CY-MCfZ9.d.ts} +17 -15
- package/dist/{sugar-D02n5JjF.d.cts → sugar-DHttV0LX.d.cts} +17 -15
- package/dist/topology-tree-CVjt2gp7.d.cts +25 -0
- package/dist/topology-tree-one6oSKY.d.ts +25 -0
- package/dist/types-O3GzJY2U.d.cts +401 -0
- package/dist/types-u64Ose53.d.ts +401 -0
- package/package.json +252 -22
- package/dist/ai-CaR_912Q.d.cts +0 -1033
- package/dist/ai-WlRltJV7.d.ts +0 -1033
- package/dist/chunk-3ZWCKRHX.js +0 -117
- package/dist/chunk-3ZWCKRHX.js.map +0 -1
- package/dist/chunk-7TAQJHQV.js +0 -103
- package/dist/chunk-7TAQJHQV.js.map +0 -1
- package/dist/chunk-APFNLIRG.js +0 -62
- package/dist/chunk-APFNLIRG.js.map +0 -1
- package/dist/chunk-AT5LKYNL.js +0 -395
- package/dist/chunk-AT5LKYNL.js.map +0 -1
- package/dist/chunk-BQ6RQQFF.js +0 -5087
- package/dist/chunk-BQ6RQQFF.js.map +0 -1
- package/dist/chunk-BVZYTZ5H.js +0 -599
- package/dist/chunk-BVZYTZ5H.js.map +0 -1
- package/dist/chunk-DST5DKZS.js +0 -1371
- package/dist/chunk-DST5DKZS.js.map +0 -1
- package/dist/chunk-GTE6PWRZ.js +0 -866
- package/dist/chunk-GTE6PWRZ.js.map +0 -1
- package/dist/chunk-HXZEYDUR.js +0 -94
- package/dist/chunk-HXZEYDUR.js.map +0 -1
- package/dist/chunk-J22W6HV3.js +0 -107
- package/dist/chunk-J22W6HV3.js.map +0 -1
- package/dist/chunk-J2VBW3DZ.js +0 -302
- package/dist/chunk-J2VBW3DZ.js.map +0 -1
- package/dist/chunk-JSCT3CR4.js +0 -38
- package/dist/chunk-JSCT3CR4.js.map +0 -1
- package/dist/chunk-JWBCY4NC.js +0 -330
- package/dist/chunk-JWBCY4NC.js.map +0 -1
- package/dist/chunk-K2AUJHVP.js +0 -2251
- package/dist/chunk-K2AUJHVP.js.map +0 -1
- package/dist/chunk-MJ2NKQQL.js +0 -119
- package/dist/chunk-MJ2NKQQL.js.map +0 -1
- package/dist/chunk-N6UR7YVY.js +0 -198
- package/dist/chunk-N6UR7YVY.js.map +0 -1
- package/dist/chunk-NC6S43JJ.js +0 -456
- package/dist/chunk-NC6S43JJ.js.map +0 -1
- package/dist/chunk-OFVJBJXR.js +0 -98
- package/dist/chunk-OFVJBJXR.js.map +0 -1
- package/dist/chunk-OHISZPOJ.js +0 -97
- package/dist/chunk-OHISZPOJ.js.map +0 -1
- package/dist/chunk-OU5CQKNW.js +0 -102
- package/dist/chunk-OU5CQKNW.js.map +0 -1
- package/dist/chunk-PF7GRZMW.js +0 -2712
- package/dist/chunk-PF7GRZMW.js.map +0 -1
- package/dist/chunk-PHOUUNK7.js +0 -2291
- package/dist/chunk-PHOUUNK7.js.map +0 -1
- package/dist/chunk-RNHBMHKA.js +0 -1665
- package/dist/chunk-RNHBMHKA.js.map +0 -1
- package/dist/chunk-SX52TAR4.js +0 -110
- package/dist/chunk-SX52TAR4.js.map +0 -1
- package/dist/chunk-VYPWMZ6H.js +0 -98
- package/dist/chunk-VYPWMZ6H.js.map +0 -1
- package/dist/chunk-WBZOVTYK.js +0 -171
- package/dist/chunk-WBZOVTYK.js.map +0 -1
- package/dist/chunk-WKNUIZOY.js +0 -354
- package/dist/chunk-WKNUIZOY.js.map +0 -1
- package/dist/chunk-X3VMZYBT.js +0 -713
- package/dist/chunk-X3VMZYBT.js.map +0 -1
- package/dist/chunk-X5R3GL6H.js +0 -525
- package/dist/chunk-X5R3GL6H.js.map +0 -1
- package/dist/chunk-XGPU467M.js +0 -136
- package/dist/chunk-XGPU467M.js.map +0 -1
- package/dist/compat/index.cjs.map +0 -1
- package/dist/compat/index.js.map +0 -1
- package/dist/compat/jotai/index.cjs.map +0 -1
- package/dist/compat/jotai/index.js.map +0 -1
- package/dist/compat/nanostores/index.cjs.map +0 -1
- package/dist/compat/nanostores/index.js.map +0 -1
- package/dist/compat/nestjs/index.cjs.map +0 -1
- package/dist/compat/nestjs/index.js.map +0 -1
- package/dist/compat/react/index.cjs.map +0 -1
- package/dist/compat/react/index.js.map +0 -1
- package/dist/compat/solid/index.cjs.map +0 -1
- package/dist/compat/solid/index.js.map +0 -1
- package/dist/compat/svelte/index.cjs.map +0 -1
- package/dist/compat/svelte/index.js.map +0 -1
- package/dist/compat/vue/index.cjs.map +0 -1
- package/dist/compat/vue/index.js.map +0 -1
- package/dist/compat/zustand/index.cjs.map +0 -1
- package/dist/compat/zustand/index.js.map +0 -1
- package/dist/core/index.cjs.map +0 -1
- package/dist/core/index.js.map +0 -1
- package/dist/extra/index.cjs.map +0 -1
- package/dist/extra/index.js.map +0 -1
- package/dist/extra/sources.cjs.map +0 -1
- package/dist/extra/sources.js.map +0 -1
- package/dist/graph/index.cjs.map +0 -1
- package/dist/graph/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/patterns/ai.cjs +0 -7930
- package/dist/patterns/ai.cjs.map +0 -1
- package/dist/patterns/ai.d.cts +0 -10
- package/dist/patterns/ai.d.ts +0 -10
- package/dist/patterns/ai.js +0 -71
- package/dist/patterns/ai.js.map +0 -1
- package/dist/patterns/audit.cjs +0 -5805
- package/dist/patterns/audit.cjs.map +0 -1
- package/dist/patterns/audit.d.cts +0 -6
- package/dist/patterns/audit.d.ts +0 -6
- package/dist/patterns/audit.js +0 -29
- package/dist/patterns/audit.js.map +0 -1
- package/dist/patterns/demo-shell.cjs +0 -5604
- package/dist/patterns/demo-shell.cjs.map +0 -1
- package/dist/patterns/demo-shell.d.cts +0 -6
- package/dist/patterns/demo-shell.d.ts +0 -6
- package/dist/patterns/demo-shell.js +0 -15
- package/dist/patterns/demo-shell.js.map +0 -1
- package/dist/patterns/memory.cjs +0 -5283
- package/dist/patterns/memory.cjs.map +0 -1
- package/dist/patterns/memory.d.cts +0 -5
- package/dist/patterns/memory.d.ts +0 -5
- package/dist/patterns/memory.js +0 -20
- package/dist/patterns/memory.js.map +0 -1
- package/dist/patterns/reactive-layout/index.cjs.map +0 -1
- package/dist/patterns/reactive-layout/index.js.map +0 -1
- package/dist/storage-CMjUUuxn.d.ts +0 -190
- package/dist/storage-DdWlZo6U.d.cts +0 -190
package/dist/chunk-PF7GRZMW.js
DELETED
|
@@ -1,2712 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describeNode,
|
|
3
|
-
resolveDescribeFields
|
|
4
|
-
} from "./chunk-VYPWMZ6H.js";
|
|
5
|
-
import {
|
|
6
|
-
ResettableTimer,
|
|
7
|
-
RingBuffer
|
|
8
|
-
} from "./chunk-7TAQJHQV.js";
|
|
9
|
-
import {
|
|
10
|
-
GuardDenied,
|
|
11
|
-
NodeImpl,
|
|
12
|
-
batch,
|
|
13
|
-
decodeEnvelope,
|
|
14
|
-
defaultConfig,
|
|
15
|
-
encodeEnvelope,
|
|
16
|
-
isBatching,
|
|
17
|
-
monotonicNs,
|
|
18
|
-
producer,
|
|
19
|
-
state,
|
|
20
|
-
wallClockNs
|
|
21
|
-
} from "./chunk-PHOUUNK7.js";
|
|
22
|
-
import {
|
|
23
|
-
COMPLETE,
|
|
24
|
-
DATA,
|
|
25
|
-
DIRTY,
|
|
26
|
-
ERROR,
|
|
27
|
-
INVALIDATE,
|
|
28
|
-
PAUSE,
|
|
29
|
-
RESOLVED,
|
|
30
|
-
RESUME,
|
|
31
|
-
TEARDOWN
|
|
32
|
-
} from "./chunk-SX52TAR4.js";
|
|
33
|
-
|
|
34
|
-
// src/graph/explain.ts
|
|
35
|
-
function explainPath(described, from, to, opts = {}) {
|
|
36
|
-
const fromExists = from in described.nodes;
|
|
37
|
-
const toExists = to in described.nodes;
|
|
38
|
-
if (!fromExists) return makeFailure(from, to, "no-such-from");
|
|
39
|
-
if (!toExists) return makeFailure(from, to, "no-such-to");
|
|
40
|
-
const maxDepth = opts.maxDepth;
|
|
41
|
-
if (maxDepth != null && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
|
|
42
|
-
throw new Error(`explainPath: maxDepth must be an integer >= 0`);
|
|
43
|
-
}
|
|
44
|
-
if (from === to) {
|
|
45
|
-
if (opts.findCycle === true) {
|
|
46
|
-
const cycle = findShortestCycle(described, from, opts);
|
|
47
|
-
if (cycle != null) return cycle;
|
|
48
|
-
}
|
|
49
|
-
const step = buildStep(from, described.nodes[from], 0, opts);
|
|
50
|
-
return makeSuccess(from, to, [step]);
|
|
51
|
-
}
|
|
52
|
-
if (maxDepth === 0) return makeFailure(from, to, "no-path");
|
|
53
|
-
const result = bfsShortestPath(described, from, to, maxDepth);
|
|
54
|
-
if (!result.found) {
|
|
55
|
-
return makeFailure(from, to, result.truncated ? "max-depth-exceeded" : "no-path");
|
|
56
|
-
}
|
|
57
|
-
return makeSuccess(from, to, materializeSteps(described, result.pathOrder, opts));
|
|
58
|
-
}
|
|
59
|
-
function bfsShortestPath(described, from, to, maxDepth) {
|
|
60
|
-
const pred = /* @__PURE__ */ new Map();
|
|
61
|
-
const queue = [{ path: to, depth: 0 }];
|
|
62
|
-
const visited = /* @__PURE__ */ new Set([to]);
|
|
63
|
-
let head = 0;
|
|
64
|
-
let truncated = false;
|
|
65
|
-
while (head < queue.length) {
|
|
66
|
-
const cur = queue[head++];
|
|
67
|
-
if (cur.path === from) break;
|
|
68
|
-
if (maxDepth != null && cur.depth >= maxDepth) {
|
|
69
|
-
const node2 = described.nodes[cur.path];
|
|
70
|
-
if (node2?.deps && node2.deps.length > 0) truncated = true;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
const node = described.nodes[cur.path];
|
|
74
|
-
if (node == null) continue;
|
|
75
|
-
const deps = node.deps ?? [];
|
|
76
|
-
const slots = /* @__PURE__ */ new Map();
|
|
77
|
-
for (let i = 0; i < deps.length; i++) {
|
|
78
|
-
const dep = deps[i];
|
|
79
|
-
if (!dep) continue;
|
|
80
|
-
let arr = slots.get(dep);
|
|
81
|
-
if (arr == null) {
|
|
82
|
-
arr = [];
|
|
83
|
-
slots.set(dep, arr);
|
|
84
|
-
}
|
|
85
|
-
arr.push(i);
|
|
86
|
-
}
|
|
87
|
-
for (const [dep, indices] of slots) {
|
|
88
|
-
if (visited.has(dep)) continue;
|
|
89
|
-
visited.add(dep);
|
|
90
|
-
pred.set(dep, { from: cur.path, depIndices: indices });
|
|
91
|
-
queue.push({ path: dep, depth: cur.depth + 1 });
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (!pred.has(from)) {
|
|
95
|
-
return { found: false, pathOrder: [], truncated };
|
|
96
|
-
}
|
|
97
|
-
const pathOrder = [{ path: from }];
|
|
98
|
-
let cursor = from;
|
|
99
|
-
while (cursor !== to) {
|
|
100
|
-
const p = pred.get(cursor);
|
|
101
|
-
if (p == null) return { found: false, pathOrder: [], truncated: false };
|
|
102
|
-
pathOrder[pathOrder.length - 1].depIndices = p.depIndices;
|
|
103
|
-
pathOrder.push({ path: p.from });
|
|
104
|
-
cursor = p.from;
|
|
105
|
-
}
|
|
106
|
-
return { found: true, pathOrder, truncated: false };
|
|
107
|
-
}
|
|
108
|
-
function findShortestCycle(described, start, opts) {
|
|
109
|
-
const startNode = described.nodes[start];
|
|
110
|
-
if (startNode == null) return null;
|
|
111
|
-
const startDeps = startNode.deps ?? [];
|
|
112
|
-
const selfSlots = [];
|
|
113
|
-
for (let i = 0; i < startDeps.length; i++) if (startDeps[i] === start) selfSlots.push(i);
|
|
114
|
-
if (selfSlots.length > 0) {
|
|
115
|
-
const step0 = buildStep(start, startNode, 0, opts);
|
|
116
|
-
step0.dep_index = selfSlots[0];
|
|
117
|
-
const step1 = buildStep(start, startNode, 1, opts);
|
|
118
|
-
return makeSuccess(start, start, [step0, step1]);
|
|
119
|
-
}
|
|
120
|
-
let best = null;
|
|
121
|
-
for (let i = 0; i < startDeps.length; i++) {
|
|
122
|
-
const dep = startDeps[i];
|
|
123
|
-
if (!dep || dep === start) continue;
|
|
124
|
-
const sub = bfsShortestPath(described, dep, start, opts.maxDepth);
|
|
125
|
-
if (!sub.found) continue;
|
|
126
|
-
if (best == null || sub.pathOrder.length < best.pathOrder.length) {
|
|
127
|
-
best = sub;
|
|
128
|
-
best = {
|
|
129
|
-
found: true,
|
|
130
|
-
pathOrder: [{ path: start, depIndices: [i] }, ...sub.pathOrder],
|
|
131
|
-
truncated: false
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
if (best == null) return null;
|
|
136
|
-
return makeSuccess(start, start, materializeSteps(described, best.pathOrder, opts));
|
|
137
|
-
}
|
|
138
|
-
function materializeSteps(described, pathOrder, opts) {
|
|
139
|
-
return pathOrder.map((entry, i) => {
|
|
140
|
-
const node = described.nodes[entry.path];
|
|
141
|
-
const step = buildStep(entry.path, node, i, opts);
|
|
142
|
-
if (entry.depIndices != null && entry.depIndices.length > 0) {
|
|
143
|
-
step.dep_index = entry.depIndices[0];
|
|
144
|
-
if (entry.depIndices.length > 1) step.dep_indices = [...entry.depIndices];
|
|
145
|
-
}
|
|
146
|
-
return step;
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
function buildStep(path, node, hop, opts) {
|
|
150
|
-
const step = {
|
|
151
|
-
path,
|
|
152
|
-
type: node.type,
|
|
153
|
-
hop
|
|
154
|
-
};
|
|
155
|
-
if (node.status !== void 0) step.status = node.status;
|
|
156
|
-
if ("value" in node) step.value = node.value;
|
|
157
|
-
if (node.v != null) step.v = node.v;
|
|
158
|
-
const annotation = opts.annotations?.get(path) ?? node.reason;
|
|
159
|
-
if (annotation != null) step.reason = annotation;
|
|
160
|
-
const lastMutation = opts.lastMutations?.get(path) ?? node.lastMutation;
|
|
161
|
-
if (lastMutation != null) step.lastMutation = lastMutation;
|
|
162
|
-
return step;
|
|
163
|
-
}
|
|
164
|
-
function makeSuccess(from, to, steps) {
|
|
165
|
-
return finalize(from, to, true, "ok", steps);
|
|
166
|
-
}
|
|
167
|
-
function makeFailure(from, to, reason) {
|
|
168
|
-
return finalize(from, to, false, reason, []);
|
|
169
|
-
}
|
|
170
|
-
function finalize(from, to, found, reason, steps) {
|
|
171
|
-
const text = renderChain(from, to, found, reason, steps);
|
|
172
|
-
return {
|
|
173
|
-
from,
|
|
174
|
-
to,
|
|
175
|
-
found,
|
|
176
|
-
reason,
|
|
177
|
-
steps,
|
|
178
|
-
text,
|
|
179
|
-
toJSON() {
|
|
180
|
-
return { from, to, found, reason, steps };
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
function renderChain(from, to, found, reason, steps) {
|
|
185
|
-
if (!found) {
|
|
186
|
-
switch (reason) {
|
|
187
|
-
case "no-such-from":
|
|
188
|
-
return `explainPath: no node named "${from}"`;
|
|
189
|
-
case "no-such-to":
|
|
190
|
-
return `explainPath: no node named "${to}"`;
|
|
191
|
-
case "max-depth-exceeded":
|
|
192
|
-
return `explainPath: no path from "${from}" to "${to}" within maxDepth`;
|
|
193
|
-
default:
|
|
194
|
-
return `explainPath: no path from "${from}" to "${to}"`;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
const lines = [`Causal path: ${from} \u2192 ${to} (${steps.length} step(s))`];
|
|
198
|
-
for (const step of steps) {
|
|
199
|
-
const arrow = step.hop === 0 ? "\xB7" : "\u2193";
|
|
200
|
-
const head = ` ${arrow} ${step.path} (${step.type}${step.status ? `/${step.status}` : ""})`;
|
|
201
|
-
lines.push(head);
|
|
202
|
-
if ("value" in step) {
|
|
203
|
-
lines.push(` value: ${formatValue(step.value)}`);
|
|
204
|
-
}
|
|
205
|
-
if (step.reason != null) {
|
|
206
|
-
lines.push(` reason: ${step.reason}`);
|
|
207
|
-
}
|
|
208
|
-
if (step.lastMutation != null) {
|
|
209
|
-
const a = step.lastMutation.actor;
|
|
210
|
-
lines.push(` actor: ${a.type}${a.id ? `:${a.id}` : ""}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return lines.join("\n");
|
|
214
|
-
}
|
|
215
|
-
function formatValue(v) {
|
|
216
|
-
if (v === void 0) return "<sentinel>";
|
|
217
|
-
if (v === null) return "null";
|
|
218
|
-
if (typeof v === "string") return JSON.stringify(v);
|
|
219
|
-
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
|
|
220
|
-
try {
|
|
221
|
-
const s = JSON.stringify(v);
|
|
222
|
-
return s.length > 80 ? `${s.slice(0, 77)}...` : s;
|
|
223
|
-
} catch {
|
|
224
|
-
return String(v);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// src/extra/utils/sizeof.ts
|
|
229
|
-
var OVERHEAD = {
|
|
230
|
-
object: 56,
|
|
231
|
-
array: 64,
|
|
232
|
-
string: 40,
|
|
233
|
-
// header; content added separately
|
|
234
|
-
number: 8,
|
|
235
|
-
boolean: 4,
|
|
236
|
-
null: 0,
|
|
237
|
-
undefined: 0,
|
|
238
|
-
symbol: 40,
|
|
239
|
-
bigint: 16,
|
|
240
|
-
// base; scales with digit count (see `_bigintSize`)
|
|
241
|
-
function: 120,
|
|
242
|
-
map: 72,
|
|
243
|
-
set: 72,
|
|
244
|
-
mapEntry: 40,
|
|
245
|
-
setEntry: 24,
|
|
246
|
-
date: 24,
|
|
247
|
-
regexp: 48,
|
|
248
|
-
error: 64,
|
|
249
|
-
url: 80,
|
|
250
|
-
promise: 48,
|
|
251
|
-
weakmap: 40,
|
|
252
|
-
weakset: 40
|
|
253
|
-
};
|
|
254
|
-
var SIZEOF_SYMBOL = /* @__PURE__ */ Symbol.for("sizeof");
|
|
255
|
-
function sizeof(value) {
|
|
256
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
257
|
-
const seenBuffers = /* @__PURE__ */ new WeakSet();
|
|
258
|
-
const stack = [value];
|
|
259
|
-
let total = 0;
|
|
260
|
-
while (stack.length > 0) {
|
|
261
|
-
const v = stack.pop();
|
|
262
|
-
total += _shallowSize(v, seen, seenBuffers, stack);
|
|
263
|
-
}
|
|
264
|
-
return total;
|
|
265
|
-
}
|
|
266
|
-
function _shallowSize(value, seen, seenBuffers, stack) {
|
|
267
|
-
if (value === null || value === void 0) return 0;
|
|
268
|
-
const t = typeof value;
|
|
269
|
-
switch (t) {
|
|
270
|
-
case "number":
|
|
271
|
-
return OVERHEAD.number;
|
|
272
|
-
case "boolean":
|
|
273
|
-
return OVERHEAD.boolean;
|
|
274
|
-
case "string":
|
|
275
|
-
return OVERHEAD.string + value.length * 2;
|
|
276
|
-
case "bigint":
|
|
277
|
-
return OVERHEAD.bigint + _bigintSize(value);
|
|
278
|
-
case "symbol":
|
|
279
|
-
return OVERHEAD.symbol;
|
|
280
|
-
case "function":
|
|
281
|
-
if (seen.has(value)) return 0;
|
|
282
|
-
seen.add(value);
|
|
283
|
-
return OVERHEAD.function;
|
|
284
|
-
case "undefined":
|
|
285
|
-
return 0;
|
|
286
|
-
}
|
|
287
|
-
const obj = value;
|
|
288
|
-
if (seen.has(obj)) return 0;
|
|
289
|
-
seen.add(obj);
|
|
290
|
-
const hook = obj[SIZEOF_SYMBOL];
|
|
291
|
-
if (typeof hook === "function") {
|
|
292
|
-
try {
|
|
293
|
-
const reported = hook.call(obj);
|
|
294
|
-
if (typeof reported === "number" && Number.isFinite(reported)) return reported;
|
|
295
|
-
} catch {
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (obj instanceof Date) return OVERHEAD.date;
|
|
299
|
-
if (obj instanceof RegExp) return OVERHEAD.regexp + obj.source.length * 2;
|
|
300
|
-
if (obj instanceof Error) {
|
|
301
|
-
const m = obj.message ? obj.message.length * 2 : 0;
|
|
302
|
-
const s = obj.stack ? obj.stack.length * 2 : 0;
|
|
303
|
-
return OVERHEAD.error + m + s;
|
|
304
|
-
}
|
|
305
|
-
if (typeof URL !== "undefined" && obj instanceof URL) {
|
|
306
|
-
return OVERHEAD.url + obj.href.length * 2;
|
|
307
|
-
}
|
|
308
|
-
if (typeof Promise !== "undefined" && obj instanceof Promise) {
|
|
309
|
-
return OVERHEAD.promise;
|
|
310
|
-
}
|
|
311
|
-
if (obj instanceof WeakMap) return OVERHEAD.weakmap;
|
|
312
|
-
if (obj instanceof WeakSet) return OVERHEAD.weakset;
|
|
313
|
-
if (obj instanceof Map) {
|
|
314
|
-
let size2 = OVERHEAD.map;
|
|
315
|
-
for (const [k, v] of obj) {
|
|
316
|
-
size2 += OVERHEAD.mapEntry;
|
|
317
|
-
stack.push(k);
|
|
318
|
-
stack.push(v);
|
|
319
|
-
}
|
|
320
|
-
return size2;
|
|
321
|
-
}
|
|
322
|
-
if (obj instanceof Set) {
|
|
323
|
-
let size2 = OVERHEAD.set;
|
|
324
|
-
for (const v of obj) {
|
|
325
|
-
size2 += OVERHEAD.setEntry;
|
|
326
|
-
stack.push(v);
|
|
327
|
-
}
|
|
328
|
-
return size2;
|
|
329
|
-
}
|
|
330
|
-
if (Array.isArray(obj)) {
|
|
331
|
-
const size2 = OVERHEAD.array + obj.length * 8;
|
|
332
|
-
for (const item of obj) stack.push(item);
|
|
333
|
-
return size2;
|
|
334
|
-
}
|
|
335
|
-
if (obj instanceof ArrayBuffer) {
|
|
336
|
-
if (seenBuffers.has(obj)) return 0;
|
|
337
|
-
seenBuffers.add(obj);
|
|
338
|
-
return obj.byteLength;
|
|
339
|
-
}
|
|
340
|
-
if (ArrayBuffer.isView(obj)) {
|
|
341
|
-
const view = obj;
|
|
342
|
-
if (seenBuffers.has(view.buffer)) return 48;
|
|
343
|
-
seenBuffers.add(view.buffer);
|
|
344
|
-
return view.buffer.byteLength + 48;
|
|
345
|
-
}
|
|
346
|
-
let size = OVERHEAD.object;
|
|
347
|
-
const keys = Object.keys(obj);
|
|
348
|
-
for (const key of keys) {
|
|
349
|
-
size += OVERHEAD.string + key.length * 2;
|
|
350
|
-
try {
|
|
351
|
-
stack.push(obj[key]);
|
|
352
|
-
} catch {
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return size;
|
|
356
|
-
}
|
|
357
|
-
function _bigintSize(n) {
|
|
358
|
-
const abs = n < 0n ? -n : n;
|
|
359
|
-
if (abs === 0n) return 0;
|
|
360
|
-
const bits = abs.toString(2).length;
|
|
361
|
-
return Math.ceil(bits / 32) * 8;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// src/graph/profile.ts
|
|
365
|
-
function graphProfile(graph, opts) {
|
|
366
|
-
const topN = opts?.topN ?? 10;
|
|
367
|
-
const desc = graph.describe({ detail: "standard" });
|
|
368
|
-
const targets = [];
|
|
369
|
-
const collector = graph._collectObserveTargets;
|
|
370
|
-
if (typeof collector === "function") {
|
|
371
|
-
collector.call(graph, "", targets);
|
|
372
|
-
}
|
|
373
|
-
const pathToNode = /* @__PURE__ */ new Map();
|
|
374
|
-
for (const [p, n] of targets) pathToNode.set(p, n);
|
|
375
|
-
const profiles = [];
|
|
376
|
-
for (const [path, nodeDesc] of Object.entries(desc.nodes)) {
|
|
377
|
-
const nd = pathToNode.get(path);
|
|
378
|
-
const impl = nd instanceof NodeImpl ? nd : null;
|
|
379
|
-
const valueSizeBytes = impl ? sizeof(impl.cache) : 0;
|
|
380
|
-
const subscriberCount = impl ? impl._sinkCount : 0;
|
|
381
|
-
const depCount = nodeDesc.deps?.length ?? 0;
|
|
382
|
-
const isOrphanEffect = nodeDesc.type === "effect" && subscriberCount === 0;
|
|
383
|
-
const orphanKind = subscriberCount === 0 ? nodeDesc.type === "effect" ? "orphan-effect" : nodeDesc.type === "derived" ? "idle-derived" : nodeDesc.type === "producer" ? "idle-producer" : null : null;
|
|
384
|
-
profiles.push({
|
|
385
|
-
path,
|
|
386
|
-
type: nodeDesc.type,
|
|
387
|
-
status: nodeDesc.status ?? "unknown",
|
|
388
|
-
valueSizeBytes,
|
|
389
|
-
subscriberCount,
|
|
390
|
-
depCount,
|
|
391
|
-
isOrphanEffect,
|
|
392
|
-
orphanKind
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
const totalValueSizeBytes = profiles.reduce((sum, p) => sum + p.valueSizeBytes, 0);
|
|
396
|
-
const topBy = (key, cmp) => [...profiles].sort(cmp ?? ((a, b) => b[key] - a[key])).slice(0, topN);
|
|
397
|
-
const orphans = profiles.filter((p) => p.orphanKind != null);
|
|
398
|
-
const orphanEffects = profiles.filter((p) => p.isOrphanEffect);
|
|
399
|
-
return {
|
|
400
|
-
nodeCount: profiles.length,
|
|
401
|
-
edgeCount: desc.edges.length,
|
|
402
|
-
subgraphCount: desc.subgraphs.length,
|
|
403
|
-
nodes: profiles,
|
|
404
|
-
totalValueSizeBytes,
|
|
405
|
-
hotspots: {
|
|
406
|
-
byValueSize: topBy("valueSizeBytes"),
|
|
407
|
-
bySubscriberCount: topBy("subscriberCount"),
|
|
408
|
-
byDepCount: topBy("depCount")
|
|
409
|
-
},
|
|
410
|
-
orphans,
|
|
411
|
-
orphanEffects
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// src/graph/graph.ts
|
|
416
|
-
var PATH_SEP = "::";
|
|
417
|
-
var GRAPH_META_SEGMENT = "__meta__";
|
|
418
|
-
var SNAPSHOT_VERSION = 1;
|
|
419
|
-
function drainDisposers(set, graphName) {
|
|
420
|
-
const cap = Math.max(16, set.size * 4);
|
|
421
|
-
let iterations = 0;
|
|
422
|
-
while (set.size > 0) {
|
|
423
|
-
if (iterations++ >= cap) {
|
|
424
|
-
console.error(
|
|
425
|
-
`[Graph "${graphName}".destroy] disposer drain exceeded cap (${cap}); ${set.size} disposer(s) discarded`
|
|
426
|
-
);
|
|
427
|
-
set.clear();
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const it = set.values().next();
|
|
431
|
-
if (it.done) return;
|
|
432
|
-
const dispose = it.value;
|
|
433
|
-
set.delete(dispose);
|
|
434
|
-
try {
|
|
435
|
-
dispose();
|
|
436
|
-
} catch (err) {
|
|
437
|
-
console.error(`[Graph "${graphName}".destroy] disposer threw:`, err);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function computeVersionFingerprint(nodes) {
|
|
442
|
-
const parts = [];
|
|
443
|
-
for (const path of Object.keys(nodes).sort()) {
|
|
444
|
-
const v = nodes[path].v;
|
|
445
|
-
if (v != null) parts.push(`${path} ${v.id} ${v.version}`);
|
|
446
|
-
}
|
|
447
|
-
return parts.join("\n");
|
|
448
|
-
}
|
|
449
|
-
function parseSnapshotEnvelope(data) {
|
|
450
|
-
if (data.version !== SNAPSHOT_VERSION) {
|
|
451
|
-
throw new Error(
|
|
452
|
-
`unsupported snapshot version ${String(data.version)} (expected ${SNAPSHOT_VERSION})`
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
for (const key of ["name", "nodes", "edges", "subgraphs"]) {
|
|
456
|
-
if (!(key in data)) {
|
|
457
|
-
throw new Error(`snapshot missing required key "${key}"`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
if (typeof data.name !== "string") {
|
|
461
|
-
throw new TypeError(`snapshot 'name' must be a string`);
|
|
462
|
-
}
|
|
463
|
-
if (typeof data.nodes !== "object" || data.nodes === null || Array.isArray(data.nodes)) {
|
|
464
|
-
throw new TypeError(`snapshot 'nodes' must be an object`);
|
|
465
|
-
}
|
|
466
|
-
if (!Array.isArray(data.edges)) {
|
|
467
|
-
throw new TypeError(`snapshot 'edges' must be an array`);
|
|
468
|
-
}
|
|
469
|
-
if (!Array.isArray(data.subgraphs)) {
|
|
470
|
-
throw new TypeError(`snapshot 'subgraphs' must be an array`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
function deepEqual(a, b) {
|
|
474
|
-
const seen = /* @__PURE__ */ new WeakMap();
|
|
475
|
-
const walk = (x, y) => {
|
|
476
|
-
if (Object.is(x, y)) return true;
|
|
477
|
-
if (x == null || y == null || typeof x !== "object" || typeof y !== "object") return false;
|
|
478
|
-
let seenRhs = seen.get(x);
|
|
479
|
-
if (seenRhs == null) {
|
|
480
|
-
seenRhs = /* @__PURE__ */ new WeakSet();
|
|
481
|
-
seen.set(x, seenRhs);
|
|
482
|
-
}
|
|
483
|
-
if (seenRhs.has(y)) return true;
|
|
484
|
-
seenRhs.add(y);
|
|
485
|
-
const ctorA = x.constructor;
|
|
486
|
-
const ctorB = y.constructor;
|
|
487
|
-
if (ctorA !== ctorB) return false;
|
|
488
|
-
if (x instanceof Date) return x.getTime() === y.getTime();
|
|
489
|
-
if (x instanceof RegExp)
|
|
490
|
-
return x.source === y.source && x.flags === y.flags;
|
|
491
|
-
if (Array.isArray(x)) {
|
|
492
|
-
const arrB = y;
|
|
493
|
-
if (x.length !== arrB.length) return false;
|
|
494
|
-
for (let i = 0; i < x.length; i++) {
|
|
495
|
-
if (!walk(x[i], arrB[i])) return false;
|
|
496
|
-
}
|
|
497
|
-
return true;
|
|
498
|
-
}
|
|
499
|
-
if (x instanceof Map) {
|
|
500
|
-
const mB = y;
|
|
501
|
-
if (x.size !== mB.size) return false;
|
|
502
|
-
for (const [k, v] of x) {
|
|
503
|
-
if (!mB.has(k) || !walk(v, mB.get(k))) return false;
|
|
504
|
-
}
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
if (x instanceof Set) {
|
|
508
|
-
const sB = y;
|
|
509
|
-
if (x.size !== sB.size) return false;
|
|
510
|
-
for (const v of x) {
|
|
511
|
-
let found = false;
|
|
512
|
-
for (const w of sB) {
|
|
513
|
-
if (walk(v, w)) {
|
|
514
|
-
found = true;
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
if (!found) return false;
|
|
519
|
-
}
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
|
-
if (ArrayBuffer.isView(x)) {
|
|
523
|
-
const taA = x;
|
|
524
|
-
const taB = y;
|
|
525
|
-
if (taA.length !== taB.length) return false;
|
|
526
|
-
for (let i = 0; i < taA.length; i++) if (taA[i] !== taB[i]) return false;
|
|
527
|
-
return true;
|
|
528
|
-
}
|
|
529
|
-
const keysA = Object.keys(x);
|
|
530
|
-
const keysB = Object.keys(y);
|
|
531
|
-
if (keysA.length !== keysB.length) return false;
|
|
532
|
-
const setB = new Set(keysB);
|
|
533
|
-
for (const k of keysA) {
|
|
534
|
-
if (!setB.has(k)) return false;
|
|
535
|
-
if (!walk(x[k], y[k])) return false;
|
|
536
|
-
}
|
|
537
|
-
return true;
|
|
538
|
-
};
|
|
539
|
-
return walk(a, b);
|
|
540
|
-
}
|
|
541
|
-
function sortJsonValue(value) {
|
|
542
|
-
if (value === null || typeof value !== "object") {
|
|
543
|
-
return value;
|
|
544
|
-
}
|
|
545
|
-
if (Array.isArray(value)) {
|
|
546
|
-
return value.map(sortJsonValue);
|
|
547
|
-
}
|
|
548
|
-
const obj = value;
|
|
549
|
-
const keys = Object.keys(obj).sort();
|
|
550
|
-
const out = {};
|
|
551
|
-
for (const k of keys) {
|
|
552
|
-
out[k] = sortJsonValue(obj[k]);
|
|
553
|
-
}
|
|
554
|
-
return out;
|
|
555
|
-
}
|
|
556
|
-
function escapeMermaidLabel(value) {
|
|
557
|
-
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
558
|
-
}
|
|
559
|
-
function escapeD2Label(value) {
|
|
560
|
-
return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
561
|
-
}
|
|
562
|
-
function d2DirectionFromGraphDirection(direction) {
|
|
563
|
-
if (direction === "TD") return "down";
|
|
564
|
-
if (direction === "BT") return "up";
|
|
565
|
-
if (direction === "RL") return "left";
|
|
566
|
-
return "right";
|
|
567
|
-
}
|
|
568
|
-
function collectDiagramArrows(described) {
|
|
569
|
-
const seen = /* @__PURE__ */ new Set();
|
|
570
|
-
const arrows = [];
|
|
571
|
-
function add(from, to) {
|
|
572
|
-
const key = `${from}\0${to}`;
|
|
573
|
-
if (seen.has(key)) return;
|
|
574
|
-
seen.add(key);
|
|
575
|
-
arrows.push([from, to]);
|
|
576
|
-
}
|
|
577
|
-
for (const [path, info] of Object.entries(described.nodes)) {
|
|
578
|
-
const deps = info.deps;
|
|
579
|
-
if (deps) {
|
|
580
|
-
for (const dep of deps) add(dep, path);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
for (const edge of described.edges) add(edge.from, edge.to);
|
|
584
|
-
return arrows;
|
|
585
|
-
}
|
|
586
|
-
function normalizeDiagramDirection(direction) {
|
|
587
|
-
if (direction === void 0) return "LR";
|
|
588
|
-
if (direction === "TD" || direction === "LR" || direction === "BT" || direction === "RL") {
|
|
589
|
-
return direction;
|
|
590
|
-
}
|
|
591
|
-
throw new Error(
|
|
592
|
-
`invalid diagram direction ${String(direction)}; expected one of: TD, LR, BT, RL`
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
function renderDescribeAsJson(d, options) {
|
|
596
|
-
const includeEdges = options.includeEdges ?? true;
|
|
597
|
-
const includeSubgraphs = options.includeSubgraphs ?? true;
|
|
598
|
-
const { expand: _expand, ...rest } = d;
|
|
599
|
-
const payload = {
|
|
600
|
-
...rest,
|
|
601
|
-
edges: includeEdges ? d.edges : [],
|
|
602
|
-
subgraphs: includeSubgraphs ? d.subgraphs : []
|
|
603
|
-
};
|
|
604
|
-
const text = JSON.stringify(sortJsonValue(payload), null, options.indent ?? 2);
|
|
605
|
-
options.logger?.(text);
|
|
606
|
-
return text;
|
|
607
|
-
}
|
|
608
|
-
function renderDescribeAsPretty(d, options) {
|
|
609
|
-
const includeEdges = options.includeEdges ?? true;
|
|
610
|
-
const includeSubgraphs = options.includeSubgraphs ?? true;
|
|
611
|
-
const lines = [];
|
|
612
|
-
lines.push(`Graph ${d.name}`);
|
|
613
|
-
lines.push("Nodes:");
|
|
614
|
-
for (const path of Object.keys(d.nodes).sort()) {
|
|
615
|
-
const n = d.nodes[path];
|
|
616
|
-
lines.push(`- ${path} (${n.type}/${n.status}): ${describeData(n.value)}`);
|
|
617
|
-
}
|
|
618
|
-
if (includeEdges) {
|
|
619
|
-
lines.push("Edges:");
|
|
620
|
-
for (const edge of d.edges) {
|
|
621
|
-
lines.push(`- ${edge.from} -> ${edge.to}`);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
if (includeSubgraphs) {
|
|
625
|
-
lines.push("Subgraphs:");
|
|
626
|
-
for (const sg of d.subgraphs) {
|
|
627
|
-
lines.push(`- ${sg}`);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
const text = lines.join("\n");
|
|
631
|
-
options.logger?.(text);
|
|
632
|
-
return text;
|
|
633
|
-
}
|
|
634
|
-
function renderDescribeAsMermaid(d, options) {
|
|
635
|
-
const direction = normalizeDiagramDirection(options.direction);
|
|
636
|
-
const paths = Object.keys(d.nodes).sort();
|
|
637
|
-
const ids = /* @__PURE__ */ new Map();
|
|
638
|
-
for (let i = 0; i < paths.length; i += 1) ids.set(paths[i], `n${i}`);
|
|
639
|
-
const lines = [`flowchart ${direction}`];
|
|
640
|
-
for (const path of paths) {
|
|
641
|
-
const id = ids.get(path);
|
|
642
|
-
lines.push(` ${id}["${escapeMermaidLabel(path)}"]`);
|
|
643
|
-
}
|
|
644
|
-
for (const [from, to] of collectDiagramArrows(d)) {
|
|
645
|
-
const fromId = ids.get(from);
|
|
646
|
-
const toId = ids.get(to);
|
|
647
|
-
if (!fromId || !toId) continue;
|
|
648
|
-
lines.push(` ${fromId} --> ${toId}`);
|
|
649
|
-
}
|
|
650
|
-
return lines.join("\n");
|
|
651
|
-
}
|
|
652
|
-
function renderDescribeAsD2(d, options) {
|
|
653
|
-
const direction = normalizeDiagramDirection(options.direction);
|
|
654
|
-
const paths = Object.keys(d.nodes).sort();
|
|
655
|
-
const ids = /* @__PURE__ */ new Map();
|
|
656
|
-
for (let i = 0; i < paths.length; i += 1) ids.set(paths[i], `n${i}`);
|
|
657
|
-
const lines = [`direction: ${d2DirectionFromGraphDirection(direction)}`];
|
|
658
|
-
for (const path of paths) {
|
|
659
|
-
const id = ids.get(path);
|
|
660
|
-
lines.push(`${id}: "${escapeD2Label(path)}"`);
|
|
661
|
-
}
|
|
662
|
-
for (const [from, to] of collectDiagramArrows(d)) {
|
|
663
|
-
const fromId = ids.get(from);
|
|
664
|
-
const toId = ids.get(to);
|
|
665
|
-
if (!fromId || !toId) continue;
|
|
666
|
-
lines.push(`${fromId} -> ${toId}`);
|
|
667
|
-
}
|
|
668
|
-
return lines.join("\n");
|
|
669
|
-
}
|
|
670
|
-
function escapeRegexLiteral(value) {
|
|
671
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
672
|
-
}
|
|
673
|
-
function globToRegex(pattern) {
|
|
674
|
-
let re = "^";
|
|
675
|
-
for (let i = 0; i < pattern.length; i += 1) {
|
|
676
|
-
const ch = pattern[i];
|
|
677
|
-
if (ch === "*") {
|
|
678
|
-
re += ".*";
|
|
679
|
-
continue;
|
|
680
|
-
}
|
|
681
|
-
if (ch === "?") {
|
|
682
|
-
re += ".";
|
|
683
|
-
continue;
|
|
684
|
-
}
|
|
685
|
-
if (ch === "[") {
|
|
686
|
-
const end = pattern.indexOf("]", i + 1);
|
|
687
|
-
if (end <= i + 1) {
|
|
688
|
-
re += "\\[";
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
let cls = pattern.slice(i + 1, end);
|
|
692
|
-
if (cls.startsWith("!")) cls = `^${cls.slice(1)}`;
|
|
693
|
-
cls = cls.replace(/\\/g, "\\\\");
|
|
694
|
-
re += `[${cls}]`;
|
|
695
|
-
i = end;
|
|
696
|
-
continue;
|
|
697
|
-
}
|
|
698
|
-
re += escapeRegexLiteral(ch);
|
|
699
|
-
}
|
|
700
|
-
re += "$";
|
|
701
|
-
return new RegExp(re);
|
|
702
|
-
}
|
|
703
|
-
var OBSERVE_ANSI_THEME = {
|
|
704
|
-
data: "\x1B[32m",
|
|
705
|
-
dirty: "\x1B[33m",
|
|
706
|
-
resolved: "\x1B[36m",
|
|
707
|
-
invalidate: "\x1B[93m",
|
|
708
|
-
pause: "\x1B[90m",
|
|
709
|
-
resume: "\x1B[96m",
|
|
710
|
-
complete: "\x1B[34m",
|
|
711
|
-
error: "\x1B[31m",
|
|
712
|
-
teardown: "\x1B[91m",
|
|
713
|
-
derived: "\x1B[35m",
|
|
714
|
-
path: "\x1B[90m",
|
|
715
|
-
reset: "\x1B[0m"
|
|
716
|
-
};
|
|
717
|
-
var OBSERVE_NO_COLOR_THEME = {
|
|
718
|
-
data: "",
|
|
719
|
-
dirty: "",
|
|
720
|
-
resolved: "",
|
|
721
|
-
invalidate: "",
|
|
722
|
-
pause: "",
|
|
723
|
-
resume: "",
|
|
724
|
-
complete: "",
|
|
725
|
-
error: "",
|
|
726
|
-
teardown: "",
|
|
727
|
-
derived: "",
|
|
728
|
-
path: "",
|
|
729
|
-
reset: ""
|
|
730
|
-
};
|
|
731
|
-
function describeData(value) {
|
|
732
|
-
if (typeof value === "string") return JSON.stringify(value);
|
|
733
|
-
if (typeof value === "number" || typeof value === "boolean" || value == null)
|
|
734
|
-
return String(value);
|
|
735
|
-
try {
|
|
736
|
-
return JSON.stringify(value);
|
|
737
|
-
} catch {
|
|
738
|
-
return "[unserializable]";
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
function resolveObserveTheme(theme) {
|
|
742
|
-
if (theme === "none") return OBSERVE_NO_COLOR_THEME;
|
|
743
|
-
if (theme === "ansi" || theme == null) return OBSERVE_ANSI_THEME;
|
|
744
|
-
return {
|
|
745
|
-
data: theme.data ?? "",
|
|
746
|
-
dirty: theme.dirty ?? "",
|
|
747
|
-
resolved: theme.resolved ?? "",
|
|
748
|
-
invalidate: theme.invalidate ?? "",
|
|
749
|
-
pause: theme.pause ?? "",
|
|
750
|
-
resume: theme.resume ?? "",
|
|
751
|
-
complete: theme.complete ?? "",
|
|
752
|
-
error: theme.error ?? "",
|
|
753
|
-
teardown: theme.teardown ?? "",
|
|
754
|
-
derived: theme.derived ?? "",
|
|
755
|
-
path: theme.path ?? "",
|
|
756
|
-
reset: theme.reset ?? ""
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
function resolveObserveDetail(opts) {
|
|
760
|
-
if (opts == null) return {};
|
|
761
|
-
const detail = opts.detail;
|
|
762
|
-
if (detail === "full") {
|
|
763
|
-
return {
|
|
764
|
-
...opts,
|
|
765
|
-
structured: opts.structured ?? true,
|
|
766
|
-
timeline: opts.timeline ?? true,
|
|
767
|
-
causal: opts.causal ?? true,
|
|
768
|
-
derived: opts.derived ?? true
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
if (detail === "minimal") {
|
|
772
|
-
return { ...opts, structured: opts.structured ?? true };
|
|
773
|
-
}
|
|
774
|
-
return opts;
|
|
775
|
-
}
|
|
776
|
-
function assertNoControlChars(name, graphName, label) {
|
|
777
|
-
for (let i = 0; i < name.length; i++) {
|
|
778
|
-
const c = name.charCodeAt(i);
|
|
779
|
-
if (c < 32 || c === 127) {
|
|
780
|
-
throw new Error(
|
|
781
|
-
`Graph "${graphName}": ${label} "${name}" must not contain control character (U+${c.toString(16).padStart(4, "0").toUpperCase()} at index ${i})`
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
function assertRegisterableName(name, graphName, label) {
|
|
787
|
-
if (name === "") {
|
|
788
|
-
throw new Error(`Graph "${graphName}": ${label} name must be non-empty`);
|
|
789
|
-
}
|
|
790
|
-
if (name.includes(PATH_SEP)) {
|
|
791
|
-
throw new Error(
|
|
792
|
-
`Graph "${graphName}": ${label} "${name}" must not contain '${PATH_SEP}' (path separator)`
|
|
793
|
-
);
|
|
794
|
-
}
|
|
795
|
-
if (name === GRAPH_META_SEGMENT) {
|
|
796
|
-
throw new Error(
|
|
797
|
-
`Graph "${graphName}": ${label} name "${GRAPH_META_SEGMENT}" is reserved for meta companion paths`
|
|
798
|
-
);
|
|
799
|
-
}
|
|
800
|
-
assertNoControlChars(name, graphName, label);
|
|
801
|
-
}
|
|
802
|
-
function splitPath(path, graphName) {
|
|
803
|
-
if (path === "") {
|
|
804
|
-
throw new Error(`Graph "${graphName}": resolve path must be non-empty`);
|
|
805
|
-
}
|
|
806
|
-
const segments = path.split(PATH_SEP);
|
|
807
|
-
for (const s of segments) {
|
|
808
|
-
if (s === "") {
|
|
809
|
-
throw new Error(`Graph "${graphName}": resolve path has empty segment`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return segments;
|
|
813
|
-
}
|
|
814
|
-
function filterMetaMessages(messages, config) {
|
|
815
|
-
let anyFiltered = false;
|
|
816
|
-
for (const m of messages) {
|
|
817
|
-
if (!config.isMetaPassthrough(m[0])) {
|
|
818
|
-
anyFiltered = true;
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
if (!anyFiltered) return messages;
|
|
823
|
-
const kept = messages.filter((m) => config.isMetaPassthrough(m[0]));
|
|
824
|
-
return kept;
|
|
825
|
-
}
|
|
826
|
-
function teardownMountedGraph(root) {
|
|
827
|
-
for (const child of root._mounts.values()) {
|
|
828
|
-
teardownMountedGraph(child);
|
|
829
|
-
}
|
|
830
|
-
for (const n of root._nodes.values()) {
|
|
831
|
-
try {
|
|
832
|
-
n.down([[TEARDOWN]], { internal: true });
|
|
833
|
-
} catch {
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
var Graph = class _Graph {
|
|
838
|
-
name;
|
|
839
|
-
opts;
|
|
840
|
-
/** Protocol config bound to this graph (defaults to `defaultConfig`). */
|
|
841
|
-
config;
|
|
842
|
-
/** @internal — exposed for {@link teardownMountedGraph} and cross-graph helpers. */
|
|
843
|
-
_nodes = /* @__PURE__ */ new Map();
|
|
844
|
-
/**
|
|
845
|
-
* @internal Reverse lookup for duplicate-instance detection in
|
|
846
|
-
* {@link Graph.add} — O(1) replacement for an O(n) scan of `_nodes`.
|
|
847
|
-
* Weak so nodes can be GC'd after `remove()` even if a caller keeps the
|
|
848
|
-
* map alive via some unusual pattern.
|
|
849
|
-
*/
|
|
850
|
-
_nodeToName = /* @__PURE__ */ new WeakMap();
|
|
851
|
-
/** @internal — exposed for {@link teardownMountedGraph}. */
|
|
852
|
-
_mounts = /* @__PURE__ */ new Map();
|
|
853
|
-
/**
|
|
854
|
-
* @internal Parent graph if this instance is mounted. `undefined` when
|
|
855
|
-
* this is the root or when the graph has been unmounted. Used for
|
|
856
|
-
* reparenting rejection + O(depth) ancestor walks.
|
|
857
|
-
*/
|
|
858
|
-
_parent = void 0;
|
|
859
|
-
_storageDisposers = /* @__PURE__ */ new Set();
|
|
860
|
-
_disposers = /* @__PURE__ */ new Set();
|
|
861
|
-
/**
|
|
862
|
-
* @internal Lazy `TopologyEvent` producer. Created on first `.topology`
|
|
863
|
-
* access. Zero cost until something subscribes — producer fn only runs when
|
|
864
|
-
* the first sink attaches, registering one handler into
|
|
865
|
-
* {@link Graph._topologyEmitters}.
|
|
866
|
-
*/
|
|
867
|
-
_topology;
|
|
868
|
-
/**
|
|
869
|
-
* @internal Active emit handlers for the topology producer. Each entry is
|
|
870
|
-
* the closure registered by the producer fn on activation; cleared on
|
|
871
|
-
* deactivation. `_emitTopology` broadcasts through every entry (there is at
|
|
872
|
-
* most one per activation cycle of the producer).
|
|
873
|
-
*/
|
|
874
|
-
_topologyEmitters = /* @__PURE__ */ new Set();
|
|
875
|
-
/**
|
|
876
|
-
* @param name - Non-empty graph id (must not contain `::` and must not
|
|
877
|
-
* equal the reserved meta segment `__meta__`).
|
|
878
|
-
* @param opts - See {@link GraphOptions}. Stored frozen on the instance.
|
|
879
|
-
*/
|
|
880
|
-
constructor(name, opts) {
|
|
881
|
-
if (name === "") {
|
|
882
|
-
throw new Error("Graph name must be non-empty");
|
|
883
|
-
}
|
|
884
|
-
if (name.includes(PATH_SEP)) {
|
|
885
|
-
throw new Error(`Graph name must not contain '${PATH_SEP}' (got "${name}")`);
|
|
886
|
-
}
|
|
887
|
-
if (name === GRAPH_META_SEGMENT) {
|
|
888
|
-
throw new Error(`Graph name "${GRAPH_META_SEGMENT}" is reserved for meta companion paths`);
|
|
889
|
-
}
|
|
890
|
-
this.name = name;
|
|
891
|
-
this.opts = Object.freeze({ ...opts ?? {} });
|
|
892
|
-
this.config = opts?.config ?? defaultConfig;
|
|
893
|
-
this._traceRing = new RingBuffer(opts?.traceCapacity ?? 1e3);
|
|
894
|
-
if (opts?.versioning != null) {
|
|
895
|
-
this.setVersioning(opts.versioning);
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
/**
|
|
899
|
-
* Walk ancestors up through `_parent`. Returns the chain starting at this
|
|
900
|
-
* instance, ending at the root (a graph with no parent). O(depth).
|
|
901
|
-
*
|
|
902
|
-
* @param includeSelf - Include `this` in the chain (default `true`).
|
|
903
|
-
*/
|
|
904
|
-
ancestors(includeSelf = true) {
|
|
905
|
-
const out = [];
|
|
906
|
-
let p = includeSelf ? this : this._parent;
|
|
907
|
-
while (p != null) {
|
|
908
|
-
out.push(p);
|
|
909
|
-
p = p._parent;
|
|
910
|
-
}
|
|
911
|
-
return out;
|
|
912
|
-
}
|
|
913
|
-
// ——————————————————————————————————————————————————————————————
|
|
914
|
-
// Topology companion (structural-change event stream)
|
|
915
|
-
// ——————————————————————————————————————————————————————————————
|
|
916
|
-
/**
|
|
917
|
-
* Reactive stream of structural changes to this graph's own registry
|
|
918
|
-
* (add / mount / remove). Value mutations live on `observe()`; this
|
|
919
|
-
* companion only fires when the topology shape changes.
|
|
920
|
-
*
|
|
921
|
-
* Lazy: the underlying node is created on first access and activates when
|
|
922
|
-
* something subscribes. No emission replay — late subscribers do not
|
|
923
|
-
* receive historical events and should snapshot via {@link Graph.describe}
|
|
924
|
-
* before listening for incremental changes. Events that fire while the
|
|
925
|
-
* producer has zero subscribers are dropped (no retention).
|
|
926
|
-
*
|
|
927
|
-
* Own-graph only: a parent's `topology` does NOT emit for structural
|
|
928
|
-
* changes inside a mounted child. Transitive consumers subscribe to each
|
|
929
|
-
* child's topology separately (recurse through `topology`'s own "added"
|
|
930
|
-
* events with `nodeKind: "mount"` to discover new children).
|
|
931
|
-
*
|
|
932
|
-
* See {@link TopologyEvent} for payload shape.
|
|
933
|
-
*
|
|
934
|
-
* @category observability
|
|
935
|
-
*/
|
|
936
|
-
get topology() {
|
|
937
|
-
if (this._topology == null) {
|
|
938
|
-
this._topology = producer(
|
|
939
|
-
(actions) => {
|
|
940
|
-
const handler = (event) => {
|
|
941
|
-
actions.emit(event);
|
|
942
|
-
};
|
|
943
|
-
this._topologyEmitters.add(handler);
|
|
944
|
-
return () => {
|
|
945
|
-
this._topologyEmitters.delete(handler);
|
|
946
|
-
};
|
|
947
|
-
},
|
|
948
|
-
{ name: `${this.name}_topology` }
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
return this._topology;
|
|
952
|
-
}
|
|
953
|
-
/**
|
|
954
|
-
* @internal Fire a {@link TopologyEvent} to every active subscriber of
|
|
955
|
-
* `this.topology`. No-op when the topology node has never been accessed or
|
|
956
|
-
* currently has no sinks — zero cost for graphs nobody observes.
|
|
957
|
-
*/
|
|
958
|
-
_emitTopology(event) {
|
|
959
|
-
if (this._topology == null || this._topologyEmitters.size === 0) return;
|
|
960
|
-
for (const h of this._topologyEmitters) h(event);
|
|
961
|
-
}
|
|
962
|
-
// ——————————————————————————————————————————————————————————————
|
|
963
|
-
// Node registry
|
|
964
|
-
// ——————————————————————————————————————————————————————————————
|
|
965
|
-
/**
|
|
966
|
-
* Registers a node under a local name. Fails if the name is already used,
|
|
967
|
-
* reserved by a mount, the same node instance is already registered, or
|
|
968
|
-
* the node is torn down.
|
|
969
|
-
*
|
|
970
|
-
* Returns the registered node so callers can chain:
|
|
971
|
-
* `const counter = g.add("counter", state(0))`.
|
|
972
|
-
*
|
|
973
|
-
* @param name - Local key (no `::`).
|
|
974
|
-
* @param node - Node instance to own.
|
|
975
|
-
*/
|
|
976
|
-
add(name, node) {
|
|
977
|
-
assertRegisterableName(name, this.name, "add");
|
|
978
|
-
if (this._mounts.has(name)) {
|
|
979
|
-
throw new Error(`Graph "${this.name}": name "${name}" is already a mount point`);
|
|
980
|
-
}
|
|
981
|
-
if (this._nodes.has(name)) {
|
|
982
|
-
throw new Error(`Graph "${this.name}": node "${name}" already exists`);
|
|
983
|
-
}
|
|
984
|
-
const existingName = this._nodeToName.get(node);
|
|
985
|
-
if (existingName !== void 0) {
|
|
986
|
-
throw new Error(
|
|
987
|
-
`Graph "${this.name}": node instance already registered as "${existingName}"`
|
|
988
|
-
);
|
|
989
|
-
}
|
|
990
|
-
this._nodes.set(name, node);
|
|
991
|
-
this._nodeToName.set(node, name);
|
|
992
|
-
this._emitTopology({ kind: "added", name, nodeKind: "node" });
|
|
993
|
-
return node;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Bulk-apply a minimum versioning level to every currently-registered node
|
|
997
|
-
* in this graph (roadmap §6.0). `_applyVersioning` is monotonic — nodes
|
|
998
|
-
* already at a higher level are untouched. The method refuses to run
|
|
999
|
-
* mid-wave; invoke at setup time before any external subscribers attach.
|
|
1000
|
-
*
|
|
1001
|
-
* **Not** a default-for-future-adds mechanism — that's what
|
|
1002
|
-
* `config.defaultVersioning` is for. Nodes added after this call do NOT
|
|
1003
|
-
* automatically inherit `level`; register new nodes with their own
|
|
1004
|
-
* `opts.versioning` or set `config.defaultVersioning` before construction.
|
|
1005
|
-
*
|
|
1006
|
-
* **Scope:** local only. Does not propagate to mounted subgraphs.
|
|
1007
|
-
*
|
|
1008
|
-
* @param level - `0` for V0, `1` for V1, or `undefined` to no-op.
|
|
1009
|
-
*/
|
|
1010
|
-
setVersioning(level) {
|
|
1011
|
-
if (level == null) return;
|
|
1012
|
-
for (const node of this._nodes.values()) {
|
|
1013
|
-
if (node instanceof NodeImpl) {
|
|
1014
|
-
node._applyVersioning(level);
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Unregisters a node or unmounts a subgraph and sends `[[TEARDOWN]]` to the
|
|
1020
|
-
* removed node or recursively through the mounted subtree (§3.2).
|
|
1021
|
-
*
|
|
1022
|
-
* @param name - Local mount or node name.
|
|
1023
|
-
* @returns Audit record of what was removed: `{kind, nodes, mounts}`.
|
|
1024
|
-
* `kind: "node"` → `nodes: [name]`, `mounts: []`. `kind: "mount"` →
|
|
1025
|
-
* `nodes` lists every primary node torn down across the subtree (sorted
|
|
1026
|
-
* qualified paths relative to the unmounted subgraph) and `mounts` lists
|
|
1027
|
-
* the mounted subgraphs in depth-first order including `name` itself.
|
|
1028
|
-
*/
|
|
1029
|
-
remove(name) {
|
|
1030
|
-
assertRegisterableName(name, this.name, "remove");
|
|
1031
|
-
const child = this._mounts.get(name);
|
|
1032
|
-
if (child) {
|
|
1033
|
-
const audit2 = { kind: "mount", nodes: [], mounts: [] };
|
|
1034
|
-
const targets = [];
|
|
1035
|
-
child._collectObserveTargets("", targets);
|
|
1036
|
-
for (const [p, n] of targets) {
|
|
1037
|
-
if (!p.includes(`${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}`)) {
|
|
1038
|
-
audit2.nodes.push(p);
|
|
1039
|
-
}
|
|
1040
|
-
void n;
|
|
1041
|
-
}
|
|
1042
|
-
audit2.nodes.sort();
|
|
1043
|
-
audit2.mounts.push(name);
|
|
1044
|
-
audit2.mounts.push(...child._collectSubgraphs(`${name}${PATH_SEP}`));
|
|
1045
|
-
this._mounts.delete(name);
|
|
1046
|
-
child._parent = void 0;
|
|
1047
|
-
teardownMountedGraph(child);
|
|
1048
|
-
this._emitTopology({ kind: "removed", name, nodeKind: "mount", audit: audit2 });
|
|
1049
|
-
return audit2;
|
|
1050
|
-
}
|
|
1051
|
-
const node = this._nodes.get(name);
|
|
1052
|
-
if (!node) {
|
|
1053
|
-
throw new Error(`Graph "${this.name}": unknown node or mount "${name}"`);
|
|
1054
|
-
}
|
|
1055
|
-
this._nodes.delete(name);
|
|
1056
|
-
this._nodeToName.delete(node);
|
|
1057
|
-
node.down([[TEARDOWN]], { internal: true });
|
|
1058
|
-
const audit = { kind: "node", nodes: [name], mounts: [] };
|
|
1059
|
-
this._emitTopology({ kind: "removed", name, nodeKind: "node", audit });
|
|
1060
|
-
return audit;
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* Bulk remove — invokes {@link Graph.remove} for every local name matching
|
|
1064
|
-
* `filter`. Audit records merge into a single result. Mounted subgraphs
|
|
1065
|
-
* are included via `filter` receiving the mount name; internal subtree
|
|
1066
|
-
* entries are not walked directly (use describe + scan for tree-level
|
|
1067
|
-
* queries).
|
|
1068
|
-
*
|
|
1069
|
-
* @param filter - Predicate or glob. Glob strings support `*` within a
|
|
1070
|
-
* segment and `**` across segments (same grammar as `restore({only})`).
|
|
1071
|
-
* @returns Combined audit of all nodes + mounts removed.
|
|
1072
|
-
*/
|
|
1073
|
-
removeAll(filter) {
|
|
1074
|
-
const match = typeof filter === "function" ? filter : (() => {
|
|
1075
|
-
const re = globToRegex(filter);
|
|
1076
|
-
return (n) => re.test(n);
|
|
1077
|
-
})();
|
|
1078
|
-
const audit = { kind: "mount", nodes: [], mounts: [] };
|
|
1079
|
-
const localNames = [...this._nodes.keys(), ...this._mounts.keys()].filter((n) => match(n));
|
|
1080
|
-
for (const name of localNames) {
|
|
1081
|
-
const sub = this.remove(name);
|
|
1082
|
-
audit.nodes.push(...sub.nodes);
|
|
1083
|
-
audit.mounts.push(...sub.mounts);
|
|
1084
|
-
}
|
|
1085
|
-
audit.nodes.sort();
|
|
1086
|
-
audit.mounts.sort();
|
|
1087
|
-
return audit;
|
|
1088
|
-
}
|
|
1089
|
-
/**
|
|
1090
|
-
* Iterable over locally-registered `[localName, Node]` pairs (sorted).
|
|
1091
|
-
* Does not recurse into mounts.
|
|
1092
|
-
*/
|
|
1093
|
-
[Symbol.iterator]() {
|
|
1094
|
-
const sorted = [...this._nodes.keys()].sort();
|
|
1095
|
-
const nodes = this._nodes;
|
|
1096
|
-
let i = 0;
|
|
1097
|
-
return {
|
|
1098
|
-
[Symbol.iterator]() {
|
|
1099
|
-
return this;
|
|
1100
|
-
},
|
|
1101
|
-
next() {
|
|
1102
|
-
if (i >= sorted.length) return { value: void 0, done: true };
|
|
1103
|
-
const name = sorted[i++];
|
|
1104
|
-
return { value: [name, nodes.get(name)], done: false };
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Returns a node by local name or `::` qualified path.
|
|
1110
|
-
* Local names are looked up directly; paths with `::` delegate to {@link resolve}.
|
|
1111
|
-
*
|
|
1112
|
-
* @param name - Local name or qualified path.
|
|
1113
|
-
*/
|
|
1114
|
-
node(name) {
|
|
1115
|
-
if (name === "") {
|
|
1116
|
-
throw new Error(`Graph "${this.name}": node name must be non-empty`);
|
|
1117
|
-
}
|
|
1118
|
-
if (name.includes(PATH_SEP)) {
|
|
1119
|
-
return this.resolve(name);
|
|
1120
|
-
}
|
|
1121
|
-
const n = this._nodes.get(name);
|
|
1122
|
-
if (!n) {
|
|
1123
|
-
throw new Error(`Graph "${this.name}": unknown node "${name}"`);
|
|
1124
|
-
}
|
|
1125
|
-
return n;
|
|
1126
|
-
}
|
|
1127
|
-
/**
|
|
1128
|
-
* Reads `graph.node(name).get()` — accepts `::` qualified paths (§3.2).
|
|
1129
|
-
*
|
|
1130
|
-
* @param name - Local name or qualified path.
|
|
1131
|
-
* @returns Cached value or `undefined`.
|
|
1132
|
-
*/
|
|
1133
|
-
get(name) {
|
|
1134
|
-
return this.node(name).cache;
|
|
1135
|
-
}
|
|
1136
|
-
/**
|
|
1137
|
-
* Shorthand for `graph.node(name).down([[DATA, value]], { actor })` — accepts `::` qualified paths (§3.2).
|
|
1138
|
-
*
|
|
1139
|
-
* @param name - Local name or qualified path.
|
|
1140
|
-
* @param value - Next `DATA` payload.
|
|
1141
|
-
* @param options - Optional `actor` and `internal` guard bypass.
|
|
1142
|
-
*/
|
|
1143
|
-
set(name, value, options) {
|
|
1144
|
-
const internal = options?.internal === true;
|
|
1145
|
-
this.node(name).down([[DATA, value]], {
|
|
1146
|
-
actor: options?.actor,
|
|
1147
|
-
internal,
|
|
1148
|
-
delivery: "write"
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
/**
|
|
1152
|
-
* Atomic multi-node DATA write. Wraps every {@link Graph.set} call in a
|
|
1153
|
-
* single `batch(...)` so downstream dependents see one coalesced wave
|
|
1154
|
-
* instead of N cascading ones.
|
|
1155
|
-
*
|
|
1156
|
-
* @param entries - `{name → value}` map or `[name, value]` pairs.
|
|
1157
|
-
* @param options - Passed to each underlying `set` call (same `actor` + `internal` semantics).
|
|
1158
|
-
*/
|
|
1159
|
-
setAll(entries, options) {
|
|
1160
|
-
const iter = Symbol.iterator in entries ? entries : Object.entries(entries);
|
|
1161
|
-
batch(() => {
|
|
1162
|
-
for (const [name, value] of iter) this.set(name, value, options);
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Emit a single `[[INVALIDATE]]` (tier 1) on a node. Thin wrapper over
|
|
1167
|
-
* `node.down([[INVALIDATE]], …)` matching the {@link Graph.set} ergonomics.
|
|
1168
|
-
*/
|
|
1169
|
-
invalidate(name, options) {
|
|
1170
|
-
const internal = options?.internal === true;
|
|
1171
|
-
this.node(name).down([[INVALIDATE]], {
|
|
1172
|
-
actor: options?.actor,
|
|
1173
|
-
internal,
|
|
1174
|
-
delivery: "write"
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1177
|
-
/**
|
|
1178
|
-
* Emit a single `[[ERROR, err]]` (tier 4) on a node.
|
|
1179
|
-
*/
|
|
1180
|
-
error(name, err, options) {
|
|
1181
|
-
const internal = options?.internal === true;
|
|
1182
|
-
this.node(name).down([[ERROR, err]], {
|
|
1183
|
-
actor: options?.actor,
|
|
1184
|
-
internal,
|
|
1185
|
-
delivery: "write"
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Emit a single `[[COMPLETE]]` (tier 4) on a node, declaring the stream
|
|
1190
|
-
* cleanly finished. Distinct from {@link Graph.remove} (which emits
|
|
1191
|
-
* TEARDOWN and unregisters the node).
|
|
1192
|
-
*/
|
|
1193
|
-
complete(name, options) {
|
|
1194
|
-
const internal = options?.internal === true;
|
|
1195
|
-
this.node(name).down([[COMPLETE]], {
|
|
1196
|
-
actor: options?.actor,
|
|
1197
|
-
internal,
|
|
1198
|
-
delivery: "write"
|
|
1199
|
-
});
|
|
1200
|
-
}
|
|
1201
|
-
// ——————————————————————————————————————————————————————————————
|
|
1202
|
-
// Edges (derived on-demand from node `_deps`)
|
|
1203
|
-
// ——————————————————————————————————————————————————————————————
|
|
1204
|
-
/**
|
|
1205
|
-
* Returns the full edge list for this graph tree, derived on demand from
|
|
1206
|
-
* each registered node's `_deps` (no stored registry). Local-only
|
|
1207
|
-
* (non-recursive) by default to match the historical `edges()` surface;
|
|
1208
|
-
* pass `{recursive: true}` to include mounted subgraphs with qualified
|
|
1209
|
-
* paths relative to this graph.
|
|
1210
|
-
*
|
|
1211
|
-
* Use {@link Graph.describe} for full-tree snapshots with edges already
|
|
1212
|
-
* qualified and paired with node metadata.
|
|
1213
|
-
*/
|
|
1214
|
-
edges(opts) {
|
|
1215
|
-
const recursive = opts?.recursive === true;
|
|
1216
|
-
const nodeToLocal = /* @__PURE__ */ new Map();
|
|
1217
|
-
if (!recursive) {
|
|
1218
|
-
for (const [localName, n] of this._nodes) nodeToLocal.set(n, localName);
|
|
1219
|
-
const result2 = [];
|
|
1220
|
-
for (const [localName, n] of this._nodes) {
|
|
1221
|
-
if (!(n instanceof NodeImpl)) continue;
|
|
1222
|
-
for (const dep of n._deps) {
|
|
1223
|
-
const from = nodeToLocal.get(dep.node);
|
|
1224
|
-
if (from != null) result2.push([from, localName]);
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
result2.sort(
|
|
1228
|
-
(a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0
|
|
1229
|
-
);
|
|
1230
|
-
return result2;
|
|
1231
|
-
}
|
|
1232
|
-
const targets = [];
|
|
1233
|
-
this._collectObserveTargets("", targets);
|
|
1234
|
-
const nodeToPath = /* @__PURE__ */ new Map();
|
|
1235
|
-
for (const [p, n] of targets) nodeToPath.set(n, p);
|
|
1236
|
-
const result = [];
|
|
1237
|
-
for (const [path, n] of targets) {
|
|
1238
|
-
if (!(n instanceof NodeImpl)) continue;
|
|
1239
|
-
for (const dep of n._deps) {
|
|
1240
|
-
const from = nodeToPath.get(dep.node);
|
|
1241
|
-
if (from != null) result.push([from, path]);
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
result.sort(
|
|
1245
|
-
(a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0
|
|
1246
|
-
);
|
|
1247
|
-
return result;
|
|
1248
|
-
}
|
|
1249
|
-
// ——————————————————————————————————————————————————————————————
|
|
1250
|
-
// Composition
|
|
1251
|
-
// ——————————————————————————————————————————————————————————————
|
|
1252
|
-
/**
|
|
1253
|
-
* Embed a child graph at a local mount name (§3.4). Child nodes are reachable via
|
|
1254
|
-
* {@link Graph.resolve} using `::` delimited paths (§3.5). Lifecycle
|
|
1255
|
-
* {@link Graph.signal} visits mounted subgraphs recursively.
|
|
1256
|
-
*
|
|
1257
|
-
* Rejects: same name as existing node or mount, self-mount, mount cycles,
|
|
1258
|
-
* and the same child graph instance mounted twice on one parent.
|
|
1259
|
-
*
|
|
1260
|
-
* @param name - Local mount point.
|
|
1261
|
-
* @param child - Nested `Graph` instance.
|
|
1262
|
-
* @returns The mounted `child`, for chaining.
|
|
1263
|
-
*/
|
|
1264
|
-
mount(name, child) {
|
|
1265
|
-
assertRegisterableName(name, this.name, "mount");
|
|
1266
|
-
if (this._nodes.has(name)) {
|
|
1267
|
-
throw new Error(
|
|
1268
|
-
`Graph "${this.name}": cannot mount at "${name}" \u2014 node with that name exists`
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
if (this._mounts.has(name)) {
|
|
1272
|
-
throw new Error(`Graph "${this.name}": mount "${name}" already exists`);
|
|
1273
|
-
}
|
|
1274
|
-
if (child === this) {
|
|
1275
|
-
throw new Error(`Graph "${this.name}": cannot mount a graph into itself`);
|
|
1276
|
-
}
|
|
1277
|
-
if (child._parent != null) {
|
|
1278
|
-
throw new Error(
|
|
1279
|
-
`Graph "${this.name}": this child graph is already mounted on "${child._parent.name}"`
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
for (let p = this; p != null; p = p._parent) {
|
|
1283
|
-
if (p === child) {
|
|
1284
|
-
throw new Error(`Graph "${this.name}": mount("${name}", \u2026) would create a mount cycle`);
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
this._mounts.set(name, child);
|
|
1288
|
-
child._parent = this;
|
|
1289
|
-
this._emitTopology({ kind: "added", name, nodeKind: "mount" });
|
|
1290
|
-
return child;
|
|
1291
|
-
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Look up a node by qualified path (§3.5). Segments are separated by `::`.
|
|
1294
|
-
*
|
|
1295
|
-
* If the first segment equals this graph's {@link Graph.name}, it is stripped
|
|
1296
|
-
* (so `root.resolve("app::a")` works when `root.name === "app"`). The strip
|
|
1297
|
-
* is applied **recursively** when descending into mounted children, so
|
|
1298
|
-
* `child.resolve("child::x")` also works when `child.name === "child"`.
|
|
1299
|
-
*
|
|
1300
|
-
* @param path - Qualified `::` path or local name.
|
|
1301
|
-
* @returns The resolved `Node`.
|
|
1302
|
-
*/
|
|
1303
|
-
resolve(path) {
|
|
1304
|
-
const segments = splitPath(path, this.name);
|
|
1305
|
-
return this._resolveFromSegments(segments);
|
|
1306
|
-
}
|
|
1307
|
-
/**
|
|
1308
|
-
* Non-throwing {@link Graph.resolve}. Returns `undefined` instead of
|
|
1309
|
-
* throwing when the path does not resolve to a node.
|
|
1310
|
-
*/
|
|
1311
|
-
tryResolve(path) {
|
|
1312
|
-
try {
|
|
1313
|
-
return this.resolve(path);
|
|
1314
|
-
} catch {
|
|
1315
|
-
return void 0;
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
_resolveFromSegments(segments) {
|
|
1319
|
-
let seg = segments;
|
|
1320
|
-
if (seg[0] === this.name) {
|
|
1321
|
-
seg = seg.slice(1);
|
|
1322
|
-
if (seg.length === 0) {
|
|
1323
|
-
throw new Error(`Graph "${this.name}": resolve path ends at graph name only`);
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
const head = seg[0];
|
|
1327
|
-
const rest = seg.slice(1);
|
|
1328
|
-
if (rest.length === 0) {
|
|
1329
|
-
const n = this._nodes.get(head);
|
|
1330
|
-
if (n) return n;
|
|
1331
|
-
if (this._mounts.has(head)) {
|
|
1332
|
-
throw new Error(
|
|
1333
|
-
`Graph "${this.name}": path ends at subgraph "${head}" \u2014 not a node (GRAPHREFLY-SPEC \xA73.5)`
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
throw new Error(`Graph "${this.name}": unknown name "${head}"`);
|
|
1337
|
-
}
|
|
1338
|
-
const localN = this._nodes.get(head);
|
|
1339
|
-
if (localN && rest.length > 0 && rest[0] === GRAPH_META_SEGMENT) {
|
|
1340
|
-
return this._resolveMetaChainFromNode(localN, rest, seg.join(PATH_SEP));
|
|
1341
|
-
}
|
|
1342
|
-
const child = this._mounts.get(head);
|
|
1343
|
-
if (!child) {
|
|
1344
|
-
if (this._nodes.has(head)) {
|
|
1345
|
-
throw new Error(
|
|
1346
|
-
`Graph "${this.name}": "${head}" is a node; trailing path "${rest.join(PATH_SEP)}" is invalid`
|
|
1347
|
-
);
|
|
1348
|
-
}
|
|
1349
|
-
throw new Error(`Graph "${this.name}": unknown mount or node "${head}"`);
|
|
1350
|
-
}
|
|
1351
|
-
return child.resolve(rest.join(PATH_SEP));
|
|
1352
|
-
}
|
|
1353
|
-
/**
|
|
1354
|
-
* Resolve `::__meta__::key` segments from a registered primary node (possibly chained).
|
|
1355
|
-
*/
|
|
1356
|
-
_resolveMetaChainFromNode(n, parts, fullPath) {
|
|
1357
|
-
let current = n;
|
|
1358
|
-
let i = 0;
|
|
1359
|
-
const p = [...parts];
|
|
1360
|
-
while (i < p.length) {
|
|
1361
|
-
if (p[i] !== GRAPH_META_SEGMENT) {
|
|
1362
|
-
throw new Error(
|
|
1363
|
-
`Graph "${this.name}": expected ${GRAPH_META_SEGMENT} segment in meta path "${fullPath}"`
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
if (i + 1 >= p.length) {
|
|
1367
|
-
throw new Error(
|
|
1368
|
-
`Graph "${this.name}": meta path requires a key after ${GRAPH_META_SEGMENT} in "${fullPath}"`
|
|
1369
|
-
);
|
|
1370
|
-
}
|
|
1371
|
-
const key = p[i + 1];
|
|
1372
|
-
const next = current.meta[key];
|
|
1373
|
-
if (!next) {
|
|
1374
|
-
throw new Error(`Graph "${this.name}": unknown meta "${key}" in path "${fullPath}"`);
|
|
1375
|
-
}
|
|
1376
|
-
current = next;
|
|
1377
|
-
i += 2;
|
|
1378
|
-
}
|
|
1379
|
-
return current;
|
|
1380
|
-
}
|
|
1381
|
-
/**
|
|
1382
|
-
* Deliver a message batch to every registered node in this graph and, recursively,
|
|
1383
|
-
* in mounted child graphs (§3.7). Recurses into mounts first, then delivers to
|
|
1384
|
-
* local nodes (sorted by name). Each {@link Node} receives at most one delivery
|
|
1385
|
-
* per call (deduped by reference).
|
|
1386
|
-
*
|
|
1387
|
-
* **Primary-vs-meta filter asymmetry (intentional):** primary nodes receive the
|
|
1388
|
-
* unfiltered `messages` batch — that's the canonical data-plane flow. Companion
|
|
1389
|
-
* `meta` nodes receive a filtered subset keyed by the per-type `metaPassthrough`
|
|
1390
|
-
* flag on {@link GraphReFlyConfig}. Built-in defaults: PAUSE / RESUME / DATA /
|
|
1391
|
-
* RESOLVED pass through to meta; INVALIDATE / COMPLETE / ERROR / TEARDOWN do
|
|
1392
|
-
* not.
|
|
1393
|
-
*
|
|
1394
|
-
* **Where lifecycle terminals reach meta:**
|
|
1395
|
-
* - **TEARDOWN** — primary's `_emit` cascades to meta children directly (see
|
|
1396
|
-
* `core/node.ts` "Meta TEARDOWN fan-out" block) so meta is torn down with
|
|
1397
|
-
* its primary regardless of the signal-level filter.
|
|
1398
|
-
* - **COMPLETE / ERROR / INVALIDATE** — scoped to primaries on the broadcast
|
|
1399
|
-
* path. Meta companions are an attribution side-channel, not a lifecycle
|
|
1400
|
-
* participant; address meta directly via `meta.down(...)` if you need to
|
|
1401
|
-
* forward these. Audit confirmed 2026-04-17: no current meta consumer
|
|
1402
|
-
* relies on broadcast COMPLETE/ERROR/INVALIDATE delivery.
|
|
1403
|
-
*
|
|
1404
|
-
* @param messages - Batch to deliver to every registered node (and mounts, recursively).
|
|
1405
|
-
* @param options - Optional `actor` / `internal` for transport.
|
|
1406
|
-
*/
|
|
1407
|
-
signal(messages, options) {
|
|
1408
|
-
if (options?.internal !== true) {
|
|
1409
|
-
for (const m of messages) {
|
|
1410
|
-
const tier = this.config.messageTier(m[0]);
|
|
1411
|
-
if (tier === 3) {
|
|
1412
|
-
throw new Error(
|
|
1413
|
-
`Graph "${this.name}": Graph.signal() rejects tier-3 messages (DATA / RESOLVED). Broadcast is for control-plane tiers (START / DIRTY / INVALIDATE / PAUSE / RESUME / COMPLETE / ERROR / TEARDOWN). For per-node value writes, use Graph.set or graph.node(name).down(...).`
|
|
1414
|
-
);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
const errors = [];
|
|
1419
|
-
this._signalDeliver(messages, options ?? {}, /* @__PURE__ */ new Set(), errors);
|
|
1420
|
-
if (errors.length > 0) throw errors[0];
|
|
1421
|
-
}
|
|
1422
|
-
_signalDeliver(messages, opts, vis, errors) {
|
|
1423
|
-
for (const sub of this._mounts.values()) {
|
|
1424
|
-
sub._signalDeliver(messages, opts, vis, errors);
|
|
1425
|
-
}
|
|
1426
|
-
const internal = opts.internal === true;
|
|
1427
|
-
const downOpts = internal ? { internal: true } : { actor: opts.actor, delivery: "signal" };
|
|
1428
|
-
const metaMessages = filterMetaMessages(messages, this.config);
|
|
1429
|
-
for (const localName of [...this._nodes.keys()].sort()) {
|
|
1430
|
-
const n = this._nodes.get(localName);
|
|
1431
|
-
if (vis.has(n)) continue;
|
|
1432
|
-
vis.add(n);
|
|
1433
|
-
try {
|
|
1434
|
-
n.down(messages, downOpts);
|
|
1435
|
-
} catch (err) {
|
|
1436
|
-
if (err instanceof GuardDenied) throw err;
|
|
1437
|
-
errors.push(err);
|
|
1438
|
-
}
|
|
1439
|
-
if (metaMessages.length === 0) continue;
|
|
1440
|
-
this._signalMetaSubtree(n, metaMessages, vis, downOpts, errors);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
_signalMetaSubtree(root, messages, vis, downOpts, errors) {
|
|
1444
|
-
for (const mk of Object.keys(root.meta).sort()) {
|
|
1445
|
-
const mnode = root.meta[mk];
|
|
1446
|
-
if (vis.has(mnode)) continue;
|
|
1447
|
-
vis.add(mnode);
|
|
1448
|
-
try {
|
|
1449
|
-
mnode.down(messages, downOpts);
|
|
1450
|
-
} catch (err) {
|
|
1451
|
-
if (err instanceof GuardDenied) throw err;
|
|
1452
|
-
errors.push(err);
|
|
1453
|
-
}
|
|
1454
|
-
this._signalMetaSubtree(mnode, messages, vis, downOpts, errors);
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
describe(options) {
|
|
1458
|
-
const actor = options?.actor;
|
|
1459
|
-
const filter = options?.filter;
|
|
1460
|
-
const includeFields = resolveDescribeFields(options?.detail, options?.fields);
|
|
1461
|
-
const isSpec = options?.format === "spec";
|
|
1462
|
-
const effectiveFields = isSpec ? resolveDescribeFields("minimal") : includeFields;
|
|
1463
|
-
const targets = [];
|
|
1464
|
-
this._collectObserveTargets("", targets);
|
|
1465
|
-
const nodeToPath = /* @__PURE__ */ new Map();
|
|
1466
|
-
for (const [p, n] of targets) {
|
|
1467
|
-
nodeToPath.set(n, p);
|
|
1468
|
-
}
|
|
1469
|
-
const nodes = {};
|
|
1470
|
-
for (const [p, n] of targets) {
|
|
1471
|
-
if (actor != null && !n.allowsObserve(actor)) continue;
|
|
1472
|
-
const raw = describeNode(n, effectiveFields);
|
|
1473
|
-
const deps = n instanceof NodeImpl ? n._deps.map((d) => nodeToPath.get(d.node) ?? d.node.name ?? "") : [];
|
|
1474
|
-
const { name: _name, ...rest } = raw;
|
|
1475
|
-
const entry = { ...rest, deps };
|
|
1476
|
-
if (!isSpec) {
|
|
1477
|
-
const reason = this._annotations.get(p);
|
|
1478
|
-
if (reason != null) entry.reason = reason;
|
|
1479
|
-
}
|
|
1480
|
-
if (filter != null) {
|
|
1481
|
-
if (typeof filter === "function") {
|
|
1482
|
-
const fn = filter;
|
|
1483
|
-
const pass = fn.length >= 2 ? fn(p, entry) : fn(entry);
|
|
1484
|
-
if (!pass) continue;
|
|
1485
|
-
} else {
|
|
1486
|
-
let match = true;
|
|
1487
|
-
for (const [fk, fv] of Object.entries(filter)) {
|
|
1488
|
-
const normalizedKey = fk === "deps_includes" ? "depsIncludes" : fk === "meta_has" ? "metaHas" : fk;
|
|
1489
|
-
if (normalizedKey === "depsIncludes") {
|
|
1490
|
-
if (!entry.deps.includes(String(fv))) {
|
|
1491
|
-
match = false;
|
|
1492
|
-
break;
|
|
1493
|
-
}
|
|
1494
|
-
continue;
|
|
1495
|
-
}
|
|
1496
|
-
if (normalizedKey === "metaHas") {
|
|
1497
|
-
if (!Object.hasOwn(entry.meta ?? {}, String(fv))) {
|
|
1498
|
-
match = false;
|
|
1499
|
-
break;
|
|
1500
|
-
}
|
|
1501
|
-
continue;
|
|
1502
|
-
}
|
|
1503
|
-
if (entry[normalizedKey] !== fv) {
|
|
1504
|
-
match = false;
|
|
1505
|
-
break;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
if (!match) continue;
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
nodes[p] = entry;
|
|
1512
|
-
}
|
|
1513
|
-
const nodeKeys = new Set(Object.keys(nodes));
|
|
1514
|
-
let edges = this.edges({ recursive: true }).map(
|
|
1515
|
-
([from, to]) => ({ from, to })
|
|
1516
|
-
);
|
|
1517
|
-
if (actor != null || filter != null) {
|
|
1518
|
-
edges = edges.filter((e) => nodeKeys.has(e.from) && nodeKeys.has(e.to));
|
|
1519
|
-
}
|
|
1520
|
-
const allSubgraphs = this._collectSubgraphs("");
|
|
1521
|
-
const subgraphs = actor != null || filter != null ? allSubgraphs.filter((sg) => {
|
|
1522
|
-
const prefix = `${sg}${PATH_SEP}`;
|
|
1523
|
-
return [...nodeKeys].some((k) => k === sg || k.startsWith(prefix));
|
|
1524
|
-
}) : allSubgraphs;
|
|
1525
|
-
const graph = this;
|
|
1526
|
-
const baseOpts = options;
|
|
1527
|
-
const struct = {
|
|
1528
|
-
name: this.name,
|
|
1529
|
-
nodes,
|
|
1530
|
-
edges,
|
|
1531
|
-
subgraphs,
|
|
1532
|
-
expand(detailOrFields) {
|
|
1533
|
-
const merged = { ...baseOpts, format: void 0 };
|
|
1534
|
-
if (Array.isArray(detailOrFields)) {
|
|
1535
|
-
merged.fields = detailOrFields;
|
|
1536
|
-
merged.detail = void 0;
|
|
1537
|
-
} else {
|
|
1538
|
-
merged.detail = detailOrFields;
|
|
1539
|
-
merged.fields = void 0;
|
|
1540
|
-
}
|
|
1541
|
-
return graph.describe(merged);
|
|
1542
|
-
}
|
|
1543
|
-
};
|
|
1544
|
-
const opts = options ?? {};
|
|
1545
|
-
const fmt = opts.format;
|
|
1546
|
-
if (fmt === "json") return renderDescribeAsJson(struct, opts);
|
|
1547
|
-
if (fmt === "pretty") return renderDescribeAsPretty(struct, opts);
|
|
1548
|
-
if (fmt === "mermaid") return renderDescribeAsMermaid(struct, opts);
|
|
1549
|
-
if (fmt === "d2") return renderDescribeAsD2(struct, opts);
|
|
1550
|
-
return struct;
|
|
1551
|
-
}
|
|
1552
|
-
_collectSubgraphs(prefix) {
|
|
1553
|
-
const out = [];
|
|
1554
|
-
for (const m of [...this._mounts.keys()].sort()) {
|
|
1555
|
-
const q = prefix === "" ? m : `${prefix}${m}`;
|
|
1556
|
-
out.push(q);
|
|
1557
|
-
out.push(...this._mounts.get(m)._collectSubgraphs(`${q}${PATH_SEP}`));
|
|
1558
|
-
}
|
|
1559
|
-
return out;
|
|
1560
|
-
}
|
|
1561
|
-
/**
|
|
1562
|
-
* Snapshot-based resource profile: per-node stats, orphan effect detection,
|
|
1563
|
-
* memory hotspots. Zero runtime overhead — walks nodes on demand.
|
|
1564
|
-
*
|
|
1565
|
-
* @param opts - Optional `topN` for hotspot limit (default 10).
|
|
1566
|
-
* @returns Aggregate profile with per-node details, hotspots, and orphan effects.
|
|
1567
|
-
*/
|
|
1568
|
-
resourceProfile(opts) {
|
|
1569
|
-
return graphProfile(this, opts);
|
|
1570
|
-
}
|
|
1571
|
-
reachable(from, direction, opts = {}) {
|
|
1572
|
-
if (opts.withDetail === true) {
|
|
1573
|
-
return reachable(this.describe(), from, direction, {
|
|
1574
|
-
...opts,
|
|
1575
|
-
withDetail: true
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
return reachable(this.describe(), from, direction, opts);
|
|
1579
|
-
}
|
|
1580
|
-
/**
|
|
1581
|
-
* Causal walkback: shortest dep-chain from `from` to `to`, enriched with
|
|
1582
|
-
* each node's value, status, last-mutation actor, and reasoning annotation
|
|
1583
|
-
* from {@link Graph.trace}. Wraps {@link explainPath} (roadmap §9.2).
|
|
1584
|
-
*
|
|
1585
|
-
* @param from - Upstream node (the cause).
|
|
1586
|
-
* @param to - Downstream node (the effect).
|
|
1587
|
-
* @param opts - Optional `maxDepth` and `findCycle`. When `findCycle:true`
|
|
1588
|
-
* and `from === to`, returns the shortest cycle through other nodes
|
|
1589
|
-
* (useful for diagnosing feedback loops, COMPOSITION-GUIDE §7).
|
|
1590
|
-
* Annotations and lastMutations are collected automatically from the
|
|
1591
|
-
* live graph.
|
|
1592
|
-
*/
|
|
1593
|
-
explain(from, to, opts) {
|
|
1594
|
-
const described = this.describe({ detail: "full" });
|
|
1595
|
-
const annotations = new Map(this._annotations);
|
|
1596
|
-
const lastMutations = /* @__PURE__ */ new Map();
|
|
1597
|
-
for (const [path, n] of Object.entries(described.nodes)) {
|
|
1598
|
-
if (n.lastMutation != null) lastMutations.set(path, n.lastMutation);
|
|
1599
|
-
}
|
|
1600
|
-
return explainPath(described, from, to, {
|
|
1601
|
-
...opts?.maxDepth != null ? { maxDepth: opts.maxDepth } : {},
|
|
1602
|
-
...opts?.findCycle === true ? { findCycle: true } : {},
|
|
1603
|
-
annotations,
|
|
1604
|
-
lastMutations
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
/**
|
|
1608
|
-
* @internal Collect all qualified paths in this graph tree matching a
|
|
1609
|
-
* glob pattern. Used by scoped autoCheckpoint subscription.
|
|
1610
|
-
*/
|
|
1611
|
-
_pathsMatching(glob) {
|
|
1612
|
-
const re = globToRegex(glob);
|
|
1613
|
-
const targets = [];
|
|
1614
|
-
this._collectObserveTargets("", targets);
|
|
1615
|
-
return targets.map(([p]) => p).filter((p) => re.test(p));
|
|
1616
|
-
}
|
|
1617
|
-
_collectObserveTargets(prefix, out) {
|
|
1618
|
-
for (const m of [...this._mounts.keys()].sort()) {
|
|
1619
|
-
const p2 = prefix === "" ? m : `${prefix}${PATH_SEP}${m}`;
|
|
1620
|
-
this._mounts.get(m)._collectObserveTargets(p2, out);
|
|
1621
|
-
}
|
|
1622
|
-
for (const loc of [...this._nodes.keys()].sort()) {
|
|
1623
|
-
const n = this._nodes.get(loc);
|
|
1624
|
-
const p = prefix === "" ? loc : `${prefix}${PATH_SEP}${loc}`;
|
|
1625
|
-
out.push([p, n]);
|
|
1626
|
-
this._appendMetaObserveTargets(p, n, out);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
_appendMetaObserveTargets(basePath, n, out) {
|
|
1630
|
-
for (const mk of Object.keys(n.meta).sort()) {
|
|
1631
|
-
const m = n.meta[mk];
|
|
1632
|
-
const mp = `${basePath}${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}${mk}`;
|
|
1633
|
-
out.push([mp, m]);
|
|
1634
|
-
this._appendMetaObserveTargets(mp, m, out);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
observe(pathOrOpts, options) {
|
|
1638
|
-
const isPath = typeof pathOrOpts === "string";
|
|
1639
|
-
const rawOpts = isPath ? options : pathOrOpts;
|
|
1640
|
-
const resolved = resolveObserveDetail(rawOpts);
|
|
1641
|
-
const wantsStructured = resolved.structured === true || resolved.timeline === true || resolved.causal === true || resolved.derived === true || resolved.detail === "minimal" || resolved.detail === "full" || resolved.format != null;
|
|
1642
|
-
const actor = resolved.actor;
|
|
1643
|
-
if (isPath) {
|
|
1644
|
-
const path = pathOrOpts;
|
|
1645
|
-
const target = this.resolve(path);
|
|
1646
|
-
if (actor != null && !target.allowsObserve(actor)) {
|
|
1647
|
-
throw new GuardDenied({ actor, action: "observe", nodeName: path });
|
|
1648
|
-
}
|
|
1649
|
-
if (wantsStructured) return this._buildStructuredObserver([[path, target]], resolved, "one");
|
|
1650
|
-
return {
|
|
1651
|
-
subscribe(sink) {
|
|
1652
|
-
return target.subscribe(sink);
|
|
1653
|
-
},
|
|
1654
|
-
up(messages) {
|
|
1655
|
-
try {
|
|
1656
|
-
target.up?.(messages);
|
|
1657
|
-
} catch (err) {
|
|
1658
|
-
if (err instanceof GuardDenied) return;
|
|
1659
|
-
throw err;
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
};
|
|
1663
|
-
}
|
|
1664
|
-
const collected = [];
|
|
1665
|
-
this._collectObserveTargets("", collected);
|
|
1666
|
-
collected.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
1667
|
-
const picked = actor == null ? collected : collected.filter(([, nd]) => nd.allowsObserve(actor));
|
|
1668
|
-
if (wantsStructured) return this._buildStructuredObserver(picked, resolved, "all");
|
|
1669
|
-
return {
|
|
1670
|
-
subscribe: (sink) => {
|
|
1671
|
-
const unsubs = picked.map(
|
|
1672
|
-
([p, nd]) => nd.subscribe((msgs) => {
|
|
1673
|
-
sink(p, msgs);
|
|
1674
|
-
})
|
|
1675
|
-
);
|
|
1676
|
-
return () => {
|
|
1677
|
-
for (const u of unsubs) u();
|
|
1678
|
-
};
|
|
1679
|
-
},
|
|
1680
|
-
up: (upPath, messages) => {
|
|
1681
|
-
try {
|
|
1682
|
-
const nd = this.resolve(upPath);
|
|
1683
|
-
nd.up?.(messages);
|
|
1684
|
-
} catch (err) {
|
|
1685
|
-
if (err instanceof GuardDenied) return;
|
|
1686
|
-
throw err;
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
};
|
|
1690
|
-
}
|
|
1691
|
-
/** Dispatch helper — builds a unified observer + its expand closure. */
|
|
1692
|
-
_buildStructuredObserver(targets, options, mode) {
|
|
1693
|
-
const firstPath = mode === "one" ? targets[0]?.[0] : void 0;
|
|
1694
|
-
const expand = (merged) => {
|
|
1695
|
-
if (mode === "one" && firstPath != null) {
|
|
1696
|
-
const target = this.resolve(firstPath);
|
|
1697
|
-
return this._buildStructuredObserver([[firstPath, target]], merged, "one");
|
|
1698
|
-
}
|
|
1699
|
-
const collected = [];
|
|
1700
|
-
this._collectObserveTargets("", collected);
|
|
1701
|
-
collected.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
1702
|
-
const actor = merged.actor;
|
|
1703
|
-
const picked = actor == null ? collected : collected.filter(([, nd]) => nd.allowsObserve(actor));
|
|
1704
|
-
return this._buildStructuredObserver(picked, merged, "all");
|
|
1705
|
-
};
|
|
1706
|
-
return this._createObserveResult(targets, options, expand);
|
|
1707
|
-
}
|
|
1708
|
-
/**
|
|
1709
|
-
* Unified observer builder — replaces the four ex-creators
|
|
1710
|
-
* (`_createObserveResult` / `...ForAll` / `_createFallback…`). Accepts a
|
|
1711
|
-
* list of `[path, node]` targets (single-element for one-node observe,
|
|
1712
|
-
* N-element for all-nodes). Inspector hooks attach per-target when
|
|
1713
|
-
* `causal`/`derived` requested AND `config.inspectorEnabled`; otherwise
|
|
1714
|
-
* those fields gracefully drop.
|
|
1715
|
-
*
|
|
1716
|
-
* Events flow through a `recordEvent()` helper so the format logger,
|
|
1717
|
-
* ring-buffer, and async-iterable hooks all share one push path.
|
|
1718
|
-
*/
|
|
1719
|
-
_createObserveResult(targets, options, expand) {
|
|
1720
|
-
const timeline = options.timeline === true;
|
|
1721
|
-
const causal = options.causal === true;
|
|
1722
|
-
const derived = options.derived === true;
|
|
1723
|
-
const minimal = options.detail === "minimal";
|
|
1724
|
-
const inspectorOn = this.config.inspectorEnabled;
|
|
1725
|
-
const wantInspector = (causal || derived) && inspectorOn;
|
|
1726
|
-
const maxEvents = options.maxEvents;
|
|
1727
|
-
const ring = maxEvents != null && maxEvents > 0 ? new RingBuffer(maxEvents) : null;
|
|
1728
|
-
const events = [];
|
|
1729
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1730
|
-
const values = {};
|
|
1731
|
-
const nodeErrored = /* @__PURE__ */ new Set();
|
|
1732
|
-
let dirtyCount = 0;
|
|
1733
|
-
let resolvedCount = 0;
|
|
1734
|
-
let invalidateCount = 0;
|
|
1735
|
-
let pauseCount = 0;
|
|
1736
|
-
let resumeCount = 0;
|
|
1737
|
-
let teardownCount = 0;
|
|
1738
|
-
let anyCompletedCleanly = false;
|
|
1739
|
-
let anyErrored = false;
|
|
1740
|
-
let batchSeq = 0;
|
|
1741
|
-
const lastTriggerDepIndex = /* @__PURE__ */ new Map();
|
|
1742
|
-
const lastRunDepValues = /* @__PURE__ */ new Map();
|
|
1743
|
-
const lastRunDepBatches = /* @__PURE__ */ new Map();
|
|
1744
|
-
const recordEvent = (event) => {
|
|
1745
|
-
if (ring) ring.push(event);
|
|
1746
|
-
else events.push(event);
|
|
1747
|
-
for (const listener of listeners) listener(event);
|
|
1748
|
-
};
|
|
1749
|
-
const baseMeta = () => timeline ? { timestamp_ns: monotonicNs(), in_batch: isBatching(), batch_id: batchSeq } : {};
|
|
1750
|
-
const attachInspector = (target, path) => {
|
|
1751
|
-
if (!wantInspector || !(target instanceof NodeImpl)) return void 0;
|
|
1752
|
-
return target._setInspectorHook((ev) => {
|
|
1753
|
-
if (ev.kind === "dep_message") {
|
|
1754
|
-
lastTriggerDepIndex.set(target, ev.depIndex);
|
|
1755
|
-
} else if (ev.kind === "run") {
|
|
1756
|
-
const effective = ev.batchData.map(
|
|
1757
|
-
(b, i) => b != null && b.length > 0 ? b.at(-1) : ev.prevData[i]
|
|
1758
|
-
);
|
|
1759
|
-
lastRunDepValues.set(target, effective);
|
|
1760
|
-
const batches = ev.batchData.map(
|
|
1761
|
-
(b) => b != null ? [...b] : void 0
|
|
1762
|
-
);
|
|
1763
|
-
lastRunDepBatches.set(target, batches);
|
|
1764
|
-
if (derived) {
|
|
1765
|
-
recordEvent({
|
|
1766
|
-
type: "derived",
|
|
1767
|
-
path,
|
|
1768
|
-
dep_values: effective,
|
|
1769
|
-
dep_batches: batches,
|
|
1770
|
-
...baseMeta()
|
|
1771
|
-
});
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
});
|
|
1775
|
-
};
|
|
1776
|
-
const buildCausal = (target) => {
|
|
1777
|
-
const idx = lastTriggerDepIndex.get(target);
|
|
1778
|
-
const depValues = lastRunDepValues.get(target);
|
|
1779
|
-
if (!causal || depValues == null) return {};
|
|
1780
|
-
const triggerDep = idx != null && idx >= 0 && target instanceof NodeImpl ? target._deps[idx] : void 0;
|
|
1781
|
-
const triggerNode = triggerDep?.node;
|
|
1782
|
-
const tv = triggerNode?.v;
|
|
1783
|
-
const depBatches = lastRunDepBatches.get(target);
|
|
1784
|
-
return {
|
|
1785
|
-
trigger_dep_index: idx,
|
|
1786
|
-
trigger_dep_name: triggerNode?.name,
|
|
1787
|
-
...tv != null ? { trigger_version: { id: tv.id, version: tv.version } } : {},
|
|
1788
|
-
dep_values: [...depValues],
|
|
1789
|
-
...depBatches != null ? { dep_batches: depBatches } : {}
|
|
1790
|
-
};
|
|
1791
|
-
};
|
|
1792
|
-
const inspectorDetaches = [];
|
|
1793
|
-
const unsubs = [];
|
|
1794
|
-
for (const [path, target] of targets) {
|
|
1795
|
-
const detach = attachInspector(target, path);
|
|
1796
|
-
if (detach) inspectorDetaches.push(detach);
|
|
1797
|
-
unsubs.push(
|
|
1798
|
-
target.subscribe((msgs) => {
|
|
1799
|
-
batchSeq++;
|
|
1800
|
-
for (const m of msgs) {
|
|
1801
|
-
const t = m[0];
|
|
1802
|
-
const base = baseMeta();
|
|
1803
|
-
if (t === DATA) {
|
|
1804
|
-
values[path] = m[1];
|
|
1805
|
-
recordEvent({
|
|
1806
|
-
type: "data",
|
|
1807
|
-
path,
|
|
1808
|
-
data: m[1],
|
|
1809
|
-
...base,
|
|
1810
|
-
...buildCausal(target)
|
|
1811
|
-
});
|
|
1812
|
-
} else if (minimal) {
|
|
1813
|
-
if (t === DIRTY) dirtyCount++;
|
|
1814
|
-
else if (t === RESOLVED) resolvedCount++;
|
|
1815
|
-
else if (t === INVALIDATE) invalidateCount++;
|
|
1816
|
-
else if (t === PAUSE) pauseCount++;
|
|
1817
|
-
else if (t === RESUME) resumeCount++;
|
|
1818
|
-
else if (t === TEARDOWN) teardownCount++;
|
|
1819
|
-
else if (t === COMPLETE && !nodeErrored.has(path)) anyCompletedCleanly = true;
|
|
1820
|
-
else if (t === ERROR) {
|
|
1821
|
-
anyErrored = true;
|
|
1822
|
-
nodeErrored.add(path);
|
|
1823
|
-
}
|
|
1824
|
-
} else if (t === DIRTY) {
|
|
1825
|
-
dirtyCount++;
|
|
1826
|
-
recordEvent({ type: "dirty", path, ...base });
|
|
1827
|
-
} else if (t === RESOLVED) {
|
|
1828
|
-
resolvedCount++;
|
|
1829
|
-
recordEvent({
|
|
1830
|
-
type: "resolved",
|
|
1831
|
-
path,
|
|
1832
|
-
...base,
|
|
1833
|
-
...buildCausal(target)
|
|
1834
|
-
});
|
|
1835
|
-
} else if (t === INVALIDATE) {
|
|
1836
|
-
invalidateCount++;
|
|
1837
|
-
recordEvent({ type: "invalidate", path, ...base });
|
|
1838
|
-
} else if (t === PAUSE) {
|
|
1839
|
-
pauseCount++;
|
|
1840
|
-
recordEvent({ type: "pause", path, lockId: m[1], ...base });
|
|
1841
|
-
} else if (t === RESUME) {
|
|
1842
|
-
resumeCount++;
|
|
1843
|
-
recordEvent({ type: "resume", path, lockId: m[1], ...base });
|
|
1844
|
-
} else if (t === COMPLETE) {
|
|
1845
|
-
if (!nodeErrored.has(path)) anyCompletedCleanly = true;
|
|
1846
|
-
recordEvent({ type: "complete", path, ...base });
|
|
1847
|
-
} else if (t === ERROR) {
|
|
1848
|
-
anyErrored = true;
|
|
1849
|
-
nodeErrored.add(path);
|
|
1850
|
-
recordEvent({
|
|
1851
|
-
type: "error",
|
|
1852
|
-
path,
|
|
1853
|
-
data: m[1],
|
|
1854
|
-
...base
|
|
1855
|
-
});
|
|
1856
|
-
} else if (t === TEARDOWN) {
|
|
1857
|
-
teardownCount++;
|
|
1858
|
-
recordEvent({ type: "teardown", path, ...base });
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
})
|
|
1862
|
-
);
|
|
1863
|
-
}
|
|
1864
|
-
let disposed = false;
|
|
1865
|
-
const dispose = () => {
|
|
1866
|
-
if (disposed) return;
|
|
1867
|
-
disposed = true;
|
|
1868
|
-
for (const u of unsubs) u();
|
|
1869
|
-
for (const d of inspectorDetaches) d();
|
|
1870
|
-
for (const resolve of asyncResolvers) resolve({ value: void 0, done: true });
|
|
1871
|
-
asyncResolvers.length = 0;
|
|
1872
|
-
};
|
|
1873
|
-
const asyncQueue = [];
|
|
1874
|
-
const asyncResolvers = [];
|
|
1875
|
-
listeners.add((ev) => {
|
|
1876
|
-
const resolve = asyncResolvers.shift();
|
|
1877
|
-
if (resolve) resolve({ value: ev, done: false });
|
|
1878
|
-
else asyncQueue.push(ev);
|
|
1879
|
-
});
|
|
1880
|
-
const result = {
|
|
1881
|
-
get values() {
|
|
1882
|
-
return values;
|
|
1883
|
-
},
|
|
1884
|
-
get dirtyCount() {
|
|
1885
|
-
return dirtyCount;
|
|
1886
|
-
},
|
|
1887
|
-
get resolvedCount() {
|
|
1888
|
-
return resolvedCount;
|
|
1889
|
-
},
|
|
1890
|
-
get invalidateCount() {
|
|
1891
|
-
return invalidateCount;
|
|
1892
|
-
},
|
|
1893
|
-
get pauseCount() {
|
|
1894
|
-
return pauseCount;
|
|
1895
|
-
},
|
|
1896
|
-
get resumeCount() {
|
|
1897
|
-
return resumeCount;
|
|
1898
|
-
},
|
|
1899
|
-
get teardownCount() {
|
|
1900
|
-
return teardownCount;
|
|
1901
|
-
},
|
|
1902
|
-
get events() {
|
|
1903
|
-
return ring ? ring.toArray() : [...events];
|
|
1904
|
-
},
|
|
1905
|
-
get anyCompletedCleanly() {
|
|
1906
|
-
return anyCompletedCleanly;
|
|
1907
|
-
},
|
|
1908
|
-
get anyErrored() {
|
|
1909
|
-
return anyErrored;
|
|
1910
|
-
},
|
|
1911
|
-
get completedWithoutErrors() {
|
|
1912
|
-
return anyCompletedCleanly && !anyErrored;
|
|
1913
|
-
},
|
|
1914
|
-
onEvent(listener) {
|
|
1915
|
-
listeners.add(listener);
|
|
1916
|
-
return () => listeners.delete(listener);
|
|
1917
|
-
},
|
|
1918
|
-
dispose,
|
|
1919
|
-
expand(extra) {
|
|
1920
|
-
dispose();
|
|
1921
|
-
const merged = { ...options };
|
|
1922
|
-
if (typeof extra === "string") {
|
|
1923
|
-
merged.detail = extra;
|
|
1924
|
-
} else {
|
|
1925
|
-
Object.assign(merged, extra);
|
|
1926
|
-
}
|
|
1927
|
-
return expand(resolveObserveDetail(merged));
|
|
1928
|
-
},
|
|
1929
|
-
[Symbol.asyncIterator]() {
|
|
1930
|
-
return {
|
|
1931
|
-
next() {
|
|
1932
|
-
if (asyncQueue.length > 0) {
|
|
1933
|
-
return Promise.resolve({ value: asyncQueue.shift(), done: false });
|
|
1934
|
-
}
|
|
1935
|
-
if (disposed) return Promise.resolve({ value: void 0, done: true });
|
|
1936
|
-
return new Promise((resolve) => asyncResolvers.push(resolve));
|
|
1937
|
-
},
|
|
1938
|
-
return() {
|
|
1939
|
-
dispose();
|
|
1940
|
-
return Promise.resolve({ value: void 0, done: true });
|
|
1941
|
-
}
|
|
1942
|
-
};
|
|
1943
|
-
}
|
|
1944
|
-
};
|
|
1945
|
-
if (options.format != null) this._attachFormatLogger(result, options);
|
|
1946
|
-
return result;
|
|
1947
|
-
}
|
|
1948
|
-
/**
|
|
1949
|
-
* Attach format-rendering logger to an ObserveResult by subscribing to its
|
|
1950
|
-
* event stream (no monkey-patching). Renders each event per `format` and
|
|
1951
|
-
* `theme`, filtered by `includeTypes` / `excludeTypes`.
|
|
1952
|
-
*/
|
|
1953
|
-
_attachFormatLogger(result, options) {
|
|
1954
|
-
const format = options.format;
|
|
1955
|
-
if (format == null) return;
|
|
1956
|
-
const logger = options.logger ?? ((line) => console.log(line));
|
|
1957
|
-
const include = options.includeTypes ? new Set(options.includeTypes) : null;
|
|
1958
|
-
const exclude = options.excludeTypes ? new Set(options.excludeTypes) : null;
|
|
1959
|
-
const shouldLog = include == null && exclude == null ? () => true : (type) => (include == null || include.has(type)) && (exclude == null || !exclude.has(type));
|
|
1960
|
-
const theme = resolveObserveTheme(options.theme);
|
|
1961
|
-
const renderEvent = (event) => {
|
|
1962
|
-
if (format === "json") {
|
|
1963
|
-
try {
|
|
1964
|
-
return JSON.stringify(event);
|
|
1965
|
-
} catch {
|
|
1966
|
-
return JSON.stringify({
|
|
1967
|
-
type: event.type,
|
|
1968
|
-
path: event.path,
|
|
1969
|
-
data: "[unserializable]"
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
const color = theme[event.type] ?? "";
|
|
1974
|
-
const pathPart = event.path ? `${theme.path}${event.path}${theme.reset} ` : "";
|
|
1975
|
-
const isDataBearing = event.type === "data" || event.type === "error";
|
|
1976
|
-
const isLockBearing = event.type === "pause" || event.type === "resume";
|
|
1977
|
-
const dataPart = isDataBearing ? ` ${describeData(event.data)}` : isLockBearing ? ` ${describeData(event.lockId)}` : "";
|
|
1978
|
-
const causal = event.type === "data" || event.type === "resolved" || event.type === "derived" ? event : void 0;
|
|
1979
|
-
const triggerPart = causal?.trigger_dep_name != null ? ` <- ${causal.trigger_dep_name}` : causal?.trigger_dep_index != null ? ` <- #${causal.trigger_dep_index}` : "";
|
|
1980
|
-
const batchPart = event.in_batch ? " [batch]" : "";
|
|
1981
|
-
return `${pathPart}${color}${event.type.toUpperCase()}${theme.reset}${dataPart}${triggerPart}${batchPart}`;
|
|
1982
|
-
};
|
|
1983
|
-
result.onEvent((event) => {
|
|
1984
|
-
if (shouldLog(event.type)) logger(renderEvent(event), event);
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1987
|
-
// `dumpGraph` is folded into `describe({format: "pretty" | "json"})` (Unit 12).
|
|
1988
|
-
// `toMermaid` / `toD2` are folded into `describe({format: "mermaid" | "d2"})` (Unit 20).
|
|
1989
|
-
// ——————————————————————————————————————————————————————————————
|
|
1990
|
-
// Lifecycle & persistence (§3.7–§3.8)
|
|
1991
|
-
// ——————————————————————————————————————————————————————————————
|
|
1992
|
-
/**
|
|
1993
|
-
* Register a cleanup function to be called on {@link Graph.destroy}.
|
|
1994
|
-
*
|
|
1995
|
-
* Factories use this to attach teardown logic for internal nodes, keepalive
|
|
1996
|
-
* subscriptions, or other resources that are not registered on the graph and
|
|
1997
|
-
* would otherwise leak on repeated create/destroy cycles.
|
|
1998
|
-
*
|
|
1999
|
-
* Returns a removal function — call it to unregister the disposer early.
|
|
2000
|
-
*/
|
|
2001
|
-
addDisposer(fn) {
|
|
2002
|
-
this._disposers.add(fn);
|
|
2003
|
-
return () => {
|
|
2004
|
-
this._disposers.delete(fn);
|
|
2005
|
-
};
|
|
2006
|
-
}
|
|
2007
|
-
/**
|
|
2008
|
-
* Drains disposers (registered via {@link addDisposer}), then sends `[[TEARDOWN]]` to all
|
|
2009
|
-
* nodes and clears registries on this graph and every mounted subgraph (§3.7).
|
|
2010
|
-
* The instance is left empty and may be reused with {@link Graph.add}.
|
|
2011
|
-
*/
|
|
2012
|
-
destroy() {
|
|
2013
|
-
drainDisposers(this._disposers, this.name);
|
|
2014
|
-
this.signal([[TEARDOWN]], { internal: true });
|
|
2015
|
-
drainDisposers(this._storageDisposers, this.name);
|
|
2016
|
-
for (const child of [...this._mounts.values()]) {
|
|
2017
|
-
child._parent = void 0;
|
|
2018
|
-
child._destroyClearOnly();
|
|
2019
|
-
}
|
|
2020
|
-
this._mounts.clear();
|
|
2021
|
-
this._nodes.clear();
|
|
2022
|
-
this._parent = void 0;
|
|
2023
|
-
}
|
|
2024
|
-
/** Clear structure after parent already signaled TEARDOWN through this subtree. */
|
|
2025
|
-
_destroyClearOnly() {
|
|
2026
|
-
for (const child of [...this._mounts.values()]) {
|
|
2027
|
-
child._parent = void 0;
|
|
2028
|
-
child._destroyClearOnly();
|
|
2029
|
-
}
|
|
2030
|
-
this._mounts.clear();
|
|
2031
|
-
this._nodes.clear();
|
|
2032
|
-
this._parent = void 0;
|
|
2033
|
-
}
|
|
2034
|
-
snapshot(opts) {
|
|
2035
|
-
const { expand: _, ...d } = this.describe({ detail: "full" });
|
|
2036
|
-
const sortedNodes = {};
|
|
2037
|
-
for (const key of Object.keys(d.nodes).sort()) {
|
|
2038
|
-
const { lastMutation: _lm, guard: _g, ...node } = d.nodes[key];
|
|
2039
|
-
sortedNodes[key] = node;
|
|
2040
|
-
}
|
|
2041
|
-
const sortedSubgraphs = [...d.subgraphs].sort();
|
|
2042
|
-
const snap = {
|
|
2043
|
-
...d,
|
|
2044
|
-
version: 1,
|
|
2045
|
-
nodes: sortedNodes,
|
|
2046
|
-
subgraphs: sortedSubgraphs
|
|
2047
|
-
};
|
|
2048
|
-
if (opts?.format == null) return snap;
|
|
2049
|
-
if (opts.format === "json-string") return JSON.stringify(snap);
|
|
2050
|
-
if (opts.format === "bytes") {
|
|
2051
|
-
if (opts.codec == null) {
|
|
2052
|
-
throw new Error("snapshot({format: 'bytes'}) requires a `codec` name");
|
|
2053
|
-
}
|
|
2054
|
-
const codec = this.config.lookupCodec(opts.codec);
|
|
2055
|
-
if (codec == null) {
|
|
2056
|
-
throw new Error(
|
|
2057
|
-
`snapshot: codec "${opts.codec}" is not registered on this graph's config. Call config.registerCodec(...) before creating nodes.`
|
|
2058
|
-
);
|
|
2059
|
-
}
|
|
2060
|
-
return encodeEnvelope(codec, codec.encode(snap));
|
|
2061
|
-
}
|
|
2062
|
-
throw new Error(`snapshot: unknown format "${String(opts.format)}"`);
|
|
2063
|
-
}
|
|
2064
|
-
/**
|
|
2065
|
-
* Auto-dispatch a byte buffer produced by {@link Graph.snapshot} with
|
|
2066
|
-
* `{format: "bytes", codec: name}`. Reads the v1 envelope, resolves the
|
|
2067
|
-
* named codec on `config` (defaults to `defaultConfig`), and returns the
|
|
2068
|
-
* decoded snapshot. Combine with {@link Graph.fromSnapshot} to rehydrate
|
|
2069
|
-
* a full graph topology from bytes.
|
|
2070
|
-
*
|
|
2071
|
-
* @throws If the envelope is malformed or the named codec isn't
|
|
2072
|
-
* registered on the target config.
|
|
2073
|
-
*/
|
|
2074
|
-
static decode(bytes, opts) {
|
|
2075
|
-
const cfg = opts?.config ?? defaultConfig;
|
|
2076
|
-
const { codec, codecVersion, payload } = decodeEnvelope(bytes, cfg);
|
|
2077
|
-
return codec.decode(payload, codecVersion);
|
|
2078
|
-
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Apply persisted values onto an existing graph whose topology matches the snapshot
|
|
2081
|
-
* (§3.8). Only {@link DescribeNodeOutput.type} `state` entries with a `value` field
|
|
2082
|
-
* are written by default; `derived` / `operator` / `effect` are always skipped so
|
|
2083
|
-
* deps drive recomputation. `producer` entries are skipped unless `includeProducers`
|
|
2084
|
-
* is set (producers recompute on activation, so restoring is usually a no-op
|
|
2085
|
-
* overwritten on the next wave — opt in for audit / forensic round-trip use cases).
|
|
2086
|
-
* Unknown paths are ignored.
|
|
2087
|
-
*
|
|
2088
|
-
* @param data - Snapshot envelope with matching `name` and node slices.
|
|
2089
|
-
* @throws If `data.name` does not equal {@link Graph.name}.
|
|
2090
|
-
*/
|
|
2091
|
-
restore(data, options) {
|
|
2092
|
-
parseSnapshotEnvelope(data);
|
|
2093
|
-
if (data.name !== this.name) {
|
|
2094
|
-
throw new Error(
|
|
2095
|
-
`Graph "${this.name}": restore snapshot name "${data.name}" does not match this graph`
|
|
2096
|
-
);
|
|
2097
|
-
}
|
|
2098
|
-
const onlyPatterns = options?.only == null ? null : (Array.isArray(options.only) ? options.only : [options.only]).map((p) => globToRegex(p));
|
|
2099
|
-
const includeProducers = options?.includeProducers === true;
|
|
2100
|
-
for (const path of Object.keys(data.nodes).sort()) {
|
|
2101
|
-
if (onlyPatterns !== null && !onlyPatterns.some((re) => re.test(path))) continue;
|
|
2102
|
-
const slice = data.nodes[path];
|
|
2103
|
-
if (slice === void 0) continue;
|
|
2104
|
-
if (!("value" in slice) || slice.value === void 0) {
|
|
2105
|
-
if ("value" in slice && slice.value === void 0) {
|
|
2106
|
-
options?.onError?.(
|
|
2107
|
-
path,
|
|
2108
|
-
new Error(
|
|
2109
|
-
`restore: slice.value is undefined for "${path}" (undefined is the global SENTINEL; not valid DATA)`
|
|
2110
|
-
)
|
|
2111
|
-
);
|
|
2112
|
-
}
|
|
2113
|
-
continue;
|
|
2114
|
-
}
|
|
2115
|
-
if (slice.type === "derived" || slice.type === "effect") {
|
|
2116
|
-
continue;
|
|
2117
|
-
}
|
|
2118
|
-
if (slice.type === "producer" && !includeProducers) {
|
|
2119
|
-
continue;
|
|
2120
|
-
}
|
|
2121
|
-
if (slice.v != null) {
|
|
2122
|
-
const live = this.tryResolve(path);
|
|
2123
|
-
const lv = live?.v;
|
|
2124
|
-
if (lv != null && lv.id === slice.v.id && lv.version === slice.v.version) {
|
|
2125
|
-
continue;
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
try {
|
|
2129
|
-
this.set(path, slice.value);
|
|
2130
|
-
} catch (err) {
|
|
2131
|
-
options?.onError?.(path, err);
|
|
2132
|
-
}
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
/**
|
|
2136
|
-
* Creates a graph named from the snapshot, optionally runs `build` to register nodes
|
|
2137
|
-
* and mounts, then {@link Graph.restore} values (§3.8).
|
|
2138
|
-
*
|
|
2139
|
-
* @param data - Snapshot envelope (`version` checked).
|
|
2140
|
-
* @param opts - Either a legacy `build(g)` callback, or an options object:
|
|
2141
|
-
* - `build?` — topology constructor; skips auto-hydration when present.
|
|
2142
|
-
* - `factories?` — map from glob pattern to {@link GraphNodeFactory},
|
|
2143
|
-
* used by auto-hydration to reconstruct non-state nodes. Per-call (no
|
|
2144
|
-
* process-global registry). First matching pattern wins.
|
|
2145
|
-
* @returns Hydrated `Graph` instance.
|
|
2146
|
-
*/
|
|
2147
|
-
static fromSnapshot(data, opts) {
|
|
2148
|
-
parseSnapshotEnvelope(data);
|
|
2149
|
-
const build = typeof opts === "function" ? opts : opts?.build;
|
|
2150
|
-
const factoryMap = typeof opts === "function" ? void 0 : opts?.factories;
|
|
2151
|
-
const g = new _Graph(data.name);
|
|
2152
|
-
if (build) {
|
|
2153
|
-
build(g);
|
|
2154
|
-
g.restore(data);
|
|
2155
|
-
return g;
|
|
2156
|
-
}
|
|
2157
|
-
for (const mount of [...data.subgraphs].sort((a, b) => {
|
|
2158
|
-
const da = a.split(PATH_SEP).length;
|
|
2159
|
-
const db = b.split(PATH_SEP).length;
|
|
2160
|
-
if (da !== db) return da - db;
|
|
2161
|
-
if (a < b) return -1;
|
|
2162
|
-
if (a > b) return 1;
|
|
2163
|
-
return 0;
|
|
2164
|
-
})) {
|
|
2165
|
-
const parts = mount.split(PATH_SEP);
|
|
2166
|
-
let target = g;
|
|
2167
|
-
for (const seg of parts) {
|
|
2168
|
-
if (!target._mounts.has(seg)) {
|
|
2169
|
-
target.mount(seg, new _Graph(seg));
|
|
2170
|
-
}
|
|
2171
|
-
target = target._mounts.get(seg);
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
const factories = factoryMap ? Object.entries(factoryMap).map(([pattern, factory]) => ({
|
|
2175
|
-
re: globToRegex(pattern),
|
|
2176
|
-
factory
|
|
2177
|
-
})) : [];
|
|
2178
|
-
const factoryForPath = (path) => {
|
|
2179
|
-
for (const entry of factories) {
|
|
2180
|
-
if (entry.re.test(path)) return entry.factory;
|
|
2181
|
-
}
|
|
2182
|
-
return void 0;
|
|
2183
|
-
};
|
|
2184
|
-
const ownerForPath = (path) => {
|
|
2185
|
-
const segments = path.split(PATH_SEP);
|
|
2186
|
-
const local = segments.pop();
|
|
2187
|
-
if (local == null || local.length === 0) {
|
|
2188
|
-
throw new Error(`invalid snapshot path "${path}"`);
|
|
2189
|
-
}
|
|
2190
|
-
let owner = g;
|
|
2191
|
-
for (const seg of segments) {
|
|
2192
|
-
const next = owner._mounts.get(seg);
|
|
2193
|
-
if (!next) throw new Error(`unknown mount "${seg}" in path "${path}"`);
|
|
2194
|
-
owner = next;
|
|
2195
|
-
}
|
|
2196
|
-
return [owner, local];
|
|
2197
|
-
};
|
|
2198
|
-
const primaryEntries = Object.entries(data.nodes).filter(([path]) => !path.includes(`${PATH_SEP}${GRAPH_META_SEGMENT}${PATH_SEP}`)).sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
2199
|
-
const pending = new Map(primaryEntries);
|
|
2200
|
-
const created = /* @__PURE__ */ new Map();
|
|
2201
|
-
let progressed = true;
|
|
2202
|
-
while (pending.size > 0 && progressed) {
|
|
2203
|
-
progressed = false;
|
|
2204
|
-
for (const [path, slice] of [...pending.entries()]) {
|
|
2205
|
-
const deps = slice?.deps ?? [];
|
|
2206
|
-
if (!deps.every((dep) => created.has(dep))) continue;
|
|
2207
|
-
const [owner, localName] = ownerForPath(path);
|
|
2208
|
-
const meta = { ...slice?.meta ?? {} };
|
|
2209
|
-
const factory = factoryForPath(path);
|
|
2210
|
-
let node;
|
|
2211
|
-
if (slice?.type === "state") {
|
|
2212
|
-
node = state(slice.value, { meta });
|
|
2213
|
-
} else {
|
|
2214
|
-
if (factory == null) continue;
|
|
2215
|
-
node = factory(localName, {
|
|
2216
|
-
path,
|
|
2217
|
-
type: slice.type,
|
|
2218
|
-
value: slice.value,
|
|
2219
|
-
meta,
|
|
2220
|
-
deps,
|
|
2221
|
-
resolvedDeps: deps.map((dep) => created.get(dep))
|
|
2222
|
-
});
|
|
2223
|
-
}
|
|
2224
|
-
owner.add(localName, node);
|
|
2225
|
-
created.set(path, node);
|
|
2226
|
-
pending.delete(path);
|
|
2227
|
-
progressed = true;
|
|
2228
|
-
}
|
|
2229
|
-
}
|
|
2230
|
-
if (pending.size > 0) {
|
|
2231
|
-
const unresolved = [...pending.keys()].sort().join(", ");
|
|
2232
|
-
throw new Error(
|
|
2233
|
-
`Graph.fromSnapshot could not reconstruct nodes without build callback: ${unresolved}. Pass matching factories via fromSnapshot(data, { factories: { pattern: factoryFn } }).`
|
|
2234
|
-
);
|
|
2235
|
-
}
|
|
2236
|
-
g.restore(data);
|
|
2237
|
-
return g;
|
|
2238
|
-
}
|
|
2239
|
-
/**
|
|
2240
|
-
* ECMAScript `JSON.stringify` hook — returns the same object as
|
|
2241
|
-
* {@link Graph.snapshot}. Makes `JSON.stringify(graph)` "just work"
|
|
2242
|
-
* without double-encoding.
|
|
2243
|
-
*/
|
|
2244
|
-
toJSON() {
|
|
2245
|
-
return this.snapshot();
|
|
2246
|
-
}
|
|
2247
|
-
/**
|
|
2248
|
-
* Unified persistence surface (§3.8). Cascades snapshot records through
|
|
2249
|
-
* one or more {@link StorageTier}s, each with its own `debounceMs` /
|
|
2250
|
-
* `compactEvery` cadence and independent diff baseline.
|
|
2251
|
-
*
|
|
2252
|
-
* Subscription gates on {@link messageTier} ≥ 3 (DATA/RESOLVED/terminal),
|
|
2253
|
-
* never on tier-0/1/2 control waves (START/DIRTY/INVALIDATE/PAUSE/RESUME)
|
|
2254
|
-
* or tier-5 TEARDOWN (graceful shutdown is the caller's responsibility).
|
|
2255
|
-
*
|
|
2256
|
-
* Per-tier cadence lets the hot tier stay sync while cold tiers absorb
|
|
2257
|
-
* async writes without blocking the hot path. Each tier holds its own
|
|
2258
|
-
* `{lastSnapshot, lastVersionFingerprint}` so cold-tier diff baselines
|
|
2259
|
-
* aren't polluted by hot-tier flushes. Tiers with `debounceMs === 0`
|
|
2260
|
-
* share a single snapshot computation per observe event; debounced tiers
|
|
2261
|
-
* compute their own snapshot when their timer fires.
|
|
2262
|
-
*/
|
|
2263
|
-
attachStorage(tiers, options = {}) {
|
|
2264
|
-
const states = tiers.map((tier) => ({
|
|
2265
|
-
tier,
|
|
2266
|
-
debounceMs: Math.max(0, tier.debounceMs ?? 0),
|
|
2267
|
-
compactEvery: Math.max(1, tier.compactEvery ?? 10),
|
|
2268
|
-
timer: void 0,
|
|
2269
|
-
seq: 0,
|
|
2270
|
-
lastSnapshot: void 0,
|
|
2271
|
-
lastFingerprint: "",
|
|
2272
|
-
disposed: false,
|
|
2273
|
-
savePending: void 0
|
|
2274
|
-
}));
|
|
2275
|
-
if (options.autoRestore === true) {
|
|
2276
|
-
void this._cascadeRestore(tiers, options.onError);
|
|
2277
|
-
}
|
|
2278
|
-
const runFlush = (s, snapshot) => {
|
|
2279
|
-
if (s.disposed) return;
|
|
2280
|
-
const fingerprint = computeVersionFingerprint(snapshot.nodes);
|
|
2281
|
-
if (s.lastSnapshot != null && fingerprint !== "" && fingerprint === s.lastFingerprint) {
|
|
2282
|
-
return;
|
|
2283
|
-
}
|
|
2284
|
-
const nextSeq = s.seq + 1;
|
|
2285
|
-
const timestamp_ns = wallClockNs();
|
|
2286
|
-
const isFirst = s.lastSnapshot == null;
|
|
2287
|
-
const shouldCompact = isFirst || nextSeq % s.compactEvery === 0;
|
|
2288
|
-
const record = shouldCompact ? {
|
|
2289
|
-
mode: "full",
|
|
2290
|
-
snapshot,
|
|
2291
|
-
seq: nextSeq,
|
|
2292
|
-
timestamp_ns,
|
|
2293
|
-
format_version: SNAPSHOT_VERSION
|
|
2294
|
-
} : {
|
|
2295
|
-
mode: "diff",
|
|
2296
|
-
diff: diffForWAL(s.lastSnapshot, snapshot),
|
|
2297
|
-
seq: nextSeq,
|
|
2298
|
-
timestamp_ns,
|
|
2299
|
-
format_version: SNAPSHOT_VERSION
|
|
2300
|
-
};
|
|
2301
|
-
if (s.tier.filter && !s.tier.filter(this.name, record)) {
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
let result;
|
|
2305
|
-
try {
|
|
2306
|
-
result = s.tier.save(this.name, record);
|
|
2307
|
-
} catch (error) {
|
|
2308
|
-
options.onError?.(error, s.tier);
|
|
2309
|
-
return;
|
|
2310
|
-
}
|
|
2311
|
-
if (result && typeof result.then === "function") {
|
|
2312
|
-
const prev = s.savePending ?? Promise.resolve();
|
|
2313
|
-
const chained = prev.then(
|
|
2314
|
-
() => result,
|
|
2315
|
-
// Previous rejection already surfaced; don't block this save.
|
|
2316
|
-
() => result
|
|
2317
|
-
);
|
|
2318
|
-
const final = chained.then(
|
|
2319
|
-
() => {
|
|
2320
|
-
if (s.disposed) return;
|
|
2321
|
-
s.seq = nextSeq;
|
|
2322
|
-
s.lastSnapshot = snapshot;
|
|
2323
|
-
s.lastFingerprint = fingerprint;
|
|
2324
|
-
},
|
|
2325
|
-
(err) => {
|
|
2326
|
-
options.onError?.(err, s.tier);
|
|
2327
|
-
}
|
|
2328
|
-
);
|
|
2329
|
-
s.savePending = final.finally(() => {
|
|
2330
|
-
if (s.savePending === final) s.savePending = void 0;
|
|
2331
|
-
});
|
|
2332
|
-
} else {
|
|
2333
|
-
s.seq = nextSeq;
|
|
2334
|
-
s.lastSnapshot = snapshot;
|
|
2335
|
-
s.lastFingerprint = fingerprint;
|
|
2336
|
-
}
|
|
2337
|
-
};
|
|
2338
|
-
const flushTier = (s, snapshot) => {
|
|
2339
|
-
try {
|
|
2340
|
-
runFlush(s, snapshot);
|
|
2341
|
-
} catch (error) {
|
|
2342
|
-
options.onError?.(error, s.tier);
|
|
2343
|
-
}
|
|
2344
|
-
};
|
|
2345
|
-
const onEvent = (path, messages) => {
|
|
2346
|
-
const triggeredByTier = messages.some((m) => {
|
|
2347
|
-
const tier = this.config.messageTier(m[0]);
|
|
2348
|
-
return tier >= 3 && tier < 5;
|
|
2349
|
-
});
|
|
2350
|
-
if (!triggeredByTier) return;
|
|
2351
|
-
if (options.filter) {
|
|
2352
|
-
const nd = this.tryResolve(path);
|
|
2353
|
-
if (nd == null) return;
|
|
2354
|
-
const described = describeNode(nd, resolveDescribeFields("standard"));
|
|
2355
|
-
if (!options.filter(path, described)) return;
|
|
2356
|
-
}
|
|
2357
|
-
let sharedSnapshot;
|
|
2358
|
-
const getSnapshot = () => {
|
|
2359
|
-
if (sharedSnapshot == null) sharedSnapshot = this.snapshot();
|
|
2360
|
-
return sharedSnapshot;
|
|
2361
|
-
};
|
|
2362
|
-
for (const s of states) {
|
|
2363
|
-
if (s.disposed) continue;
|
|
2364
|
-
if (s.debounceMs === 0) {
|
|
2365
|
-
flushTier(s, getSnapshot());
|
|
2366
|
-
} else {
|
|
2367
|
-
if (s.timer == null) s.timer = new ResettableTimer();
|
|
2368
|
-
s.timer.start(s.debounceMs, () => {
|
|
2369
|
-
if (s.disposed) return;
|
|
2370
|
-
flushTier(s, this.snapshot());
|
|
2371
|
-
});
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
};
|
|
2375
|
-
let off;
|
|
2376
|
-
if (options.paths != null) {
|
|
2377
|
-
const paths = typeof options.paths === "string" ? this._pathsMatching(options.paths) : options.paths;
|
|
2378
|
-
const unsubs = paths.map((p) => {
|
|
2379
|
-
const nd = this.tryResolve(p);
|
|
2380
|
-
if (nd == null) return () => {
|
|
2381
|
-
};
|
|
2382
|
-
return nd.subscribe((msgs) => onEvent(p, msgs));
|
|
2383
|
-
});
|
|
2384
|
-
off = () => {
|
|
2385
|
-
for (const u of unsubs) u();
|
|
2386
|
-
};
|
|
2387
|
-
} else {
|
|
2388
|
-
off = this.observe().subscribe((path, messages) => onEvent(path, messages));
|
|
2389
|
-
}
|
|
2390
|
-
const dispose = () => {
|
|
2391
|
-
off();
|
|
2392
|
-
for (const s of states) {
|
|
2393
|
-
s.disposed = true;
|
|
2394
|
-
s.timer?.cancel();
|
|
2395
|
-
}
|
|
2396
|
-
this._storageDisposers.delete(dispose);
|
|
2397
|
-
};
|
|
2398
|
-
this._storageDisposers.add(dispose);
|
|
2399
|
-
return { dispose };
|
|
2400
|
-
}
|
|
2401
|
-
/**
|
|
2402
|
-
* Try tiers in order (hottest first); apply the first record that hits
|
|
2403
|
-
* via {@link Graph.restore}. Returns `true` if any tier produced a
|
|
2404
|
-
* restorable snapshot, `false` if all missed.
|
|
2405
|
-
*
|
|
2406
|
-
* Resilience: a tier that returns data which cannot be restored (load
|
|
2407
|
-
* throws, shape unrecognized, or `restore()` itself throws) does not abort
|
|
2408
|
-
* the cascade — the error is routed through `onError` (if supplied) and
|
|
2409
|
-
* the next colder tier is tried. This mirrors how a multi-tier cache
|
|
2410
|
-
* falls through on a corrupt hot entry.
|
|
2411
|
-
*
|
|
2412
|
-
* Note: `restore()` mutates state incrementally. If a restore throws
|
|
2413
|
-
* partway through, the graph may hold a mixed state (some slices from
|
|
2414
|
-
* the bad tier, some pre-existing). A subsequent successful tier's
|
|
2415
|
-
* `restore()` overwrites the overlapping slices.
|
|
2416
|
-
*
|
|
2417
|
-
* Internal helper shared by {@link Graph.attachStorage}'s `autoRestore`
|
|
2418
|
-
* option and the static {@link Graph.fromStorage} factory.
|
|
2419
|
-
*/
|
|
2420
|
-
async _cascadeRestore(tiers, onError) {
|
|
2421
|
-
for (const tier of tiers) {
|
|
2422
|
-
let raw;
|
|
2423
|
-
try {
|
|
2424
|
-
raw = await tier.load(this.name);
|
|
2425
|
-
} catch (err) {
|
|
2426
|
-
onError?.(err, tier);
|
|
2427
|
-
continue;
|
|
2428
|
-
}
|
|
2429
|
-
if (raw == null) continue;
|
|
2430
|
-
if (typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2431
|
-
const record = raw;
|
|
2432
|
-
try {
|
|
2433
|
-
if (record.mode === "full" && record.snapshot != null) {
|
|
2434
|
-
this.restore(record.snapshot);
|
|
2435
|
-
return true;
|
|
2436
|
-
}
|
|
2437
|
-
if (record.version === SNAPSHOT_VERSION && record.nodes != null) {
|
|
2438
|
-
this.restore(record);
|
|
2439
|
-
return true;
|
|
2440
|
-
}
|
|
2441
|
-
} catch (err) {
|
|
2442
|
-
onError?.(err, tier);
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
return false;
|
|
2446
|
-
}
|
|
2447
|
-
/**
|
|
2448
|
-
* Construct a fresh {@link Graph} pre-hydrated from the first tier that
|
|
2449
|
-
* hits. Delegates topology reconstruction to {@link Graph.fromSnapshot}
|
|
2450
|
-
* on `"full"` records and direct {@link Graph.restore} on bare snapshots.
|
|
2451
|
-
*
|
|
2452
|
-
* Always asynchronous — awaits `tier.load()` for async tier support even
|
|
2453
|
-
* when all tiers are sync. Callers that know they only pass sync tiers
|
|
2454
|
-
* can safely `await` immediately.
|
|
2455
|
-
*
|
|
2456
|
-
* @throws If no tier holds a restorable record matching `name` *and* no
|
|
2457
|
-
* `factories` override is provided for dynamic nodes.
|
|
2458
|
-
*/
|
|
2459
|
-
static async fromStorage(name, tiers, opts) {
|
|
2460
|
-
for (const tier of tiers) {
|
|
2461
|
-
let raw;
|
|
2462
|
-
try {
|
|
2463
|
-
raw = await tier.load(name);
|
|
2464
|
-
} catch (err) {
|
|
2465
|
-
opts?.onError?.(err, tier);
|
|
2466
|
-
continue;
|
|
2467
|
-
}
|
|
2468
|
-
if (raw == null) continue;
|
|
2469
|
-
if (typeof raw !== "object" || Array.isArray(raw)) continue;
|
|
2470
|
-
const record = raw;
|
|
2471
|
-
const snapshot = record.mode === "full" && record.snapshot != null ? record.snapshot : record.version === SNAPSHOT_VERSION && record.nodes != null ? record : void 0;
|
|
2472
|
-
if (snapshot == null) continue;
|
|
2473
|
-
try {
|
|
2474
|
-
return _Graph.fromSnapshot(snapshot, opts);
|
|
2475
|
-
} catch (err) {
|
|
2476
|
-
opts?.onError?.(err, tier);
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
throw new Error(
|
|
2480
|
-
`Graph.fromStorage: no tier held a restorable record for "${name}" across ${tiers.length} tier(s)`
|
|
2481
|
-
);
|
|
2482
|
-
}
|
|
2483
|
-
// ——————————————————————————————————————————————————————————————
|
|
2484
|
-
// Inspector (roadmap 3.3) — reasoning trace, overhead gating
|
|
2485
|
-
// ——————————————————————————————————————————————————————————————
|
|
2486
|
-
// Inspector gating lives on `this.config.inspectorEnabled` (see
|
|
2487
|
-
// `core/config.ts`). Default: `true` outside `NODE_ENV === "production"`.
|
|
2488
|
-
_annotations = /* @__PURE__ */ new Map();
|
|
2489
|
-
_traceRing;
|
|
2490
|
-
trace(path, reason, opts) {
|
|
2491
|
-
if (path != null && reason != null) {
|
|
2492
|
-
if (!this.config.inspectorEnabled) return;
|
|
2493
|
-
if (this.tryResolve(path) == null) return;
|
|
2494
|
-
this._annotations.set(path, reason);
|
|
2495
|
-
const entry = {
|
|
2496
|
-
path,
|
|
2497
|
-
reason,
|
|
2498
|
-
timestamp_ns: monotonicNs(),
|
|
2499
|
-
...opts?.actor != null ? { actor: opts.actor } : {}
|
|
2500
|
-
};
|
|
2501
|
-
this._traceRing.push(entry);
|
|
2502
|
-
return;
|
|
2503
|
-
}
|
|
2504
|
-
if (!this.config.inspectorEnabled) return [];
|
|
2505
|
-
return this._traceRing.toArray();
|
|
2506
|
-
}
|
|
2507
|
-
/**
|
|
2508
|
-
* Latest reason annotation attached to `path` via {@link Graph.trace},
|
|
2509
|
-
* or `undefined` if none. `describe()` surfaces this via the `reason`
|
|
2510
|
-
* field on each node entry (when present).
|
|
2511
|
-
*/
|
|
2512
|
-
annotation(path) {
|
|
2513
|
-
return this._annotations.get(path);
|
|
2514
|
-
}
|
|
2515
|
-
/**
|
|
2516
|
-
* Clear all reasoning-trace state (both the per-path annotations map and
|
|
2517
|
-
* the ring buffer). Useful for long-running processes that want periodic
|
|
2518
|
-
* resets, or tests that need a clean slate.
|
|
2519
|
-
*/
|
|
2520
|
-
clearTrace() {
|
|
2521
|
-
this._annotations.clear();
|
|
2522
|
-
this._traceRing.clear();
|
|
2523
|
-
}
|
|
2524
|
-
/**
|
|
2525
|
-
* Remove trace entries matching `predicate`. Returns the number of
|
|
2526
|
-
* entries removed. Does not touch the per-path annotations map — call
|
|
2527
|
-
* {@link Graph.clearTrace} for a full reset.
|
|
2528
|
-
*/
|
|
2529
|
-
pruneTrace(predicate) {
|
|
2530
|
-
const kept = this._traceRing.toArray().filter((e) => !predicate(e));
|
|
2531
|
-
const removed = this._traceRing.size - kept.length;
|
|
2532
|
-
this._traceRing.clear();
|
|
2533
|
-
for (const e of kept) this._traceRing.push(e);
|
|
2534
|
-
return removed;
|
|
2535
|
-
}
|
|
2536
|
-
/**
|
|
2537
|
-
* Computes structural + value diff between two {@link Graph.describe} snapshots.
|
|
2538
|
-
*
|
|
2539
|
-
* @param a - Earlier describe output.
|
|
2540
|
-
* @param b - Later describe output.
|
|
2541
|
-
* @returns Added/removed nodes, changed fields, and edge deltas.
|
|
2542
|
-
*/
|
|
2543
|
-
static diff(a, b) {
|
|
2544
|
-
const aKeys = new Set(Object.keys(a.nodes));
|
|
2545
|
-
const bKeys = new Set(Object.keys(b.nodes));
|
|
2546
|
-
const nodesAdded = [...bKeys].filter((k) => !aKeys.has(k)).sort();
|
|
2547
|
-
const nodesRemoved = [...aKeys].filter((k) => !bKeys.has(k)).sort();
|
|
2548
|
-
const nodesChanged = [];
|
|
2549
|
-
const versionChanges = [];
|
|
2550
|
-
for (const key of aKeys) {
|
|
2551
|
-
if (!bKeys.has(key)) continue;
|
|
2552
|
-
const na = a.nodes[key];
|
|
2553
|
-
const nb = b.nodes[key];
|
|
2554
|
-
const av = na.v;
|
|
2555
|
-
const bv = nb.v;
|
|
2556
|
-
if (av != null && bv != null && av.id === bv.id && av.version !== bv.version) {
|
|
2557
|
-
versionChanges.push({
|
|
2558
|
-
path: key,
|
|
2559
|
-
id: av.id,
|
|
2560
|
-
from: av.version,
|
|
2561
|
-
to: bv.version
|
|
2562
|
-
});
|
|
2563
|
-
}
|
|
2564
|
-
const versionMatches = av != null && bv != null && av.id === bv.id && av.version === bv.version;
|
|
2565
|
-
for (const field of ["type", "status", "sentinel"]) {
|
|
2566
|
-
const va = na[field];
|
|
2567
|
-
const vb = nb[field];
|
|
2568
|
-
if (va !== vb) {
|
|
2569
|
-
nodesChanged.push({ path: key, field, from: va, to: vb });
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
|
-
if (versionMatches) continue;
|
|
2573
|
-
for (const field of ["value", "meta"]) {
|
|
2574
|
-
const va = na[field];
|
|
2575
|
-
const vb = nb[field];
|
|
2576
|
-
if (!deepEqual(va, vb)) {
|
|
2577
|
-
nodesChanged.push({ path: key, field, from: va, to: vb });
|
|
2578
|
-
}
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
const edgeKey = (e) => `${e.from} ${e.to}`;
|
|
2582
|
-
const aEdges = new Set(a.edges.map(edgeKey));
|
|
2583
|
-
const bEdges = new Set(b.edges.map(edgeKey));
|
|
2584
|
-
const edgesAdded = b.edges.filter((e) => !aEdges.has(edgeKey(e)));
|
|
2585
|
-
const edgesRemoved = a.edges.filter((e) => !bEdges.has(edgeKey(e)));
|
|
2586
|
-
const aSubgraphs = new Set(a.subgraphs);
|
|
2587
|
-
const bSubgraphs = new Set(b.subgraphs);
|
|
2588
|
-
const subgraphsAdded = [...bSubgraphs].filter((s) => !aSubgraphs.has(s)).sort();
|
|
2589
|
-
const subgraphsRemoved = [...aSubgraphs].filter((s) => !bSubgraphs.has(s)).sort();
|
|
2590
|
-
return {
|
|
2591
|
-
nodesAdded,
|
|
2592
|
-
nodesRemoved,
|
|
2593
|
-
nodesChanged,
|
|
2594
|
-
versionChanges,
|
|
2595
|
-
edgesAdded,
|
|
2596
|
-
edgesRemoved,
|
|
2597
|
-
subgraphsAdded,
|
|
2598
|
-
subgraphsRemoved
|
|
2599
|
-
};
|
|
2600
|
-
}
|
|
2601
|
-
};
|
|
2602
|
-
function diffForWAL(a, b) {
|
|
2603
|
-
const base = Graph.diff(a, b);
|
|
2604
|
-
const nodesAddedFull = {};
|
|
2605
|
-
for (const path of base.nodesAdded) {
|
|
2606
|
-
const slice = b.nodes[path];
|
|
2607
|
-
if (slice != null) nodesAddedFull[path] = slice;
|
|
2608
|
-
}
|
|
2609
|
-
return { ...base, nodesAddedFull };
|
|
2610
|
-
}
|
|
2611
|
-
function reachable(described, from, direction, options = {}) {
|
|
2612
|
-
const empty = { paths: [], depths: /* @__PURE__ */ new Map(), truncated: false };
|
|
2613
|
-
if (!from) return options.withDetail ? empty : [];
|
|
2614
|
-
if (!options.both && direction !== "upstream" && direction !== "downstream") {
|
|
2615
|
-
throw new Error(`reachable: direction must be "upstream" or "downstream"`);
|
|
2616
|
-
}
|
|
2617
|
-
const maxDepth = options.maxDepth;
|
|
2618
|
-
if (maxDepth != null && (!Number.isInteger(maxDepth) || maxDepth < 0)) {
|
|
2619
|
-
throw new Error(`reachable: maxDepth must be an integer >= 0`);
|
|
2620
|
-
}
|
|
2621
|
-
if (maxDepth === 0) return options.withDetail ? empty : [];
|
|
2622
|
-
const depsByPath = /* @__PURE__ */ new Map();
|
|
2623
|
-
const reverseDeps = /* @__PURE__ */ new Map();
|
|
2624
|
-
const incomingEdges = /* @__PURE__ */ new Map();
|
|
2625
|
-
const outgoingEdges = /* @__PURE__ */ new Map();
|
|
2626
|
-
const universe = /* @__PURE__ */ new Set();
|
|
2627
|
-
for (const [path, node] of Object.entries(described.nodes)) {
|
|
2628
|
-
if (!path) continue;
|
|
2629
|
-
universe.add(path);
|
|
2630
|
-
const deps = node.deps ?? [];
|
|
2631
|
-
depsByPath.set(path, deps);
|
|
2632
|
-
for (const dep of deps) {
|
|
2633
|
-
if (!dep) continue;
|
|
2634
|
-
universe.add(dep);
|
|
2635
|
-
if (!reverseDeps.has(dep)) reverseDeps.set(dep, /* @__PURE__ */ new Set());
|
|
2636
|
-
reverseDeps.get(dep).add(path);
|
|
2637
|
-
}
|
|
2638
|
-
}
|
|
2639
|
-
for (const edge of described.edges) {
|
|
2640
|
-
if (edge == null || typeof edge !== "object") continue;
|
|
2641
|
-
const from2 = typeof edge.from === "string" ? edge.from : "";
|
|
2642
|
-
const to = typeof edge.to === "string" ? edge.to : "";
|
|
2643
|
-
if (!from2 || !to) continue;
|
|
2644
|
-
universe.add(from2);
|
|
2645
|
-
universe.add(to);
|
|
2646
|
-
if (!outgoingEdges.has(from2)) outgoingEdges.set(from2, /* @__PURE__ */ new Set());
|
|
2647
|
-
outgoingEdges.get(from2).add(to);
|
|
2648
|
-
if (!incomingEdges.has(to)) incomingEdges.set(to, /* @__PURE__ */ new Set());
|
|
2649
|
-
incomingEdges.get(to).add(from2);
|
|
2650
|
-
}
|
|
2651
|
-
if (!universe.has(from)) return options.withDetail ? empty : [];
|
|
2652
|
-
const doBoth = options.both === true;
|
|
2653
|
-
const visit = (path) => {
|
|
2654
|
-
if (doBoth) {
|
|
2655
|
-
const up = depsByPath.get(path) ?? [];
|
|
2656
|
-
const upEdges = incomingEdges.get(path);
|
|
2657
|
-
const down2 = reverseDeps.get(path);
|
|
2658
|
-
const downEdges2 = outgoingEdges.get(path);
|
|
2659
|
-
const acc2 = [...up];
|
|
2660
|
-
if (upEdges) acc2.push(...upEdges);
|
|
2661
|
-
if (down2) acc2.push(...down2);
|
|
2662
|
-
if (downEdges2) acc2.push(...downEdges2);
|
|
2663
|
-
return acc2;
|
|
2664
|
-
}
|
|
2665
|
-
if (direction === "upstream") {
|
|
2666
|
-
const up = depsByPath.get(path) ?? [];
|
|
2667
|
-
const upEdges = incomingEdges.get(path);
|
|
2668
|
-
if (!upEdges) return up;
|
|
2669
|
-
return [...up, ...upEdges];
|
|
2670
|
-
}
|
|
2671
|
-
const down = reverseDeps.get(path);
|
|
2672
|
-
const downEdges = outgoingEdges.get(path);
|
|
2673
|
-
const acc = down ? [...down] : [];
|
|
2674
|
-
if (downEdges) acc.push(...downEdges);
|
|
2675
|
-
return acc;
|
|
2676
|
-
};
|
|
2677
|
-
const visited = /* @__PURE__ */ new Set([from]);
|
|
2678
|
-
const depths = /* @__PURE__ */ new Map();
|
|
2679
|
-
const queue = [{ path: from, depth: 0 }];
|
|
2680
|
-
let head = 0;
|
|
2681
|
-
let truncated = false;
|
|
2682
|
-
while (head < queue.length) {
|
|
2683
|
-
const next = queue[head++];
|
|
2684
|
-
if (maxDepth != null && next.depth >= maxDepth) {
|
|
2685
|
-
if (visit(next.path).length > 0) truncated = true;
|
|
2686
|
-
continue;
|
|
2687
|
-
}
|
|
2688
|
-
for (const nb of visit(next.path)) {
|
|
2689
|
-
if (!nb || visited.has(nb)) continue;
|
|
2690
|
-
visited.add(nb);
|
|
2691
|
-
depths.set(nb, next.depth + 1);
|
|
2692
|
-
queue.push({ path: nb, depth: next.depth + 1 });
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
const paths = [...depths.keys()].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2696
|
-
if (options.withDetail) return { paths, depths, truncated };
|
|
2697
|
-
return paths;
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
export {
|
|
2701
|
-
explainPath,
|
|
2702
|
-
OVERHEAD,
|
|
2703
|
-
SIZEOF_SYMBOL,
|
|
2704
|
-
sizeof,
|
|
2705
|
-
graphProfile,
|
|
2706
|
-
GRAPH_META_SEGMENT,
|
|
2707
|
-
SNAPSHOT_VERSION,
|
|
2708
|
-
Graph,
|
|
2709
|
-
diffForWAL,
|
|
2710
|
-
reachable
|
|
2711
|
-
};
|
|
2712
|
-
//# sourceMappingURL=chunk-PF7GRZMW.js.map
|