@aussieljk/forkbook 0.0.0-stage → 0.0.1-2
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/LICENSE +22 -0
- package/README.md +77 -2
- package/assets/addon-vitest/templates/vitest.config.4.template.ts +36 -0
- package/assets/browser/favicon-wrapper.svg +46 -0
- package/assets/browser/favicon.svg +1 -0
- package/assets/browser/nunito-sans-bold-italic.woff2 +0 -0
- package/assets/browser/nunito-sans-bold.woff2 +0 -0
- package/assets/browser/nunito-sans-italic.woff2 +0 -0
- package/assets/browser/nunito-sans-regular.woff2 +0 -0
- package/assets/builder-vite/iframe.html +99 -0
- package/assets/create/rendererAssets/.eslintrc.json +16 -0
- package/assets/create/rendererAssets/common/Configure.mdx +388 -0
- package/assets/create/rendererAssets/common/assets/accessibility.png +0 -0
- package/assets/create/rendererAssets/common/assets/accessibility.svg +1 -0
- package/assets/create/rendererAssets/common/assets/addon-library.png +0 -0
- package/assets/create/rendererAssets/common/assets/assets.png +0 -0
- package/assets/create/rendererAssets/common/assets/avif-test-image.avif +0 -0
- package/assets/create/rendererAssets/common/assets/context.png +0 -0
- package/assets/create/rendererAssets/common/assets/discord.svg +1 -0
- package/assets/create/rendererAssets/common/assets/docs.png +0 -0
- package/assets/create/rendererAssets/common/assets/figma-plugin.png +0 -0
- package/assets/create/rendererAssets/common/assets/github.svg +1 -0
- package/assets/create/rendererAssets/common/assets/share.png +0 -0
- package/assets/create/rendererAssets/common/assets/styling.png +0 -0
- package/assets/create/rendererAssets/common/assets/testing.png +0 -0
- package/assets/create/rendererAssets/common/assets/theming.png +0 -0
- package/assets/create/rendererAssets/common/assets/tutorials.svg +1 -0
- package/assets/create/rendererAssets/common/assets/youtube.svg +1 -0
- package/assets/create/rendererAssets/common/button.css +30 -0
- package/assets/create/rendererAssets/common/header.css +32 -0
- package/assets/create/rendererAssets/common/page.css +68 -0
- package/assets/create/templates/react-native/index.expo.js +20 -0
- package/assets/create/templates/react-native/index.js +25 -0
- package/assets/docs/addon-backgrounds.gif +0 -0
- package/assets/docs/addon-controls-args-annotated.png +0 -0
- package/assets/docs/addon-controls-args-background-color.png +0 -0
- package/assets/docs/addon-controls-args-background-string.png +0 -0
- package/assets/docs/addon-controls-args-docs.png +0 -0
- package/assets/docs/addon-controls-args-logging.png +0 -0
- package/assets/docs/addon-controls-args-no-annotation.png +0 -0
- package/assets/docs/addon-controls-args-reflow-slider.png +0 -0
- package/assets/docs/addon-controls-args-reflow.png +0 -0
- package/assets/docs/addon-controls-args-template.png +0 -0
- package/assets/docs/addon-controls-expanded.png +0 -0
- package/assets/docs/addon-controls-hero.gif +0 -0
- package/assets/docs/addon-controls-install.png +0 -0
- package/assets/docs/hero.gif +0 -0
- package/assets/docs/highlight.png +0 -0
- package/assets/docs/message-reference.png +0 -0
- package/assets/docs/viewport.png +0 -0
- package/assets/react-native/metro-env.d.ts +69 -0
- package/assets/server/addon.tsconfig.json +6 -0
- package/assets/server/base-preview-body.html +116 -0
- package/assets/server/base-preview-head.html +494 -0
- package/assets/server/openBrowser.applescript +94 -0
- package/assets/server/template.ejs +87 -0
- package/dist/.build-stamp +0 -0
- package/dist/_browser-chunks/Color-PCLE2J36.js +1025 -0
- package/dist/_browser-chunks/DocsRenderer-JXND3PVQ.js +10 -0
- package/dist/_browser-chunks/WithTooltip-XSQFTBMB.js +1619 -0
- package/dist/_browser-chunks/chunk-2JDQ35OA.js +247 -0
- package/dist/_browser-chunks/chunk-2JT76G3E.js +104 -0
- package/dist/_browser-chunks/chunk-2MEPHP35.js +17 -0
- package/dist/_browser-chunks/chunk-2SCZVUKW.js +24 -0
- package/dist/_browser-chunks/chunk-46Z7NGAS.js +41 -0
- package/dist/_browser-chunks/chunk-4ATQOMJB.js +30 -0
- package/dist/_browser-chunks/chunk-52CW7FF7.js +49 -0
- package/dist/_browser-chunks/chunk-5I7E4GMO.js +62 -0
- package/dist/_browser-chunks/chunk-5P4QA6MY.js +20 -0
- package/dist/_browser-chunks/chunk-5WMXI5GW.js +816 -0
- package/dist/_browser-chunks/chunk-66OCZ54D.js +2604 -0
- package/dist/_browser-chunks/chunk-6SXYJQST.js +253 -0
- package/dist/_browser-chunks/chunk-AYRF6EJB.js +36 -0
- package/dist/_browser-chunks/chunk-B4AUNNU4.js +156 -0
- package/dist/_browser-chunks/chunk-BCP2FFXF.js +369 -0
- package/dist/_browser-chunks/chunk-C3D4HEFY.js +3396 -0
- package/dist/_browser-chunks/chunk-CFAOWGL7.js +43 -0
- package/dist/_browser-chunks/chunk-CFWWCCZV.js +223 -0
- package/dist/_browser-chunks/chunk-CNED6RYH.js +18 -0
- package/dist/_browser-chunks/chunk-DRQKCCAG.js +136 -0
- package/dist/_browser-chunks/chunk-ECQ75MKQ.js +0 -0
- package/dist/_browser-chunks/chunk-EIYAKDGP.js +302 -0
- package/dist/_browser-chunks/chunk-ENKK74N4.js +12 -0
- package/dist/_browser-chunks/chunk-FENFYI4X.js +698 -0
- package/dist/_browser-chunks/chunk-FJGWSWL5.js +155 -0
- package/dist/_browser-chunks/chunk-FVSEPWWX.js +2810 -0
- package/dist/_browser-chunks/chunk-GNZY6LOW.js +10116 -0
- package/dist/_browser-chunks/chunk-IE4ULIOQ.js +14 -0
- package/dist/_browser-chunks/chunk-IMSF75WX.js +42 -0
- package/dist/_browser-chunks/chunk-IWQGIXJS.js +14 -0
- package/dist/_browser-chunks/chunk-JMC7AIKT.js +108 -0
- package/dist/_browser-chunks/chunk-JPUOQJUI.js +8 -0
- package/dist/_browser-chunks/chunk-JSBKXXXF.js +218 -0
- package/dist/_browser-chunks/chunk-JWSKPMPC.js +73 -0
- package/dist/_browser-chunks/chunk-JYVGARBG.js +2943 -0
- package/dist/_browser-chunks/chunk-KBK7GKBN.js +35 -0
- package/dist/_browser-chunks/chunk-KQ7CRO2B.js +108 -0
- package/dist/_browser-chunks/chunk-LAWXTSXK.js +18 -0
- package/dist/_browser-chunks/chunk-LE2LTDW3.js +14 -0
- package/dist/_browser-chunks/chunk-LEVSOOLQ.js +48 -0
- package/dist/_browser-chunks/chunk-LV5PIKV4.js +93 -0
- package/dist/_browser-chunks/chunk-MDOL7KML.js +56 -0
- package/dist/_browser-chunks/chunk-MFMDGWP6.js +34 -0
- package/dist/_browser-chunks/chunk-MLBOOZL4.js +67 -0
- package/dist/_browser-chunks/chunk-NQUD4B7H.js +91 -0
- package/dist/_browser-chunks/chunk-NULBREPZ.js +43 -0
- package/dist/_browser-chunks/chunk-NZMVUW5T.js +7 -0
- package/dist/_browser-chunks/chunk-OD6IBQCC.js +117 -0
- package/dist/_browser-chunks/chunk-ODVJ3XZJ.js +157 -0
- package/dist/_browser-chunks/chunk-OQWFVDIN.js +386 -0
- package/dist/_browser-chunks/chunk-OVH3USFB.js +20 -0
- package/dist/_browser-chunks/chunk-OZZPSYQR.js +14 -0
- package/dist/_browser-chunks/chunk-P6PK47TQ.js +1970 -0
- package/dist/_browser-chunks/chunk-PFRNG3UC.js +22 -0
- package/dist/_browser-chunks/chunk-PJSMZ27A.js +13 -0
- package/dist/_browser-chunks/chunk-QC4HFS6K.js +41 -0
- package/dist/_browser-chunks/chunk-QDXFODK4.js +274 -0
- package/dist/_browser-chunks/chunk-RBCHTR7C.js +28 -0
- package/dist/_browser-chunks/chunk-RMYQ3QN3.js +294 -0
- package/dist/_browser-chunks/chunk-RNTIV5CE.js +411 -0
- package/dist/_browser-chunks/chunk-RSPAQWVJ.js +80 -0
- package/dist/_browser-chunks/chunk-S6TK43XQ.js +836 -0
- package/dist/_browser-chunks/chunk-SC67XZST.js +18 -0
- package/dist/_browser-chunks/chunk-SFVA4SBT.js +683 -0
- package/dist/_browser-chunks/chunk-SZYFOQMT.js +25 -0
- package/dist/_browser-chunks/chunk-TBJCJXK4.js +18 -0
- package/dist/_browser-chunks/chunk-UMDO3KB7.js +205 -0
- package/dist/_browser-chunks/chunk-V2G23FV7.js +11 -0
- package/dist/_browser-chunks/chunk-V5M7Q4HT.js +88 -0
- package/dist/_browser-chunks/chunk-W5M6NPS3.js +198 -0
- package/dist/_browser-chunks/chunk-W7NE2JXW.js +84 -0
- package/dist/_browser-chunks/chunk-WGVAAM43.js +1110 -0
- package/dist/_browser-chunks/chunk-WHQRNHXD.js +8 -0
- package/dist/_browser-chunks/chunk-WOSOWBCA.js +12 -0
- package/dist/_browser-chunks/chunk-WZ7KR7SH.js +85 -0
- package/dist/_browser-chunks/chunk-YEKO6BHJ.js +20 -0
- package/dist/_browser-chunks/chunk-YI2IBIQS.js +7 -0
- package/dist/_browser-chunks/chunk-YTIT5NA5.js +1966 -0
- package/dist/_browser-chunks/chunk-ZEHRUHTA.js +6 -0
- package/dist/_browser-chunks/chunk-ZTQ5MEJ5.js +203 -0
- package/dist/_browser-chunks/matchers-YLBYSYAF.js +1263 -0
- package/dist/_browser-chunks/react-dom-client-4OHE3QDV.js +9 -0
- package/dist/_node-chunks/attached-runtime-I2G4WLJI.js +282 -0
- package/dist/_node-chunks/builder-manager-VVHYQXCH.js +1661 -0
- package/dist/_node-chunks/chunk-2BUPBPFB.js +82 -0
- package/dist/_node-chunks/chunk-2DSIDINN.js +884 -0
- package/dist/_node-chunks/chunk-2RU7BKEW.js +23 -0
- package/dist/_node-chunks/chunk-34EO2PUT.js +26 -0
- package/dist/_node-chunks/chunk-3BKIBF5S.js +3008 -0
- package/dist/_node-chunks/chunk-3SDUYDXH.js +98 -0
- package/dist/_node-chunks/chunk-3T4ZSNPH.js +662 -0
- package/dist/_node-chunks/chunk-3W7F2B7P.js +79 -0
- package/dist/_node-chunks/chunk-3Y6AAY5H.js +80 -0
- package/dist/_node-chunks/chunk-42UWAGPW.js +1193 -0
- package/dist/_node-chunks/chunk-4FEANXWU.js +1986 -0
- package/dist/_node-chunks/chunk-5DQFEZEA.js +40 -0
- package/dist/_node-chunks/chunk-6KEHV53S.js +1218 -0
- package/dist/_node-chunks/chunk-6LMWA44H.js +3837 -0
- package/dist/_node-chunks/chunk-6YKBRP72.js +20 -0
- package/dist/_node-chunks/chunk-7GUDZVRQ.js +110 -0
- package/dist/_node-chunks/chunk-B5J7Q7J4.js +21 -0
- package/dist/_node-chunks/chunk-BS2WMZMR.js +90 -0
- package/dist/_node-chunks/chunk-BSKFCWQD.js +47 -0
- package/dist/_node-chunks/chunk-BXVYT7SB.js +36 -0
- package/dist/_node-chunks/chunk-CCOTJ4FC.js +688 -0
- package/dist/_node-chunks/chunk-CQKMLL5J.js +33 -0
- package/dist/_node-chunks/chunk-CXRU72ET.js +63 -0
- package/dist/_node-chunks/chunk-DC7DKR5K.js +20 -0
- package/dist/_node-chunks/chunk-DHJFTB4K.js +25 -0
- package/dist/_node-chunks/chunk-DUR55DJJ.js +1094 -0
- package/dist/_node-chunks/chunk-E6YESGLX.js +7032 -0
- package/dist/_node-chunks/chunk-EHUNOMDD.js +52345 -0
- package/dist/_node-chunks/chunk-ET7PWEO7.js +298 -0
- package/dist/_node-chunks/chunk-EWOX7HPJ.js +20 -0
- package/dist/_node-chunks/chunk-F3ZFKBHY.js +844 -0
- package/dist/_node-chunks/chunk-FK7KGTV7.js +26 -0
- package/dist/_node-chunks/chunk-FPZB5OI7.js +35 -0
- package/dist/_node-chunks/chunk-FW2HQUBQ.js +25 -0
- package/dist/_node-chunks/chunk-FY6UXW6G.js +11 -0
- package/dist/_node-chunks/chunk-GEFQN7K3.js +499 -0
- package/dist/_node-chunks/chunk-GRBLDJCR.js +989 -0
- package/dist/_node-chunks/chunk-H32DYT6P.js +87 -0
- package/dist/_node-chunks/chunk-H4PPVMP5.js +23 -0
- package/dist/_node-chunks/chunk-HMP2G23X.js +25 -0
- package/dist/_node-chunks/chunk-ICPBH3E7.js +59 -0
- package/dist/_node-chunks/chunk-IDEL2J23.js +23 -0
- package/dist/_node-chunks/chunk-IK4FGZLR.js +223 -0
- package/dist/_node-chunks/chunk-JDXXX6LB.js +68 -0
- package/dist/_node-chunks/chunk-JGGFYIG5.js +5468 -0
- package/dist/_node-chunks/chunk-JPAZLYJ3.js +29 -0
- package/dist/_node-chunks/chunk-JZOO7GGC.js +48 -0
- package/dist/_node-chunks/chunk-KISMCCP5.js +4744 -0
- package/dist/_node-chunks/chunk-LCK4ZQKG.js +115 -0
- package/dist/_node-chunks/chunk-LO56I6E2.js +132 -0
- package/dist/_node-chunks/chunk-LXBD4UL4.js +104 -0
- package/dist/_node-chunks/chunk-MA24JW6C.js +7162 -0
- package/dist/_node-chunks/chunk-MK2KYAI6.js +5414 -0
- package/dist/_node-chunks/chunk-NDWKJ4CL.js +2102 -0
- package/dist/_node-chunks/chunk-NIBT7XDZ.js +168 -0
- package/dist/_node-chunks/chunk-NPO45IMH.js +33 -0
- package/dist/_node-chunks/chunk-OISX4AGQ.js +414 -0
- package/dist/_node-chunks/chunk-OUW6R7C3.js +26 -0
- package/dist/_node-chunks/chunk-OWNFOGTA.js +194 -0
- package/dist/_node-chunks/chunk-Q7QNUM5H.js +49 -0
- package/dist/_node-chunks/chunk-QEQLR22M.js +1877 -0
- package/dist/_node-chunks/chunk-RRN2V55B.js +45 -0
- package/dist/_node-chunks/chunk-RWFIFGO5.js +144 -0
- package/dist/_node-chunks/chunk-RXNJDRGO.js +209 -0
- package/dist/_node-chunks/chunk-RZMQ7S3L.js +92 -0
- package/dist/_node-chunks/chunk-SIEXZVCR.js +51 -0
- package/dist/_node-chunks/chunk-SLWQNBGO.js +2034 -0
- package/dist/_node-chunks/chunk-SVTBJ7Q3.js +35 -0
- package/dist/_node-chunks/chunk-SWO5CDYU.js +32 -0
- package/dist/_node-chunks/chunk-TD5PLLQG.js +121 -0
- package/dist/_node-chunks/chunk-TEQGGSP7.js +33 -0
- package/dist/_node-chunks/chunk-TFCEV2XX.js +155 -0
- package/dist/_node-chunks/chunk-TFEWM4E3.js +1073 -0
- package/dist/_node-chunks/chunk-TINO74G5.js +21 -0
- package/dist/_node-chunks/chunk-TNJIXE37.js +40 -0
- package/dist/_node-chunks/chunk-TWI4GVH7.js +19 -0
- package/dist/_node-chunks/chunk-U5W53R2J.js +1011 -0
- package/dist/_node-chunks/chunk-UBOGWBEA.js +148 -0
- package/dist/_node-chunks/chunk-UMUBKI6E.js +3787 -0
- package/dist/_node-chunks/chunk-UVINFLGY.js +56 -0
- package/dist/_node-chunks/chunk-V2DY426G.js +1133 -0
- package/dist/_node-chunks/chunk-VLOXOXXE.js +45 -0
- package/dist/_node-chunks/chunk-W4OMYLSP.js +1297 -0
- package/dist/_node-chunks/chunk-WAXVSN3O.js +18 -0
- package/dist/_node-chunks/chunk-WCULS5TP.js +3767 -0
- package/dist/_node-chunks/chunk-WNZ2UMFM.js +153 -0
- package/dist/_node-chunks/chunk-WZJF2OUH.js +74 -0
- package/dist/_node-chunks/chunk-XGIUVY52.js +611 -0
- package/dist/_node-chunks/chunk-XHL2LVBM.js +415 -0
- package/dist/_node-chunks/chunk-XI6B6P47.js +59 -0
- package/dist/_node-chunks/chunk-XLP2ZKXV.js +60 -0
- package/dist/_node-chunks/chunk-YCMRA6RX.js +95 -0
- package/dist/_node-chunks/chunk-YZFWBA5L.js +2700 -0
- package/dist/_node-chunks/chunk-ZMAMXQKP.js +4731 -0
- package/dist/_node-chunks/chunk-ZWBCQ4MJ.js +110 -0
- package/dist/_node-chunks/dist-VTSTZKUB.js +16 -0
- package/dist/_node-chunks/globby-OEMHPBFY.js +2884 -0
- package/dist/_node-chunks/jiti-NFX6UU74.js +3848 -0
- package/dist/_node-chunks/lib-MDVARD7W.js +366 -0
- package/dist/_node-chunks/local-runtime-NTKIN2QT.js +78 -0
- package/dist/_node-chunks/mdx-plugin-MWISDWDQ.js +5680 -0
- package/dist/_node-chunks/monorepo-LIRFMQXE.js +121 -0
- package/dist/_node-chunks/monorepo-optimized-tests-relaxed-limits-no-story-deletion-CPDW4XTW.js +121 -0
- package/dist/_node-chunks/optimized-tests-HDOLR3EY.js +22 -0
- package/dist/_node-chunks/pattern-copy-play-EKHIUAU2.js +807 -0
- package/dist/_node-chunks/postinstallAddon-GYGMWDL6.js +20 -0
- package/dist/_node-chunks/rehype-external-links-BUN3HUCO.js +121 -0
- package/dist/_node-chunks/rehype-slug-PLADX223.js +52 -0
- package/dist/_node-chunks/relaxed-limits-GBHW6KIQ.js +117 -0
- package/dist/_node-chunks/run-KDTIZNSY.js +103 -0
- package/dist/_node-chunks/run-NL66VTNR.js +4532 -0
- package/dist/_node-chunks/session-id-KIIVOQLR.js +23 -0
- package/dist/_node-chunks/setup-MGC7BQO6.js +278 -0
- package/dist/_node-chunks/vitest.config.4.template-JH2THZFG.js +54 -0
- package/dist/actions/decorator.d.ts +7 -0
- package/dist/actions/decorator.js +43 -0
- package/dist/actions/index.d.ts +67 -0
- package/dist/actions/index.js +28 -0
- package/dist/addon-a11y/index.d.ts +52 -0
- package/dist/addon-a11y/index.js +16 -0
- package/dist/addon-a11y/manager.js +1429 -0
- package/dist/addon-a11y/preset.js +18 -0
- package/dist/addon-a11y/preview.d.ts +17 -0
- package/dist/addon-a11y/preview.js +15 -0
- package/dist/addon-docs/blocks.d.ts +1061 -0
- package/dist/addon-docs/blocks.js +2461 -0
- package/dist/addon-docs/index.d.ts +236 -0
- package/dist/addon-docs/index.js +16 -0
- package/dist/addon-docs/manager.js +90 -0
- package/dist/addon-docs/mdx-react-shim.d.ts +1 -0
- package/dist/addon-docs/mdx-react-shim.js +2 -0
- package/dist/addon-docs/preset.js +378 -0
- package/dist/addon-docs/preview.d.ts +4 -0
- package/dist/addon-docs/preview.js +7 -0
- package/dist/addon-links/index.d.ts +22 -0
- package/dist/addon-links/index.js +20 -0
- package/dist/addon-links/manager.js +14 -0
- package/dist/addon-links/preview.d.ts +6 -0
- package/dist/addon-links/preview.js +9 -0
- package/dist/addon-links/react/index.d.ts +25 -0
- package/dist/addon-links/react/index.js +52 -0
- package/dist/addon-mcp/preset.js +1345 -0
- package/dist/addon-mcp/preview.js +29 -0
- package/dist/addon-mcp/tools/preview-stories/preview-stories-app-script.js +1153 -0
- package/dist/addon-ondevice-actions/index.d.ts +4 -0
- package/dist/addon-ondevice-actions/index.js +7 -0
- package/dist/addon-ondevice-actions/register.js +7 -0
- package/dist/addon-ondevice-controls/index.d.ts +6 -0
- package/dist/addon-ondevice-controls/index.js +14 -0
- package/dist/addon-ondevice-controls/register.js +10 -0
- package/dist/addon-pseudo-states/index.d.ts +35 -0
- package/dist/addon-pseudo-states/index.js +15 -0
- package/dist/addon-pseudo-states/manager.js +53 -0
- package/dist/addon-pseudo-states/preview.d.ts +9 -0
- package/dist/addon-pseudo-states/preview.js +10 -0
- package/dist/addon-themes/index.d.ts +80 -0
- package/dist/addon-themes/index.js +104 -0
- package/dist/addon-themes/manager.js +97 -0
- package/dist/addon-themes/postinstall.js +33 -0
- package/dist/addon-themes/preview.d.ts +6 -0
- package/dist/addon-themes/preview.js +8 -0
- package/dist/addon-vitest/constants.d.ts +132 -0
- package/dist/addon-vitest/constants.js +66 -0
- package/dist/addon-vitest/index.d.ts +4 -0
- package/dist/addon-vitest/index.js +8 -0
- package/dist/addon-vitest/manager.js +665 -0
- package/dist/addon-vitest/node/coverage-reporter.js +1284 -0
- package/dist/addon-vitest/node/vitest.js +674 -0
- package/dist/addon-vitest/postinstall.js +24 -0
- package/dist/addon-vitest/preset.js +943 -0
- package/dist/addon-vitest/vitest-plugin/global-setup.js +162 -0
- package/dist/addon-vitest/vitest-plugin/index.d.ts +56 -0
- package/dist/addon-vitest/vitest-plugin/index.js +463 -0
- package/dist/addon-vitest/vitest-plugin/setup-file-with-project-annotations.js +9 -0
- package/dist/addon-vitest/vitest-plugin/setup-file.browser.4.js +20 -0
- package/dist/addon-vitest/vitest-plugin/setup-file.js +50 -0
- package/dist/addon-vitest/vitest-plugin/test-utils.js +128 -0
- package/dist/babel/index.d.ts +2301 -0
- package/dist/babel/index.js +61 -0
- package/dist/backgrounds/index.d.ts +6 -0
- package/dist/backgrounds/index.js +15 -0
- package/dist/bin/core.js +1705 -0
- package/dist/bin/dispatcher.js +43 -0
- package/dist/bin/loader.js +115 -0
- package/dist/builder-vite/index.d.ts +50 -0
- package/dist/builder-vite/index.js +245 -0
- package/dist/builder-vite/preset.js +27 -0
- package/dist/channels/index.d.ts +2 -0
- package/dist/channels/index.js +352 -0
- package/dist/chunk-B2t1tGRh.d.ts +284 -0
- package/dist/chunk-BCoJQE-f.d.ts +602 -0
- package/dist/chunk-BGkgZp-H.d.ts +214 -0
- package/dist/chunk-BTVrWliM.d.ts +115 -0
- package/dist/chunk-BXLImeLy.d.ts +197 -0
- package/dist/chunk-BfBzpgct.d.ts +916 -0
- package/dist/chunk-Bpi2tz-6.d.ts +511 -0
- package/dist/chunk-BxWufQms.d.ts +142 -0
- package/dist/chunk-C-bavsOH.d.ts +34 -0
- package/dist/chunk-C2p5r3Vt.d.ts +13001 -0
- package/dist/chunk-C6PAgHLT.d.ts +12 -0
- package/dist/chunk-CMeX4WRe.d.ts +179 -0
- package/dist/chunk-Ct9xd6IM.d.ts +65 -0
- package/dist/chunk-CwQKNaYL.d.ts +15 -0
- package/dist/chunk-D7DwV41f.d.ts +3622 -0
- package/dist/chunk-DEVFdzxR.d.ts +253 -0
- package/dist/chunk-DaVb55-_.d.ts +124 -0
- package/dist/chunk-DgRNfmj_.d.ts +40 -0
- package/dist/chunk-DoIGKY33.d.ts +666 -0
- package/dist/chunk-Dr3R0DXk.d.ts +40 -0
- package/dist/chunk-DtHByo32.d.ts +55 -0
- package/dist/chunk-Dw1z1Pki.d.ts +933 -0
- package/dist/chunk-DzuY9DXg.d.ts +79 -0
- package/dist/chunk-FjDVzZTj.d.ts +62 -0
- package/dist/chunk-Kt1tW-D1.d.ts +59 -0
- package/dist/chunk-XP8oHzpE.d.ts +6 -0
- package/dist/chunk-gCX81nOr.d.ts +61 -0
- package/dist/chunk-pcFXCimE.d.ts +741 -0
- package/dist/cli/index.d.ts +1174 -0
- package/dist/cli/index.js +5613 -0
- package/dist/cli/skills/content/index.d.ts +72 -0
- package/dist/cli/skills/content/index.js +335 -0
- package/dist/cli/tools/sdk/child-host.d.ts +61 -0
- package/dist/cli/tools/sdk/child-host.js +131 -0
- package/dist/cli/tools/sdk/index.d.ts +137 -0
- package/dist/cli/tools/sdk/index.js +44 -0
- package/dist/cli-commands/bin/index.js +25 -0
- package/dist/client-logger/index.d.ts +31 -0
- package/dist/client-logger/index.js +63 -0
- package/dist/common/index.d.ts +1103 -0
- package/dist/common/index.js +364 -0
- package/dist/components/index.d.ts +1924 -0
- package/dist/components/index.js +13759 -0
- package/dist/core-events/index.d.ts +155 -0
- package/dist/core-events/index.js +136 -0
- package/dist/core-server/index.d.ts +2200 -0
- package/dist/core-server/index.js +4165 -0
- package/dist/core-server/presets/common-manager.css +170 -0
- package/dist/core-server/presets/common-manager.js +4168 -0
- package/dist/core-server/presets/common-override-preset.js +81 -0
- package/dist/core-server/presets/common-preset.js +2187 -0
- package/dist/create/bin/index.js +23 -0
- package/dist/create/index.js +25 -0
- package/dist/csf/csf-utils.d.ts +2 -0
- package/dist/csf/csf-utils.js +19 -0
- package/dist/csf/index.d.ts +7 -0
- package/dist/csf/index.js +2142 -0
- package/dist/csf-tools/index.d.ts +1157 -0
- package/dist/csf-tools/index.js +736 -0
- package/dist/docs-tools/index.d.ts +197 -0
- package/dist/docs-tools/index.js +61 -0
- package/dist/highlight/index.d.ts +10 -0
- package/dist/highlight/index.js +15 -0
- package/dist/instrumenter/index.d.ts +113 -0
- package/dist/instrumenter/index.js +2595 -0
- package/dist/manager/_manager-chunks/globals-runtime-8HkdnU2F.js +6144 -0
- package/dist/manager/_manager-chunks/globals-runtime-BIKBO23x.js +3389 -0
- package/dist/manager/_manager-chunks/globals-runtime-BdGkqNEt.js +45 -0
- package/dist/manager/_manager-chunks/globals-runtime-BmphMP6z.js +1190 -0
- package/dist/manager/_manager-chunks/globals-runtime-BvM-NY2g.js +13307 -0
- package/dist/manager/_manager-chunks/globals-runtime-C02fvgcd.js +9243 -0
- package/dist/manager/_manager-chunks/globals-runtime-DkAPXCvc.js +9766 -0
- package/dist/manager/_manager-chunks/globals-runtime-DpNYC-qi.js +5564 -0
- package/dist/manager/_manager-chunks/globals-runtime-DtcOnYHR.js +5357 -0
- package/dist/manager/_manager-chunks/globals-runtime-DwFqpqya.js +722 -0
- package/dist/manager/_manager-chunks/globals-runtime-FC201B7s.js +6837 -0
- package/dist/manager/_manager-chunks/globals-runtime-Rp_zjGnh.js +121 -0
- package/dist/manager/_manager-chunks/globals-runtime-ZHrQFa3-.js +10463 -0
- package/dist/manager/_manager-chunks/globals-runtime-eLPTIfX-.js +7759 -0
- package/dist/manager/_manager-chunks/globals-runtime-qKvCZHFY.js +4468 -0
- package/dist/manager/_manager-chunks/globals-runtime-sGpNlIJ1.js +5102 -0
- package/dist/manager/_manager-chunks/runtime-B1L9TH89.js +452 -0
- package/dist/manager/_manager-chunks/runtime-BK0zF7FN.js +22 -0
- package/dist/manager/_manager-chunks/runtime-BhkT2CXl.js +1408 -0
- package/dist/manager/_manager-chunks/runtime-Bly6WJLN.js +998 -0
- package/dist/manager/_manager-chunks/runtime-D0UOWx0U.js +979 -0
- package/dist/manager/_manager-chunks/runtime-D94Vq3oR.js +4014 -0
- package/dist/manager/_manager-chunks/runtime-DMig8mT7.js +77 -0
- package/dist/manager/_manager-chunks/runtime-EITvVbkG.js +46 -0
- package/dist/manager/_manager-chunks/runtime-cp5pazY2.js +8936 -0
- package/dist/manager/_manager-chunks/runtime-eDVAYF7N.js +6860 -0
- package/dist/manager/_manager-chunks/runtime-i-PDAHLR.js +2493 -0
- package/dist/manager/globals-runtime.js +2 -0
- package/dist/manager/globals.d.ts +23 -0
- package/dist/manager/globals.js +26 -0
- package/dist/manager/manager-stores.d.ts +2 -0
- package/dist/manager/manager-stores.js +19 -0
- package/dist/manager/runtime.js +2 -0
- package/dist/manager-api/index.d.ts +2 -0
- package/dist/manager-api/index.js +3257 -0
- package/dist/manager-errors.d.ts +89 -0
- package/dist/manager-errors.js +20 -0
- package/dist/mcp/index.d.ts +270 -0
- package/dist/mcp/index.js +305 -0
- package/dist/mocking-utils/index.d.ts +689 -0
- package/dist/mocking-utils/index.js +521 -0
- package/dist/mocking-utils/mocker-runtime.js +1254 -0
- package/dist/nextjs-vite/config/preview.js +3 -0
- package/dist/nextjs-vite/export-mocks/cache/index.d.ts +25 -0
- package/dist/nextjs-vite/export-mocks/cache/index.js +29 -0
- package/dist/nextjs-vite/export-mocks/headers/index.d.ts +37 -0
- package/dist/nextjs-vite/export-mocks/headers/index.js +54 -0
- package/dist/nextjs-vite/export-mocks/link/index.d.ts +9 -0
- package/dist/nextjs-vite/export-mocks/link/index.js +59 -0
- package/dist/nextjs-vite/export-mocks/navigation/index.d.ts +28 -0
- package/dist/nextjs-vite/export-mocks/navigation/index.js +58 -0
- package/dist/nextjs-vite/export-mocks/router/index.d.ts +34 -0
- package/dist/nextjs-vite/export-mocks/router/index.js +63 -0
- package/dist/nextjs-vite/index.d.ts +92 -0
- package/dist/nextjs-vite/index.js +58 -0
- package/dist/nextjs-vite/node/index.d.ts +6 -0
- package/dist/nextjs-vite/node/index.js +20 -0
- package/dist/nextjs-vite/preset.js +648 -0
- package/dist/nextjs-vite/preview.d.ts +20 -0
- package/dist/nextjs-vite/preview.js +11 -0
- package/dist/nextjs-vite/vite-plugin/index.d.ts +6 -0
- package/dist/nextjs-vite/vite-plugin/index.js +19 -0
- package/dist/nextjs-vite-plugin/index.d.ts +28 -0
- package/dist/nextjs-vite-plugin/index.js +923 -0
- package/dist/nextjs-vite-plugin/mocks/storybook.global.js +29 -0
- package/dist/nextjs-vite-plugin/plugins/next-image/alias/image-context.js +8 -0
- package/dist/nextjs-vite-plugin/plugins/next-image/alias/image-default-loader.js +23 -0
- package/dist/nextjs-vite-plugin/plugins/next-image/alias/next-image.js +28 -0
- package/dist/nextjs-vite-plugin/plugins/next-image/alias/next-legacy-image.js +17 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/cache/index.js +29 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/dynamic/index.js +40 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/headers/index.js +55 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/link/index.js +63 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/navigation/index.js +71 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/router/index.js +74 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/rsc/server-only.js +7 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/alias/safe-stable-stringify/index.js +13 -0
- package/dist/nextjs-vite-plugin/plugins/next-mocks/compatibility/draft-mode.compat.js +7 -0
- package/dist/node-logger/index.d.ts +495 -0
- package/dist/node-logger/index.js +42 -0
- package/dist/oxc-parser/index.d.ts +99 -0
- package/dist/oxc-parser/index.js +275 -0
- package/dist/oxc-parser/worker.js +37 -0
- package/dist/preview/_chunks/runtime-BIzIdDbb.js +10263 -0
- package/dist/preview/_chunks/runtime-BTPtVY1p.js +1102 -0
- package/dist/preview/_chunks/runtime-BZSnICCg.js +8484 -0
- package/dist/preview/_chunks/runtime-BdGkqNEt.js +45 -0
- package/dist/preview/_chunks/runtime-BwSxqk3t.js +63 -0
- package/dist/preview/_chunks/runtime-CqHxptiK.js +6143 -0
- package/dist/preview/_chunks/runtime-CsV4HZPV.js +5064 -0
- package/dist/preview/_chunks/runtime-CxZK_Ln3.js +284 -0
- package/dist/preview/_chunks/runtime-DZToXfvO.js +9767 -0
- package/dist/preview/_chunks/runtime-DrTjJkPY.js +3872 -0
- package/dist/preview/_chunks/runtime-DuHdxMFA.js +387 -0
- package/dist/preview/_chunks/runtime-iXdo8CR8.js +3635 -0
- package/dist/preview/_chunks/runtime-q3wt0e_N.js +6838 -0
- package/dist/preview/globals.d.ts +16 -0
- package/dist/preview/globals.js +20 -0
- package/dist/preview/runtime.js +6 -0
- package/dist/preview-api/index.d.ts +4 -0
- package/dist/preview-api/index.js +147 -0
- package/dist/preview-errors.d.ts +186 -0
- package/dist/preview-errors.js +56 -0
- package/dist/react/docgen/docgen-worker.js +38 -0
- package/dist/react/entry-preview-argtypes.js +9 -0
- package/dist/react/entry-preview-docs.js +12 -0
- package/dist/react/entry-preview-rsc.js +11 -0
- package/dist/react/entry-preview.js +21 -0
- package/dist/react/index.d.ts +119 -0
- package/dist/react/index.js +26 -0
- package/dist/react/preset.js +301 -0
- package/dist/react/preview.d.ts +2 -0
- package/dist/react/preview.js +11 -0
- package/dist/react-native/bin/get-stories.js +34 -0
- package/dist/react-native/index.d.ts +160 -0
- package/dist/react-native/index.js +1547 -0
- package/dist/react-native/metro/node.d.ts +83 -0
- package/dist/react-native/metro/node.js +21 -0
- package/dist/react-native/metro/withStorybook.d.ts +2269 -0
- package/dist/react-native/metro/withStorybook.js +138 -0
- package/dist/react-native/preview.d.ts +6 -0
- package/dist/react-native/preview.js +20 -0
- package/dist/react-native/scripts/generate.d.ts +22 -0
- package/dist/react-native/scripts/generate.js +18 -0
- package/dist/react-native/stub.js +20 -0
- package/dist/react-native-theming/index.d.ts +175 -0
- package/dist/react-native-theming/index.js +2591 -0
- package/dist/react-native-ui/index.d.ts +130 -0
- package/dist/react-native-ui/index.js +1448 -0
- package/dist/react-native-ui-common/index.d.ts +230 -0
- package/dist/react-native-ui-common/index.js +514 -0
- package/dist/react-vite/index.d.ts +4 -0
- package/dist/react-vite/index.js +8 -0
- package/dist/react-vite/node/index.d.ts +6 -0
- package/dist/react-vite/node/index.js +20 -0
- package/dist/react-vite/preset.d.ts +6 -0
- package/dist/react-vite/preset.js +21 -0
- package/dist/router/index.d.ts +141 -0
- package/dist/router/index.js +1217 -0
- package/dist/server-errors.d.ts +2 -0
- package/dist/server-errors.js +149 -0
- package/dist/shared/open-service/index.d.ts +3 -0
- package/dist/shared/open-service/index.js +115 -0
- package/dist/shared/open-service/services/docgen/worker/docgen-worker.js +75 -0
- package/dist/shared/open-service/toolsets/docs/public.d.ts +1296 -0
- package/dist/shared/open-service/toolsets/docs/public.js +991 -0
- package/dist/telemetry/index.d.ts +314 -0
- package/dist/telemetry/index.js +103 -0
- package/dist/test/index.d.ts +158 -0
- package/dist/test/index.js +19421 -0
- package/dist/theming/create.d.ts +15 -0
- package/dist/theming/create.js +11 -0
- package/dist/theming/index.d.ts +54 -0
- package/dist/theming/index.js +511 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +48 -0
- package/dist/viewport/index.d.ts +275 -0
- package/dist/viewport/index.js +21 -0
- package/package.json +1054 -4
- package/template/nextjs-vite/cli/.eslintrc.json +6 -0
- package/template/nextjs-vite/cli/js/Button.jsx +42 -0
- package/template/nextjs-vite/cli/js/Button.stories.js +49 -0
- package/template/nextjs-vite/cli/js/Configure.mdx +470 -0
- package/template/nextjs-vite/cli/js/Header.jsx +58 -0
- package/template/nextjs-vite/cli/js/Header.stories.js +30 -0
- package/template/nextjs-vite/cli/js/Page.jsx +68 -0
- package/template/nextjs-vite/cli/js/Page.stories.js +28 -0
- package/template/nextjs-vite/cli/ts/Button.stories.ts +54 -0
- package/template/nextjs-vite/cli/ts/Button.tsx +39 -0
- package/template/nextjs-vite/cli/ts/Configure.mdx +446 -0
- package/template/nextjs-vite/cli/ts/Header.stories.ts +34 -0
- package/template/nextjs-vite/cli/ts/Header.tsx +54 -0
- package/template/nextjs-vite/cli/ts/Page.stories.ts +33 -0
- package/template/nextjs-vite/cli/ts/Page.tsx +73 -0
- package/template/react/cli/.eslintrc.json +5 -0
- package/template/react/cli/js/Button.jsx +39 -0
- package/template/react/cli/js/Button.stories.js +49 -0
- package/template/react/cli/js/Header.jsx +56 -0
- package/template/react/cli/js/Header.stories.js +29 -0
- package/template/react/cli/js/Page.jsx +69 -0
- package/template/react/cli/js/Page.stories.js +28 -0
- package/template/react/cli/ts/Button.stories.ts +54 -0
- package/template/react/cli/ts/Button.tsx +37 -0
- package/template/react/cli/ts/Header.stories.ts +34 -0
- package/template/react/cli/ts/Header.tsx +56 -0
- package/template/react/cli/ts/Page.stories.ts +33 -0
- package/template/react/cli/ts/Page.tsx +73 -0
- package/template/react-vite/cli/.eslintrc.json +5 -0
- package/template/react-vite/cli/js/Button.jsx +39 -0
- package/template/react-vite/cli/js/Button.stories.js +49 -0
- package/template/react-vite/cli/js/Header.jsx +56 -0
- package/template/react-vite/cli/js/Header.stories.js +29 -0
- package/template/react-vite/cli/js/Page.jsx +69 -0
- package/template/react-vite/cli/js/Page.stories.js +28 -0
- package/template/react-vite/cli/ts/Button.stories.ts +54 -0
- package/template/react-vite/cli/ts/Button.tsx +37 -0
- package/template/react-vite/cli/ts/Header.stories.ts +34 -0
- package/template/react-vite/cli/ts/Header.tsx +56 -0
- package/template/react-vite/cli/ts/Page.stories.ts +33 -0
- package/template/react-vite/cli/ts/Page.tsx +73 -0
|
@@ -0,0 +1,3635 @@
|
|
|
1
|
+
import { n as __esmMin } from "./runtime-BdGkqNEt.js";
|
|
2
|
+
import { A as variant, C as pipe, D as string, E as safeParse, J as isEqual, M as init_index_browser, N as nanoid, O as undefined_, S as optional, T as safeInteger, _ as looseObject, b as number, c as slash, d as l, f as array, g as literal, h as init_dist, j as void_, k as unknown, l as g, lt as dedent, m as custom, p as check, q as init_isEqual, u as init_deepsignal_core_module, ut as init_esm, v as minLength, w as record, x as object, y as minValue } from "./runtime-BZSnICCg.js";
|
|
3
|
+
import { n as init_storybook_error, t as StorybookError } from "./runtime-BwSxqk3t.js";
|
|
4
|
+
import { At as init_manager_errors, Dt as OpenServiceStaticSnapshotInvalidError, F as init_channel_slot, Ot as OpenServiceStaticSnapshotLoadError, P as getChannel, Tt as logger, f as init_storyDocsCodePanel, kt as UniversalStoreFollowerTimeoutError, p as shouldSkipStoryDocsEmit, r as definePreviewAddon, wt as init_client_logger } from "./runtime-DrTjJkPY.js";
|
|
5
|
+
import { i as j, n as g$1, o as n, r as init_signals_core_module, s as y, t as f } from "./runtime-BTPtVY1p.js";
|
|
6
|
+
import { i as emitTransformCode, r as init_preview_api } from "./runtime-CsV4HZPV.js";
|
|
7
|
+
//#region src/shared/universal-store/first-party-store-ids.ts
|
|
8
|
+
var FIRST_PARTY_STORE_IDS, isFirstPartyStoreId;
|
|
9
|
+
function init_first_party_store_ids() {
|
|
10
|
+
return (init_first_party_store_ids = __esmMin((() => {
|
|
11
|
+
FIRST_PARTY_STORE_IDS = /* @__PURE__ */ new Set([
|
|
12
|
+
"@aussieljk/forkbook/status",
|
|
13
|
+
"@aussieljk/forkbook/test-provider",
|
|
14
|
+
"@aussieljk/forkbook/test"
|
|
15
|
+
]);
|
|
16
|
+
isFirstPartyStoreId = (id) => FIRST_PARTY_STORE_IDS.has(id);
|
|
17
|
+
})))();
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/shared/universal-store/instances.ts
|
|
21
|
+
var instances;
|
|
22
|
+
function init_instances() {
|
|
23
|
+
return (init_instances = __esmMin((() => {
|
|
24
|
+
instances = /* @__PURE__ */ new Map();
|
|
25
|
+
})))();
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/shared/universal-store/index.ts
|
|
29
|
+
var CHANNEL_EVENT_PREFIX, ProgressState, UniversalStore;
|
|
30
|
+
function init_universal_store() {
|
|
31
|
+
return (init_universal_store = __esmMin((() => {
|
|
32
|
+
init_esm();
|
|
33
|
+
init_manager_errors();
|
|
34
|
+
init_first_party_store_ids();
|
|
35
|
+
init_instances();
|
|
36
|
+
CHANNEL_EVENT_PREFIX = "UNIVERSAL_STORE:";
|
|
37
|
+
ProgressState = {
|
|
38
|
+
PENDING: "PENDING",
|
|
39
|
+
RESOLVED: "RESOLVED",
|
|
40
|
+
REJECTED: "REJECTED"
|
|
41
|
+
};
|
|
42
|
+
UniversalStore = class UniversalStore {
|
|
43
|
+
static {
|
|
44
|
+
this.ActorType = {
|
|
45
|
+
LEADER: "LEADER",
|
|
46
|
+
FOLLOWER: "FOLLOWER"
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
static {
|
|
50
|
+
this.Environment = {
|
|
51
|
+
SERVER: "SERVER",
|
|
52
|
+
MANAGER: "MANAGER",
|
|
53
|
+
PREVIEW: "PREVIEW",
|
|
54
|
+
UNKNOWN: "UNKNOWN",
|
|
55
|
+
MOCK: "MOCK"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** The environment this realm was prepared as, or `undefined` before `__prepare`. */
|
|
59
|
+
static get preparedEnvironment() {
|
|
60
|
+
return UniversalStore.preparation.environment;
|
|
61
|
+
}
|
|
62
|
+
static {
|
|
63
|
+
this.InternalEventType = {
|
|
64
|
+
EXISTING_STATE_REQUEST: "__EXISTING_STATE_REQUEST",
|
|
65
|
+
EXISTING_STATE_RESPONSE: "__EXISTING_STATE_RESPONSE",
|
|
66
|
+
SET_STATE: "__SET_STATE",
|
|
67
|
+
LEADER_CREATED: "__LEADER_CREATED",
|
|
68
|
+
FOLLOWER_CREATED: "__FOLLOWER_CREATED"
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
static {
|
|
72
|
+
this.Status = {
|
|
73
|
+
UNPREPARED: "UNPREPARED",
|
|
74
|
+
SYNCING: "SYNCING",
|
|
75
|
+
READY: "READY",
|
|
76
|
+
ERROR: "ERROR"
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
static {
|
|
80
|
+
this.isInternalConstructing = false;
|
|
81
|
+
}
|
|
82
|
+
static {
|
|
83
|
+
UniversalStore.setupPreparationPromise();
|
|
84
|
+
}
|
|
85
|
+
static setupPreparationPromise() {
|
|
86
|
+
let resolveRef;
|
|
87
|
+
let rejectRef;
|
|
88
|
+
const promise = new Promise((resolve, reject) => {
|
|
89
|
+
resolveRef = (args) => {
|
|
90
|
+
resolve(args);
|
|
91
|
+
};
|
|
92
|
+
rejectRef = (...args) => {
|
|
93
|
+
reject(args);
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
UniversalStore.preparation = {
|
|
97
|
+
resolve: resolveRef,
|
|
98
|
+
reject: rejectRef,
|
|
99
|
+
promise
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** The actor object representing the store instance with a unique ID and a type */
|
|
103
|
+
get actor() {
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
id: this.actorId,
|
|
106
|
+
type: this.actorType,
|
|
107
|
+
environment: this.environment ?? UniversalStore.Environment.UNKNOWN
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The current state of the store, that signals both if the store is prepared by Forkbook and
|
|
112
|
+
* also - in the case of a follower - if the state has been synced with the leader's state.
|
|
113
|
+
*/
|
|
114
|
+
get status() {
|
|
115
|
+
if (!this.channel || !this.environment) return UniversalStore.Status.UNPREPARED;
|
|
116
|
+
switch (this.syncing?.state) {
|
|
117
|
+
case ProgressState.PENDING:
|
|
118
|
+
case void 0: return UniversalStore.Status.SYNCING;
|
|
119
|
+
case ProgressState.REJECTED: return UniversalStore.Status.ERROR;
|
|
120
|
+
case ProgressState.RESOLVED:
|
|
121
|
+
default: return UniversalStore.Status.READY;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A promise that resolves when the store is fully ready. A leader will be ready when the store
|
|
126
|
+
* has been prepared by Forkbook, which is almost instantly.
|
|
127
|
+
*
|
|
128
|
+
* A follower will be ready when the state has been synced with the leader's state, within a few
|
|
129
|
+
* hundred milliseconds.
|
|
130
|
+
*/
|
|
131
|
+
untilReady() {
|
|
132
|
+
const preparation = this.channel && this.environment ? Promise.resolve() : UniversalStore.preparation.promise;
|
|
133
|
+
return Promise.all([preparation, this.syncing?.promise]);
|
|
134
|
+
}
|
|
135
|
+
constructor(options, environmentOverrides) {
|
|
136
|
+
this.debugging = false;
|
|
137
|
+
this.listeners = /* @__PURE__ */ new Map([["*", /* @__PURE__ */ new Set()]]);
|
|
138
|
+
this.getState = () => {
|
|
139
|
+
this.debug("getState", { state: this.state });
|
|
140
|
+
return this.state;
|
|
141
|
+
};
|
|
142
|
+
this.subscribe = (eventTypeOrListener, maybeListener) => {
|
|
143
|
+
const subscribesToAllEvents = typeof eventTypeOrListener === "function";
|
|
144
|
+
const eventType = subscribesToAllEvents ? "*" : eventTypeOrListener;
|
|
145
|
+
const listener = subscribesToAllEvents ? eventTypeOrListener : maybeListener;
|
|
146
|
+
this.debug("subscribe", {
|
|
147
|
+
eventType,
|
|
148
|
+
listener
|
|
149
|
+
});
|
|
150
|
+
if (!listener) throw new TypeError(`Missing first subscribe argument, or second if first is the event type, when subscribing to a UniversalStore with id '${this.id}'`);
|
|
151
|
+
if (!this.listeners.has(eventType)) this.listeners.set(eventType, /* @__PURE__ */ new Set());
|
|
152
|
+
this.listeners.get(eventType).add(listener);
|
|
153
|
+
return () => {
|
|
154
|
+
this.debug("unsubscribe", {
|
|
155
|
+
eventType,
|
|
156
|
+
listener
|
|
157
|
+
});
|
|
158
|
+
if (!this.listeners.has(eventType)) return;
|
|
159
|
+
this.listeners.get(eventType).delete(listener);
|
|
160
|
+
if (this.listeners.get(eventType)?.size === 0) this.listeners.delete(eventType);
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
this.send = (event) => {
|
|
164
|
+
this.debug("send", { event });
|
|
165
|
+
if (this.status !== UniversalStore.Status.READY) throw new TypeError(dedent`Cannot send event before store is ready. You can get the current status with store.status,
|
|
166
|
+
or await store.readyPromise to wait for the store to be ready before sending events.
|
|
167
|
+
${JSON.stringify({
|
|
168
|
+
event,
|
|
169
|
+
id: this.id,
|
|
170
|
+
actor: this.actor,
|
|
171
|
+
environment: this.environment
|
|
172
|
+
}, null, 2)}`);
|
|
173
|
+
this.emitToListeners(event, { actor: this.actor });
|
|
174
|
+
this.emitToChannel(event, { actor: this.actor });
|
|
175
|
+
};
|
|
176
|
+
this.debugging = options.debug ?? false;
|
|
177
|
+
if (!UniversalStore.isInternalConstructing) throw new TypeError("UniversalStore is not constructable - use UniversalStore.create() instead");
|
|
178
|
+
UniversalStore.isInternalConstructing = false;
|
|
179
|
+
this.id = options.id;
|
|
180
|
+
this.actorId = Date.now().toString(36) + Math.random().toString(36).substring(2);
|
|
181
|
+
this.actorType = options.leader ? UniversalStore.ActorType.LEADER : UniversalStore.ActorType.FOLLOWER;
|
|
182
|
+
this.state = options.initialState;
|
|
183
|
+
this.channelEventName = `${CHANNEL_EVENT_PREFIX}${this.id}`;
|
|
184
|
+
this.debug("constructor", {
|
|
185
|
+
options,
|
|
186
|
+
environmentOverrides,
|
|
187
|
+
channelEventName: this.channelEventName
|
|
188
|
+
});
|
|
189
|
+
if (this.actor.type === UniversalStore.ActorType.LEADER) this.syncing = {
|
|
190
|
+
state: ProgressState.RESOLVED,
|
|
191
|
+
promise: Promise.resolve()
|
|
192
|
+
};
|
|
193
|
+
else {
|
|
194
|
+
let syncingResolve;
|
|
195
|
+
let syncingReject;
|
|
196
|
+
const syncingPromise = new Promise((resolve, reject) => {
|
|
197
|
+
syncingResolve = () => {
|
|
198
|
+
if (this.syncing.state !== ProgressState.PENDING) return;
|
|
199
|
+
this.syncing.state = ProgressState.RESOLVED;
|
|
200
|
+
resolve();
|
|
201
|
+
};
|
|
202
|
+
syncingReject = (reason) => {
|
|
203
|
+
if (this.syncing.state !== ProgressState.PENDING) return;
|
|
204
|
+
this.syncing.state = ProgressState.REJECTED;
|
|
205
|
+
reject(reason);
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
this.syncing = {
|
|
209
|
+
state: ProgressState.PENDING,
|
|
210
|
+
promise: syncingPromise,
|
|
211
|
+
resolve: syncingResolve,
|
|
212
|
+
reject: syncingReject
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
this.getState = this.getState.bind(this);
|
|
216
|
+
this.setState = this.setState.bind(this);
|
|
217
|
+
this.subscribe = this.subscribe.bind(this);
|
|
218
|
+
this.onStateChange = this.onStateChange.bind(this);
|
|
219
|
+
this.send = this.send.bind(this);
|
|
220
|
+
this.emitToChannel = this.emitToChannel.bind(this);
|
|
221
|
+
this.prepareThis = this.prepareThis.bind(this);
|
|
222
|
+
this.emitToListeners = this.emitToListeners.bind(this);
|
|
223
|
+
this.handleChannelEvents = this.handleChannelEvents.bind(this);
|
|
224
|
+
this.debug = this.debug.bind(this);
|
|
225
|
+
this.channel = environmentOverrides?.channel ?? UniversalStore.preparation.channel;
|
|
226
|
+
this.environment = environmentOverrides?.environment ?? UniversalStore.preparation.environment;
|
|
227
|
+
if (this.channel && this.environment) {
|
|
228
|
+
if (!environmentOverrides) UniversalStore.preparation.resolve({
|
|
229
|
+
channel: this.channel,
|
|
230
|
+
environment: this.environment
|
|
231
|
+
});
|
|
232
|
+
this.prepareThis({
|
|
233
|
+
channel: this.channel,
|
|
234
|
+
environment: this.environment
|
|
235
|
+
});
|
|
236
|
+
} else UniversalStore.preparation.promise.then(this.prepareThis);
|
|
237
|
+
}
|
|
238
|
+
/** Creates a new instance of UniversalStore */
|
|
239
|
+
static create(options) {
|
|
240
|
+
if (!options || typeof options?.id !== "string") throw new TypeError("id is required and must be a string, when creating a UniversalStore");
|
|
241
|
+
if (!isFirstPartyStoreId(options.id)) throw new TypeError(`UniversalStore is internal to Forkbook and cannot create a store with id "${options.id}"`);
|
|
242
|
+
if (options.debug) console.debug(dedent`[UniversalStore]
|
|
243
|
+
create`, { options });
|
|
244
|
+
const existing = instances.get(options.id);
|
|
245
|
+
if (existing) {
|
|
246
|
+
console.warn(dedent`UniversalStore with id "${options.id}" already exists in this environment, re-using existing.
|
|
247
|
+
You should reuse the existing instance instead of trying to create a new one.`);
|
|
248
|
+
return existing;
|
|
249
|
+
}
|
|
250
|
+
UniversalStore.isInternalConstructing = true;
|
|
251
|
+
const store = new UniversalStore(options);
|
|
252
|
+
instances.set(options.id, store);
|
|
253
|
+
return store;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Used by Forkbook to set the channel for all instances of UniversalStore in the given
|
|
257
|
+
* environment.
|
|
258
|
+
*
|
|
259
|
+
* @internal
|
|
260
|
+
*/
|
|
261
|
+
static __prepare(channel, environment) {
|
|
262
|
+
UniversalStore.preparation.channel = channel;
|
|
263
|
+
UniversalStore.preparation.environment = environment;
|
|
264
|
+
UniversalStore.preparation.resolve({
|
|
265
|
+
channel,
|
|
266
|
+
environment
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Updates the store's state
|
|
271
|
+
*
|
|
272
|
+
* Either a new state or a state updater function can be passed to the method.
|
|
273
|
+
*/
|
|
274
|
+
setState(updater) {
|
|
275
|
+
const previousState = this.state;
|
|
276
|
+
const newState = typeof updater === "function" ? updater(previousState) : updater;
|
|
277
|
+
this.debug("setState", {
|
|
278
|
+
newState,
|
|
279
|
+
previousState,
|
|
280
|
+
updater
|
|
281
|
+
});
|
|
282
|
+
if (this.status !== UniversalStore.Status.READY) throw new TypeError(dedent`Cannot set state before store is ready. You can get the current status with store.status,
|
|
283
|
+
or await store.readyPromise to wait for the store to be ready before sending events.
|
|
284
|
+
${JSON.stringify({
|
|
285
|
+
newState,
|
|
286
|
+
id: this.id,
|
|
287
|
+
actor: this.actor,
|
|
288
|
+
environment: this.environment
|
|
289
|
+
}, null, 2)}`);
|
|
290
|
+
this.state = newState;
|
|
291
|
+
const event = {
|
|
292
|
+
type: UniversalStore.InternalEventType.SET_STATE,
|
|
293
|
+
payload: {
|
|
294
|
+
state: newState,
|
|
295
|
+
previousState
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
this.emitToChannel(event, { actor: this.actor });
|
|
299
|
+
this.emitToListeners(event, { actor: this.actor });
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Subscribes to state changes
|
|
303
|
+
*
|
|
304
|
+
* @returns Unsubscribe function
|
|
305
|
+
*/
|
|
306
|
+
onStateChange(listener) {
|
|
307
|
+
this.debug("onStateChange", { listener });
|
|
308
|
+
return this.subscribe(UniversalStore.InternalEventType.SET_STATE, ({ payload }, eventInfo) => {
|
|
309
|
+
listener(payload.state, payload.previousState, eventInfo);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
emitToChannel(event, eventInfo) {
|
|
313
|
+
this.debug("emitToChannel", {
|
|
314
|
+
event,
|
|
315
|
+
eventInfo,
|
|
316
|
+
channel: !!this.channel
|
|
317
|
+
});
|
|
318
|
+
this.channel?.emit(this.channelEventName, {
|
|
319
|
+
event,
|
|
320
|
+
eventInfo
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
prepareThis({ channel, environment }) {
|
|
324
|
+
this.channel = channel;
|
|
325
|
+
this.environment = environment;
|
|
326
|
+
this.debug("prepared", {
|
|
327
|
+
channel: !!channel,
|
|
328
|
+
environment
|
|
329
|
+
});
|
|
330
|
+
this.channel.on(this.channelEventName, this.handleChannelEvents);
|
|
331
|
+
if (this.actor.type === UniversalStore.ActorType.LEADER) this.emitToChannel({ type: UniversalStore.InternalEventType.LEADER_CREATED }, { actor: this.actor });
|
|
332
|
+
else {
|
|
333
|
+
this.emitToChannel({ type: UniversalStore.InternalEventType.FOLLOWER_CREATED }, { actor: this.actor });
|
|
334
|
+
this.emitToChannel({ type: UniversalStore.InternalEventType.EXISTING_STATE_REQUEST }, { actor: this.actor });
|
|
335
|
+
setTimeout(() => {
|
|
336
|
+
this.syncing.reject(new UniversalStoreFollowerTimeoutError(this.id));
|
|
337
|
+
}, 1e3);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
emitToListeners(event, eventInfo) {
|
|
341
|
+
const eventTypeListeners = this.listeners.get(event.type);
|
|
342
|
+
const everythingListeners = this.listeners.get("*");
|
|
343
|
+
this.debug("emitToListeners", {
|
|
344
|
+
event,
|
|
345
|
+
eventInfo,
|
|
346
|
+
eventTypeListeners,
|
|
347
|
+
everythingListeners
|
|
348
|
+
});
|
|
349
|
+
[...eventTypeListeners ?? [], ...everythingListeners ?? []].forEach((listener) => listener(event, eventInfo));
|
|
350
|
+
}
|
|
351
|
+
handleChannelEvents(channelEvent) {
|
|
352
|
+
const { event, eventInfo } = channelEvent;
|
|
353
|
+
if ([eventInfo.actor.id, eventInfo.forwardingActor?.id].includes(this.actor.id)) {
|
|
354
|
+
this.debug("handleChannelEvents: Ignoring event from self", { channelEvent });
|
|
355
|
+
return;
|
|
356
|
+
} else if (this.syncing?.state === ProgressState.PENDING && event.type !== UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE) {
|
|
357
|
+
this.debug("handleChannelEvents: Ignoring event while syncing", { channelEvent });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
this.debug("handleChannelEvents", { channelEvent });
|
|
361
|
+
if (this.actor.type === UniversalStore.ActorType.LEADER) {
|
|
362
|
+
let shouldForwardEvent = true;
|
|
363
|
+
switch (event.type) {
|
|
364
|
+
case UniversalStore.InternalEventType.EXISTING_STATE_REQUEST:
|
|
365
|
+
shouldForwardEvent = false;
|
|
366
|
+
const responseEvent = {
|
|
367
|
+
type: UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE,
|
|
368
|
+
payload: this.state
|
|
369
|
+
};
|
|
370
|
+
this.debug("handleChannelEvents: responding to existing state request", { responseEvent });
|
|
371
|
+
this.emitToChannel(responseEvent, { actor: this.actor });
|
|
372
|
+
this.emitToListeners(responseEvent, { actor: this.actor });
|
|
373
|
+
break;
|
|
374
|
+
case UniversalStore.InternalEventType.LEADER_CREATED:
|
|
375
|
+
shouldForwardEvent = false;
|
|
376
|
+
this.syncing.state = ProgressState.REJECTED;
|
|
377
|
+
this.debug("handleChannelEvents: erroring due to second leader being created", { event });
|
|
378
|
+
console.error(dedent`Detected multiple UniversalStore leaders created with the same id "${this.id}".
|
|
379
|
+
Only one leader can exists at a time, your stores are now in an invalid state.
|
|
380
|
+
Leaders detected:
|
|
381
|
+
this: ${JSON.stringify(this.actor, null, 2)}
|
|
382
|
+
other: ${JSON.stringify(eventInfo.actor, null, 2)}`);
|
|
383
|
+
}
|
|
384
|
+
if (shouldForwardEvent) {
|
|
385
|
+
this.debug("handleChannelEvents: forwarding event", { channelEvent });
|
|
386
|
+
this.emitToChannel(event, {
|
|
387
|
+
actor: eventInfo.actor,
|
|
388
|
+
forwardingActor: this.actor
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (this.actor.type === UniversalStore.ActorType.FOLLOWER) switch (event.type) {
|
|
393
|
+
case UniversalStore.InternalEventType.EXISTING_STATE_RESPONSE:
|
|
394
|
+
this.debug("handleChannelEvents: Setting state from leader's existing state response", { event });
|
|
395
|
+
if (this.syncing?.state !== ProgressState.PENDING) break;
|
|
396
|
+
this.syncing.resolve?.();
|
|
397
|
+
const setStateEvent = {
|
|
398
|
+
type: UniversalStore.InternalEventType.SET_STATE,
|
|
399
|
+
payload: {
|
|
400
|
+
state: event.payload,
|
|
401
|
+
previousState: this.state
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
this.state = event.payload;
|
|
405
|
+
this.emitToListeners(setStateEvent, eventInfo);
|
|
406
|
+
}
|
|
407
|
+
switch (event.type) {
|
|
408
|
+
case UniversalStore.InternalEventType.SET_STATE:
|
|
409
|
+
this.debug("handleChannelEvents: Setting state", { event });
|
|
410
|
+
this.state = event.payload.state;
|
|
411
|
+
}
|
|
412
|
+
this.emitToListeners(event, { actor: eventInfo.actor });
|
|
413
|
+
}
|
|
414
|
+
debug(message, data) {
|
|
415
|
+
if (this.debugging) console.debug(dedent`[UniversalStore::${this.id}::${this.environment ?? UniversalStore.Environment.UNKNOWN}]
|
|
416
|
+
${message}`, JSON.stringify({
|
|
417
|
+
data,
|
|
418
|
+
actor: this.actor,
|
|
419
|
+
state: this.state,
|
|
420
|
+
status: this.status
|
|
421
|
+
}, null, 2));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Used to reset the static fields of the UniversalStore class when cleaning up tests
|
|
425
|
+
*
|
|
426
|
+
* @internal
|
|
427
|
+
*/
|
|
428
|
+
static __reset() {
|
|
429
|
+
UniversalStore.preparation.reject(/* @__PURE__ */ new Error("reset"));
|
|
430
|
+
UniversalStore.setupPreparationPromise();
|
|
431
|
+
UniversalStore.isInternalConstructing = false;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
})))();
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/shared/utils/local-hosts.ts
|
|
438
|
+
var LOOPBACK_HOSTNAMES, matchesHostname;
|
|
439
|
+
function init_local_hosts() {
|
|
440
|
+
return (init_local_hosts = __esmMin((() => {
|
|
441
|
+
LOOPBACK_HOSTNAMES = [
|
|
442
|
+
"localhost",
|
|
443
|
+
"*.localhost",
|
|
444
|
+
"127.0.0.1"
|
|
445
|
+
];
|
|
446
|
+
matchesHostname = (hostname, patterns) => patterns.some((pattern) => pattern.startsWith("*.") ? hostname.endsWith(pattern.slice(1)) : hostname === pattern);
|
|
447
|
+
})))();
|
|
448
|
+
}
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/shared/open-service/errors.ts
|
|
451
|
+
/**
|
|
452
|
+
* Formats a schema issue path into the field notation shown in validation errors.
|
|
453
|
+
*
|
|
454
|
+
* Examples:
|
|
455
|
+
* - `['foo']` -> `foo`
|
|
456
|
+
* - `['items', 0, 'id']` -> `items[0].id`
|
|
457
|
+
*/
|
|
458
|
+
function formatIssuePath(path) {
|
|
459
|
+
if (!path?.length) return "";
|
|
460
|
+
return path.map((segment) => {
|
|
461
|
+
const key = typeof segment === "object" && segment !== null && "key" in segment ? segment.key : segment;
|
|
462
|
+
return typeof key === "number" ? `[${key}]` : `.${String(key)}`;
|
|
463
|
+
}).join("").replace(/^\./, "");
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Converts schema issues into the newline-separated detail block appended to user-facing errors.
|
|
467
|
+
*/
|
|
468
|
+
function formatIssues(issues) {
|
|
469
|
+
return issues.map((issue) => {
|
|
470
|
+
const path = formatIssuePath(issue.path);
|
|
471
|
+
return path === "" ? issue.message : `${path}: ${issue.message}`;
|
|
472
|
+
}).join("\n");
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/server-errors.ts
|
|
476
|
+
var OpenServiceValidationError, OpenServiceMissingServiceError, OpenServiceInternalServiceError, OpenServiceUnimplementedOperationError, OpenServiceInvalidStaticPathError, OpenServiceAsyncSchemaError, OpenServiceLoadedDrainExceededError, OpenServiceMissingChannelError, OpenServiceRemoteCommandDisconnectedError, OpenServiceRemoteCommandUnhandledError, OpenServiceRemoteCommandConfigDriftError, OpenServiceAsyncRecipeError, OpenServiceCyclicStateError, OpenServiceOperationNameCollisionError;
|
|
477
|
+
function init_server_errors() {
|
|
478
|
+
return (init_server_errors = __esmMin((() => {
|
|
479
|
+
init_storybook_error();
|
|
480
|
+
OpenServiceValidationError = class extends StorybookError {
|
|
481
|
+
constructor(data) {
|
|
482
|
+
super({
|
|
483
|
+
name: "OpenServiceValidationError",
|
|
484
|
+
category: "CORE-COMMON",
|
|
485
|
+
code: 5,
|
|
486
|
+
message: `Invalid ${data.phase} for ${data.kind} "${data.serviceId}.${data.name}":\n${formatIssues(data.issues)}`
|
|
487
|
+
});
|
|
488
|
+
this.data = data;
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
OpenServiceMissingServiceError = class extends StorybookError {
|
|
492
|
+
constructor(data) {
|
|
493
|
+
super({
|
|
494
|
+
name: "OpenServiceMissingServiceError",
|
|
495
|
+
category: "CORE-COMMON",
|
|
496
|
+
code: 7,
|
|
497
|
+
message: `No registered service with id "${data.serviceId}" exists in this environment.`
|
|
498
|
+
});
|
|
499
|
+
this.data = data;
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
OpenServiceInternalServiceError = class extends StorybookError {
|
|
503
|
+
constructor(data) {
|
|
504
|
+
super({
|
|
505
|
+
name: "OpenServiceInternalServiceError",
|
|
506
|
+
category: "CORE-COMMON",
|
|
507
|
+
code: 19,
|
|
508
|
+
message: `Service "${data.serviceId}" is internal. Pass { internal: true } to getService() only if you intentionally depend on an unstable OSA surface. Internal services may change without notice.`
|
|
509
|
+
});
|
|
510
|
+
this.data = data;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
OpenServiceUnimplementedOperationError = class extends StorybookError {
|
|
514
|
+
constructor(data) {
|
|
515
|
+
super({
|
|
516
|
+
name: "OpenServiceUnimplementedOperationError",
|
|
517
|
+
category: "CORE-COMMON",
|
|
518
|
+
code: 8,
|
|
519
|
+
message: `${data.kind[0].toUpperCase()}${data.kind.slice(1)} "${data.serviceId}.${data.name}" is not implemented for this environment.`
|
|
520
|
+
});
|
|
521
|
+
this.data = data;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
OpenServiceInvalidStaticPathError = class extends StorybookError {
|
|
525
|
+
constructor(data) {
|
|
526
|
+
super({
|
|
527
|
+
name: "OpenServiceInvalidStaticPathError",
|
|
528
|
+
category: "CORE-COMMON",
|
|
529
|
+
code: 10,
|
|
530
|
+
message: `Invalid static path "${data.path}" for query "${data.serviceId}.${data.name}": use a relative path with forward slashes and no ".." segments.`
|
|
531
|
+
});
|
|
532
|
+
this.data = data;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
OpenServiceAsyncSchemaError = class extends StorybookError {
|
|
536
|
+
constructor(data) {
|
|
537
|
+
super({
|
|
538
|
+
name: "OpenServiceAsyncSchemaError",
|
|
539
|
+
category: "CORE-COMMON",
|
|
540
|
+
code: 9,
|
|
541
|
+
message: `Async schema for ${data.kind} "${data.serviceId}.${data.name}" (${data.phase}): query input and output schemas must validate synchronously.`
|
|
542
|
+
});
|
|
543
|
+
this.data = data;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
OpenServiceLoadedDrainExceededError = class extends StorybookError {
|
|
547
|
+
constructor(data) {
|
|
548
|
+
super({
|
|
549
|
+
name: "OpenServiceLoadedDrainExceededError",
|
|
550
|
+
category: "CORE-COMMON",
|
|
551
|
+
code: 11,
|
|
552
|
+
message: `Query "${data.serviceId}.${data.name}".loaded(...) did not settle after ${data.iterations} drain iterations. Check for handlers that keep discovering new dependencies after every state change.`
|
|
553
|
+
});
|
|
554
|
+
this.data = data;
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
OpenServiceMissingChannelError = class extends StorybookError {
|
|
558
|
+
constructor(data = {}) {
|
|
559
|
+
super({
|
|
560
|
+
name: "OpenServiceMissingChannelError",
|
|
561
|
+
category: "CORE-COMMON",
|
|
562
|
+
code: 13,
|
|
563
|
+
message: data.serviceId ? `Cannot register service "${data.serviceId}": the Forkbook addons channel is not installed in this runtime.` : "The Forkbook addons channel is not installed in this runtime."
|
|
564
|
+
});
|
|
565
|
+
this.data = data;
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
OpenServiceRemoteCommandDisconnectedError = class extends StorybookError {
|
|
569
|
+
constructor(data) {
|
|
570
|
+
super({
|
|
571
|
+
name: "OpenServiceRemoteCommandDisconnectedError",
|
|
572
|
+
category: "CORE-COMMON",
|
|
573
|
+
code: 14,
|
|
574
|
+
message: `Service "${data.serviceId}" was unregistered before a remote command resolved.`
|
|
575
|
+
});
|
|
576
|
+
this.data = data;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
OpenServiceRemoteCommandUnhandledError = class extends StorybookError {
|
|
580
|
+
constructor(data) {
|
|
581
|
+
super({
|
|
582
|
+
name: "OpenServiceRemoteCommandUnhandledError",
|
|
583
|
+
category: "CORE-COMMON",
|
|
584
|
+
code: 15,
|
|
585
|
+
message: data.delegated ? `The Forkbook this runtime is attached to did not acknowledge remote command "${data.serviceId}.${data.commandName}" in time — it may be busy or unreachable. Retry; note the command may still have executed on that instance.` : `No runtime acknowledged remote command "${data.serviceId}.${data.commandName}"; its handler is not implemented in any connected runtime.`
|
|
586
|
+
});
|
|
587
|
+
this.data = data;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
OpenServiceRemoteCommandConfigDriftError = class extends StorybookError {
|
|
591
|
+
constructor(data) {
|
|
592
|
+
super({
|
|
593
|
+
name: "OpenServiceRemoteCommandConfigDriftError",
|
|
594
|
+
category: "CORE-COMMON",
|
|
595
|
+
code: 30,
|
|
596
|
+
message: `The Forkbook this runtime is attached to reported it has no handler for remote command "${data.serviceId}.${data.commandName}". The two processes are running different configurations (for example a feature flag enabled in one but not the other). Restart the attached Forkbook with a configuration matching this process.`
|
|
597
|
+
});
|
|
598
|
+
this.data = data;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
OpenServiceAsyncRecipeError = class extends StorybookError {
|
|
602
|
+
constructor() {
|
|
603
|
+
super({
|
|
604
|
+
name: "OpenServiceAsyncRecipeError",
|
|
605
|
+
category: "CORE-COMMON",
|
|
606
|
+
code: 31,
|
|
607
|
+
message: "setState recipes must be synchronous. A write after an await would change state without authoring a sync entry. Split the command into one setState per synchronous step."
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
OpenServiceCyclicStateError = class extends StorybookError {
|
|
612
|
+
constructor() {
|
|
613
|
+
super({
|
|
614
|
+
name: "OpenServiceCyclicStateError",
|
|
615
|
+
category: "CORE-COMMON",
|
|
616
|
+
code: 32,
|
|
617
|
+
message: "Service state must be JSON-serializable, but the value is cyclic or nested more than 256 levels deep. Store an id and look the value up in a query instead of a reference."
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
OpenServiceOperationNameCollisionError = class extends StorybookError {
|
|
622
|
+
constructor(data) {
|
|
623
|
+
super({
|
|
624
|
+
name: "OpenServiceOperationNameCollisionError",
|
|
625
|
+
category: "CORE-COMMON",
|
|
626
|
+
code: 16,
|
|
627
|
+
message: `Service "${data.serviceId}" cannot register "${data.operationName}" as both a query and a command.`
|
|
628
|
+
});
|
|
629
|
+
this.data = data;
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
})))();
|
|
633
|
+
}
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/shared/status-store/index.ts
|
|
636
|
+
var CHANGE_DETECTION_STATUS_TYPE_ID, REVIEW_STATUS_TYPE_ID, NON_AGGREGATED_STATUS_TYPE_IDS;
|
|
637
|
+
function init_status_store() {
|
|
638
|
+
return (init_status_store = __esmMin((() => {
|
|
639
|
+
CHANGE_DETECTION_STATUS_TYPE_ID = "@aussieljk/forkbook/change-detection";
|
|
640
|
+
REVIEW_STATUS_TYPE_ID = "@aussieljk/forkbook/review";
|
|
641
|
+
NON_AGGREGATED_STATUS_TYPE_IDS = [CHANGE_DETECTION_STATUS_TYPE_ID, REVIEW_STATUS_TYPE_ID];
|
|
642
|
+
})))();
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/shared/review/events.ts
|
|
646
|
+
var REVIEW_NAMESPACE, REVIEW_EVENTS;
|
|
647
|
+
function init_events() {
|
|
648
|
+
return (init_events = __esmMin((() => {
|
|
649
|
+
REVIEW_NAMESPACE = "@aussieljk/forkbook/review";
|
|
650
|
+
REVIEW_EVENTS = { PAGEVIEW: `${REVIEW_NAMESPACE}/pageview` };
|
|
651
|
+
})))();
|
|
652
|
+
}
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/shared/constants/tags.ts
|
|
655
|
+
var Tag;
|
|
656
|
+
function init_tags() {
|
|
657
|
+
return (init_tags = __esmMin((() => {
|
|
658
|
+
Tag = {
|
|
659
|
+
/** Indicates that autodocs should be generated for this component */
|
|
660
|
+
AUTODOCS: "autodocs",
|
|
661
|
+
/** MDX documentation attached to a component's stories file */
|
|
662
|
+
ATTACHED_MDX: "attached-mdx",
|
|
663
|
+
/** Standalone MDX documentation not attached to stories */
|
|
664
|
+
UNATTACHED_MDX: "unattached-mdx",
|
|
665
|
+
/** Story has a play function */
|
|
666
|
+
PLAY_FN: "play-fn",
|
|
667
|
+
/** Story has a test function */
|
|
668
|
+
TEST_FN: "test-fn",
|
|
669
|
+
/** Development environment tag */
|
|
670
|
+
DEV: "dev",
|
|
671
|
+
/** Test environment tag */
|
|
672
|
+
TEST: "test",
|
|
673
|
+
/** Manifest generation tag */
|
|
674
|
+
MANIFEST: "manifest"
|
|
675
|
+
};
|
|
676
|
+
})))();
|
|
677
|
+
}
|
|
678
|
+
//#endregion
|
|
679
|
+
//#region src/shared/open-service/plain-object.ts
|
|
680
|
+
function isReservedKey(key) {
|
|
681
|
+
return FORBIDDEN_KEYS.has(key) || key.startsWith("$");
|
|
682
|
+
}
|
|
683
|
+
function hasOwn(obj, key) {
|
|
684
|
+
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
685
|
+
}
|
|
686
|
+
function isPlainObject$1(value) {
|
|
687
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Deep-copies a JSON-shaped value, giving every path its own object.
|
|
691
|
+
*
|
|
692
|
+
* Used for every value on its way into service state: a definition's `initialState`, an object
|
|
693
|
+
* assigned inside a `setState` recipe, and the value carried by a recorded op.
|
|
694
|
+
*
|
|
695
|
+
* Not `structuredClone`. That keeps a reference shared between two keys as one object, so a write
|
|
696
|
+
* through one key would also change the other on the writing runtime, while peers, who only ever
|
|
697
|
+
* receive JSON, would see one path change. Rebuilding key by key removes the alias on both sides.
|
|
698
|
+
* It also accepts proxies and drops the prototype-pollution keys in {@link FORBIDDEN_KEYS} and
|
|
699
|
+
* deepsignal's `$`-prefixed accessor keys. A cyclic value throws {@link OpenServiceCyclicStateError},
|
|
700
|
+
* since it could never be serialized for peers; the check is a depth limit, which costs nothing per
|
|
701
|
+
* node. An `undefined` property is dropped, as JSON drops it, so the author and peers agree on which
|
|
702
|
+
* keys exist.
|
|
703
|
+
*
|
|
704
|
+
* Only own enumerable string keys are copied. Class instances, `Map`, `Set`, and `Date` become plain
|
|
705
|
+
* objects, which matches the JSON-serializable contract service state already has.
|
|
706
|
+
*/
|
|
707
|
+
function clonePlain(value, depth = 0) {
|
|
708
|
+
if (value === null || typeof value !== "object") return value;
|
|
709
|
+
if (depth > MAX_DEPTH) throw new OpenServiceCyclicStateError();
|
|
710
|
+
if (Array.isArray(value)) return value.map((entry) => clonePlain(entry, depth + 1));
|
|
711
|
+
const copy = {};
|
|
712
|
+
for (const key of Object.keys(value)) {
|
|
713
|
+
const entry = value[key];
|
|
714
|
+
if (isReservedKey(key) || entry === void 0) continue;
|
|
715
|
+
copy[key] = clonePlain(entry, depth + 1);
|
|
716
|
+
}
|
|
717
|
+
return copy;
|
|
718
|
+
}
|
|
719
|
+
var FORBIDDEN_KEYS, MAX_DEPTH;
|
|
720
|
+
function init_plain_object() {
|
|
721
|
+
return (init_plain_object = __esmMin((() => {
|
|
722
|
+
init_server_errors();
|
|
723
|
+
FORBIDDEN_KEYS = /* @__PURE__ */ new Set([
|
|
724
|
+
"__proto__",
|
|
725
|
+
"constructor",
|
|
726
|
+
"prototype"
|
|
727
|
+
]);
|
|
728
|
+
MAX_DEPTH = 256;
|
|
729
|
+
})))();
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/shared/open-service/service-channel.ts
|
|
733
|
+
function decodeSegment(encoded) {
|
|
734
|
+
return encoded.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
735
|
+
}
|
|
736
|
+
/** RFC 6901 JSON Pointer: `~0` encodes `~`, `~1` encodes `/`; no segments is the root pointer `''`. */
|
|
737
|
+
function encodePointer(segments) {
|
|
738
|
+
return segments.map((segment) => `/${segment.replaceAll("~", "~0").replaceAll("/", "~1")}`).join("");
|
|
739
|
+
}
|
|
740
|
+
/** Inverse of {@link encodePointer}. `pointer` must be empty or start with `/`. */
|
|
741
|
+
function decodePointer(pointer) {
|
|
742
|
+
if (pointer === "") return [];
|
|
743
|
+
return pointer.slice(1).split("/").map(decodeSegment);
|
|
744
|
+
}
|
|
745
|
+
function isRfc6901Pointer(pointer) {
|
|
746
|
+
if (!pointer.startsWith("/")) return false;
|
|
747
|
+
return pointer.slice(1).split("/").every((encoded) => {
|
|
748
|
+
return RFC_6901_ENCODED_SEGMENT.test(encoded) && !isReservedKey(decodeSegment(encoded));
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
/** Canonical log key and warning label: `seq:runtimeId:counter`. */
|
|
752
|
+
function entryStampKey(stamp) {
|
|
753
|
+
return `${stamp.seq}:${stamp.runtimeId}:${stamp.counter}`;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Unique id for one service registration.
|
|
757
|
+
*
|
|
758
|
+
* It is the loop guard that drops a runtime's own `services:sync-request`. `nanoid` is used (over
|
|
759
|
+
* `Math.random`) so collisions cannot silently break that determinism.
|
|
760
|
+
*/
|
|
761
|
+
function generateRuntimeId() {
|
|
762
|
+
return nanoid();
|
|
763
|
+
}
|
|
764
|
+
/** Unique id for one remote-command invocation. Replies correlate on this, not on `runtimeId`. */
|
|
765
|
+
function generateCallId() {
|
|
766
|
+
return nanoid();
|
|
767
|
+
}
|
|
768
|
+
var SERVICE_SYNC_REQUEST, SERVICE_SYNC_REPLY, SERVICE_ENTRY, SERVICE_COMMAND_INVOKE, SERVICE_COMMAND_ACK, SERVICE_COMMAND_RESULT, SERVICE_COMMAND_ERROR, SERVICE_COMMAND_UNHANDLED, stateSnapshotSchema, nonNegativeSafeInteger, positiveSafeInteger, RFC_6901_ENCODED_SEGMENT, jsonPointerSchema, jsonPatchOperationSchema, entryStampSchema, entrySchema, frontierSchema, syncRequestSchema, syncReplySchema, commandInvokeSchema, commandAckSchema, commandUnhandledSchema, commandResultSchema, commandErrorSchema;
|
|
769
|
+
function init_service_channel() {
|
|
770
|
+
return (init_service_channel = __esmMin((() => {
|
|
771
|
+
init_index_browser();
|
|
772
|
+
init_dist();
|
|
773
|
+
init_plain_object();
|
|
774
|
+
SERVICE_SYNC_REQUEST = "services:sync-request";
|
|
775
|
+
SERVICE_SYNC_REPLY = "services:sync-reply";
|
|
776
|
+
SERVICE_ENTRY = "services:entry";
|
|
777
|
+
SERVICE_COMMAND_INVOKE = "services:command-invoke";
|
|
778
|
+
SERVICE_COMMAND_ACK = "services:command-ack";
|
|
779
|
+
SERVICE_COMMAND_RESULT = "services:command-result";
|
|
780
|
+
SERVICE_COMMAND_ERROR = "services:command-error";
|
|
781
|
+
SERVICE_COMMAND_UNHANDLED = "services:command-unhandled";
|
|
782
|
+
stateSnapshotSchema = custom(isPlainObject$1);
|
|
783
|
+
nonNegativeSafeInteger = pipe(number(), safeInteger(), minValue(0));
|
|
784
|
+
positiveSafeInteger = pipe(number(), safeInteger(), minValue(1));
|
|
785
|
+
RFC_6901_ENCODED_SEGMENT = /^(?:[^~]|~[01])*$/;
|
|
786
|
+
jsonPointerSchema = pipe(string(), check(isRfc6901Pointer));
|
|
787
|
+
jsonPatchOperationSchema = variant("op", [
|
|
788
|
+
object({
|
|
789
|
+
op: literal("add"),
|
|
790
|
+
path: jsonPointerSchema,
|
|
791
|
+
value: unknown()
|
|
792
|
+
}),
|
|
793
|
+
object({
|
|
794
|
+
op: literal("replace"),
|
|
795
|
+
path: jsonPointerSchema,
|
|
796
|
+
value: unknown()
|
|
797
|
+
}),
|
|
798
|
+
object({
|
|
799
|
+
op: literal("remove"),
|
|
800
|
+
path: jsonPointerSchema
|
|
801
|
+
})
|
|
802
|
+
]);
|
|
803
|
+
entryStampSchema = object({
|
|
804
|
+
seq: positiveSafeInteger,
|
|
805
|
+
runtimeId: string(),
|
|
806
|
+
counter: positiveSafeInteger
|
|
807
|
+
});
|
|
808
|
+
entrySchema = object({
|
|
809
|
+
serviceId: string(),
|
|
810
|
+
stamp: entryStampSchema,
|
|
811
|
+
command: string(),
|
|
812
|
+
patch: pipe(array(jsonPatchOperationSchema), minLength(1))
|
|
813
|
+
});
|
|
814
|
+
frontierSchema = object({
|
|
815
|
+
vector: record(string(), nonNegativeSafeInteger),
|
|
816
|
+
clock: nonNegativeSafeInteger
|
|
817
|
+
});
|
|
818
|
+
syncRequestSchema = object({
|
|
819
|
+
serviceId: string(),
|
|
820
|
+
runtimeId: string(),
|
|
821
|
+
frontier: frontierSchema
|
|
822
|
+
});
|
|
823
|
+
syncReplySchema = object({
|
|
824
|
+
serviceId: string(),
|
|
825
|
+
runtimeId: string(),
|
|
826
|
+
frontier: frontierSchema,
|
|
827
|
+
state: stateSnapshotSchema
|
|
828
|
+
});
|
|
829
|
+
commandInvokeSchema = object({
|
|
830
|
+
serviceId: string(),
|
|
831
|
+
commandName: string(),
|
|
832
|
+
input: optional(unknown()),
|
|
833
|
+
callId: string(),
|
|
834
|
+
runtimeId: string()
|
|
835
|
+
});
|
|
836
|
+
commandAckSchema = object({
|
|
837
|
+
serviceId: string(),
|
|
838
|
+
callId: string(),
|
|
839
|
+
runtimeId: string()
|
|
840
|
+
});
|
|
841
|
+
commandUnhandledSchema = object({
|
|
842
|
+
serviceId: string(),
|
|
843
|
+
callId: string()
|
|
844
|
+
});
|
|
845
|
+
commandResultSchema = object({
|
|
846
|
+
serviceId: string(),
|
|
847
|
+
callId: string(),
|
|
848
|
+
result: optional(unknown()),
|
|
849
|
+
runtimeId: string()
|
|
850
|
+
});
|
|
851
|
+
commandErrorSchema = object({
|
|
852
|
+
serviceId: string(),
|
|
853
|
+
callId: string(),
|
|
854
|
+
error: custom((value) => typeof value === "object" && value !== null && !Array.isArray(value)),
|
|
855
|
+
runtimeId: string()
|
|
856
|
+
});
|
|
857
|
+
})))();
|
|
858
|
+
}
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/shared/open-service/patch-recorder.ts
|
|
861
|
+
function peekProp(obj, key) {
|
|
862
|
+
return g(obj, key);
|
|
863
|
+
}
|
|
864
|
+
function isPrimitive(value) {
|
|
865
|
+
return value === null || typeof value !== "object";
|
|
866
|
+
}
|
|
867
|
+
function ownValue(inner, name) {
|
|
868
|
+
const existed = hasOwn(inner, name);
|
|
869
|
+
return {
|
|
870
|
+
existed,
|
|
871
|
+
firstValue: existed ? clonePlain(peekProp(inner, name)) : void 0
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function readPath(root, segments) {
|
|
875
|
+
let current = root;
|
|
876
|
+
for (const segment of segments) {
|
|
877
|
+
if (current === null || typeof current !== "object") return {
|
|
878
|
+
found: false,
|
|
879
|
+
value: void 0
|
|
880
|
+
};
|
|
881
|
+
if (!hasOwn(current, segment)) return {
|
|
882
|
+
found: false,
|
|
883
|
+
value: void 0
|
|
884
|
+
};
|
|
885
|
+
current = peekProp(current, segment);
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
found: true,
|
|
889
|
+
value: current
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Runs `mutate` against `state` through a recording proxy and passes the resulting RFC 6902 ops to
|
|
894
|
+
* `author`.
|
|
895
|
+
*
|
|
896
|
+
* `author` runs even when the recipe throws, with the paths written before the throw, and the
|
|
897
|
+
* throw then propagates. A recipe that changes nothing does not call `author`. A call made inside
|
|
898
|
+
* another recipe on the same state writes into that recipe's draft, so its writes join that entry.
|
|
899
|
+
*/
|
|
900
|
+
function recordPatch(state, mutate, author) {
|
|
901
|
+
const activeDraft = activeDrafts.get(state);
|
|
902
|
+
if (activeDraft) {
|
|
903
|
+
const nested = mutate(activeDraft);
|
|
904
|
+
if (isThenable(nested)) {
|
|
905
|
+
Promise.resolve(nested).catch(() => {});
|
|
906
|
+
throw new OpenServiceAsyncRecipeError();
|
|
907
|
+
}
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const root = state;
|
|
911
|
+
const touches = /* @__PURE__ */ new Map();
|
|
912
|
+
const order = [];
|
|
913
|
+
const wrapperByTarget = /* @__PURE__ */ new WeakMap();
|
|
914
|
+
const targetByWrapper = /* @__PURE__ */ new WeakMap();
|
|
915
|
+
const revokes = [];
|
|
916
|
+
const preRecipeValue = (segments) => {
|
|
917
|
+
const { found, value } = readPath(root, segments);
|
|
918
|
+
if (!found) return {
|
|
919
|
+
existed: false,
|
|
920
|
+
firstValue: void 0
|
|
921
|
+
};
|
|
922
|
+
const snapshot = clonePlain(value);
|
|
923
|
+
const prefix = encodePointer(segments);
|
|
924
|
+
for (const pointer of order.toReversed()) {
|
|
925
|
+
if (!pointer.startsWith(`${prefix}/`)) continue;
|
|
926
|
+
const descendant = touches.get(pointer);
|
|
927
|
+
let parent = snapshot;
|
|
928
|
+
const relative = descendant.segments.slice(segments.length);
|
|
929
|
+
for (const segment of relative.slice(0, -1)) parent = parent !== null && typeof parent === "object" ? parent[segment] : void 0;
|
|
930
|
+
if (parent === null || typeof parent !== "object") continue;
|
|
931
|
+
const key = relative[relative.length - 1];
|
|
932
|
+
if (descendant.existed) parent[key] = clonePlain(descendant.firstValue);
|
|
933
|
+
else delete parent[key];
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
existed: true,
|
|
937
|
+
firstValue: snapshot
|
|
938
|
+
};
|
|
939
|
+
};
|
|
940
|
+
const note = (segments, capture) => {
|
|
941
|
+
const pointer = encodePointer(segments);
|
|
942
|
+
if (touches.has(pointer)) return;
|
|
943
|
+
const prefix = `${pointer}/`;
|
|
944
|
+
const subsumesEarlierTouch = order.some((recorded) => recorded.startsWith(prefix));
|
|
945
|
+
touches.set(pointer, {
|
|
946
|
+
segments,
|
|
947
|
+
...subsumesEarlierTouch ? preRecipeValue(segments) : capture()
|
|
948
|
+
});
|
|
949
|
+
order.push(pointer);
|
|
950
|
+
};
|
|
951
|
+
const noteArray = (arrayRoot) => {
|
|
952
|
+
note(arrayRoot.path, () => ({
|
|
953
|
+
existed: true,
|
|
954
|
+
firstValue: clonePlain(arrayRoot.target)
|
|
955
|
+
}));
|
|
956
|
+
};
|
|
957
|
+
const storedValue = (value) => {
|
|
958
|
+
if (value === null || typeof value !== "object") return value;
|
|
959
|
+
return clonePlain(targetByWrapper.get(value) ?? value);
|
|
960
|
+
};
|
|
961
|
+
const isUnchangedAssignment = (inner, name, value) => {
|
|
962
|
+
if (!hasOwn(inner, name)) return false;
|
|
963
|
+
if (Object.is(peekProp(inner, name), value)) return true;
|
|
964
|
+
if (value !== null && typeof value === "object") {
|
|
965
|
+
const target = targetByWrapper.get(value);
|
|
966
|
+
return target !== void 0 && Object.is(Reflect.get(inner, name), target);
|
|
967
|
+
}
|
|
968
|
+
return false;
|
|
969
|
+
};
|
|
970
|
+
const wrap = (target, path, arrayRoot) => {
|
|
971
|
+
const cached = wrapperByTarget.get(target);
|
|
972
|
+
if (cached) return cached;
|
|
973
|
+
const effectiveArrayRoot = arrayRoot ?? (Array.isArray(target) ? {
|
|
974
|
+
path,
|
|
975
|
+
target
|
|
976
|
+
} : null);
|
|
977
|
+
const { proxy, revoke } = Proxy.revocable(target, {
|
|
978
|
+
get(inner, key) {
|
|
979
|
+
if (typeof key === "symbol") return Reflect.get(inner, key);
|
|
980
|
+
if (FORBIDDEN_KEYS.has(key) || key.startsWith("$")) return;
|
|
981
|
+
const value = Reflect.get(inner, key);
|
|
982
|
+
if (typeof value === "function") return value;
|
|
983
|
+
if (value !== null && typeof value === "object") return wrap(value, path.concat(String(key)), effectiveArrayRoot);
|
|
984
|
+
return value;
|
|
985
|
+
},
|
|
986
|
+
set(inner, key, value) {
|
|
987
|
+
if (typeof key === "symbol") return Reflect.set(inner, key, value);
|
|
988
|
+
const name = String(key);
|
|
989
|
+
if (FORBIDDEN_KEYS.has(name)) return true;
|
|
990
|
+
if (name.startsWith("$")) {
|
|
991
|
+
const plain = name.slice(1);
|
|
992
|
+
if (FORBIDDEN_KEYS.has(plain)) return true;
|
|
993
|
+
note(path.concat(plain), () => ownValue(inner, plain));
|
|
994
|
+
return Reflect.set(inner, key, value);
|
|
995
|
+
}
|
|
996
|
+
if (isUnchangedAssignment(inner, name, value)) return true;
|
|
997
|
+
if (effectiveArrayRoot) {
|
|
998
|
+
noteArray(effectiveArrayRoot);
|
|
999
|
+
return Reflect.set(inner, name, storedValue(value));
|
|
1000
|
+
}
|
|
1001
|
+
const segments = path.concat(name);
|
|
1002
|
+
if (value === void 0) {
|
|
1003
|
+
if (hasOwn(inner, name)) {
|
|
1004
|
+
note(segments, () => ownValue(inner, name));
|
|
1005
|
+
return Reflect.deleteProperty(inner, name);
|
|
1006
|
+
}
|
|
1007
|
+
return true;
|
|
1008
|
+
}
|
|
1009
|
+
note(segments, () => ownValue(inner, name));
|
|
1010
|
+
return Reflect.set(inner, name, storedValue(value));
|
|
1011
|
+
},
|
|
1012
|
+
deleteProperty(inner, key) {
|
|
1013
|
+
if (typeof key === "symbol") return Reflect.deleteProperty(inner, key);
|
|
1014
|
+
const name = String(key);
|
|
1015
|
+
if (FORBIDDEN_KEYS.has(name)) return true;
|
|
1016
|
+
if (name.startsWith("$")) return Reflect.deleteProperty(inner, key);
|
|
1017
|
+
if (!hasOwn(inner, name)) return true;
|
|
1018
|
+
if (effectiveArrayRoot) {
|
|
1019
|
+
noteArray(effectiveArrayRoot);
|
|
1020
|
+
return Reflect.deleteProperty(inner, name);
|
|
1021
|
+
}
|
|
1022
|
+
note(path.concat(name), () => ownValue(inner, name));
|
|
1023
|
+
return Reflect.deleteProperty(inner, name);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
revokes.push(revoke);
|
|
1027
|
+
wrapperByTarget.set(target, proxy);
|
|
1028
|
+
targetByWrapper.set(proxy, target);
|
|
1029
|
+
return proxy;
|
|
1030
|
+
};
|
|
1031
|
+
const flush = () => {
|
|
1032
|
+
const touched = new Set(order);
|
|
1033
|
+
const ops = [];
|
|
1034
|
+
const inverse = [];
|
|
1035
|
+
for (const pointer of order) {
|
|
1036
|
+
const touch = touches.get(pointer);
|
|
1037
|
+
if (touch.segments.some((_, index) => {
|
|
1038
|
+
if (index === 0) return false;
|
|
1039
|
+
return touched.has(encodePointer(touch.segments.slice(0, index)));
|
|
1040
|
+
})) continue;
|
|
1041
|
+
const { found, value } = readPath(root, touch.segments);
|
|
1042
|
+
if (!found) {
|
|
1043
|
+
if (touch.existed) {
|
|
1044
|
+
ops.push({
|
|
1045
|
+
op: "remove",
|
|
1046
|
+
path: pointer
|
|
1047
|
+
});
|
|
1048
|
+
inverse.push({
|
|
1049
|
+
op: "add",
|
|
1050
|
+
path: pointer,
|
|
1051
|
+
value: touch.firstValue
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
if (isPrimitive(value) && Object.is(value, touch.firstValue)) continue;
|
|
1057
|
+
ops.push({
|
|
1058
|
+
op: touch.existed ? "replace" : "add",
|
|
1059
|
+
path: pointer,
|
|
1060
|
+
value: clonePlain(value)
|
|
1061
|
+
});
|
|
1062
|
+
inverse.push(touch.existed ? {
|
|
1063
|
+
op: "replace",
|
|
1064
|
+
path: pointer,
|
|
1065
|
+
value: touch.firstValue
|
|
1066
|
+
} : {
|
|
1067
|
+
op: "remove",
|
|
1068
|
+
path: pointer
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
ops,
|
|
1073
|
+
inverse
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
|
+
n(() => {
|
|
1077
|
+
f(() => {
|
|
1078
|
+
const draft = wrap(state, [], null);
|
|
1079
|
+
activeDrafts.set(state, draft);
|
|
1080
|
+
try {
|
|
1081
|
+
const result = mutate(draft);
|
|
1082
|
+
if (isThenable(result)) {
|
|
1083
|
+
Promise.resolve(result).catch(() => {});
|
|
1084
|
+
throw new OpenServiceAsyncRecipeError();
|
|
1085
|
+
}
|
|
1086
|
+
} finally {
|
|
1087
|
+
activeDrafts.delete(state);
|
|
1088
|
+
for (const revoke of revokes) revoke();
|
|
1089
|
+
const recorded = flush();
|
|
1090
|
+
if (recorded.ops.length > 0) author(recorded);
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
function isThenable(value) {
|
|
1096
|
+
return value !== null && typeof value === "object" && typeof value.then === "function";
|
|
1097
|
+
}
|
|
1098
|
+
var activeDrafts;
|
|
1099
|
+
function init_patch_recorder() {
|
|
1100
|
+
return (init_patch_recorder = __esmMin((() => {
|
|
1101
|
+
init_signals_core_module();
|
|
1102
|
+
init_deepsignal_core_module();
|
|
1103
|
+
init_server_errors();
|
|
1104
|
+
init_plain_object();
|
|
1105
|
+
init_service_channel();
|
|
1106
|
+
activeDrafts = /* @__PURE__ */ new WeakMap();
|
|
1107
|
+
})))();
|
|
1108
|
+
}
|
|
1109
|
+
//#endregion
|
|
1110
|
+
//#region src/shared/open-service/query-state.ts
|
|
1111
|
+
/**
|
|
1112
|
+
* Expands a `(data, lifecycle)` pair into the full {@link QueryState} surfaced to subscribers.
|
|
1113
|
+
*
|
|
1114
|
+
* The single source of truth for the derived booleans, shared by the runtime's subscription
|
|
1115
|
+
* emissions and `useServiceQuery`'s first-render seed so they can never drift apart. The booleans
|
|
1116
|
+
* intentionally follow our `load` vocabulary: `isLoading` is "any load in flight" (TanStack's
|
|
1117
|
+
* `isFetching`) and `isInitialLoading` is "the first load with nothing to show yet".
|
|
1118
|
+
*
|
|
1119
|
+
* Unlike TanStack Query, a subscription here can attach to a query whose `data` is already cached in
|
|
1120
|
+
* service state (e.g. revisiting an already-extracted component): its per-subscription lifecycle
|
|
1121
|
+
* still starts `pending`/`loading`, but `data` is present from the first emission. `isInitialLoading`
|
|
1122
|
+
* therefore also requires `data === undefined`, so consumers gate a "nothing yet" skeleton on it
|
|
1123
|
+
* without flashing it over already-available data while a background (re)load runs.
|
|
1124
|
+
*/
|
|
1125
|
+
function buildQueryState(data, lifecycle) {
|
|
1126
|
+
const isPending = lifecycle.status === "pending";
|
|
1127
|
+
const isLoading = lifecycle.loadStatus === "loading";
|
|
1128
|
+
return {
|
|
1129
|
+
data,
|
|
1130
|
+
error: lifecycle.error,
|
|
1131
|
+
status: lifecycle.status,
|
|
1132
|
+
loadStatus: lifecycle.loadStatus,
|
|
1133
|
+
isPending,
|
|
1134
|
+
isSuccess: lifecycle.status === "success",
|
|
1135
|
+
isError: lifecycle.status === "error",
|
|
1136
|
+
isLoading,
|
|
1137
|
+
isInitialLoading: isPending && isLoading && data === void 0,
|
|
1138
|
+
isRefreshing: isLoading && !isPending
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
/** Coerces an unknown thrown value into an `Error` for the `error` field of a {@link QueryState}. */
|
|
1142
|
+
function toError(value) {
|
|
1143
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region src/shared/open-service/service-validation.ts
|
|
1147
|
+
/**
|
|
1148
|
+
* Re-throws asynchronous subscription failures on the microtask queue so they are not silently
|
|
1149
|
+
* swallowed by promise chains started from reactive listeners.
|
|
1150
|
+
*/
|
|
1151
|
+
function rethrowAsync(error) {
|
|
1152
|
+
queueMicrotask(() => {
|
|
1153
|
+
throw error;
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Validates a value with a Standard Schema and returns the parsed output value.
|
|
1158
|
+
*
|
|
1159
|
+
* Any schema issues are wrapped in `OpenServiceValidationError`, which standardizes the operation
|
|
1160
|
+
* metadata while preserving the schema's own expectation text for the actionable details.
|
|
1161
|
+
*/
|
|
1162
|
+
async function validateSchema(schema, value, meta) {
|
|
1163
|
+
const validationResult = await schema["~standard"].validate(value);
|
|
1164
|
+
if (validationResult.issues) throw new OpenServiceValidationError({
|
|
1165
|
+
...meta,
|
|
1166
|
+
issues: validationResult.issues
|
|
1167
|
+
});
|
|
1168
|
+
return validationResult.value;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Synchronous variant of `validateSchema` used on the sync query call path.
|
|
1172
|
+
*
|
|
1173
|
+
* Query input and output schemas must produce sync validation results so the public `query(input)`
|
|
1174
|
+
* function can return a value immediately. If a schema accidentally returns a Promise, the runtime
|
|
1175
|
+
* surfaces a dedicated error instead of silently switching to async behavior.
|
|
1176
|
+
*/
|
|
1177
|
+
function validateSchemaSync(schema, value, meta) {
|
|
1178
|
+
const validationResult = schema["~standard"].validate(value);
|
|
1179
|
+
if (validationResult instanceof Promise) throw new OpenServiceAsyncSchemaError({
|
|
1180
|
+
kind: meta.kind,
|
|
1181
|
+
serviceId: meta.serviceId,
|
|
1182
|
+
name: meta.name,
|
|
1183
|
+
phase: meta.phase
|
|
1184
|
+
});
|
|
1185
|
+
if (validationResult.issues) throw new OpenServiceValidationError({
|
|
1186
|
+
...meta,
|
|
1187
|
+
issues: validationResult.issues
|
|
1188
|
+
});
|
|
1189
|
+
return validationResult.value;
|
|
1190
|
+
}
|
|
1191
|
+
function init_service_validation() {
|
|
1192
|
+
return (init_service_validation = __esmMin((() => {
|
|
1193
|
+
init_server_errors();
|
|
1194
|
+
})))();
|
|
1195
|
+
}
|
|
1196
|
+
//#endregion
|
|
1197
|
+
//#region src/shared/open-service/query-runtime.ts
|
|
1198
|
+
/**
|
|
1199
|
+
* Returns a deterministic JSON encoding so the same logical input always produces the same key.
|
|
1200
|
+
*
|
|
1201
|
+
* Object keys are sorted recursively so `{a:1, b:2}` and `{b:2, a:1}` hash identically. Inputs are
|
|
1202
|
+
* expected to be JSON-safe; non-serializable values like `Date`, `Map`, or functions fall back to
|
|
1203
|
+
* `JSON.stringify`'s defaults and may produce ambiguous keys.
|
|
1204
|
+
*/
|
|
1205
|
+
function stableHash(value) {
|
|
1206
|
+
const encode = (raw) => {
|
|
1207
|
+
if (raw === void 0) return { __t: "undefined" };
|
|
1208
|
+
if (raw === null || typeof raw !== "object") return raw;
|
|
1209
|
+
if (Array.isArray(raw)) return raw.map(encode);
|
|
1210
|
+
const sorted = {};
|
|
1211
|
+
for (const k of Object.keys(raw).sort()) sorted[k] = encode(raw[k]);
|
|
1212
|
+
return {
|
|
1213
|
+
__t: "object",
|
|
1214
|
+
value: sorted
|
|
1215
|
+
};
|
|
1216
|
+
};
|
|
1217
|
+
return JSON.stringify(encode(value));
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Scopes a load key to one runtime instance for {@link inFlightLoads}.
|
|
1221
|
+
*
|
|
1222
|
+
* The unscoped {@link makeLoadKey} stays the identity used for cycle detection and settled-key
|
|
1223
|
+
* bookkeeping, which are per-load-graph rather than per-runtime.
|
|
1224
|
+
*/
|
|
1225
|
+
function makeInFlightKey(loadScopeId, loadKey) {
|
|
1226
|
+
return `${loadScopeId}::${loadKey}`;
|
|
1227
|
+
}
|
|
1228
|
+
function makeLoadKey(serviceId, queryName, validatedInput) {
|
|
1229
|
+
return `${serviceId}::${queryName}::${stableHash(validatedInput)}`;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Surfaces the first rejected settlement as the thrown error, aggregating the rest under `cause`.
|
|
1233
|
+
*
|
|
1234
|
+
* Drainers use `Promise.allSettled` so one rejected dependency does not prevent siblings from
|
|
1235
|
+
* finishing their work; this helper preserves all rejections without losing the primary one.
|
|
1236
|
+
*/
|
|
1237
|
+
function surfaceRejections(settlements) {
|
|
1238
|
+
const rejections = settlements.filter((s) => s.status === "rejected");
|
|
1239
|
+
if (rejections.length === 0) return;
|
|
1240
|
+
const [first, ...rest] = rejections.map((r) => r.reason);
|
|
1241
|
+
if (rest.length > 0) {
|
|
1242
|
+
if (first instanceof Error) {
|
|
1243
|
+
if (Reflect.get(first, "cause") === void 0) try {
|
|
1244
|
+
Reflect.set(first, "cause", { aggregated: rest });
|
|
1245
|
+
} catch {}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
throw first;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* Drains a collector of pending load promises until no new entries appear.
|
|
1252
|
+
*
|
|
1253
|
+
* Each iteration snapshots the current entries, clears the collector, awaits them with
|
|
1254
|
+
* `Promise.allSettled`, marks their keys as settled (for caller bookkeeping), then loops if the
|
|
1255
|
+
* settled loads' bodies surfaced more entries. Caps at `MAX_DRAIN_ITERATIONS` to avoid hanging on
|
|
1256
|
+
* pathological oscillation.
|
|
1257
|
+
*/
|
|
1258
|
+
async function drainCollector(collector, settledKeys, serviceId, queryName) {
|
|
1259
|
+
let iterations = 0;
|
|
1260
|
+
while (collector.size > 0) {
|
|
1261
|
+
if (iterations++ > MAX_DRAIN_ITERATIONS) throw new OpenServiceLoadedDrainExceededError({
|
|
1262
|
+
serviceId,
|
|
1263
|
+
name: queryName,
|
|
1264
|
+
iterations: MAX_DRAIN_ITERATIONS
|
|
1265
|
+
});
|
|
1266
|
+
const pending = [...collector];
|
|
1267
|
+
collector.clear();
|
|
1268
|
+
const settlements = await Promise.allSettled(pending.map((entry) => entry.promise));
|
|
1269
|
+
if (settledKeys) for (const entry of pending) settledKeys.add(entry.key);
|
|
1270
|
+
surfaceRejections(settlements);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Detaches a value from the reactive deep-signal proxy into a plain snapshot.
|
|
1275
|
+
*
|
|
1276
|
+
* Used for `selector` results on the subscription path: a selector can return a live proxy slice,
|
|
1277
|
+
* which we strip so consumers get plain, comparable data (and reads inside the `computed` register
|
|
1278
|
+
* only the fields the selector actually touched). Primitives pass through untouched.
|
|
1279
|
+
*
|
|
1280
|
+
* `structuredClone` cannot clone a `Proxy`, so this uses a JSON round-trip. That is sufficient
|
|
1281
|
+
* because open-service state is required to be JSON-serializable (the same constraint the
|
|
1282
|
+
* static-build pipeline relies on). For plain (already-detached) values such as the whole-state
|
|
1283
|
+
* snapshot, the runtime uses `structuredClone` directly instead.
|
|
1284
|
+
*/
|
|
1285
|
+
function detachSnapshot(value) {
|
|
1286
|
+
if (value === null || typeof value !== "object") return value;
|
|
1287
|
+
return JSON.parse(JSON.stringify(value));
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Validates query input synchronously, falling through to the dedicated async-schema error if a
|
|
1291
|
+
* schema returns a Promise.
|
|
1292
|
+
*/
|
|
1293
|
+
function validateQueryInput(refs, queryName, queryDef, input) {
|
|
1294
|
+
return validateSchemaSync(queryDef.input, input, {
|
|
1295
|
+
kind: "query",
|
|
1296
|
+
serviceId: refs.serviceId,
|
|
1297
|
+
name: queryName,
|
|
1298
|
+
phase: "input"
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
function validateQueryOutput(refs, queryName, queryDef, output) {
|
|
1302
|
+
return validateSchemaSync(queryDef.output, output, {
|
|
1303
|
+
kind: "query",
|
|
1304
|
+
serviceId: refs.serviceId,
|
|
1305
|
+
name: queryName,
|
|
1306
|
+
phase: "output"
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Runs the query handler synchronously and returns its raw result (no output validation).
|
|
1311
|
+
*
|
|
1312
|
+
* The `selfQueries` parameter lets the caller swap in load-aware wrappers when running inside a
|
|
1313
|
+
* load body or a `.loaded()` discovery pass; ordinary handler calls pass the default queries.
|
|
1314
|
+
*/
|
|
1315
|
+
function runHandlerSync(refs, queryName, queryDef, validatedInput, selfQueries, getService) {
|
|
1316
|
+
if (!queryDef.handler) throw new OpenServiceUnimplementedOperationError({
|
|
1317
|
+
kind: "query",
|
|
1318
|
+
serviceId: refs.serviceId,
|
|
1319
|
+
name: queryName
|
|
1320
|
+
});
|
|
1321
|
+
const handlerCtx = {
|
|
1322
|
+
self: {
|
|
1323
|
+
get state() {
|
|
1324
|
+
return refs.state;
|
|
1325
|
+
},
|
|
1326
|
+
queries: selfQueries
|
|
1327
|
+
},
|
|
1328
|
+
getService
|
|
1329
|
+
};
|
|
1330
|
+
return queryDef.handler(validatedInput, handlerCtx);
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Builds the shared synchronous `get(input)` for a query: validate input → fire the load per the
|
|
1334
|
+
* caller's policy → run the handler against `selfQueries` → validate output.
|
|
1335
|
+
*
|
|
1336
|
+
* The three query surfaces — the default consumer query ({@link createDefaultQuery}), the reactive
|
|
1337
|
+
* subscription-load wrappers ({@link buildReactiveLoadQueries}), and the awaited load-body wrappers
|
|
1338
|
+
* ({@link buildLoadWrappedQueries}) — differ only in (a) which `self.queries` map the handler sees
|
|
1339
|
+
* and (b) how a load-backed read fires its `load`. Everything else (the validate → run → validate
|
|
1340
|
+
* skeleton, the zero-arg input resolution) is identical, so each surface passes its own `selfQueries`
|
|
1341
|
+
* map and `fireLoad` policy and shares this body.
|
|
1342
|
+
*
|
|
1343
|
+
* `fireLoad` is invoked only for queries that declare a `load`; queries without one skip it. Pass
|
|
1344
|
+
* `selfQueries` by reference even when it is the same map being populated by the caller — `get`
|
|
1345
|
+
* closes over the reference, which is fully built by the time any handler runs.
|
|
1346
|
+
*/
|
|
1347
|
+
function createQueryGet(refs, queryName, queryDef, selfQueries, fireLoad) {
|
|
1348
|
+
return function get(input) {
|
|
1349
|
+
const validatedInput = validateQueryInput(refs, queryName, queryDef, arguments.length === 0 ? void 0 : input);
|
|
1350
|
+
if (queryDef.load) fireLoad?.(validatedInput);
|
|
1351
|
+
return validateQueryOutput(refs, queryName, queryDef, runHandlerSync(refs, queryName, queryDef, validatedInput, selfQueries, refs.registryApi.getService));
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Triggers a `load` if one is not already in flight for the same key, or returns the existing
|
|
1356
|
+
* promise so concurrent callers share the work.
|
|
1357
|
+
*
|
|
1358
|
+
* Returns the in-flight promise (whether newly created or reused). The body runs through
|
|
1359
|
+
* {@link runLoadBody} and its promise is registered into {@link inFlightLoads} **before** that
|
|
1360
|
+
* body starts; see the "register before run" comment below for why.
|
|
1361
|
+
*
|
|
1362
|
+
* ### Ancestor chain
|
|
1363
|
+
*
|
|
1364
|
+
* The parent caller passes its own ancestor chain (the load keys it is currently nested
|
|
1365
|
+
* inside). This function extends that chain with the dependency's own key before kicking off the
|
|
1366
|
+
* body, so any transitive read of an ancestor — e.g. `a.load` calls `b.load` which calls `a.load`
|
|
1367
|
+
* again — short-circuits via cycle detection in {@link createDefaultQuery} and
|
|
1368
|
+
* {@link buildLoadWrappedQueries}, rather than deadlocking on its own ancestor's promise.
|
|
1369
|
+
*
|
|
1370
|
+
* ### Cycle example
|
|
1371
|
+
*
|
|
1372
|
+
* Service exposes queries `a` and `b`; `a.load` reads `b`, `b.load` reads `a`.
|
|
1373
|
+
*
|
|
1374
|
+
* 1. `a.loaded()` → `triggerLoad(a, parentChain={})` → body runs with chain `{a}`.
|
|
1375
|
+
* 2. `a.load` body calls `ctx.self.queries.b.get(...)` → wrapper calls
|
|
1376
|
+
* `triggerLoad(b, parentChain={a})` → body runs with chain `{a, b}`.
|
|
1377
|
+
* 3. `b.load` body calls `ctx.self.queries.a.get(...)` → wrapper calls
|
|
1378
|
+
* `triggerLoad(a, parentChain={a, b})`. `a` is already in flight, so the existing promise is
|
|
1379
|
+
* reused. But the wrapper sees `aKey ∈ ancestorChain` and **skips** adding it to b's local
|
|
1380
|
+
* collector — `b` does not wait on `a`'s promise.
|
|
1381
|
+
* 4. Both load bodies progress past their sync reads, await their commands, settle.
|
|
1382
|
+
*/
|
|
1383
|
+
function triggerLoad(refs, queryName, queryDef, validatedInput, loadKey, parentAncestorChain) {
|
|
1384
|
+
const inFlightKey = makeInFlightKey(refs.loadScopeId, loadKey);
|
|
1385
|
+
const existing = inFlightLoads.get(inFlightKey);
|
|
1386
|
+
if (existing) return existing;
|
|
1387
|
+
const extendedChain = new Set(parentAncestorChain);
|
|
1388
|
+
extendedChain.add(loadKey);
|
|
1389
|
+
const promise = Promise.resolve().then(() => runLoadBody(refs, queryName, queryDef, validatedInput, extendedChain)).finally(() => {
|
|
1390
|
+
if (inFlightLoads.get(inFlightKey) === promise) inFlightLoads.delete(inFlightKey);
|
|
1391
|
+
});
|
|
1392
|
+
inFlightLoads.set(inFlightKey, promise);
|
|
1393
|
+
return promise;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Executes one `load` invocation with its own local collector and a wrapped `self`.
|
|
1397
|
+
*
|
|
1398
|
+
* Each `load` invocation gets its **own** collector and its **own** map of wrapped queries. This
|
|
1399
|
+
* matters because the wrappers close over the collector and ancestor chain that belong to this
|
|
1400
|
+
* particular load — a different load running concurrently for a different key has a different
|
|
1401
|
+
* collector and a different chain, so the same `defaultQuery` cannot serve both.
|
|
1402
|
+
*
|
|
1403
|
+
* The wrapper around `self.queries` registers transitively triggered loads into the local
|
|
1404
|
+
* collector. After the user's load body resolves, we still await that collector via
|
|
1405
|
+
* {@link drainCollector} so the returned promise only resolves once every dependency the load
|
|
1406
|
+
* body touched has also settled. That is what gives `.loaded()` its transitive guarantee through
|
|
1407
|
+
* async load bodies: an outer caller awaiting this load promise is, by construction, also
|
|
1408
|
+
* waiting for all descendant loads triggered by self.queries reads.
|
|
1409
|
+
*
|
|
1410
|
+
* Cross-service `ctx.getService(id).queries.*` calls are intentionally **not** wrapped — that
|
|
1411
|
+
* would require recursively wrapping the registry's runtime services and would entangle load
|
|
1412
|
+
* scoping across service boundaries. Authors must use `.loaded()` explicitly when they need a
|
|
1413
|
+
* cross-service dependency awaited from inside a load body.
|
|
1414
|
+
*/
|
|
1415
|
+
async function runLoadBody(refs, queryName, queryDef, validatedInput, ancestorChain) {
|
|
1416
|
+
if (!queryDef.load) return;
|
|
1417
|
+
const collector = /* @__PURE__ */ new Set();
|
|
1418
|
+
const loadCtx = {
|
|
1419
|
+
self: {
|
|
1420
|
+
get state() {
|
|
1421
|
+
return refs.state;
|
|
1422
|
+
},
|
|
1423
|
+
queries: buildLoadWrappedQueries(refs, ancestorChain, collector),
|
|
1424
|
+
commands: refs.getLoadCommands()
|
|
1425
|
+
},
|
|
1426
|
+
getService: refs.registryApi.getService
|
|
1427
|
+
};
|
|
1428
|
+
await Promise.resolve(queryDef.load(validatedInput, loadCtx));
|
|
1429
|
+
await drainCollector(collector, void 0, refs.serviceId, queryName);
|
|
1430
|
+
}
|
|
1431
|
+
/**
|
|
1432
|
+
* Runs a query's `load` as the body of a subscription's reactive effect.
|
|
1433
|
+
*
|
|
1434
|
+
* Unlike {@link runLoadBody}, this is invoked synchronously inside an `effect()`, so the external
|
|
1435
|
+
* signals the load reads in its synchronous prefix are tracked — when they later change, the effect
|
|
1436
|
+
* re-runs and the load re-fires, turning it into a reactive async resource. Loads are therefore an
|
|
1437
|
+
* idempotent warming step (the documented guideline, now a hard contract); one-shot side effects
|
|
1438
|
+
* belong in a command.
|
|
1439
|
+
*
|
|
1440
|
+
* Writes go through gated commands: if `isCurrent()` flips to false because a newer run started
|
|
1441
|
+
* (deps changed again), this run's later writes are dropped, so a slow stale load cannot clobber the
|
|
1442
|
+
* newer result. Sibling reads use the default queries — their loads fire-and-forget — so transitive
|
|
1443
|
+
* dependencies stay warm without the `.loaded()` drain, which is only meaningful for awaited pulls.
|
|
1444
|
+
*/
|
|
1445
|
+
async function runReactiveLoad(refs, queryName, queryDef, validatedInput, isCurrent) {
|
|
1446
|
+
if (!queryDef.load) return;
|
|
1447
|
+
const loadCtx = {
|
|
1448
|
+
self: {
|
|
1449
|
+
get state() {
|
|
1450
|
+
return refs.state;
|
|
1451
|
+
},
|
|
1452
|
+
queries: refs.reactiveLoadQueries,
|
|
1453
|
+
commands: refs.buildGatedCommands(isCurrent)
|
|
1454
|
+
},
|
|
1455
|
+
getService: refs.registryApi.getService
|
|
1456
|
+
};
|
|
1457
|
+
await Promise.resolve(queryDef.load(validatedInput, loadCtx));
|
|
1458
|
+
}
|
|
1459
|
+
/**
|
|
1460
|
+
* Builds the `ctx.self.queries` map used inside a *reactive subscription load* body.
|
|
1461
|
+
*
|
|
1462
|
+
* `.get()` here fires the dependency's `load` fire-and-forget (deduped via {@link inFlightLoads}),
|
|
1463
|
+
* scoped to the reactive-load context only, so a plain consumer `.get()` stays a pure read. There is no drain: a subscription
|
|
1464
|
+
* does not await its dependencies, it re-fires reactively when their tracked state changes.
|
|
1465
|
+
*/
|
|
1466
|
+
function buildReactiveLoadQueries(refs) {
|
|
1467
|
+
const wrappedQueries = {};
|
|
1468
|
+
for (const [name, queryDef] of refs.queryDefinitions) {
|
|
1469
|
+
const defaultQuery = refs.defaultQueries[name];
|
|
1470
|
+
wrappedQueries[name] = {
|
|
1471
|
+
get: createQueryGet(refs, name, queryDef, wrappedQueries, (validatedInput) => {
|
|
1472
|
+
const loadKey = makeLoadKey(refs.serviceId, name, validatedInput);
|
|
1473
|
+
triggerLoad(refs, name, queryDef, validatedInput, loadKey, EMPTY_SET).catch(rethrowAsync);
|
|
1474
|
+
}),
|
|
1475
|
+
loaded: defaultQuery.loaded,
|
|
1476
|
+
subscribe: defaultQuery.subscribe
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
return wrappedQueries;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Builds the wrapped `self.queries` map exposed inside one load body.
|
|
1483
|
+
*
|
|
1484
|
+
* The returned map shadows the runtime's default query map. Each wrapped call still validates
|
|
1485
|
+
* input, fires the dependency's `load` via {@link triggerLoad}, and runs the same sync handler —
|
|
1486
|
+
* but it also **registers the dependency's load promise into the load-local collector** so
|
|
1487
|
+
* `runLoadBody`'s drain can await it before returning. The wrappers therefore turn an ordinary
|
|
1488
|
+
* sync read of a dependency into "fire load + remember it for the outer drain to wait on."
|
|
1489
|
+
*
|
|
1490
|
+
* ### Why one map per load invocation?
|
|
1491
|
+
*
|
|
1492
|
+
* Two separate `load` calls (different keys, possibly different services) have different
|
|
1493
|
+
* `ancestorChain` sets and different `collector` instances. Each wrapped function closes over
|
|
1494
|
+
* those values, so the maps cannot be reused — they are recreated cheaply per invocation. Inside
|
|
1495
|
+
* a single load body, all nested handler reads share **this same** wrapped map (the closure
|
|
1496
|
+
* captures `wrappedQueries` from this scope), so transitive dependency reads continue to register
|
|
1497
|
+
* against the same collector.
|
|
1498
|
+
*
|
|
1499
|
+
* ### Cycle detection
|
|
1500
|
+
*
|
|
1501
|
+
* If the dependency's load key is already on this load's ancestor chain, we still call
|
|
1502
|
+
* `triggerLoad` (it returns the in-flight promise) but we **skip** adding it to the collector.
|
|
1503
|
+
* Adding it would deadlock: the outer load's drain would wait on its own ancestor's promise,
|
|
1504
|
+
* which is itself waiting on this load. See {@link triggerLoad}'s walkthrough for a concrete
|
|
1505
|
+
* `a → b → a` example.
|
|
1506
|
+
*
|
|
1507
|
+
* ### `.loaded()` and `.subscribe` on the wrapped queries
|
|
1508
|
+
*
|
|
1509
|
+
* `.loaded()` on a wrapped query forwards the **current** ancestor chain so a load body author
|
|
1510
|
+
* can write `await ctx.self.queries.foo.loaded(input)` and trust that the resulting drain
|
|
1511
|
+
* inherits the right cycle-protection set. `.subscribe` is passed through unchanged because
|
|
1512
|
+
* subscriptions are never part of a load drain — they have their own lifecycle.
|
|
1513
|
+
*/
|
|
1514
|
+
function buildLoadWrappedQueries(refs, ancestorChain, collector) {
|
|
1515
|
+
const wrappedQueries = {};
|
|
1516
|
+
for (const [name, queryDef] of refs.queryDefinitions) {
|
|
1517
|
+
const defaultQuery = refs.defaultQueries[name];
|
|
1518
|
+
wrappedQueries[name] = {
|
|
1519
|
+
get: createQueryGet(refs, name, queryDef, wrappedQueries, (validatedInput) => {
|
|
1520
|
+
const loadKey = makeLoadKey(refs.serviceId, name, validatedInput);
|
|
1521
|
+
const promise = triggerLoad(refs, name, queryDef, validatedInput, loadKey, ancestorChain);
|
|
1522
|
+
if (!ancestorChain.has(loadKey)) collector.add({
|
|
1523
|
+
key: loadKey,
|
|
1524
|
+
promise
|
|
1525
|
+
});
|
|
1526
|
+
}),
|
|
1527
|
+
loaded(input) {
|
|
1528
|
+
return runLoaded(refs, name, queryDef, arguments.length === 0 ? void 0 : input, ancestorChain);
|
|
1529
|
+
},
|
|
1530
|
+
subscribe: defaultQuery.subscribe
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
return wrappedQueries;
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Implements `query.loaded(input)`.
|
|
1537
|
+
*
|
|
1538
|
+
* Returns a promise that resolves to the validated handler output once this query's `load` and
|
|
1539
|
+
* every dependency the handler transitively reads has settled.
|
|
1540
|
+
*
|
|
1541
|
+
* ### Algorithm
|
|
1542
|
+
*
|
|
1543
|
+
* 1. **Setup** — validate input, build the session (`ancestorChain` extended with this load key,
|
|
1544
|
+
* an empty `collector`, an empty `settledKeys`). The ancestor chain inherits from
|
|
1545
|
+
* `parentAncestorChain` when this is called from inside a wrapped query (so the inner
|
|
1546
|
+
* `.loaded()` shares cycle protection with the outer load body).
|
|
1547
|
+
*
|
|
1548
|
+
* 2. **Fire own load** — if this query has a `load` hook, push its in-flight promise into
|
|
1549
|
+
* `session.collector`. Skip if the key is already on the parent's ancestor chain (we are
|
|
1550
|
+
* inside that load already; we cannot deadlock on ourselves).
|
|
1551
|
+
*
|
|
1552
|
+
* 3. **Drain + discover loop**, up to {@link MAX_DRAIN_ITERATIONS} times:
|
|
1553
|
+
* - Inner loop: while `session.collector` has entries, snapshot them, clear, await with
|
|
1554
|
+
* `Promise.allSettled`, mark their keys in `session.settledKeys`, surface rejections.
|
|
1555
|
+
* - Run the handler synchronously under `activeHandlerLoadSession = session` (a discovery
|
|
1556
|
+
* pass). Sync reads of dependencies inside the handler go through {@link createDefaultQuery}
|
|
1557
|
+
* and register any non-settled, non-ancestor load into `session.collector`.
|
|
1558
|
+
* - If the handler threw, swallow (state might still be partial; a later iteration may fix it).
|
|
1559
|
+
* - If the handler added nothing to the collector, we have converged — exit the loop.
|
|
1560
|
+
*
|
|
1561
|
+
* 4. **Return** — run the handler one final time under the same session (so dependency reads
|
|
1562
|
+
* respect `settledKeys` and do not refire loads) and return the validated output. This is the
|
|
1563
|
+
* value the caller sees. If state is still incomplete at this point the handler may throw, and
|
|
1564
|
+
* that throw propagates.
|
|
1565
|
+
*
|
|
1566
|
+
* ### Worked example: `bar.loaded(input)` where `bar.handler` reads `foo`
|
|
1567
|
+
*
|
|
1568
|
+
* Assume `bar` has no `load` of its own; `foo` does.
|
|
1569
|
+
*
|
|
1570
|
+
* - **Setup**: session = `{ ancestorChain: {barKey}, collector: ∅, settledKeys: ∅ }`. No own load
|
|
1571
|
+
* to fire (`bar.load` is undefined).
|
|
1572
|
+
* - **Iteration 1**:
|
|
1573
|
+
* - Inner drain: collector is empty, skip.
|
|
1574
|
+
* - Discovery pass: handler runs, reads `ctx.self.queries.foo.get(...)`. The default `foo` query
|
|
1575
|
+
* sees `activeHandlerLoadSession === session`, sees that `fooKey` is neither in
|
|
1576
|
+
* `ancestorChain` nor in `settledKeys`, and fires + registers foo's load into
|
|
1577
|
+
* `session.collector`. The handler returns (possibly with stale state).
|
|
1578
|
+
* - `hasMoreWork = true` (collector now has one entry).
|
|
1579
|
+
* - **Iteration 2**:
|
|
1580
|
+
* - Inner drain: `await Promise.allSettled([fooPromise])`, mark `fooKey` settled, surface any
|
|
1581
|
+
* rejection.
|
|
1582
|
+
* - Discovery pass: handler runs again. The default `foo` query is now in `settledKeys`, so
|
|
1583
|
+
* it fires nothing and the collector stays empty.
|
|
1584
|
+
* - `hasMoreWork = false`; exit.
|
|
1585
|
+
* - **Final**: run handler once more (state is now populated by foo's load), validate, return.
|
|
1586
|
+
*
|
|
1587
|
+
* The same machinery handles deeper chains — every settled load may have populated state that
|
|
1588
|
+
* causes the handler to read **more** queries on the next iteration. The loop keeps draining
|
|
1589
|
+
* until the read set stabilizes.
|
|
1590
|
+
*/
|
|
1591
|
+
async function runLoaded(refs, queryName, queryDef, rawInput, parentAncestorChain = EMPTY_SET) {
|
|
1592
|
+
const validatedInput = validateQueryInput(refs, queryName, queryDef, rawInput);
|
|
1593
|
+
const loadKey = makeLoadKey(refs.serviceId, queryName, validatedInput);
|
|
1594
|
+
const ancestorChain = new Set(parentAncestorChain);
|
|
1595
|
+
ancestorChain.add(loadKey);
|
|
1596
|
+
const session = {
|
|
1597
|
+
ancestorChain,
|
|
1598
|
+
collector: /* @__PURE__ */ new Set(),
|
|
1599
|
+
settledKeys: /* @__PURE__ */ new Set()
|
|
1600
|
+
};
|
|
1601
|
+
if (queryDef.load && !parentAncestorChain.has(loadKey)) {
|
|
1602
|
+
const promise = triggerLoad(refs, queryName, queryDef, validatedInput, loadKey, parentAncestorChain);
|
|
1603
|
+
session.collector.add({
|
|
1604
|
+
key: loadKey,
|
|
1605
|
+
promise
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
let iterations = 0;
|
|
1609
|
+
let hasMoreWork = true;
|
|
1610
|
+
while (hasMoreWork) {
|
|
1611
|
+
if (iterations++ > MAX_DRAIN_ITERATIONS) throw new OpenServiceLoadedDrainExceededError({
|
|
1612
|
+
serviceId: refs.serviceId,
|
|
1613
|
+
name: queryName,
|
|
1614
|
+
iterations: MAX_DRAIN_ITERATIONS
|
|
1615
|
+
});
|
|
1616
|
+
while (session.collector.size > 0) {
|
|
1617
|
+
const pending = [...session.collector];
|
|
1618
|
+
session.collector.clear();
|
|
1619
|
+
const settlements = await Promise.allSettled(pending.map((entry) => entry.promise));
|
|
1620
|
+
for (const entry of pending) session.settledKeys.add(entry.key);
|
|
1621
|
+
surfaceRejections(settlements);
|
|
1622
|
+
}
|
|
1623
|
+
const previousSession = activeHandlerLoadSession;
|
|
1624
|
+
activeHandlerLoadSession = session;
|
|
1625
|
+
try {
|
|
1626
|
+
runHandlerSync(refs, queryName, queryDef, validatedInput, refs.defaultQueries, refs.registryApi.getService);
|
|
1627
|
+
} catch {} finally {
|
|
1628
|
+
activeHandlerLoadSession = previousSession;
|
|
1629
|
+
}
|
|
1630
|
+
hasMoreWork = session.collector.size > 0;
|
|
1631
|
+
}
|
|
1632
|
+
const previousSession = activeHandlerLoadSession;
|
|
1633
|
+
activeHandlerLoadSession = session;
|
|
1634
|
+
try {
|
|
1635
|
+
return validateQueryOutput(refs, queryName, queryDef, runHandlerSync(refs, queryName, queryDef, validatedInput, refs.defaultQueries, refs.registryApi.getService));
|
|
1636
|
+
} finally {
|
|
1637
|
+
activeHandlerLoadSession = previousSession;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Creates the default query object exposed on the service runtime as `service.queries.foo`.
|
|
1642
|
+
*
|
|
1643
|
+
* The returned object is what consumers use directly **and** what handlers see in
|
|
1644
|
+
* `ctx.self.queries.foo` (load bodies see a different, wrapped version — see
|
|
1645
|
+
* {@link buildLoadWrappedQueries}). Its surface:
|
|
1646
|
+
*
|
|
1647
|
+
* - **`get(input)`** validates input synchronously, runs the handler against current state, and
|
|
1648
|
+
* returns the validated result. It does **not** fire this query's `load` for an ordinary consumer
|
|
1649
|
+
* read. The one exception is dependency tracking: when a `.get()` runs inside a `.loaded()`
|
|
1650
|
+
* discovery pass (`activeHandlerLoadSession` set), it fires + registers the load into the session
|
|
1651
|
+
* collector so the outer drain awaits it (skipping cycles and already-settled keys). Reactive
|
|
1652
|
+
* subscription loads warm dependencies through {@link buildReactiveLoadQueries} instead.
|
|
1653
|
+
* - **`loaded(input)`** drives the full `.loaded()` drain.
|
|
1654
|
+
* - **`subscribe(...)`** sets up a reactive subscription (this is what fires the reactive `load`).
|
|
1655
|
+
*/
|
|
1656
|
+
function createDefaultQuery(refs, queryName, queryDef) {
|
|
1657
|
+
const resolveInput = (input, argsLength) => argsLength === 0 ? void 0 : input;
|
|
1658
|
+
const get = createQueryGet(refs, queryName, queryDef, refs.defaultQueries, (validatedInput) => {
|
|
1659
|
+
const session = activeHandlerLoadSession;
|
|
1660
|
+
if (!session) return;
|
|
1661
|
+
const loadKey = makeLoadKey(refs.serviceId, queryName, validatedInput);
|
|
1662
|
+
if (!session.ancestorChain.has(loadKey) && !session.settledKeys.has(loadKey)) {
|
|
1663
|
+
const promise = triggerLoad(refs, queryName, queryDef, validatedInput, loadKey, session.ancestorChain);
|
|
1664
|
+
session.collector.add({
|
|
1665
|
+
key: loadKey,
|
|
1666
|
+
promise
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
});
|
|
1670
|
+
const subscribe = ((...args) => {
|
|
1671
|
+
if (args.length === 1 && typeof args[0] === "function") return subscribeToQuery(refs, queryName, queryDef, void 0, void 0, args[0]);
|
|
1672
|
+
if (args.length === 2 && typeof args[0] === "function" && typeof args[1] === "function") return subscribeToQuery(refs, queryName, queryDef, void 0, args[0], args[1]);
|
|
1673
|
+
const [input, selectorOrCallback, maybeCallback] = args;
|
|
1674
|
+
return subscribeToQuery(refs, queryName, queryDef, input, maybeCallback ? selectorOrCallback : void 0, maybeCallback ?? selectorOrCallback);
|
|
1675
|
+
});
|
|
1676
|
+
return {
|
|
1677
|
+
get,
|
|
1678
|
+
loaded(input) {
|
|
1679
|
+
return runLoaded(refs, queryName, queryDef, resolveInput(input, arguments.length));
|
|
1680
|
+
},
|
|
1681
|
+
subscribe
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* Subscribes to a query by running its handler inside a deep-signal-aware `computed()` and
|
|
1686
|
+
* notifying through an `effect()`. The callback receives a {@link QueryState} combining the current
|
|
1687
|
+
* `data` (the synchronous handler result, or the selected slice) with the per-subscription `load`
|
|
1688
|
+
* lifecycle (`status` / `loadStatus` / `error`).
|
|
1689
|
+
*
|
|
1690
|
+
* The first emission is **synchronous**: `subscribe()` invokes the callback once with the current
|
|
1691
|
+
* {@link QueryState} before it returns the unsubscribe handle. The runtime kicks `load` off in the
|
|
1692
|
+
* background but does not wait for it — subscribers see the current state immediately (a load-backed
|
|
1693
|
+
* query already reads `loading`, since the reactive-load effect runs synchronously here) and
|
|
1694
|
+
* follow-up emissions as the load settles (or re-fires) and tracked state changes.
|
|
1695
|
+
*
|
|
1696
|
+
* Three layers keep emissions precise:
|
|
1697
|
+
*
|
|
1698
|
+
* 1. **Fine-grained reads** — the handler (and the optional `selector`) read through the deep-signal
|
|
1699
|
+
* proxy, so the computed only re-runs when the exact fields it touched change. A write to an
|
|
1700
|
+
* unrelated key or field never re-runs the handler.
|
|
1701
|
+
* 2. **Lifecycle signal** — a separate per-subscription signal carries `status`/`loadStatus`/`error`,
|
|
1702
|
+
* updated by the reactive-load effect. The emit effect reads both it and the data computed, so a
|
|
1703
|
+
* status change (e.g. a background re-load) fires the callback even when the selected slice is
|
|
1704
|
+
* unchanged.
|
|
1705
|
+
* 3. **Object dedup** — the whole emitted `QueryState` is compared with the previously emitted one via
|
|
1706
|
+
* `isEqual`; a re-run that produces a deeply-equal state (e.g. a load rewriting an equal payload
|
|
1707
|
+
* with no status change) does not fire the callback.
|
|
1708
|
+
*
|
|
1709
|
+
* Error sources all surface as `status: 'error'` (keeping the last successful `data`): input
|
|
1710
|
+
* validation failure, a synchronous handler / output-validation throw, and a `load` rejection.
|
|
1711
|
+
*/
|
|
1712
|
+
function subscribeToQuery(refs, queryName, queryDef, rawInput, selector, callback) {
|
|
1713
|
+
let active = true;
|
|
1714
|
+
let validatedInput;
|
|
1715
|
+
try {
|
|
1716
|
+
validatedInput = validateQueryInput(refs, queryName, queryDef, rawInput);
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
callback(buildQueryState(void 0, {
|
|
1719
|
+
status: "error",
|
|
1720
|
+
error: toError(error),
|
|
1721
|
+
loadStatus: "idle"
|
|
1722
|
+
}));
|
|
1723
|
+
return () => {
|
|
1724
|
+
active = false;
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
const lifecycle = y({
|
|
1728
|
+
status: queryDef.load ? "pending" : "success",
|
|
1729
|
+
error: void 0,
|
|
1730
|
+
loadStatus: "idle"
|
|
1731
|
+
});
|
|
1732
|
+
let loadTeardown;
|
|
1733
|
+
if (queryDef.load) {
|
|
1734
|
+
let epoch = 0;
|
|
1735
|
+
loadTeardown = j(() => {
|
|
1736
|
+
const myEpoch = ++epoch;
|
|
1737
|
+
const isCurrent = () => myEpoch === epoch;
|
|
1738
|
+
const previous = f(() => lifecycle.value);
|
|
1739
|
+
lifecycle.value = {
|
|
1740
|
+
status: previous.status,
|
|
1741
|
+
error: previous.error,
|
|
1742
|
+
loadStatus: "loading"
|
|
1743
|
+
};
|
|
1744
|
+
runReactiveLoad(refs, queryName, queryDef, validatedInput, isCurrent).then(() => {
|
|
1745
|
+
if (!isCurrent()) return;
|
|
1746
|
+
lifecycle.value = {
|
|
1747
|
+
status: "success",
|
|
1748
|
+
error: void 0,
|
|
1749
|
+
loadStatus: "idle"
|
|
1750
|
+
};
|
|
1751
|
+
}, (error) => {
|
|
1752
|
+
if (!isCurrent()) return;
|
|
1753
|
+
lifecycle.value = {
|
|
1754
|
+
status: "error",
|
|
1755
|
+
error: toError(error),
|
|
1756
|
+
loadStatus: "idle"
|
|
1757
|
+
};
|
|
1758
|
+
});
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
const comp = g$1(() => {
|
|
1762
|
+
const output = runHandlerSync(refs, queryName, queryDef, validatedInput, refs.defaultQueries, refs.registryApi.getService);
|
|
1763
|
+
if (selector) {
|
|
1764
|
+
const validated = f(() => validateQueryOutput(refs, queryName, queryDef, output));
|
|
1765
|
+
selector(output);
|
|
1766
|
+
return detachSnapshot(selector(validated));
|
|
1767
|
+
}
|
|
1768
|
+
return detachSnapshot(validateQueryOutput(refs, queryName, queryDef, output));
|
|
1769
|
+
});
|
|
1770
|
+
let hasEmitted = false;
|
|
1771
|
+
let lastEmitted;
|
|
1772
|
+
let lastData;
|
|
1773
|
+
const teardown = j(() => {
|
|
1774
|
+
const life = lifecycle.value;
|
|
1775
|
+
let data;
|
|
1776
|
+
let status = life.status;
|
|
1777
|
+
let error = life.error;
|
|
1778
|
+
try {
|
|
1779
|
+
data = comp.value;
|
|
1780
|
+
lastData = data;
|
|
1781
|
+
} catch (handlerError) {
|
|
1782
|
+
data = lastData;
|
|
1783
|
+
status = "error";
|
|
1784
|
+
error = toError(handlerError);
|
|
1785
|
+
}
|
|
1786
|
+
if (!active) return;
|
|
1787
|
+
const state = buildQueryState(data, {
|
|
1788
|
+
status,
|
|
1789
|
+
error,
|
|
1790
|
+
loadStatus: life.loadStatus
|
|
1791
|
+
});
|
|
1792
|
+
if (hasEmitted && isEqual(state, lastEmitted)) return;
|
|
1793
|
+
hasEmitted = true;
|
|
1794
|
+
lastEmitted = state;
|
|
1795
|
+
callback(state);
|
|
1796
|
+
});
|
|
1797
|
+
return () => {
|
|
1798
|
+
active = false;
|
|
1799
|
+
teardown();
|
|
1800
|
+
loadTeardown?.();
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
/** Builds the runtime query map for one service runtime. */
|
|
1804
|
+
function buildQueries(refs) {
|
|
1805
|
+
const result = {};
|
|
1806
|
+
for (const [name, queryDef] of refs.queryDefinitions) result[name] = createDefaultQuery(refs, name, queryDef);
|
|
1807
|
+
return result;
|
|
1808
|
+
}
|
|
1809
|
+
var MAX_DRAIN_ITERATIONS, inFlightLoads, nextLoadScopeSequence, nextLoadScopeId, activeHandlerLoadSession, EMPTY_SET;
|
|
1810
|
+
function init_query_runtime() {
|
|
1811
|
+
return (init_query_runtime = __esmMin((() => {
|
|
1812
|
+
init_signals_core_module();
|
|
1813
|
+
init_isEqual();
|
|
1814
|
+
init_server_errors();
|
|
1815
|
+
init_service_validation();
|
|
1816
|
+
MAX_DRAIN_ITERATIONS = 32;
|
|
1817
|
+
inFlightLoads = /* @__PURE__ */ new Map();
|
|
1818
|
+
nextLoadScopeSequence = 0;
|
|
1819
|
+
nextLoadScopeId = () => String(nextLoadScopeSequence += 1);
|
|
1820
|
+
EMPTY_SET = /* @__PURE__ */ new Set();
|
|
1821
|
+
})))();
|
|
1822
|
+
}
|
|
1823
|
+
//#endregion
|
|
1824
|
+
//#region src/shared/open-service/json-patch.ts
|
|
1825
|
+
function parentAt(root, segments) {
|
|
1826
|
+
let current = root;
|
|
1827
|
+
for (let index = 0; index < segments.length - 1; index += 1) {
|
|
1828
|
+
const segment = segments[index];
|
|
1829
|
+
if (isReservedKey(segment) || !isPlainObject$1(current) || !hasOwn(current, segment)) return;
|
|
1830
|
+
current = current[segment];
|
|
1831
|
+
}
|
|
1832
|
+
if (!isPlainObject$1(current)) return;
|
|
1833
|
+
return {
|
|
1834
|
+
parent: current,
|
|
1835
|
+
key: segments[segments.length - 1]
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Apply an RFC 6902 document onto `target` in place.
|
|
1840
|
+
*
|
|
1841
|
+
* `add` and `replace` both upsert. `remove` of a missing key calls `onMissingRemove` and continues.
|
|
1842
|
+
* Incoming values are cloned. On success, `inverse` is reverse apply order. A missing parent, reserved
|
|
1843
|
+
* key, or invalid pointer rolls back every op already applied in this call and returns
|
|
1844
|
+
* `{ ok: false, path }` for that pointer. Any other throw also rolls back and reports
|
|
1845
|
+
* `{ ok: false, path }` for the op that threw.
|
|
1846
|
+
*/
|
|
1847
|
+
function applyJsonPatch(target, patch, onMissingRemove) {
|
|
1848
|
+
const undo = [];
|
|
1849
|
+
const inverse = [];
|
|
1850
|
+
let currentPath = "";
|
|
1851
|
+
try {
|
|
1852
|
+
for (const operation of patch) {
|
|
1853
|
+
currentPath = operation.path;
|
|
1854
|
+
applyOne(target, operation, undo, inverse, onMissingRemove);
|
|
1855
|
+
}
|
|
1856
|
+
return {
|
|
1857
|
+
ok: true,
|
|
1858
|
+
inverse: inverse.toReversed()
|
|
1859
|
+
};
|
|
1860
|
+
} catch (error) {
|
|
1861
|
+
for (const revert of undo.toReversed()) revert();
|
|
1862
|
+
if (error instanceof MissingParentError) return {
|
|
1863
|
+
ok: false,
|
|
1864
|
+
path: error.path
|
|
1865
|
+
};
|
|
1866
|
+
return {
|
|
1867
|
+
ok: false,
|
|
1868
|
+
path: currentPath
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
function applyOne(target, operation, undo, inverse, onMissingRemove) {
|
|
1873
|
+
if (!isRfc6901Pointer(operation.path)) throw new MissingParentError(operation.path);
|
|
1874
|
+
const location = parentAt(target, decodePointer(operation.path));
|
|
1875
|
+
if (!location || isReservedKey(location.key)) throw new MissingParentError(operation.path);
|
|
1876
|
+
const { parent, key } = location;
|
|
1877
|
+
switch (operation.op) {
|
|
1878
|
+
case "add":
|
|
1879
|
+
case "replace": {
|
|
1880
|
+
const existed = hasOwn(parent, key);
|
|
1881
|
+
const previous = existed ? clonePlain(parent[key]) : void 0;
|
|
1882
|
+
parent[key] = clonePlain(operation.value);
|
|
1883
|
+
undo.push(() => {
|
|
1884
|
+
if (existed) parent[key] = previous;
|
|
1885
|
+
else delete parent[key];
|
|
1886
|
+
});
|
|
1887
|
+
if (existed) inverse.push({
|
|
1888
|
+
op: "replace",
|
|
1889
|
+
path: operation.path,
|
|
1890
|
+
value: previous
|
|
1891
|
+
});
|
|
1892
|
+
else inverse.push({
|
|
1893
|
+
op: "remove",
|
|
1894
|
+
path: operation.path
|
|
1895
|
+
});
|
|
1896
|
+
return;
|
|
1897
|
+
}
|
|
1898
|
+
case "remove": {
|
|
1899
|
+
if (!hasOwn(parent, key)) {
|
|
1900
|
+
onMissingRemove(operation.path);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
const previous = clonePlain(parent[key]);
|
|
1904
|
+
delete parent[key];
|
|
1905
|
+
undo.push(() => {
|
|
1906
|
+
parent[key] = previous;
|
|
1907
|
+
});
|
|
1908
|
+
inverse.push({
|
|
1909
|
+
op: "add",
|
|
1910
|
+
path: operation.path,
|
|
1911
|
+
value: previous
|
|
1912
|
+
});
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
var MissingParentError;
|
|
1918
|
+
function init_json_patch() {
|
|
1919
|
+
return (init_json_patch = __esmMin((() => {
|
|
1920
|
+
init_plain_object();
|
|
1921
|
+
init_service_channel();
|
|
1922
|
+
MissingParentError = class extends Error {
|
|
1923
|
+
constructor(path) {
|
|
1924
|
+
super(`Missing parent for JSON Patch path ${path}`);
|
|
1925
|
+
this.name = "MissingParentError";
|
|
1926
|
+
this.path = path;
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
})))();
|
|
1930
|
+
}
|
|
1931
|
+
//#endregion
|
|
1932
|
+
//#region src/shared/open-service/service-sync.ts
|
|
1933
|
+
function vectorCounter(vector, runtimeId) {
|
|
1934
|
+
return vector[runtimeId] ?? 0;
|
|
1935
|
+
}
|
|
1936
|
+
function isEmptyVector(vector) {
|
|
1937
|
+
for (const value of Object.values(vector)) if (value > 0) return false;
|
|
1938
|
+
return true;
|
|
1939
|
+
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Returns whether `left` dominates `right`.
|
|
1942
|
+
*
|
|
1943
|
+
* Every counter in `left` is at least the matching counter in `right`, and at least one is
|
|
1944
|
+
* greater. An empty `right` is dominated by any non-empty `left`, so a fresh joiner hears from
|
|
1945
|
+
* every peer that has writes. Two empty vectors do not dominate each other.
|
|
1946
|
+
*/
|
|
1947
|
+
function vectorDominates(left, right) {
|
|
1948
|
+
if (isEmptyVector(right)) return !isEmptyVector(left);
|
|
1949
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1950
|
+
let greater = false;
|
|
1951
|
+
for (const key of keys) {
|
|
1952
|
+
const leftCount = vectorCounter(left, key);
|
|
1953
|
+
const rightCount = vectorCounter(right, key);
|
|
1954
|
+
if (leftCount < rightCount) return false;
|
|
1955
|
+
if (leftCount > rightCount) greater = true;
|
|
1956
|
+
}
|
|
1957
|
+
return greater;
|
|
1958
|
+
}
|
|
1959
|
+
function vectorsEqual(left, right) {
|
|
1960
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1961
|
+
for (const key of keys) if (vectorCounter(left, key) !== vectorCounter(right, key)) return false;
|
|
1962
|
+
return true;
|
|
1963
|
+
}
|
|
1964
|
+
function vectorsConcurrent(left, right) {
|
|
1965
|
+
return !vectorDominates(left, right) && !vectorDominates(right, left) && !vectorsEqual(left, right);
|
|
1966
|
+
}
|
|
1967
|
+
function formatFrontier(frontier) {
|
|
1968
|
+
const body = Object.keys(frontier.vector).sort().map((key) => `${key}:${frontier.vector[key]}`).join(",");
|
|
1969
|
+
return `{clock:${frontier.clock} vector:{${body}}}`;
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Canonical entry order: ascending `seq`, then ascending `runtimeId` with plain string comparison.
|
|
1973
|
+
*
|
|
1974
|
+
* "Later in the log", "greater stamp", and "wins the path" coincide.
|
|
1975
|
+
*/
|
|
1976
|
+
function compareStamps(left, right) {
|
|
1977
|
+
if (left.seq !== right.seq) return left.seq < right.seq ? -1 : 1;
|
|
1978
|
+
if (left.runtimeId !== right.runtimeId) return left.runtimeId < right.runtimeId ? -1 : 1;
|
|
1979
|
+
return 0;
|
|
1980
|
+
}
|
|
1981
|
+
/**
|
|
1982
|
+
* Applies `source` onto `target` in place while preserving object identity for unchanged branches.
|
|
1983
|
+
*
|
|
1984
|
+
* Open-service runtimes expose their state through deep-signal proxies. Replacing the whole state
|
|
1985
|
+
* object for every incoming snapshot would invalidate all subscriptions, even when only one nested
|
|
1986
|
+
* field changed. This helper instead walks both objects and mutates `target` only where values
|
|
1987
|
+
* differ:
|
|
1988
|
+
*
|
|
1989
|
+
* - Recurses into plain objects so nested deep-signal subscriptions stay attached.
|
|
1990
|
+
* - Replaces arrays wholesale, matching the sync contract that arrays are values rather than maps.
|
|
1991
|
+
* - Assigns primitives only when changed to avoid spurious signal invalidation.
|
|
1992
|
+
* - Copies `source` through `clonePlain` first, like every other value on its way into state, so
|
|
1993
|
+
* untrusted channel payloads and static files cannot pollute prototypes or write deepsignal's
|
|
1994
|
+
* `$` accessors, and state never shares an object with the payload.
|
|
1995
|
+
*
|
|
1996
|
+
* The `preserveMissingKeys` mode selects the source contract:
|
|
1997
|
+
*
|
|
1998
|
+
* - `false` means `source` is a full peer snapshot. Keys missing from `source` are deleted from
|
|
1999
|
+
* `target`, allowing deletions to propagate through cross-peer sync.
|
|
2000
|
+
* - `true` means `source` is a partial static snapshot. Keys missing from `source` are left alone
|
|
2001
|
+
* so snapshots for one static query input do not erase state populated by other inputs.
|
|
2002
|
+
*/
|
|
2003
|
+
function applyStatePatch(target, source, options) {
|
|
2004
|
+
mergeInto(target, clonePlain(source), options.preserveMissingKeys);
|
|
2005
|
+
}
|
|
2006
|
+
function mergeInto(target, source, preserveMissingKeys) {
|
|
2007
|
+
if (!preserveMissingKeys) {
|
|
2008
|
+
for (const key of Object.keys(target)) if (!hasOwn(source, key)) delete target[key];
|
|
2009
|
+
}
|
|
2010
|
+
for (const key of Object.keys(source)) {
|
|
2011
|
+
const sourceValue = source[key];
|
|
2012
|
+
const targetValue = target[key];
|
|
2013
|
+
if (isPlainObject$1(sourceValue) && isPlainObject$1(targetValue)) mergeInto(targetValue, sourceValue, preserveMissingKeys);
|
|
2014
|
+
else if (targetValue !== sourceValue) target[key] = sourceValue;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Builds a {@link Reconciler} bound to one runtime's state.
|
|
2019
|
+
*
|
|
2020
|
+
* `setState` is the runtime's `applyLocal` adapted to a plain record, so placing and installing never
|
|
2021
|
+
* author an entry. `serviceId` names the service in warnings. `window` bounds the Log.
|
|
2022
|
+
*/
|
|
2023
|
+
function createReconciler(options) {
|
|
2024
|
+
const { serviceId, setState } = options;
|
|
2025
|
+
const logWindow = {
|
|
2026
|
+
maxAgeMs: options.window?.maxAgeMs ?? 15e3,
|
|
2027
|
+
maxEntries: options.window?.maxEntries ?? 256
|
|
2028
|
+
};
|
|
2029
|
+
let clock = 0;
|
|
2030
|
+
let snapshotSeq = 0;
|
|
2031
|
+
const vector = /* @__PURE__ */ new Map();
|
|
2032
|
+
const log = [];
|
|
2033
|
+
const logKeys = /* @__PURE__ */ new Set();
|
|
2034
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
2035
|
+
let floor;
|
|
2036
|
+
const vectorRecord = () => Object.fromEntries(vector);
|
|
2037
|
+
const vectorOf = (runtimeId) => vector.get(runtimeId) ?? 0;
|
|
2038
|
+
const hasStamp = (stamp) => logKeys.has(entryStampKey(stamp));
|
|
2039
|
+
const silentMissingRemove = () => void 0;
|
|
2040
|
+
const patchPaths = (patch) => patch.map((operation) => operation.path).join(",");
|
|
2041
|
+
const undoEntry = (current, entry) => {
|
|
2042
|
+
const result = applyJsonPatch(current, entry.inverse, silentMissingRemove);
|
|
2043
|
+
if (!result.ok) logger.warn(`Open-service sync: undo failed. service=${serviceId} stamp=${entryStampKey(entry.stamp)} path=${result.path} command=${entry.command}`);
|
|
2044
|
+
};
|
|
2045
|
+
const redoEntry = (current, entry) => {
|
|
2046
|
+
const result = applyJsonPatch(current, entry.patch, silentMissingRemove);
|
|
2047
|
+
entry.inverse = result.ok ? result.inverse : [];
|
|
2048
|
+
if (!result.ok) logger.warn(`Open-service sync: replay failed. service=${serviceId} stamp=${entryStampKey(entry.stamp)} path=${result.path} command=${entry.command}`);
|
|
2049
|
+
};
|
|
2050
|
+
const undoToIndex = (current, index) => {
|
|
2051
|
+
const undone = [];
|
|
2052
|
+
for (let cursor = log.length - 1; cursor >= index; cursor -= 1) {
|
|
2053
|
+
const entry = log[cursor];
|
|
2054
|
+
undoEntry(current, entry);
|
|
2055
|
+
undone.push(entry);
|
|
2056
|
+
}
|
|
2057
|
+
return undone;
|
|
2058
|
+
};
|
|
2059
|
+
const redoUndone = (current, undone) => {
|
|
2060
|
+
for (let cursor = undone.length - 1; cursor >= 0; cursor -= 1) redoEntry(current, undone[cursor]);
|
|
2061
|
+
};
|
|
2062
|
+
const insertIndexFor = (stamp) => {
|
|
2063
|
+
let index = 0;
|
|
2064
|
+
while (index < log.length && compareStamps(log[index].stamp, stamp) < 0) index += 1;
|
|
2065
|
+
return index;
|
|
2066
|
+
};
|
|
2067
|
+
const tryAdvanceVector = (stamp) => {
|
|
2068
|
+
if (stamp.counter !== vectorOf(stamp.runtimeId) + 1) return;
|
|
2069
|
+
const counters = /* @__PURE__ */ new Set();
|
|
2070
|
+
for (const entry of log) if (entry.stamp.runtimeId === stamp.runtimeId) counters.add(entry.stamp.counter);
|
|
2071
|
+
let next = stamp.counter;
|
|
2072
|
+
while (counters.has(next + 1)) next += 1;
|
|
2073
|
+
vector.set(stamp.runtimeId, next);
|
|
2074
|
+
};
|
|
2075
|
+
const evict = (now) => {
|
|
2076
|
+
const minKeepIndex = Math.max(0, log.length - logWindow.maxEntries);
|
|
2077
|
+
const kept = [];
|
|
2078
|
+
for (let index = 0; index < log.length; index += 1) {
|
|
2079
|
+
const entry = log[index];
|
|
2080
|
+
const amongNewest = index >= minKeepIndex;
|
|
2081
|
+
const young = now - entry.appliedAt < logWindow.maxAgeMs;
|
|
2082
|
+
if (amongNewest || young) kept.push(entry);
|
|
2083
|
+
else {
|
|
2084
|
+
logKeys.delete(entryStampKey(entry.stamp));
|
|
2085
|
+
if (floor === void 0 || compareStamps(entry.stamp, floor) > 0) floor = entry.stamp;
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
if (kept.length === log.length) return;
|
|
2089
|
+
log.length = 0;
|
|
2090
|
+
log.push(...kept);
|
|
2091
|
+
};
|
|
2092
|
+
const remember = (entry) => {
|
|
2093
|
+
logKeys.add(entryStampKey(entry.stamp));
|
|
2094
|
+
tryAdvanceVector(entry.stamp);
|
|
2095
|
+
evict(entry.appliedAt);
|
|
2096
|
+
};
|
|
2097
|
+
const appendOrInsert = (entry, index) => {
|
|
2098
|
+
if (index === log.length) log.push(entry);
|
|
2099
|
+
else log.splice(index, 0, entry);
|
|
2100
|
+
remember(entry);
|
|
2101
|
+
};
|
|
2102
|
+
const advanceClock = (seq) => {
|
|
2103
|
+
if (seq > clock) clock = seq;
|
|
2104
|
+
};
|
|
2105
|
+
return {
|
|
2106
|
+
get clock() {
|
|
2107
|
+
return clock;
|
|
2108
|
+
},
|
|
2109
|
+
get vector() {
|
|
2110
|
+
return vectorRecord();
|
|
2111
|
+
},
|
|
2112
|
+
get frontier() {
|
|
2113
|
+
return {
|
|
2114
|
+
vector: vectorRecord(),
|
|
2115
|
+
clock
|
|
2116
|
+
};
|
|
2117
|
+
},
|
|
2118
|
+
get log() {
|
|
2119
|
+
return log.map(({ stamp, command, patch }) => ({
|
|
2120
|
+
stamp,
|
|
2121
|
+
command,
|
|
2122
|
+
patch
|
|
2123
|
+
}));
|
|
2124
|
+
},
|
|
2125
|
+
has(stamp) {
|
|
2126
|
+
return hasStamp(stamp);
|
|
2127
|
+
},
|
|
2128
|
+
advanceLocal(runtimeId, authored) {
|
|
2129
|
+
const stamp = {
|
|
2130
|
+
seq: clock + 1,
|
|
2131
|
+
runtimeId,
|
|
2132
|
+
counter: vectorOf(runtimeId) + 1
|
|
2133
|
+
};
|
|
2134
|
+
advanceClock(stamp.seq);
|
|
2135
|
+
const now = Date.now();
|
|
2136
|
+
const entry = {
|
|
2137
|
+
stamp,
|
|
2138
|
+
command: authored.command,
|
|
2139
|
+
patch: [...authored.patch],
|
|
2140
|
+
inverse: [...authored.inverse],
|
|
2141
|
+
appliedAt: now
|
|
2142
|
+
};
|
|
2143
|
+
appendOrInsert(entry, log.length);
|
|
2144
|
+
return stamp;
|
|
2145
|
+
},
|
|
2146
|
+
tryInstall(frontier, state) {
|
|
2147
|
+
advanceClock(frontier.clock);
|
|
2148
|
+
const localVector = vectorRecord();
|
|
2149
|
+
if (!vectorDominates(frontier.vector, localVector)) return vectorsConcurrent(frontier.vector, localVector) ? "concurrent" : "not-ahead";
|
|
2150
|
+
snapshotSeq = frontier.clock;
|
|
2151
|
+
const remaining = [];
|
|
2152
|
+
for (const entry of log) {
|
|
2153
|
+
if (entry.stamp.counter <= vectorCounter(frontier.vector, entry.stamp.runtimeId)) continue;
|
|
2154
|
+
if (entry.stamp.seq <= frontier.clock) {
|
|
2155
|
+
logger.warn(`Open-service sync: uncovered entry at or below the reply clock dropped on install. service=${serviceId} stamp=${entryStampKey(entry.stamp)} clock=${frontier.clock} paths=${patchPaths(entry.patch)} command=${entry.command}`);
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
remaining.push(entry);
|
|
2159
|
+
}
|
|
2160
|
+
setState((current) => {
|
|
2161
|
+
applyStatePatch(current, state, { preserveMissingKeys: false });
|
|
2162
|
+
logKeys.clear();
|
|
2163
|
+
dropped.clear();
|
|
2164
|
+
log.length = 0;
|
|
2165
|
+
floor = void 0;
|
|
2166
|
+
vector.clear();
|
|
2167
|
+
for (const [runtimeId, counter] of Object.entries(frontier.vector)) if (counter > 0) vector.set(runtimeId, counter);
|
|
2168
|
+
for (const entry of remaining) {
|
|
2169
|
+
const replayed = { ...entry };
|
|
2170
|
+
redoEntry(current, replayed);
|
|
2171
|
+
log.push(replayed);
|
|
2172
|
+
remember(replayed);
|
|
2173
|
+
}
|
|
2174
|
+
});
|
|
2175
|
+
return "installed";
|
|
2176
|
+
},
|
|
2177
|
+
tryPlaceEntry(incoming) {
|
|
2178
|
+
const { stamp, command, patch } = incoming;
|
|
2179
|
+
const key = entryStampKey(stamp);
|
|
2180
|
+
advanceClock(stamp.seq);
|
|
2181
|
+
if (hasStamp(stamp) || dropped.has(key) || stamp.counter <= vectorOf(stamp.runtimeId)) return "duplicate";
|
|
2182
|
+
if (stamp.seq <= snapshotSeq || floor && compareStamps(stamp, floor) <= 0) {
|
|
2183
|
+
const floorLabel = floor ? `,${entryStampKey(floor)}` : "";
|
|
2184
|
+
logger.warn(`Open-service sync: entry beyond the log window. service=${serviceId} stamps=${key}${floorLabel} paths=${patchPaths(patch)} command=${command}`);
|
|
2185
|
+
dropped.add(key);
|
|
2186
|
+
return "beyond-window";
|
|
2187
|
+
}
|
|
2188
|
+
const gap = stamp.counter > vectorOf(stamp.runtimeId) + 1;
|
|
2189
|
+
const index = insertIndexFor(stamp);
|
|
2190
|
+
const now = Date.now();
|
|
2191
|
+
let failedPath;
|
|
2192
|
+
setState((current) => {
|
|
2193
|
+
let inverse = [];
|
|
2194
|
+
const undone = index === log.length ? [] : undoToIndex(current, index);
|
|
2195
|
+
const result = applyJsonPatch(current, patch, (path) => {
|
|
2196
|
+
logger.debug(`Open-service sync: remove of missing key. service=${serviceId} stamp=${key} path=${path} command=${command}`);
|
|
2197
|
+
});
|
|
2198
|
+
if (result.ok) inverse = result.inverse;
|
|
2199
|
+
else failedPath = result.path;
|
|
2200
|
+
redoUndone(current, undone);
|
|
2201
|
+
appendOrInsert({
|
|
2202
|
+
stamp,
|
|
2203
|
+
command,
|
|
2204
|
+
patch: [...patch],
|
|
2205
|
+
inverse,
|
|
2206
|
+
appliedAt: now
|
|
2207
|
+
}, index);
|
|
2208
|
+
});
|
|
2209
|
+
if (failedPath !== void 0) logger.warn(`Open-service sync: entry did not apply, kept as a no-op. service=${serviceId} stamp=${key} path=${failedPath} command=${command}`);
|
|
2210
|
+
if (gap) logger.warn(`Open-service sync: gap in writer counters. service=${serviceId} stamps=${key} paths=${patchPaths(patch)} command=${command}`);
|
|
2211
|
+
return gap ? "gap" : failedPath !== void 0 ? "unapplied" : "accepted";
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
}
|
|
2215
|
+
function init_service_sync() {
|
|
2216
|
+
return (init_service_sync = __esmMin((() => {
|
|
2217
|
+
init_client_logger();
|
|
2218
|
+
init_json_patch();
|
|
2219
|
+
init_plain_object();
|
|
2220
|
+
init_service_channel();
|
|
2221
|
+
})))();
|
|
2222
|
+
}
|
|
2223
|
+
//#endregion
|
|
2224
|
+
//#region src/shared/open-service/service-runtime.ts
|
|
2225
|
+
/**
|
|
2226
|
+
* Resolves which serialized static-state file should back a query input.
|
|
2227
|
+
*
|
|
2228
|
+
* The returned value is a logical slash-separated store key scoped under the service id, not a raw
|
|
2229
|
+
* filesystem path.
|
|
2230
|
+
*/
|
|
2231
|
+
function normalizeStaticStoragePath(serviceId, name, rawPath) {
|
|
2232
|
+
const segments = rawPath.replaceAll("\\", "/").split("/").filter((segment) => segment.length > 0 && segment !== ".");
|
|
2233
|
+
if (segments.length === 0 || segments.some((segment) => segment === "..")) throw new OpenServiceInvalidStaticPathError({
|
|
2234
|
+
serviceId,
|
|
2235
|
+
name,
|
|
2236
|
+
path: rawPath
|
|
2237
|
+
});
|
|
2238
|
+
return segments.join("/");
|
|
2239
|
+
}
|
|
2240
|
+
function resolveStaticPath(serviceId, name, queryDef, input) {
|
|
2241
|
+
return `${serviceId}/${normalizeStaticStoragePath(serviceId, name, queryDef.staticPath(input))}`;
|
|
2242
|
+
}
|
|
2243
|
+
/**
|
|
2244
|
+
* Builds the runtime command map from the declarative command definitions.
|
|
2245
|
+
*
|
|
2246
|
+
* Each runtime command validates raw caller input, invokes the handler with parsed values, and
|
|
2247
|
+
* validates the resolved output before returning it to the caller.
|
|
2248
|
+
*/
|
|
2249
|
+
function buildCommands(serviceId, commands, createCommandCtx) {
|
|
2250
|
+
return Object.fromEntries(Object.entries(commands).map(([name, def]) => {
|
|
2251
|
+
return [name, async (input) => {
|
|
2252
|
+
if (!def.handler) throw new OpenServiceUnimplementedOperationError({
|
|
2253
|
+
kind: "command",
|
|
2254
|
+
serviceId,
|
|
2255
|
+
name
|
|
2256
|
+
});
|
|
2257
|
+
const validatedInput = await validateSchema(def.input, input, {
|
|
2258
|
+
kind: "command",
|
|
2259
|
+
serviceId,
|
|
2260
|
+
name,
|
|
2261
|
+
phase: "input"
|
|
2262
|
+
});
|
|
2263
|
+
const output = await def.handler(validatedInput, createCommandCtx(name));
|
|
2264
|
+
return validateSchema(def.output, output, {
|
|
2265
|
+
kind: "command",
|
|
2266
|
+
serviceId,
|
|
2267
|
+
name,
|
|
2268
|
+
phase: "output"
|
|
2269
|
+
});
|
|
2270
|
+
}];
|
|
2271
|
+
}));
|
|
2272
|
+
}
|
|
2273
|
+
/**
|
|
2274
|
+
* When a browser static loader is active, queries with `staticPath` fetch prebuilt JSON instead of
|
|
2275
|
+
* running their authored `load` hook (which typically invokes server-only commands).
|
|
2276
|
+
*/
|
|
2277
|
+
function buildQueryDefinitionsWithStaticLoader(serviceId, queries, staticLoader, applyLocal) {
|
|
2278
|
+
return new Map(Object.entries(queries).map(([name, queryDef]) => {
|
|
2279
|
+
if (!queryDef.staticPath) return [name, queryDef];
|
|
2280
|
+
const { staticPath } = queryDef;
|
|
2281
|
+
return [name, {
|
|
2282
|
+
...queryDef,
|
|
2283
|
+
load: async (input) => {
|
|
2284
|
+
const snapshot = await staticLoader(resolveStaticPath(serviceId, name, { staticPath }, input), {
|
|
2285
|
+
serviceId,
|
|
2286
|
+
queryName: name,
|
|
2287
|
+
input
|
|
2288
|
+
});
|
|
2289
|
+
applyLocal((state) => {
|
|
2290
|
+
applyStatePatch(state, snapshot, { preserveMissingKeys: true });
|
|
2291
|
+
});
|
|
2292
|
+
}
|
|
2293
|
+
}];
|
|
2294
|
+
}));
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Creates the full runtime backing for a service definition.
|
|
2298
|
+
*
|
|
2299
|
+
* Callers must supply the registry API that query and command contexts should expose.
|
|
2300
|
+
*/
|
|
2301
|
+
function createServiceRuntime(def, runtimeOptions, initialState = def.initialState) {
|
|
2302
|
+
const rawState = clonePlain(initialState);
|
|
2303
|
+
const state = l(rawState);
|
|
2304
|
+
const getStateSnapshot = () => structuredClone(rawState);
|
|
2305
|
+
const { registryApi, staticLoader } = runtimeOptions;
|
|
2306
|
+
let author = () => {};
|
|
2307
|
+
const applyLocal = (mutate) => {
|
|
2308
|
+
n(() => {
|
|
2309
|
+
mutate(state);
|
|
2310
|
+
});
|
|
2311
|
+
};
|
|
2312
|
+
const writeState = (command, mutate) => {
|
|
2313
|
+
recordPatch(state, mutate, (recorded) => author({
|
|
2314
|
+
command,
|
|
2315
|
+
...recorded
|
|
2316
|
+
}));
|
|
2317
|
+
};
|
|
2318
|
+
const defaultQueries = {};
|
|
2319
|
+
const reactiveLoadQueries = {};
|
|
2320
|
+
const createCommandSelf = (setState, commandsForSelf) => ({
|
|
2321
|
+
get state() {
|
|
2322
|
+
return state;
|
|
2323
|
+
},
|
|
2324
|
+
setState,
|
|
2325
|
+
queries: defaultQueries,
|
|
2326
|
+
get commands() {
|
|
2327
|
+
return commandsForSelf();
|
|
2328
|
+
}
|
|
2329
|
+
});
|
|
2330
|
+
const commands = buildCommands(def.id, def.commands, (commandName) => ({
|
|
2331
|
+
self: createCommandSelf((mutate) => writeState(commandName, mutate), () => commands),
|
|
2332
|
+
getService: registryApi.getService
|
|
2333
|
+
}));
|
|
2334
|
+
let loadCommands = commands;
|
|
2335
|
+
const remoteCommandNames = /* @__PURE__ */ new Set();
|
|
2336
|
+
const queryDefinitions = staticLoader ? buildQueryDefinitionsWithStaticLoader(def.id, def.queries, staticLoader, applyLocal) : new Map(Object.entries(def.queries));
|
|
2337
|
+
const buildGatedCommands = (isCurrent) => {
|
|
2338
|
+
const gated = buildCommands(def.id, def.commands, (commandName) => ({
|
|
2339
|
+
self: createCommandSelf((mutate) => {
|
|
2340
|
+
if (isCurrent()) writeState(commandName, mutate);
|
|
2341
|
+
}, () => gated),
|
|
2342
|
+
getService: registryApi.getService
|
|
2343
|
+
}));
|
|
2344
|
+
if (remoteCommandNames.size === 0) return gated;
|
|
2345
|
+
return Object.fromEntries(Object.keys(def.commands).map((name) => [name, remoteCommandNames.has(name) ? loadCommands[name] : gated[name]]));
|
|
2346
|
+
};
|
|
2347
|
+
const loadScopeId = nextLoadScopeId();
|
|
2348
|
+
const refs = {
|
|
2349
|
+
serviceId: def.id,
|
|
2350
|
+
loadScopeId,
|
|
2351
|
+
state,
|
|
2352
|
+
registryApi,
|
|
2353
|
+
queryDefinitions,
|
|
2354
|
+
defaultQueries,
|
|
2355
|
+
reactiveLoadQueries,
|
|
2356
|
+
getLoadCommands: () => loadCommands,
|
|
2357
|
+
buildGatedCommands
|
|
2358
|
+
};
|
|
2359
|
+
const builtQueries = buildQueries(refs);
|
|
2360
|
+
for (const [name, query] of Object.entries(builtQueries)) defaultQueries[name] = query;
|
|
2361
|
+
for (const [name, query] of Object.entries(buildReactiveLoadQueries(refs))) reactiveLoadQueries[name] = query;
|
|
2362
|
+
const queries = defaultQueries;
|
|
2363
|
+
const queryCtx = {
|
|
2364
|
+
self: {
|
|
2365
|
+
get state() {
|
|
2366
|
+
return state;
|
|
2367
|
+
},
|
|
2368
|
+
queries: defaultQueries
|
|
2369
|
+
},
|
|
2370
|
+
getService: registryApi.getService
|
|
2371
|
+
};
|
|
2372
|
+
const loadCtxForStatic = {
|
|
2373
|
+
self: {
|
|
2374
|
+
get state() {
|
|
2375
|
+
return state;
|
|
2376
|
+
},
|
|
2377
|
+
queries: defaultQueries,
|
|
2378
|
+
commands
|
|
2379
|
+
},
|
|
2380
|
+
getService: registryApi.getService
|
|
2381
|
+
};
|
|
2382
|
+
/**
|
|
2383
|
+
* Runs one query's `load` body against this runtime instance, drained to completion.
|
|
2384
|
+
*
|
|
2385
|
+
* Used by the static build pipeline to populate state for a single input without holding the
|
|
2386
|
+
* load in the in-flight registry afterwards.
|
|
2387
|
+
*/
|
|
2388
|
+
const runLoadOnce = async (queryName, validatedInput) => {
|
|
2389
|
+
const queryDef = queryDefinitions.get(queryName);
|
|
2390
|
+
if (!queryDef || !queryDef.load) return;
|
|
2391
|
+
const loadKey = makeLoadKey(def.id, queryName, validatedInput);
|
|
2392
|
+
const ancestorChain = /* @__PURE__ */ new Set([loadKey]);
|
|
2393
|
+
const inFlightKey = makeInFlightKey(loadScopeId, loadKey);
|
|
2394
|
+
const promise = Promise.resolve().then(() => runLoadBody(refs, queryName, queryDef, validatedInput, ancestorChain)).finally(() => {
|
|
2395
|
+
if (inFlightLoads.get(inFlightKey) === promise) inFlightLoads.delete(inFlightKey);
|
|
2396
|
+
});
|
|
2397
|
+
inFlightLoads.set(inFlightKey, promise);
|
|
2398
|
+
await promise;
|
|
2399
|
+
};
|
|
2400
|
+
const attachChannelCommands = (channelCommands, implementedCommandNames) => {
|
|
2401
|
+
loadCommands = channelCommands;
|
|
2402
|
+
remoteCommandNames.clear();
|
|
2403
|
+
for (const name of Object.keys(def.commands)) if (!implementedCommandNames.has(name)) remoteCommandNames.add(name);
|
|
2404
|
+
};
|
|
2405
|
+
return {
|
|
2406
|
+
getStateSnapshot,
|
|
2407
|
+
applyLocal,
|
|
2408
|
+
attachEntryAuthor: (next) => {
|
|
2409
|
+
author = next;
|
|
2410
|
+
},
|
|
2411
|
+
queryCtx,
|
|
2412
|
+
loadCtxForStatic,
|
|
2413
|
+
commands,
|
|
2414
|
+
queries,
|
|
2415
|
+
runLoadOnce,
|
|
2416
|
+
attachChannelCommands
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
function init_service_runtime() {
|
|
2420
|
+
return (init_service_runtime = __esmMin((() => {
|
|
2421
|
+
init_signals_core_module();
|
|
2422
|
+
init_deepsignal_core_module();
|
|
2423
|
+
init_server_errors();
|
|
2424
|
+
init_patch_recorder();
|
|
2425
|
+
init_plain_object();
|
|
2426
|
+
init_query_runtime();
|
|
2427
|
+
init_service_sync();
|
|
2428
|
+
init_service_validation();
|
|
2429
|
+
})))();
|
|
2430
|
+
}
|
|
2431
|
+
//#endregion
|
|
2432
|
+
//#region src/shared/open-service/service-error-serialization.ts
|
|
2433
|
+
function isPlainObject(value) {
|
|
2434
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2435
|
+
}
|
|
2436
|
+
function isSerializedError(value) {
|
|
2437
|
+
return isPlainObject(value) && value[ERROR_MARKER] === true;
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Reduces any value to one a channel transport can clone.
|
|
2441
|
+
*
|
|
2442
|
+
* Functions, symbols, and `undefined` are dropped (structuredClone throws on functions/symbols);
|
|
2443
|
+
* `bigint` is stringified; plain objects and arrays are walked; `Error`s become {@link SerializedError}.
|
|
2444
|
+
*/
|
|
2445
|
+
function toTransportSafe(value) {
|
|
2446
|
+
if (value instanceof Error) return serializeError(value);
|
|
2447
|
+
if (Array.isArray(value)) return value.map((entry) => toTransportSafe(entry));
|
|
2448
|
+
if (isPlainObject(value)) {
|
|
2449
|
+
const result = {};
|
|
2450
|
+
for (const [key, entry] of Object.entries(value)) result[key] = toTransportSafe(entry);
|
|
2451
|
+
return result;
|
|
2452
|
+
}
|
|
2453
|
+
if (value === null) return null;
|
|
2454
|
+
const kind = typeof value;
|
|
2455
|
+
if (kind === "string" || kind === "number" || kind === "boolean") return value;
|
|
2456
|
+
if (kind === "bigint") return value.toString();
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Converts a thrown value into a transport-safe {@link SerializedError}.
|
|
2460
|
+
*
|
|
2461
|
+
* Non-`Error` throws (a string, an object, …) are wrapped in a synthetic error whose message is the
|
|
2462
|
+
* stringified value, so the requester always receives a real `Error` to reject with.
|
|
2463
|
+
*/
|
|
2464
|
+
function serializeError(value) {
|
|
2465
|
+
if (!(value instanceof Error)) return {
|
|
2466
|
+
[ERROR_MARKER]: true,
|
|
2467
|
+
name: "Error",
|
|
2468
|
+
message: String(value)
|
|
2469
|
+
};
|
|
2470
|
+
const properties = {};
|
|
2471
|
+
for (const key of Object.keys(value)) if (!RESERVED_KEYS.has(key)) properties[key] = toTransportSafe(value[key]);
|
|
2472
|
+
return {
|
|
2473
|
+
[ERROR_MARKER]: true,
|
|
2474
|
+
name: value.name,
|
|
2475
|
+
message: value.message,
|
|
2476
|
+
...value.stack !== void 0 ? { stack: value.stack } : {},
|
|
2477
|
+
...value.cause !== void 0 ? { cause: toTransportSafe(value.cause) } : {},
|
|
2478
|
+
...Object.keys(properties).length > 0 ? { properties } : {}
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
/** Inverse of {@link toTransportSafe}: rebuilds nested errors while passing other values through. */
|
|
2482
|
+
function fromTransportSafe(value) {
|
|
2483
|
+
if (isSerializedError(value)) return deserializeError(value);
|
|
2484
|
+
if (Array.isArray(value)) return value.map((entry) => fromTransportSafe(entry));
|
|
2485
|
+
if (isPlainObject(value)) {
|
|
2486
|
+
const result = {};
|
|
2487
|
+
for (const [key, entry] of Object.entries(value)) result[key] = fromTransportSafe(entry);
|
|
2488
|
+
return result;
|
|
2489
|
+
}
|
|
2490
|
+
return value;
|
|
2491
|
+
}
|
|
2492
|
+
/**
|
|
2493
|
+
* Rebuilds an `Error` from a {@link SerializedError}, restoring its `name`, `stack`, `cause` chain,
|
|
2494
|
+
* and any extra fields (so a Forkbook error arrives with `code`/`fromStorybook` intact).
|
|
2495
|
+
*
|
|
2496
|
+
* The result is a plain `Error`, not the original subclass — reconstructing arbitrary classes across
|
|
2497
|
+
* a realm boundary is neither possible nor needed; callers branch on the restored fields instead.
|
|
2498
|
+
*/
|
|
2499
|
+
function deserializeError(serialized) {
|
|
2500
|
+
const error = new Error(serialized.message);
|
|
2501
|
+
error.name = serialized.name;
|
|
2502
|
+
if (serialized.stack !== void 0) error.stack = serialized.stack;
|
|
2503
|
+
if ("cause" in serialized) error.cause = fromTransportSafe(serialized.cause);
|
|
2504
|
+
if (serialized.properties) for (const [key, value] of Object.entries(serialized.properties)) error[key] = fromTransportSafe(value);
|
|
2505
|
+
return error;
|
|
2506
|
+
}
|
|
2507
|
+
var ERROR_MARKER, RESERVED_KEYS;
|
|
2508
|
+
function init_service_error_serialization() {
|
|
2509
|
+
return (init_service_error_serialization = __esmMin((() => {
|
|
2510
|
+
ERROR_MARKER = "__openServiceError__";
|
|
2511
|
+
RESERVED_KEYS = /* @__PURE__ */ new Set([
|
|
2512
|
+
ERROR_MARKER,
|
|
2513
|
+
"name",
|
|
2514
|
+
"message",
|
|
2515
|
+
"stack",
|
|
2516
|
+
"cause"
|
|
2517
|
+
]);
|
|
2518
|
+
})))();
|
|
2519
|
+
}
|
|
2520
|
+
//#endregion
|
|
2521
|
+
//#region src/shared/open-service/service-transport.ts
|
|
2522
|
+
/**
|
|
2523
|
+
* Builds the receiver for the entries a runtime's `setState` recipes author.
|
|
2524
|
+
*
|
|
2525
|
+
* Each entry is stamped BEFORE it is emitted, so the copy that bounces back carries a stamp this
|
|
2526
|
+
* runtime has already seen and is dropped as a duplicate.
|
|
2527
|
+
*/
|
|
2528
|
+
function createEntryAuthor(context) {
|
|
2529
|
+
const { serviceId, ownRuntimeId, reconciler, channel } = context;
|
|
2530
|
+
return ({ command, ops, inverse }) => {
|
|
2531
|
+
const stamp = reconciler.advanceLocal(ownRuntimeId, {
|
|
2532
|
+
command,
|
|
2533
|
+
patch: ops,
|
|
2534
|
+
inverse
|
|
2535
|
+
});
|
|
2536
|
+
channel.emit(SERVICE_ENTRY, {
|
|
2537
|
+
serviceId,
|
|
2538
|
+
stamp,
|
|
2539
|
+
command,
|
|
2540
|
+
patch: ops
|
|
2541
|
+
});
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Attaches the channel listeners that keep one runtime in sync with its peers, and returns a teardown.
|
|
2546
|
+
*
|
|
2547
|
+
* Wires three handlers and emits a bootstrap `services:sync-request` so a freshly-registered runtime
|
|
2548
|
+
* catches up to state authored before it joined:
|
|
2549
|
+
* - sync-request → reply `{ frontier, state }` only when our vector dominates the requester's;
|
|
2550
|
+
* - sync-reply → install only if dominating (and, on a relay hub, forward the original payload);
|
|
2551
|
+
* - entry → place by the five-case rule (and, on a relay hub, forward when logged or dropped as
|
|
2552
|
+
* beyond-window for the first time).
|
|
2553
|
+
*
|
|
2554
|
+
* A hub forwards the original payload object so unknown fields survive the hop. Leaves keep
|
|
2555
|
+
* `relay: false`. The request policy (one outstanding, {@link SYNC_REQUEST_SILENCE_MS} of silence,
|
|
2556
|
+
* one queued repair) is in the README's Bootstrap and repair section.
|
|
2557
|
+
*/
|
|
2558
|
+
function connectRuntimeToChannel(context) {
|
|
2559
|
+
const { serviceId, ownRuntimeId, reconciler, getSnapshot, channel, relay } = context;
|
|
2560
|
+
let requestOutstanding = false;
|
|
2561
|
+
let withinReplyWindow = false;
|
|
2562
|
+
let repairQueued = false;
|
|
2563
|
+
let silenceTimer;
|
|
2564
|
+
let forwardedReplyKey;
|
|
2565
|
+
const clearSilenceTimer = () => {
|
|
2566
|
+
if (silenceTimer !== void 0) {
|
|
2567
|
+
clearTimeout(silenceTimer);
|
|
2568
|
+
silenceTimer = void 0;
|
|
2569
|
+
}
|
|
2570
|
+
};
|
|
2571
|
+
const emitSyncRequest = () => {
|
|
2572
|
+
if (requestOutstanding) {
|
|
2573
|
+
repairQueued = true;
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
requestOutstanding = true;
|
|
2577
|
+
withinReplyWindow = true;
|
|
2578
|
+
repairQueued = false;
|
|
2579
|
+
channel.emit(SERVICE_SYNC_REQUEST, {
|
|
2580
|
+
serviceId,
|
|
2581
|
+
runtimeId: ownRuntimeId,
|
|
2582
|
+
frontier: reconciler.frontier
|
|
2583
|
+
});
|
|
2584
|
+
clearSilenceTimer();
|
|
2585
|
+
silenceTimer = setTimeout(() => {
|
|
2586
|
+
silenceTimer = void 0;
|
|
2587
|
+
withinReplyWindow = false;
|
|
2588
|
+
settleOutstandingRequest();
|
|
2589
|
+
}, SYNC_REQUEST_SILENCE_MS);
|
|
2590
|
+
};
|
|
2591
|
+
const settleOutstandingRequest = () => {
|
|
2592
|
+
const queued = repairQueued;
|
|
2593
|
+
requestOutstanding = false;
|
|
2594
|
+
if (queued) emitSyncRequest();
|
|
2595
|
+
};
|
|
2596
|
+
const onSyncRequest = (payload) => {
|
|
2597
|
+
const request = safeParse(syncRequestSchema, payload);
|
|
2598
|
+
if (!request.success || request.output.serviceId !== serviceId || request.output.runtimeId === ownRuntimeId) return;
|
|
2599
|
+
if (!vectorDominates(reconciler.vector, request.output.frontier.vector)) return;
|
|
2600
|
+
channel.emit(SERVICE_SYNC_REPLY, {
|
|
2601
|
+
serviceId,
|
|
2602
|
+
runtimeId: ownRuntimeId,
|
|
2603
|
+
frontier: reconciler.frontier,
|
|
2604
|
+
state: getSnapshot()
|
|
2605
|
+
});
|
|
2606
|
+
};
|
|
2607
|
+
const onSyncReply = (payload) => {
|
|
2608
|
+
const snapshot = safeParse(syncReplySchema, payload);
|
|
2609
|
+
if (!snapshot.success || snapshot.output.serviceId !== serviceId || snapshot.output.runtimeId === ownRuntimeId) return;
|
|
2610
|
+
const replyKey = `${snapshot.output.runtimeId}:${formatFrontier(snapshot.output.frontier)}`;
|
|
2611
|
+
if (replyKey === forwardedReplyKey) return;
|
|
2612
|
+
let outcome;
|
|
2613
|
+
try {
|
|
2614
|
+
outcome = reconciler.tryInstall(snapshot.output.frontier, snapshot.output.state);
|
|
2615
|
+
} finally {
|
|
2616
|
+
const settled = outcome ?? "installed";
|
|
2617
|
+
if (settled === "concurrent" && withinReplyWindow) {
|
|
2618
|
+
logger.warn(`Open-service sync: concurrent snapshot reply dropped. service=${serviceId} local=${formatFrontier(reconciler.frontier)} reply=${formatFrontier(snapshot.output.frontier)}`);
|
|
2619
|
+
repairQueued = true;
|
|
2620
|
+
}
|
|
2621
|
+
settleOutstandingRequest();
|
|
2622
|
+
if (settled === "installed" && relay) {
|
|
2623
|
+
forwardedReplyKey = replyKey;
|
|
2624
|
+
channel.emit(SERVICE_SYNC_REPLY, payload);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
};
|
|
2628
|
+
const onEntry = (payload) => {
|
|
2629
|
+
const parsed = safeParse(entrySchema, payload);
|
|
2630
|
+
if (!parsed.success || parsed.output.serviceId !== serviceId) return;
|
|
2631
|
+
let outcome;
|
|
2632
|
+
try {
|
|
2633
|
+
outcome = reconciler.tryPlaceEntry(parsed.output);
|
|
2634
|
+
} finally {
|
|
2635
|
+
const placed = outcome ?? (reconciler.has(parsed.output.stamp) ? "unapplied" : void 0);
|
|
2636
|
+
const logged = placed === "accepted" || placed === "gap" || placed === "unapplied";
|
|
2637
|
+
const needsRepair = placed === "gap" || placed === "beyond-window" || placed === "unapplied";
|
|
2638
|
+
if ((logged || placed === "beyond-window") && relay) channel.emit(SERVICE_ENTRY, payload);
|
|
2639
|
+
if (needsRepair) emitSyncRequest();
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
channel.on(SERVICE_SYNC_REQUEST, onSyncRequest);
|
|
2643
|
+
channel.on(SERVICE_SYNC_REPLY, onSyncReply);
|
|
2644
|
+
channel.on(SERVICE_ENTRY, onEntry);
|
|
2645
|
+
emitSyncRequest();
|
|
2646
|
+
return () => {
|
|
2647
|
+
channel.off(SERVICE_SYNC_REQUEST, onSyncRequest);
|
|
2648
|
+
channel.off(SERVICE_SYNC_REPLY, onSyncReply);
|
|
2649
|
+
channel.off(SERVICE_ENTRY, onEntry);
|
|
2650
|
+
repairQueued = false;
|
|
2651
|
+
requestOutstanding = false;
|
|
2652
|
+
withinReplyWindow = false;
|
|
2653
|
+
clearSilenceTimer();
|
|
2654
|
+
};
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* Wires the remote-command-execution protocol for one service runtime, returning the command map
|
|
2658
|
+
* callers should use plus a teardown.
|
|
2659
|
+
*
|
|
2660
|
+
* ## Why this exists
|
|
2661
|
+
*
|
|
2662
|
+
* A command handler can be supplied at registration in only *some* runtimes — the canonical case is
|
|
2663
|
+
* a server-only handler that needs Node APIs or server context. Runtimes without a local handler
|
|
2664
|
+
* still expose the command (so `useServiceCommand`, tests, and other services can call it), but they
|
|
2665
|
+
* cannot run it themselves; they must ask a peer that can.
|
|
2666
|
+
*
|
|
2667
|
+
* ## Protocol
|
|
2668
|
+
*
|
|
2669
|
+
* - **Requester** (no local handler): the returned command emits `services:command-invoke` with a
|
|
2670
|
+
* unique `callId` and returns a promise. The promise resolves with the first
|
|
2671
|
+
* `services:command-result` for that `callId`, or rejects with the (deserialized) error from the
|
|
2672
|
+
* first `services:command-error`.
|
|
2673
|
+
* - **Responder** (has a local handler): on a matching `services:command-invoke` it emits
|
|
2674
|
+
* `services:command-ack` immediately, runs the command locally on a deferred macrotask — so an
|
|
2675
|
+
* async channel flushes the ack before any handler work starts (each `setState` write is
|
|
2676
|
+
* broadcast by the entry author, so peers converge as usual) — then emits
|
|
2677
|
+
* `services:command-result` or `services:command-error`.
|
|
2678
|
+
* - **Non-implementer**: a non-delegated runtime that hosts the service but not the invoked
|
|
2679
|
+
* command's handler replies `services:command-unhandled` instead of staying silent. Only a
|
|
2680
|
+
* delegated requester acts on that reply (rejecting with config-drift guidance, since the
|
|
2681
|
+
* Forkbook it attached to is the only runtime that sees its invokes); other requesters ignore
|
|
2682
|
+
* it, because one peer's report cannot speak for the others.
|
|
2683
|
+
*
|
|
2684
|
+
* Both roles coexist on one runtime: it responds for the commands it implements and requests the
|
|
2685
|
+
* ones it does not. A runtime never requests a command it implements (it runs that locally), so it
|
|
2686
|
+
* never answers its own invoke echo.
|
|
2687
|
+
*
|
|
2688
|
+
* ## Multiple implementers
|
|
2689
|
+
*
|
|
2690
|
+
* If several peers implement the same command they will each run it and reply. The requester keeps
|
|
2691
|
+
* only the first reply per `callId` and ignores the rest. Running a command in more than one runtime
|
|
2692
|
+
* is therefore possible by construction; it is constrained by usage conventions and documentation
|
|
2693
|
+
* rather than enforced here.
|
|
2694
|
+
*
|
|
2695
|
+
* ## Topology
|
|
2696
|
+
*
|
|
2697
|
+
* Replies travel back over the same channel the invoke went out on. A request reaches every peer on
|
|
2698
|
+
* the requester's transports; the manager is connected to both the dev server and the preview, so it
|
|
2699
|
+
* can invoke a command implemented in either. Command events are *not* relayed across a hub's other
|
|
2700
|
+
* transports, so a preview cannot directly invoke a server-only command (and vice versa) — route such
|
|
2701
|
+
* calls through the manager or implement the command on a directly-connected peer.
|
|
2702
|
+
*
|
|
2703
|
+
* ## Delegated mode
|
|
2704
|
+
*
|
|
2705
|
+
* A delegated runtime (one attached to an already-running Forkbook) owns no dispatch at all: it
|
|
2706
|
+
* requests *every* command over the channel and answers no invoke, because the runtime it attached
|
|
2707
|
+
* to is the implementer. Local handlers stay registered and inspectable but never run, so a service
|
|
2708
|
+
* author writes the same definition either way.
|
|
2709
|
+
*/
|
|
2710
|
+
function connectCommandTransport(context) {
|
|
2711
|
+
const { serviceId, ownRuntimeId, channel, localCommands, implementedCommandNames, commandNames, delegated } = context;
|
|
2712
|
+
const dispatchesLocally = (commandName) => !delegated && implementedCommandNames.has(commandName);
|
|
2713
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2714
|
+
const settle = (callId, apply) => {
|
|
2715
|
+
const entry = pending.get(callId);
|
|
2716
|
+
if (!entry) return;
|
|
2717
|
+
pending.delete(callId);
|
|
2718
|
+
clearTimeout(entry.noAckTimer);
|
|
2719
|
+
apply(entry);
|
|
2720
|
+
};
|
|
2721
|
+
const onInvoke = (payload) => {
|
|
2722
|
+
const parsed = safeParse(commandInvokeSchema, payload);
|
|
2723
|
+
if (!parsed.success || parsed.output.serviceId !== serviceId) return;
|
|
2724
|
+
const invoke = parsed.output;
|
|
2725
|
+
if (!dispatchesLocally(invoke.commandName)) {
|
|
2726
|
+
if (!delegated && invoke.runtimeId !== ownRuntimeId) channel.emit(SERVICE_COMMAND_UNHANDLED, {
|
|
2727
|
+
serviceId,
|
|
2728
|
+
callId: invoke.callId
|
|
2729
|
+
});
|
|
2730
|
+
return;
|
|
2731
|
+
}
|
|
2732
|
+
channel.emit(SERVICE_COMMAND_ACK, {
|
|
2733
|
+
serviceId,
|
|
2734
|
+
callId: invoke.callId,
|
|
2735
|
+
runtimeId: ownRuntimeId
|
|
2736
|
+
});
|
|
2737
|
+
setTimeout(() => {
|
|
2738
|
+
Promise.resolve().then(() => localCommands[invoke.commandName](invoke.input)).then((result) => {
|
|
2739
|
+
channel.emit(SERVICE_COMMAND_RESULT, {
|
|
2740
|
+
serviceId,
|
|
2741
|
+
callId: invoke.callId,
|
|
2742
|
+
result,
|
|
2743
|
+
runtimeId: ownRuntimeId
|
|
2744
|
+
});
|
|
2745
|
+
}, (error) => {
|
|
2746
|
+
channel.emit(SERVICE_COMMAND_ERROR, {
|
|
2747
|
+
serviceId,
|
|
2748
|
+
callId: invoke.callId,
|
|
2749
|
+
error: serializeError(error),
|
|
2750
|
+
runtimeId: ownRuntimeId
|
|
2751
|
+
});
|
|
2752
|
+
});
|
|
2753
|
+
}, 0);
|
|
2754
|
+
};
|
|
2755
|
+
const onResult = (payload) => {
|
|
2756
|
+
const result = safeParse(commandResultSchema, payload);
|
|
2757
|
+
if (!result.success || result.output.serviceId !== serviceId) return;
|
|
2758
|
+
settle(result.output.callId, (entry) => entry.resolve(result.output.result));
|
|
2759
|
+
};
|
|
2760
|
+
const onError = (payload) => {
|
|
2761
|
+
const failure = safeParse(commandErrorSchema, payload);
|
|
2762
|
+
if (!failure.success || failure.output.serviceId !== serviceId) return;
|
|
2763
|
+
settle(failure.output.callId, (entry) => entry.reject(deserializeError(failure.output.error)));
|
|
2764
|
+
};
|
|
2765
|
+
const onAck = (payload) => {
|
|
2766
|
+
const ack = safeParse(commandAckSchema, payload);
|
|
2767
|
+
if (!ack.success || ack.output.serviceId !== serviceId) return;
|
|
2768
|
+
const entry = pending.get(ack.output.callId);
|
|
2769
|
+
if (!entry) return;
|
|
2770
|
+
clearTimeout(entry.noAckTimer);
|
|
2771
|
+
};
|
|
2772
|
+
const onUnhandled = (payload) => {
|
|
2773
|
+
const report = safeParse(commandUnhandledSchema, payload);
|
|
2774
|
+
if (!report.success || report.output.serviceId !== serviceId || !delegated) return;
|
|
2775
|
+
settle(report.output.callId, (entry) => entry.reject(new OpenServiceRemoteCommandConfigDriftError({
|
|
2776
|
+
serviceId,
|
|
2777
|
+
commandName: entry.commandName
|
|
2778
|
+
})));
|
|
2779
|
+
};
|
|
2780
|
+
channel.on(SERVICE_COMMAND_INVOKE, onInvoke);
|
|
2781
|
+
channel.on(SERVICE_COMMAND_RESULT, onResult);
|
|
2782
|
+
channel.on(SERVICE_COMMAND_ERROR, onError);
|
|
2783
|
+
channel.on(SERVICE_COMMAND_ACK, onAck);
|
|
2784
|
+
channel.on(SERVICE_COMMAND_UNHANDLED, onUnhandled);
|
|
2785
|
+
const requestRemote = (commandName, input) => {
|
|
2786
|
+
const callId = generateCallId();
|
|
2787
|
+
return new Promise((resolve, reject) => {
|
|
2788
|
+
const noAckTimer = setTimeout(() => {
|
|
2789
|
+
settle(callId, (entry) => entry.reject(new OpenServiceRemoteCommandUnhandledError({
|
|
2790
|
+
serviceId,
|
|
2791
|
+
commandName: entry.commandName,
|
|
2792
|
+
delegated
|
|
2793
|
+
})));
|
|
2794
|
+
}, 300);
|
|
2795
|
+
pending.set(callId, {
|
|
2796
|
+
commandName,
|
|
2797
|
+
resolve,
|
|
2798
|
+
reject,
|
|
2799
|
+
noAckTimer
|
|
2800
|
+
});
|
|
2801
|
+
channel.emit(SERVICE_COMMAND_INVOKE, {
|
|
2802
|
+
serviceId,
|
|
2803
|
+
commandName,
|
|
2804
|
+
input,
|
|
2805
|
+
callId,
|
|
2806
|
+
runtimeId: ownRuntimeId
|
|
2807
|
+
});
|
|
2808
|
+
});
|
|
2809
|
+
};
|
|
2810
|
+
const commands = {};
|
|
2811
|
+
for (const name of commandNames) commands[name] = dispatchesLocally(name) ? localCommands[name] : (input) => requestRemote(name, input);
|
|
2812
|
+
return {
|
|
2813
|
+
commands,
|
|
2814
|
+
disconnect: () => {
|
|
2815
|
+
channel.off(SERVICE_COMMAND_INVOKE, onInvoke);
|
|
2816
|
+
channel.off(SERVICE_COMMAND_RESULT, onResult);
|
|
2817
|
+
channel.off(SERVICE_COMMAND_ERROR, onError);
|
|
2818
|
+
channel.off(SERVICE_COMMAND_ACK, onAck);
|
|
2819
|
+
channel.off(SERVICE_COMMAND_UNHANDLED, onUnhandled);
|
|
2820
|
+
for (const [, entry] of pending) {
|
|
2821
|
+
clearTimeout(entry.noAckTimer);
|
|
2822
|
+
entry.reject(new OpenServiceRemoteCommandDisconnectedError({ serviceId }));
|
|
2823
|
+
}
|
|
2824
|
+
pending.clear();
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
/**
|
|
2829
|
+
* Reports invokes for service ids this realm does not register at all.
|
|
2830
|
+
*
|
|
2831
|
+
* The per-service transport can only speak for services it hosts, but the common config-drift
|
|
2832
|
+
* shape — a feature flag gating a whole service — leaves the invoke with no listener at all. One
|
|
2833
|
+
* realm-global listener closes that gap: a non-delegated realm replies `services:command-unhandled`
|
|
2834
|
+
* for any invoke whose service id it has never registered, so a delegated caller learns the drift
|
|
2835
|
+
* positively instead of waiting out the ack window. Delegated realms answer nothing, a realm never
|
|
2836
|
+
* reports its own invokes (they are only ever for services it registers), and an id that was
|
|
2837
|
+
* registered before (mid-HMR re-registration) is not reported — staying silent degrades to the
|
|
2838
|
+
* retryable timeout error, while a false report would hand out restart guidance for a transient.
|
|
2839
|
+
*/
|
|
2840
|
+
function connectUnknownServiceReporter(context) {
|
|
2841
|
+
const { channel, isServiceRegistered, isDelegated } = context;
|
|
2842
|
+
const onInvoke = (payload) => {
|
|
2843
|
+
const parsed = safeParse(commandInvokeSchema, payload);
|
|
2844
|
+
if (!parsed.success || isDelegated() || isServiceRegistered(parsed.output.serviceId)) return;
|
|
2845
|
+
channel.emit(SERVICE_COMMAND_UNHANDLED, {
|
|
2846
|
+
serviceId: parsed.output.serviceId,
|
|
2847
|
+
callId: parsed.output.callId
|
|
2848
|
+
});
|
|
2849
|
+
};
|
|
2850
|
+
channel.on(SERVICE_COMMAND_INVOKE, onInvoke);
|
|
2851
|
+
return () => {
|
|
2852
|
+
channel.off(SERVICE_COMMAND_INVOKE, onInvoke);
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
/**
|
|
2856
|
+
* Wires one service runtime to the channel end to end and returns the command map callers expose plus
|
|
2857
|
+
* a single teardown.
|
|
2858
|
+
*
|
|
2859
|
+
* This is the one entry point `registerService` uses, so the three transport halves — entry
|
|
2860
|
+
* authoring, the remote-command protocol, and the sync-request + entry listeners — are always
|
|
2861
|
+
* assembled together against the same `channel` and can never drift into using different channels.
|
|
2862
|
+
* The channel-routed command map is also installed on the runtime so load bodies invoke
|
|
2863
|
+
* peer-implemented commands remotely instead of throwing locally.
|
|
2864
|
+
*/
|
|
2865
|
+
function connectServiceToChannel(context) {
|
|
2866
|
+
const { serviceId, ownRuntimeId, reconciler, getSnapshot, channel, relay, commands, implementedCommandNames, commandNames, delegated, runtime } = context;
|
|
2867
|
+
runtime.attachEntryAuthor(createEntryAuthor({
|
|
2868
|
+
serviceId,
|
|
2869
|
+
ownRuntimeId,
|
|
2870
|
+
reconciler,
|
|
2871
|
+
channel
|
|
2872
|
+
}));
|
|
2873
|
+
const commandTransport = connectCommandTransport({
|
|
2874
|
+
serviceId,
|
|
2875
|
+
ownRuntimeId,
|
|
2876
|
+
channel,
|
|
2877
|
+
localCommands: commands,
|
|
2878
|
+
implementedCommandNames,
|
|
2879
|
+
commandNames,
|
|
2880
|
+
delegated
|
|
2881
|
+
});
|
|
2882
|
+
const disconnectSync = connectRuntimeToChannel({
|
|
2883
|
+
serviceId,
|
|
2884
|
+
ownRuntimeId,
|
|
2885
|
+
reconciler,
|
|
2886
|
+
getSnapshot,
|
|
2887
|
+
channel,
|
|
2888
|
+
relay
|
|
2889
|
+
});
|
|
2890
|
+
runtime.attachChannelCommands(commandTransport.commands, delegated ? /* @__PURE__ */ new Set() : implementedCommandNames);
|
|
2891
|
+
return {
|
|
2892
|
+
commands: commandTransport.commands,
|
|
2893
|
+
disconnect: () => {
|
|
2894
|
+
disconnectSync();
|
|
2895
|
+
commandTransport.disconnect();
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
var SYNC_REQUEST_SILENCE_MS;
|
|
2900
|
+
function init_service_transport() {
|
|
2901
|
+
return (init_service_transport = __esmMin((() => {
|
|
2902
|
+
init_dist();
|
|
2903
|
+
init_client_logger();
|
|
2904
|
+
init_server_errors();
|
|
2905
|
+
init_service_channel();
|
|
2906
|
+
init_service_error_serialization();
|
|
2907
|
+
init_service_sync();
|
|
2908
|
+
SYNC_REQUEST_SILENCE_MS = 1e3;
|
|
2909
|
+
})))();
|
|
2910
|
+
}
|
|
2911
|
+
//#endregion
|
|
2912
|
+
//#region src/shared/open-service/service-registry.ts
|
|
2913
|
+
/**
|
|
2914
|
+
* Returns the realm-global inventory backing service registration and delegated mode.
|
|
2915
|
+
*
|
|
2916
|
+
* Lazily created so importing the module does not eagerly mutate global state. Anchoring it on a
|
|
2917
|
+
* `globalThis` symbol keeps the service map and the delegated-mode flag shared even when this file is
|
|
2918
|
+
* reached through different import paths.
|
|
2919
|
+
*/
|
|
2920
|
+
function getInventory() {
|
|
2921
|
+
const registryGlobal = globalThis;
|
|
2922
|
+
registryGlobal[REGISTRY_SYMBOL] ??= {
|
|
2923
|
+
entries: /* @__PURE__ */ new Map(),
|
|
2924
|
+
delegatedMode: false,
|
|
2925
|
+
everRegisteredIds: /* @__PURE__ */ new Set()
|
|
2926
|
+
};
|
|
2927
|
+
return registryGlobal[REGISTRY_SYMBOL];
|
|
2928
|
+
}
|
|
2929
|
+
function getRegistry() {
|
|
2930
|
+
return getInventory().entries;
|
|
2931
|
+
}
|
|
2932
|
+
/** Whether this runtime delegates command dispatch to the Forkbook it is attached to. */
|
|
2933
|
+
function isDelegatedMode() {
|
|
2934
|
+
return getInventory().delegatedMode;
|
|
2935
|
+
}
|
|
2936
|
+
function ensureUnknownServiceReporter(channel) {
|
|
2937
|
+
const inventory = getInventory();
|
|
2938
|
+
if (inventory.unknownServiceReporter?.channel === channel) return;
|
|
2939
|
+
inventory.unknownServiceReporter?.disconnect();
|
|
2940
|
+
inventory.unknownServiceReporter = {
|
|
2941
|
+
channel,
|
|
2942
|
+
disconnect: connectUnknownServiceReporter({
|
|
2943
|
+
channel,
|
|
2944
|
+
isServiceRegistered: (serviceId) => inventory.everRegisteredIds.has(serviceId),
|
|
2945
|
+
isDelegated: () => inventory.delegatedMode
|
|
2946
|
+
})
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function assertUniqueOperationNames(definition) {
|
|
2950
|
+
const duplicateName = Object.keys(definition.queries).find((name) => Object.hasOwn(definition.commands, name));
|
|
2951
|
+
if (duplicateName) throw new OpenServiceOperationNameCollisionError({
|
|
2952
|
+
serviceId: definition.id,
|
|
2953
|
+
operationName: duplicateName
|
|
2954
|
+
});
|
|
2955
|
+
}
|
|
2956
|
+
/**
|
|
2957
|
+
* Converts one service definition into the serializable descriptor returned by registry metadata APIs.
|
|
2958
|
+
*
|
|
2959
|
+
* Descriptors expose schemas and descriptions but not runtime handlers, so callers can inspect a
|
|
2960
|
+
* service's contract without gaining access to executable behavior. `staticPath: true` is surfaced so
|
|
2961
|
+
* manager code can choose between live runtime queries and prebuilt JSON snapshots.
|
|
2962
|
+
*/
|
|
2963
|
+
function describeDefinition(definition) {
|
|
2964
|
+
return {
|
|
2965
|
+
id: definition.id,
|
|
2966
|
+
description: definition.description,
|
|
2967
|
+
queries: Object.fromEntries(Object.entries(definition.queries).filter(([, query]) => !query.internal).map(([name, query]) => [name, {
|
|
2968
|
+
name,
|
|
2969
|
+
description: query.description,
|
|
2970
|
+
input: query.input,
|
|
2971
|
+
output: query.output,
|
|
2972
|
+
...query.staticPath ? { staticPath: true } : {}
|
|
2973
|
+
}])),
|
|
2974
|
+
commands: Object.fromEntries(Object.entries(definition.commands).filter(([, command]) => !command.internal).map(([name, command]) => [name, {
|
|
2975
|
+
name,
|
|
2976
|
+
description: command.description,
|
|
2977
|
+
input: command.input,
|
|
2978
|
+
output: command.output
|
|
2979
|
+
}]))
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
/** Derives the lightweight summary returned by `listServices()` from a full descriptor. */
|
|
2983
|
+
function summarizeDescriptor(descriptor) {
|
|
2984
|
+
return {
|
|
2985
|
+
id: descriptor.id,
|
|
2986
|
+
description: descriptor.description,
|
|
2987
|
+
queryNames: Object.keys(descriptor.queries),
|
|
2988
|
+
commandNames: Object.keys(descriptor.commands)
|
|
2989
|
+
};
|
|
2990
|
+
}
|
|
2991
|
+
/**
|
|
2992
|
+
* Applies optional registration overrides to an authored definition.
|
|
2993
|
+
*
|
|
2994
|
+
* Query registration may supply dependency-aware `staticInputs` (used by server static builds);
|
|
2995
|
+
* command registration may override the `handler`. Anything not overridden is forwarded unchanged so
|
|
2996
|
+
* every runtime shares the same contract.
|
|
2997
|
+
*/
|
|
2998
|
+
function applyRegistration(definition, registration) {
|
|
2999
|
+
if (!registration) return definition;
|
|
3000
|
+
return {
|
|
3001
|
+
...definition,
|
|
3002
|
+
queries: Object.fromEntries(Object.entries(definition.queries).map(([name, query]) => {
|
|
3003
|
+
const override = registration.queries?.[name];
|
|
3004
|
+
return [name, override && "staticInputs" in override ? {
|
|
3005
|
+
...query,
|
|
3006
|
+
staticInputs: override.staticInputs
|
|
3007
|
+
} : query];
|
|
3008
|
+
})),
|
|
3009
|
+
commands: Object.fromEntries(Object.entries(definition.commands).map(([name, command]) => {
|
|
3010
|
+
const override = registration.commands?.[name];
|
|
3011
|
+
return [name, override && "handler" in override ? {
|
|
3012
|
+
...command,
|
|
3013
|
+
handler: override.handler
|
|
3014
|
+
} : command];
|
|
3015
|
+
}))
|
|
3016
|
+
};
|
|
3017
|
+
}
|
|
3018
|
+
/**
|
|
3019
|
+
* Registers one service definition in the realm-global registry and returns its runtime surface.
|
|
3020
|
+
*
|
|
3021
|
+
* Registration resolves any registration-time overrides, builds the runtime that query and command
|
|
3022
|
+
* callers use, installs the entry author that emits a `services:entry` for each `setState` write, and
|
|
3023
|
+
* joins the cross-peer sync protocol as a hub or leaf (`relay`). Each runtime must install the addons
|
|
3024
|
+
* channel at its entry boundary before calling this (builders, manager boot, server `services`
|
|
3025
|
+
* preset, or Node import bootstrap). Registration is idempotent by id: a repeated registration
|
|
3026
|
+
* returns the existing runtime.
|
|
3027
|
+
*/
|
|
3028
|
+
function registerService$1(definition, registration, { relay = false, staticLoader, window } = {}) {
|
|
3029
|
+
assertUniqueOperationNames(definition);
|
|
3030
|
+
const registry = getRegistry();
|
|
3031
|
+
const existingEntry = registry.get(definition.id);
|
|
3032
|
+
if (existingEntry) return existingEntry.instance;
|
|
3033
|
+
const ownRuntimeId = generateRuntimeId();
|
|
3034
|
+
const resolvedDefinition = applyRegistration(definition, registration);
|
|
3035
|
+
const runtime = createServiceRuntime(resolvedDefinition, {
|
|
3036
|
+
registryApi: serviceRegistryApi,
|
|
3037
|
+
staticLoader
|
|
3038
|
+
});
|
|
3039
|
+
const reconciler = createReconciler({
|
|
3040
|
+
serviceId: definition.id,
|
|
3041
|
+
setState: (mutate) => runtime.applyLocal((state) => mutate(state)),
|
|
3042
|
+
window
|
|
3043
|
+
});
|
|
3044
|
+
const getSnapshot = () => runtime.getStateSnapshot();
|
|
3045
|
+
const descriptor = describeDefinition(resolvedDefinition);
|
|
3046
|
+
const channel = getChannel();
|
|
3047
|
+
if (!channel) throw new OpenServiceMissingChannelError({ serviceId: definition.id });
|
|
3048
|
+
getInventory().everRegisteredIds.add(definition.id);
|
|
3049
|
+
ensureUnknownServiceReporter(channel);
|
|
3050
|
+
const implementedCommandNames = new Set(Object.entries(resolvedDefinition.commands).filter(([, command]) => typeof command.handler === "function").map(([name]) => name));
|
|
3051
|
+
const { commands, disconnect } = connectServiceToChannel({
|
|
3052
|
+
serviceId: definition.id,
|
|
3053
|
+
ownRuntimeId,
|
|
3054
|
+
reconciler,
|
|
3055
|
+
getSnapshot,
|
|
3056
|
+
channel,
|
|
3057
|
+
relay: isDelegatedMode() ? false : relay,
|
|
3058
|
+
commands: runtime.commands,
|
|
3059
|
+
implementedCommandNames,
|
|
3060
|
+
commandNames: Object.keys(resolvedDefinition.commands),
|
|
3061
|
+
delegated: isDelegatedMode(),
|
|
3062
|
+
runtime
|
|
3063
|
+
});
|
|
3064
|
+
const instance = {
|
|
3065
|
+
queries: runtime.queries,
|
|
3066
|
+
commands,
|
|
3067
|
+
...serviceRegistryApi
|
|
3068
|
+
};
|
|
3069
|
+
registry.set(definition.id, {
|
|
3070
|
+
definition: resolvedDefinition,
|
|
3071
|
+
instance,
|
|
3072
|
+
descriptor,
|
|
3073
|
+
summary: summarizeDescriptor(descriptor),
|
|
3074
|
+
disconnect
|
|
3075
|
+
});
|
|
3076
|
+
return instance;
|
|
3077
|
+
}
|
|
3078
|
+
/** Returns one summary entry per registered service — the lowest-cost discovery endpoint. */
|
|
3079
|
+
async function listServices() {
|
|
3080
|
+
return Array.from(getRegistry().values()).filter(({ definition }) => !definition.internal).map(({ summary }) => summary);
|
|
3081
|
+
}
|
|
3082
|
+
/** Returns the schema-backed descriptor for one registered service. */
|
|
3083
|
+
async function describeService(serviceId) {
|
|
3084
|
+
const entry = getRegistry().get(serviceId);
|
|
3085
|
+
if (!entry) throw new OpenServiceMissingServiceError({ serviceId });
|
|
3086
|
+
return entry.descriptor;
|
|
3087
|
+
}
|
|
3088
|
+
/**
|
|
3089
|
+
* Resolves a registered runtime service by id from the current realm.
|
|
3090
|
+
*
|
|
3091
|
+
* Query and command contexts delegate cross-service calls through this lookup so one service can reuse
|
|
3092
|
+
* another's runtime contract. Synchronous because callers need it inside sync query handlers.
|
|
3093
|
+
*
|
|
3094
|
+
* Services with `internal: true` require `{ internal: true }` — otherwise this throws
|
|
3095
|
+
* {@link OpenServiceInternalServiceError}. Do not depend on internal OSA from public adapters;
|
|
3096
|
+
* prefer public toolsets (`defineToolset`) instead.
|
|
3097
|
+
*/
|
|
3098
|
+
function getService$1(serviceId, options) {
|
|
3099
|
+
const entry = getRegistry().get(serviceId);
|
|
3100
|
+
if (!entry) throw new OpenServiceMissingServiceError({ serviceId });
|
|
3101
|
+
if (entry.definition.internal && !options?.internal) throw new OpenServiceInternalServiceError({ serviceId });
|
|
3102
|
+
return entry.instance;
|
|
3103
|
+
}
|
|
3104
|
+
var REGISTRY_SYMBOL, serviceRegistryApi;
|
|
3105
|
+
function init_service_registry() {
|
|
3106
|
+
return (init_service_registry = __esmMin((() => {
|
|
3107
|
+
init_channel_slot();
|
|
3108
|
+
init_server_errors();
|
|
3109
|
+
init_service_channel();
|
|
3110
|
+
init_service_runtime();
|
|
3111
|
+
init_service_sync();
|
|
3112
|
+
init_service_transport();
|
|
3113
|
+
REGISTRY_SYMBOL = Symbol.for("storybook.open-service.registry");
|
|
3114
|
+
serviceRegistryApi = {
|
|
3115
|
+
listServices,
|
|
3116
|
+
describeService,
|
|
3117
|
+
getService: getService$1
|
|
3118
|
+
};
|
|
3119
|
+
})))();
|
|
3120
|
+
}
|
|
3121
|
+
//#endregion
|
|
3122
|
+
//#region src/shared/open-service/static-fetch.ts
|
|
3123
|
+
function shouldUseBrowserStaticLoader() {
|
|
3124
|
+
return globalThis.CONFIG_TYPE === "PRODUCTION";
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* Returns a fetch-based loader for static build output, or `undefined` in development.
|
|
3128
|
+
*
|
|
3129
|
+
* Snapshot paths are logical keys such as `core/docgen/foo.json`, resolved relative to the
|
|
3130
|
+
* document (`./services/...`), so the manager and preview iframes share the same base wherever
|
|
3131
|
+
* the build is deployed.
|
|
3132
|
+
*/
|
|
3133
|
+
function createBrowserStaticLoader() {
|
|
3134
|
+
if (!shouldUseBrowserStaticLoader()) return;
|
|
3135
|
+
return async (logicalPath, context) => {
|
|
3136
|
+
const url = `${STATIC_SERVICES_PREFIX}${logicalPath}`;
|
|
3137
|
+
let res;
|
|
3138
|
+
try {
|
|
3139
|
+
res = await fetch(url);
|
|
3140
|
+
} catch (cause) {
|
|
3141
|
+
throw new OpenServiceStaticSnapshotLoadError({
|
|
3142
|
+
...context,
|
|
3143
|
+
logicalPath,
|
|
3144
|
+
url,
|
|
3145
|
+
cause
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
if (!res.ok) {
|
|
3149
|
+
const cause = {
|
|
3150
|
+
status: res.status,
|
|
3151
|
+
statusText: res.statusText
|
|
3152
|
+
};
|
|
3153
|
+
throw new OpenServiceStaticSnapshotLoadError({
|
|
3154
|
+
...context,
|
|
3155
|
+
logicalPath,
|
|
3156
|
+
url,
|
|
3157
|
+
cause,
|
|
3158
|
+
status: res.status,
|
|
3159
|
+
statusText: res.statusText
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
let snapshot;
|
|
3163
|
+
try {
|
|
3164
|
+
snapshot = await res.json();
|
|
3165
|
+
} catch (cause) {
|
|
3166
|
+
throw new OpenServiceStaticSnapshotLoadError({
|
|
3167
|
+
...context,
|
|
3168
|
+
logicalPath,
|
|
3169
|
+
url,
|
|
3170
|
+
cause
|
|
3171
|
+
});
|
|
3172
|
+
}
|
|
3173
|
+
if (snapshot && typeof snapshot === "object" && !Array.isArray(snapshot)) return snapshot;
|
|
3174
|
+
throw new OpenServiceStaticSnapshotInvalidError({
|
|
3175
|
+
...context,
|
|
3176
|
+
logicalPath,
|
|
3177
|
+
url,
|
|
3178
|
+
received: snapshot
|
|
3179
|
+
});
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
var STATIC_SERVICES_PREFIX;
|
|
3183
|
+
function init_static_fetch() {
|
|
3184
|
+
return (init_static_fetch = __esmMin((() => {
|
|
3185
|
+
init_manager_errors();
|
|
3186
|
+
STATIC_SERVICES_PREFIX = "./services/";
|
|
3187
|
+
})))();
|
|
3188
|
+
}
|
|
3189
|
+
//#endregion
|
|
3190
|
+
//#region src/shared/open-service/preview.ts
|
|
3191
|
+
/**
|
|
3192
|
+
* Registers a service in the preview and returns its runtime surface.
|
|
3193
|
+
*
|
|
3194
|
+
* The preview is a leaf (`relay: false`). Builders install the addons channel before preview config
|
|
3195
|
+
* loads, so registration can assume the channel is already present.
|
|
3196
|
+
*/
|
|
3197
|
+
function registerService(definition, registration) {
|
|
3198
|
+
return registerService$1(definition, registration, { staticLoader: createBrowserStaticLoader() });
|
|
3199
|
+
}
|
|
3200
|
+
var getService;
|
|
3201
|
+
function init_preview() {
|
|
3202
|
+
return (init_preview = __esmMin((() => {
|
|
3203
|
+
init_service_registry();
|
|
3204
|
+
init_static_fetch();
|
|
3205
|
+
getService = getService$1;
|
|
3206
|
+
})))();
|
|
3207
|
+
}
|
|
3208
|
+
//#endregion
|
|
3209
|
+
//#region src/shared/open-service/service-definition.ts
|
|
3210
|
+
var defineService;
|
|
3211
|
+
function init_service_definition() {
|
|
3212
|
+
return (init_service_definition = __esmMin((() => {
|
|
3213
|
+
defineService = (def) => def;
|
|
3214
|
+
})))();
|
|
3215
|
+
}
|
|
3216
|
+
//#endregion
|
|
3217
|
+
//#region src/shared/open-service/services/story-docs/snippet.ts
|
|
3218
|
+
/** Prepends a CSF file import block to a story snippet for display in docs and the Code panel. */
|
|
3219
|
+
function prependImportToSnippet(importBlock, snippet) {
|
|
3220
|
+
const trimmedImport = importBlock?.trim();
|
|
3221
|
+
if (!trimmedImport) return snippet;
|
|
3222
|
+
return `${trimmedImport}\n\n${snippet}`;
|
|
3223
|
+
}
|
|
3224
|
+
/** Resolves the display snippet for one story from a story-docs payload. */
|
|
3225
|
+
function selectSnippetForStory(payload, storyId) {
|
|
3226
|
+
const story = payload?.stories[storyId];
|
|
3227
|
+
if (story?.snippet === void 0) return;
|
|
3228
|
+
return prependImportToSnippet(payload?.import, story.snippet);
|
|
3229
|
+
}
|
|
3230
|
+
/** Resolves the incomplete-snippet warning for one story from a story-docs payload. */
|
|
3231
|
+
function selectWarningForStory(payload, storyId) {
|
|
3232
|
+
return payload?.stories[storyId]?.warning;
|
|
3233
|
+
}
|
|
3234
|
+
//#endregion
|
|
3235
|
+
//#region src/shared/open-service/services/docgen/paths.ts
|
|
3236
|
+
/**
|
|
3237
|
+
* Relative path segment for one component's static snapshot file, as returned by the `docgen`
|
|
3238
|
+
* query's `staticPath`. Must stay aligned with {@link docgenServiceDef} and manifest `$ref`s.
|
|
3239
|
+
*/
|
|
3240
|
+
function docgenQueryStaticPath(id) {
|
|
3241
|
+
return `${id}.json`;
|
|
3242
|
+
}
|
|
3243
|
+
function init_paths() {
|
|
3244
|
+
return (init_paths = __esmMin((() => {})))();
|
|
3245
|
+
}
|
|
3246
|
+
//#endregion
|
|
3247
|
+
//#region src/shared/open-service/services/docgen/definition.ts
|
|
3248
|
+
var docgenInputSchema, argTypesSchema, docgenErrorSchema, docgenJsDocTagsSchema, docgenComponentFields, docgenSubcomponentFields, docgenSubcomponentSchema, docgenPayloadSchema, docgenOutputSchema, docgenServiceDef;
|
|
3249
|
+
function init_definition() {
|
|
3250
|
+
return (init_definition = __esmMin((() => {
|
|
3251
|
+
init_dist();
|
|
3252
|
+
init_service_definition();
|
|
3253
|
+
init_paths();
|
|
3254
|
+
docgenInputSchema = object({ id: string() });
|
|
3255
|
+
argTypesSchema = custom((value) => typeof value === "object" && value !== null && !Array.isArray(value));
|
|
3256
|
+
docgenErrorSchema = object({
|
|
3257
|
+
name: string(),
|
|
3258
|
+
message: string()
|
|
3259
|
+
});
|
|
3260
|
+
docgenJsDocTagsSchema = record(string(), array(string()));
|
|
3261
|
+
docgenComponentFields = {
|
|
3262
|
+
name: string(),
|
|
3263
|
+
path: string(),
|
|
3264
|
+
description: optional(string()),
|
|
3265
|
+
summary: optional(string()),
|
|
3266
|
+
jsDocTags: docgenJsDocTagsSchema,
|
|
3267
|
+
argTypes: optional(argTypesSchema),
|
|
3268
|
+
error: optional(docgenErrorSchema)
|
|
3269
|
+
};
|
|
3270
|
+
docgenSubcomponentFields = {
|
|
3271
|
+
...docgenComponentFields,
|
|
3272
|
+
import: optional(string())
|
|
3273
|
+
};
|
|
3274
|
+
docgenSubcomponentSchema = looseObject(docgenSubcomponentFields);
|
|
3275
|
+
docgenPayloadSchema = looseObject({
|
|
3276
|
+
id: string(),
|
|
3277
|
+
...docgenComponentFields,
|
|
3278
|
+
subcomponents: optional(record(string(), docgenSubcomponentSchema))
|
|
3279
|
+
});
|
|
3280
|
+
docgenOutputSchema = optional(docgenPayloadSchema);
|
|
3281
|
+
docgenServiceDef = defineService({
|
|
3282
|
+
id: "core/docgen",
|
|
3283
|
+
internal: true,
|
|
3284
|
+
description: "Component documentation (name, description, props, JSDoc tags) keyed by component id.",
|
|
3285
|
+
initialState: { components: {} },
|
|
3286
|
+
queries: {
|
|
3287
|
+
docgen: {
|
|
3288
|
+
description: "Returns the docgen payload for one component id, or undefined when not loaded.",
|
|
3289
|
+
input: docgenInputSchema,
|
|
3290
|
+
output: docgenOutputSchema,
|
|
3291
|
+
handler: (input, ctx) => ctx.self.state.components[input.id],
|
|
3292
|
+
load: async (input, ctx) => {
|
|
3293
|
+
await ctx.self.commands.extractDocgen(input);
|
|
3294
|
+
},
|
|
3295
|
+
staticPath: (input) => docgenQueryStaticPath(input.id)
|
|
3296
|
+
},
|
|
3297
|
+
docgenForAllComponents: {
|
|
3298
|
+
description: "Returns docgen payloads for every component in the story index.",
|
|
3299
|
+
input: void_(),
|
|
3300
|
+
output: record(string(), docgenPayloadSchema),
|
|
3301
|
+
handler: (_input, ctx) => ctx.self.state.components,
|
|
3302
|
+
load: async (_input, ctx) => {
|
|
3303
|
+
await ctx.self.commands.extractAllDocgen(void 0);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
},
|
|
3307
|
+
commands: {
|
|
3308
|
+
extractDocgen: {
|
|
3309
|
+
description: "Resolves the story entry for a component id, runs the registered provider chain, writes the result into state, and returns it (or undefined when no provider produced docgen).",
|
|
3310
|
+
input: docgenInputSchema,
|
|
3311
|
+
output: docgenOutputSchema
|
|
3312
|
+
},
|
|
3313
|
+
extractAllDocgen: {
|
|
3314
|
+
description: "Extracts docgen for every component id in the story index by invoking `extractDocgen` for each.",
|
|
3315
|
+
input: undefined_(),
|
|
3316
|
+
output: void_()
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
})))();
|
|
3321
|
+
}
|
|
3322
|
+
//#endregion
|
|
3323
|
+
//#region src/shared/open-service/services/docgen/preview.ts
|
|
3324
|
+
var preview_default$1;
|
|
3325
|
+
function init_preview$1() {
|
|
3326
|
+
return (init_preview$1 = __esmMin((() => {
|
|
3327
|
+
init_preview();
|
|
3328
|
+
init_definition();
|
|
3329
|
+
preview_default$1 = () => definePreviewAddon({ beforeAll: () => {
|
|
3330
|
+
if (globalThis.FEATURES?.docgenServer) registerService(docgenServiceDef);
|
|
3331
|
+
} });
|
|
3332
|
+
})))();
|
|
3333
|
+
}
|
|
3334
|
+
//#endregion
|
|
3335
|
+
//#region src/shared/open-service/services/story-docs/paths.ts
|
|
3336
|
+
/**
|
|
3337
|
+
* Relative path segment for one component's static snapshot file, as returned by the `storyDocs`
|
|
3338
|
+
* query's `staticPath`. Must stay aligned with {@link storyDocsServiceDef} and manifest `$ref`s.
|
|
3339
|
+
*/
|
|
3340
|
+
function storyDocsQueryStaticPath(id) {
|
|
3341
|
+
return `${id}.json`;
|
|
3342
|
+
}
|
|
3343
|
+
function init_paths$1() {
|
|
3344
|
+
return (init_paths$1 = __esmMin((() => {})))();
|
|
3345
|
+
}
|
|
3346
|
+
//#endregion
|
|
3347
|
+
//#region src/shared/open-service/services/story-docs/definition.ts
|
|
3348
|
+
var storyDocsInputSchema, storyDocsErrorSchema, storyDocSchema, storyDocsPayloadSchema, storyDocsOutputSchema, storyDocsServiceDef;
|
|
3349
|
+
function init_definition$1() {
|
|
3350
|
+
return (init_definition$1 = __esmMin((() => {
|
|
3351
|
+
init_dist();
|
|
3352
|
+
init_service_definition();
|
|
3353
|
+
init_paths$1();
|
|
3354
|
+
storyDocsInputSchema = object({ id: string() });
|
|
3355
|
+
storyDocsErrorSchema = object({
|
|
3356
|
+
name: string(),
|
|
3357
|
+
message: string()
|
|
3358
|
+
});
|
|
3359
|
+
storyDocSchema = object({
|
|
3360
|
+
id: string(),
|
|
3361
|
+
name: string(),
|
|
3362
|
+
snippet: optional(string()),
|
|
3363
|
+
description: optional(string()),
|
|
3364
|
+
summary: optional(string()),
|
|
3365
|
+
warning: optional(string()),
|
|
3366
|
+
error: optional(storyDocsErrorSchema)
|
|
3367
|
+
});
|
|
3368
|
+
storyDocsPayloadSchema = object({
|
|
3369
|
+
id: string(),
|
|
3370
|
+
name: string(),
|
|
3371
|
+
path: string(),
|
|
3372
|
+
import: optional(string()),
|
|
3373
|
+
stories: record(string(), storyDocSchema),
|
|
3374
|
+
error: optional(storyDocsErrorSchema)
|
|
3375
|
+
});
|
|
3376
|
+
storyDocsOutputSchema = optional(storyDocsPayloadSchema);
|
|
3377
|
+
storyDocsServiceDef = defineService({
|
|
3378
|
+
id: "core/story-docs",
|
|
3379
|
+
internal: true,
|
|
3380
|
+
description: "Story documentation (snippets, descriptions, imports) keyed by component id.",
|
|
3381
|
+
initialState: { components: {} },
|
|
3382
|
+
queries: {
|
|
3383
|
+
storyDocs: {
|
|
3384
|
+
description: "Returns the story-docs payload for one component id, or undefined when not loaded.",
|
|
3385
|
+
input: storyDocsInputSchema,
|
|
3386
|
+
output: storyDocsOutputSchema,
|
|
3387
|
+
handler: (input, ctx) => Object.hasOwn(ctx.self.state.components, input.id) ? ctx.self.state.components[input.id] : void 0,
|
|
3388
|
+
load: async (input, ctx) => {
|
|
3389
|
+
await ctx.self.commands.extractStoryDocs(input);
|
|
3390
|
+
},
|
|
3391
|
+
staticPath: (input) => storyDocsQueryStaticPath(input.id)
|
|
3392
|
+
},
|
|
3393
|
+
storyDocsForAllComponents: {
|
|
3394
|
+
description: "Returns story-docs payloads for every component in the story index.",
|
|
3395
|
+
input: void_(),
|
|
3396
|
+
output: record(string(), storyDocsPayloadSchema),
|
|
3397
|
+
handler: (_input, ctx) => ctx.self.state.components,
|
|
3398
|
+
load: async (_input, ctx) => {
|
|
3399
|
+
await ctx.self.commands.extractAllStoryDocs(void 0);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
},
|
|
3403
|
+
commands: {
|
|
3404
|
+
extractStoryDocs: {
|
|
3405
|
+
description: "Resolves the story entry for a component id, runs the registered provider chain, writes the result into state, and returns it (or undefined when no provider produced story docs).",
|
|
3406
|
+
input: storyDocsInputSchema,
|
|
3407
|
+
output: storyDocsOutputSchema
|
|
3408
|
+
},
|
|
3409
|
+
extractAllStoryDocs: {
|
|
3410
|
+
description: "Extracts story docs for every component id in the story index by invoking `extractStoryDocs` for each.",
|
|
3411
|
+
input: undefined_(),
|
|
3412
|
+
output: void_()
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
});
|
|
3416
|
+
})))();
|
|
3417
|
+
}
|
|
3418
|
+
//#endregion
|
|
3419
|
+
//#region src/shared/open-service/services/story-docs/story-docs-source-before-each.ts
|
|
3420
|
+
/**
|
|
3421
|
+
* Preview `beforeEach` hook that emits a static story-docs snippet to the manager Code panel via
|
|
3422
|
+
* {@link emitTransformCode}. Runs once per story invocation; the snippet itself is static.
|
|
3423
|
+
*/
|
|
3424
|
+
function storyDocsSourceBeforeEach(context) {
|
|
3425
|
+
if (!globalThis.FEATURES?.docgenServer) return;
|
|
3426
|
+
if (shouldSkipStoryDocsEmit(context.parameters)) return;
|
|
3427
|
+
const service = (() => {
|
|
3428
|
+
try {
|
|
3429
|
+
return getService("core/story-docs", { internal: true });
|
|
3430
|
+
} catch {
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
})();
|
|
3434
|
+
if (!service) return;
|
|
3435
|
+
const storyId = context.id;
|
|
3436
|
+
const componentId = storyId.split("--")[0];
|
|
3437
|
+
let cancelled = false;
|
|
3438
|
+
const codePanelSnippetPromise = service.queries.storyDocs.loaded({ id: componentId }).then((payload) => {
|
|
3439
|
+
if (cancelled) return;
|
|
3440
|
+
const snippet = selectSnippetForStory(payload, storyId);
|
|
3441
|
+
const source = snippet ?? context.parameters?.docs?.source?.originalSource;
|
|
3442
|
+
if (source === void 0) return;
|
|
3443
|
+
const warning = selectWarningForStory(payload, storyId);
|
|
3444
|
+
return emitTransformCode(source, context, snippet === void 0 && warning ? `${warning} Showing the story source instead.` : warning);
|
|
3445
|
+
}).catch((error) => {
|
|
3446
|
+
logger.debug(`Story docs snippet for ${storyId} unavailable: ${String(error)}`);
|
|
3447
|
+
});
|
|
3448
|
+
return () => {
|
|
3449
|
+
cancelled = true;
|
|
3450
|
+
return codePanelSnippetPromise;
|
|
3451
|
+
};
|
|
3452
|
+
}
|
|
3453
|
+
function init_story_docs_source_before_each() {
|
|
3454
|
+
return (init_story_docs_source_before_each = __esmMin((() => {
|
|
3455
|
+
init_client_logger();
|
|
3456
|
+
init_storyDocsCodePanel();
|
|
3457
|
+
init_preview_api();
|
|
3458
|
+
})))();
|
|
3459
|
+
}
|
|
3460
|
+
//#endregion
|
|
3461
|
+
//#region src/shared/open-service/services/story-docs/preview.ts
|
|
3462
|
+
var preview_default;
|
|
3463
|
+
function init_preview$2() {
|
|
3464
|
+
return (init_preview$2 = __esmMin((() => {
|
|
3465
|
+
init_preview();
|
|
3466
|
+
init_definition$1();
|
|
3467
|
+
init_story_docs_source_before_each();
|
|
3468
|
+
preview_default = () => {
|
|
3469
|
+
if (!("FEATURES" in globalThis && globalThis.FEATURES?.docgenServer)) return definePreviewAddon({});
|
|
3470
|
+
return definePreviewAddon({
|
|
3471
|
+
beforeAll: () => {
|
|
3472
|
+
registerService(storyDocsServiceDef);
|
|
3473
|
+
},
|
|
3474
|
+
beforeEach: storyDocsSourceBeforeEach
|
|
3475
|
+
});
|
|
3476
|
+
};
|
|
3477
|
+
})))();
|
|
3478
|
+
}
|
|
3479
|
+
//#endregion
|
|
3480
|
+
//#region src/shared/story-index/autoTitle.ts
|
|
3481
|
+
/**
|
|
3482
|
+
* Combines path parts together, without duplicating separators (slashes). Used instead of
|
|
3483
|
+
* `path.join` because this code runs in the browser.
|
|
3484
|
+
*
|
|
3485
|
+
* @param paths Array of paths to join together.
|
|
3486
|
+
* @returns Joined path string, with single '/' between parts
|
|
3487
|
+
*/
|
|
3488
|
+
function pathJoin(paths) {
|
|
3489
|
+
return paths.flatMap((p) => p.split("/")).filter(Boolean).join("/");
|
|
3490
|
+
}
|
|
3491
|
+
var sanitize, userOrAutoTitleFromSpecifier, userOrAutoTitle;
|
|
3492
|
+
function init_autoTitle() {
|
|
3493
|
+
return (init_autoTitle = __esmMin((() => {
|
|
3494
|
+
sanitize = (parts) => {
|
|
3495
|
+
if (parts.length === 0) return parts;
|
|
3496
|
+
const last = parts[parts.length - 1];
|
|
3497
|
+
const lastStripped = last?.replace(/(?:[.](?:story|stories))?([.][^.]+)$/i, "");
|
|
3498
|
+
if (parts.length === 1) return [lastStripped];
|
|
3499
|
+
const nextToLast = parts[parts.length - 2];
|
|
3500
|
+
if (lastStripped && nextToLast && lastStripped.toLowerCase() === nextToLast.toLowerCase()) return [...parts.slice(0, -2), lastStripped];
|
|
3501
|
+
return lastStripped && (/^(story|stories)([.][^.]+)$/i.test(last) || /^index$/i.test(lastStripped)) ? parts.slice(0, -1) : [...parts.slice(0, -1), lastStripped];
|
|
3502
|
+
};
|
|
3503
|
+
userOrAutoTitleFromSpecifier = (fileName, entry, userTitle) => {
|
|
3504
|
+
const { directory, importPathMatcher, titlePrefix = "" } = entry || {};
|
|
3505
|
+
const normalizedFileName = slash(String(fileName));
|
|
3506
|
+
if (importPathMatcher.exec(normalizedFileName)) {
|
|
3507
|
+
if (!userTitle) {
|
|
3508
|
+
let parts = pathJoin([titlePrefix, normalizedFileName.replace(directory, "")]).split("/");
|
|
3509
|
+
parts = sanitize(parts);
|
|
3510
|
+
return parts.join("/");
|
|
3511
|
+
}
|
|
3512
|
+
if (!titlePrefix) return userTitle;
|
|
3513
|
+
return pathJoin([titlePrefix, userTitle]);
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
userOrAutoTitle = (fileName, storiesEntries, userTitle) => {
|
|
3517
|
+
for (let i = 0; i < storiesEntries.length; i += 1) {
|
|
3518
|
+
const title = userOrAutoTitleFromSpecifier(fileName, storiesEntries[i], userTitle);
|
|
3519
|
+
if (title) return title;
|
|
3520
|
+
}
|
|
3521
|
+
return userTitle || void 0;
|
|
3522
|
+
};
|
|
3523
|
+
})))();
|
|
3524
|
+
}
|
|
3525
|
+
//#endregion
|
|
3526
|
+
//#region src/shared/story-index/storySort.ts
|
|
3527
|
+
var STORY_KIND_PATH_SEPARATOR, storySort;
|
|
3528
|
+
function init_storySort() {
|
|
3529
|
+
return (init_storySort = __esmMin((() => {
|
|
3530
|
+
STORY_KIND_PATH_SEPARATOR = /\s*\/\s*/;
|
|
3531
|
+
storySort = (options = {}) => (a, b) => {
|
|
3532
|
+
if (a.title === b.title && !options.includeNames) return 0;
|
|
3533
|
+
const method = options.method || "configure";
|
|
3534
|
+
let order = options.order || [];
|
|
3535
|
+
const storyTitleA = a.title.trim().split(STORY_KIND_PATH_SEPARATOR);
|
|
3536
|
+
const storyTitleB = b.title.trim().split(STORY_KIND_PATH_SEPARATOR);
|
|
3537
|
+
if (options.includeNames) {
|
|
3538
|
+
storyTitleA.push(a.name);
|
|
3539
|
+
storyTitleB.push(b.name);
|
|
3540
|
+
}
|
|
3541
|
+
let depth = 0;
|
|
3542
|
+
while (storyTitleA[depth] || storyTitleB[depth]) {
|
|
3543
|
+
if (!storyTitleA[depth]) return -1;
|
|
3544
|
+
if (!storyTitleB[depth]) return 1;
|
|
3545
|
+
const nameA = storyTitleA[depth];
|
|
3546
|
+
const nameB = storyTitleB[depth];
|
|
3547
|
+
if (nameA !== nameB) {
|
|
3548
|
+
let indexA = order.indexOf(nameA);
|
|
3549
|
+
let indexB = order.indexOf(nameB);
|
|
3550
|
+
const indexWildcard = order.indexOf("*");
|
|
3551
|
+
if (indexA !== -1 || indexB !== -1) {
|
|
3552
|
+
if (indexA === -1) {
|
|
3553
|
+
if (indexWildcard !== -1) indexA = indexWildcard;
|
|
3554
|
+
else indexA = order.length;
|
|
3555
|
+
}
|
|
3556
|
+
if (indexB === -1) {
|
|
3557
|
+
if (indexWildcard !== -1) indexB = indexWildcard;
|
|
3558
|
+
else indexB = order.length;
|
|
3559
|
+
}
|
|
3560
|
+
return indexA - indexB;
|
|
3561
|
+
}
|
|
3562
|
+
if (method === "configure") return 0;
|
|
3563
|
+
return nameA.localeCompare(nameB, options.locales ? options.locales : void 0, {
|
|
3564
|
+
numeric: true,
|
|
3565
|
+
sensitivity: "accent"
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
let index = order.indexOf(nameA);
|
|
3569
|
+
if (index === -1) index = order.indexOf("*");
|
|
3570
|
+
const nestedOrder = order[index + 1];
|
|
3571
|
+
order = index !== -1 && Array.isArray(nestedOrder) ? nestedOrder : [];
|
|
3572
|
+
depth += 1;
|
|
3573
|
+
}
|
|
3574
|
+
/* istanbul ignore next */
|
|
3575
|
+
return 0;
|
|
3576
|
+
};
|
|
3577
|
+
})))();
|
|
3578
|
+
}
|
|
3579
|
+
//#endregion
|
|
3580
|
+
//#region src/shared/story-index/sortStories.ts
|
|
3581
|
+
var toComparator, sortStoriesCommon, sortStoriesV7;
|
|
3582
|
+
function init_sortStories() {
|
|
3583
|
+
return (init_sortStories = __esmMin((() => {
|
|
3584
|
+
init_esm();
|
|
3585
|
+
init_storySort();
|
|
3586
|
+
toComparator = (storySortParameter) => typeof storySortParameter === "function" ? storySortParameter : storySort(Array.isArray(storySortParameter) ? { order: storySortParameter } : storySortParameter);
|
|
3587
|
+
sortStoriesCommon = (stories, storySortParameter, fileNameOrder) => {
|
|
3588
|
+
if (storySortParameter) stories.sort(toComparator(storySortParameter));
|
|
3589
|
+
else stories.sort((s1, s2) => fileNameOrder.indexOf(s1.importPath) - fileNameOrder.indexOf(s2.importPath));
|
|
3590
|
+
return stories;
|
|
3591
|
+
};
|
|
3592
|
+
sortStoriesV7 = (stories, storySortParameter, fileNameOrder) => {
|
|
3593
|
+
try {
|
|
3594
|
+
return sortStoriesCommon(stories, storySortParameter, fileNameOrder);
|
|
3595
|
+
} catch (err) {
|
|
3596
|
+
throw new Error(dedent`
|
|
3597
|
+
Error sorting stories with sort parameter ${storySortParameter}:
|
|
3598
|
+
|
|
3599
|
+
> ${err.message}
|
|
3600
|
+
|
|
3601
|
+
Are you using a V6-style sort function in V7 mode?
|
|
3602
|
+
|
|
3603
|
+
More info: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#v7-style-story-sort
|
|
3604
|
+
`);
|
|
3605
|
+
}
|
|
3606
|
+
};
|
|
3607
|
+
})))();
|
|
3608
|
+
}
|
|
3609
|
+
//#endregion
|
|
3610
|
+
//#region src/shared/utils/story-index-filters.ts
|
|
3611
|
+
/** Was this docs entry generated by a `.mdx` file (as opposed to an autodocs page)? */
|
|
3612
|
+
function isMdxEntry({ tags }) {
|
|
3613
|
+
return tags?.includes(Tag.UNATTACHED_MDX) || tags?.includes(Tag.ATTACHED_MDX);
|
|
3614
|
+
}
|
|
3615
|
+
function init_story_index_filters() {
|
|
3616
|
+
return (init_story_index_filters = __esmMin((() => {
|
|
3617
|
+
init_tags();
|
|
3618
|
+
})))();
|
|
3619
|
+
}
|
|
3620
|
+
//#endregion
|
|
3621
|
+
//#region src/shared/constants/iframe-resize.ts
|
|
3622
|
+
var IFRAME_RESIZE_CONTEXT, IFRAME_RESIZE_REQUEST_CONTEXT, iframeResizeViewportsEqual;
|
|
3623
|
+
function init_iframe_resize() {
|
|
3624
|
+
return (init_iframe_resize = __esmMin((() => {
|
|
3625
|
+
IFRAME_RESIZE_CONTEXT = "iframe.resize";
|
|
3626
|
+
IFRAME_RESIZE_REQUEST_CONTEXT = "iframe.resize.request";
|
|
3627
|
+
iframeResizeViewportsEqual = (left, right) => {
|
|
3628
|
+
if (left === right) return true;
|
|
3629
|
+
if (!left || !right) return !left && !right;
|
|
3630
|
+
return left.name === right.name && left.value === right.value && left.width === right.width && left.height === right.height;
|
|
3631
|
+
};
|
|
3632
|
+
})))();
|
|
3633
|
+
}
|
|
3634
|
+
//#endregion
|
|
3635
|
+
export { matchesHostname as A, init_events as C, init_status_store as D, REVIEW_STATUS_TYPE_ID as E, init_universal_store as M, LOOPBACK_HOSTNAMES as O, REVIEW_NAMESPACE as S, NON_AGGREGATED_STATUS_TYPE_IDS as T, init_preview as _, init_story_index_filters as a, init_tags as b, sortStoriesV7 as c, userOrAutoTitleFromSpecifier as d, init_preview$2 as f, getService as g, preview_default$1 as h, init_iframe_resize as i, UniversalStore as j, init_local_hosts as k, init_autoTitle as l, init_preview$1 as m, IFRAME_RESIZE_REQUEST_CONTEXT as n, isMdxEntry as o, preview_default as p, iframeResizeViewportsEqual as r, init_sortStories as s, IFRAME_RESIZE_CONTEXT as t, userOrAutoTitle as u, registerService as v, CHANGE_DETECTION_STATUS_TYPE_ID as w, REVIEW_EVENTS as x, Tag as y };
|