sygnal 5.3.7 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +646 -0
- package/README.md +77 -47
- package/dist/astro/client.cjs.js +33 -8114
- package/dist/astro/client.cjs.js.map +1 -0
- package/dist/astro/client.mjs +33 -8114
- package/dist/astro/client.mjs.map +1 -0
- package/dist/astro/index.cjs.js +1039 -9
- package/dist/astro/index.cjs.js.map +1 -0
- package/dist/astro/index.mjs +1038 -9
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/server.cjs.js +3400 -166
- package/dist/astro/server.cjs.js.map +1 -0
- package/dist/astro/server.mjs +3400 -166
- package/dist/astro/server.mjs.map +1 -0
- package/dist/devtools.cjs.js +1431 -0
- package/dist/devtools.cjs.js.map +1 -0
- package/dist/devtools.esm.js +1419 -0
- package/dist/devtools.esm.js.map +1 -0
- package/dist/diagnostics.cjs.js +4033 -0
- package/dist/diagnostics.cjs.js.map +1 -0
- package/dist/diagnostics.esm.js +4003 -0
- package/dist/diagnostics.esm.js.map +1 -0
- package/dist/element.cjs.js +230 -0
- package/dist/element.cjs.js.map +1 -0
- package/dist/element.esm.js +228 -0
- package/dist/element.esm.js.map +1 -0
- package/dist/guide/accessibility.md +257 -0
- package/dist/guide/adapters.md +221 -0
- package/dist/guide/behaviors.md +312 -0
- package/dist/guide/browser-sources.md +203 -0
- package/dist/guide/drag-and-drop.md +342 -0
- package/dist/guide/element-commands.md +306 -0
- package/dist/guide/error-boundaries.md +169 -0
- package/dist/guide/forms-reference.md +381 -0
- package/dist/guide/forms.md +250 -0
- package/dist/guide/http.md +299 -0
- package/dist/guide/inputs.md +173 -0
- package/dist/guide/persistence.md +245 -0
- package/dist/guide/recipes/carousel.md +155 -0
- package/dist/guide/recipes/charts.md +177 -0
- package/dist/guide/recipes/code-editor.md +135 -0
- package/dist/guide/recipes/data-grid.md +155 -0
- package/dist/guide/recipes/data-table.md +196 -0
- package/dist/guide/recipes/i18n.md +222 -0
- package/dist/guide/recipes/icons.md +115 -0
- package/dist/guide/recipes/overview.md +33 -0
- package/dist/guide/recipes/rich-text.md +154 -0
- package/dist/guide/resources.md +471 -0
- package/dist/guide/ssr.md +255 -0
- package/dist/guide/timers.md +178 -0
- package/dist/guide/ui/accordion.md +93 -0
- package/dist/guide/ui/combobox.md +144 -0
- package/dist/guide/ui/dialog.md +124 -0
- package/dist/guide/ui/disclosure.md +60 -0
- package/dist/guide/ui/menu.md +125 -0
- package/dist/guide/ui/overview.md +166 -0
- package/dist/guide/ui/popover.md +101 -0
- package/dist/guide/ui/select.md +114 -0
- package/dist/guide/ui/tabs.md +161 -0
- package/dist/guide/ui/toaster.md +152 -0
- package/dist/guide/ui/tooltip.md +103 -0
- package/dist/guide/undo.md +178 -0
- package/dist/guide/virtual-collections.md +183 -0
- package/dist/guide/web-components.md +179 -0
- package/dist/guide/widgets.md +217 -0
- package/dist/index.cjs.js +15283 -6279
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +3739 -370
- package/dist/index.esm.js +15230 -6274
- package/dist/index.esm.js.map +1 -0
- package/dist/jsx-dev-runtime.cjs.js +27 -329
- package/dist/jsx-dev-runtime.cjs.js.map +1 -0
- package/dist/jsx-dev-runtime.esm.js +20 -322
- package/dist/jsx-dev-runtime.esm.js.map +1 -0
- package/dist/jsx-runtime.cjs.js +27 -329
- package/dist/jsx-runtime.cjs.js.map +1 -0
- package/dist/jsx-runtime.esm.js +20 -322
- package/dist/jsx-runtime.esm.js.map +1 -0
- package/dist/jsx.cjs.js +11 -309
- package/dist/jsx.cjs.js.map +1 -0
- package/dist/jsx.esm.js +9 -310
- package/dist/jsx.esm.js.map +1 -0
- package/dist/react.cjs.js +117 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.esm.js +115 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/shims/globalthis.cjs +20 -0
- package/dist/sygnal.min.js +2 -1
- package/dist/sygnal.min.js.map +1 -0
- package/dist/ui-combobox.cjs.js +169 -0
- package/dist/ui-combobox.cjs.js.map +1 -0
- package/dist/ui-combobox.esm.js +148 -0
- package/dist/ui-combobox.esm.js.map +1 -0
- package/dist/ui-menu.cjs.js +87 -0
- package/dist/ui-menu.cjs.js.map +1 -0
- package/dist/ui-menu.esm.js +66 -0
- package/dist/ui-menu.esm.js.map +1 -0
- package/dist/ui-select.cjs.js +104 -0
- package/dist/ui-select.cjs.js.map +1 -0
- package/dist/ui-select.esm.js +83 -0
- package/dist/ui-select.esm.js.map +1 -0
- package/dist/ui.cjs.js +738 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.esm.js +727 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +5 -14
- package/dist/vike/ClientOnly.cjs.js.map +1 -0
- package/dist/vike/ClientOnly.mjs +5 -14
- package/dist/vike/ClientOnly.mjs.map +1 -0
- package/dist/vike/{+config.js → config/+config.js} +10 -1
- package/dist/vike/config/+config.js.map +1 -0
- package/dist/vike/config/package.json +6 -0
- package/dist/vike/onRenderClient.cjs.js +166 -95
- package/dist/vike/onRenderClient.cjs.js.map +1 -0
- package/dist/vike/onRenderClient.mjs +166 -95
- package/dist/vike/onRenderClient.mjs.map +1 -0
- package/dist/vike/onRenderHtml.cjs.js +60 -24
- package/dist/vike/onRenderHtml.cjs.js.map +1 -0
- package/dist/vike/onRenderHtml.mjs +61 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -0
- package/dist/vite/plugin.cjs.js +926 -65
- package/dist/vite/plugin.cjs.js.map +1 -0
- package/dist/vite/plugin.mjs +925 -65
- package/dist/vite/plugin.mjs.map +1 -0
- package/dist/zag.cjs.js +408 -0
- package/dist/zag.cjs.js.map +1 -0
- package/dist/zag.esm.js +405 -0
- package/dist/zag.esm.js.map +1 -0
- package/llms.txt +313 -0
- package/package.json +109 -16
- package/src/astro/client.ts +40 -18
- package/src/astro/index.d.ts +48 -1
- package/src/astro/index.ts +106 -9
- package/src/astro/server.ts +11 -3
- package/src/collection.ts +6 -90
- package/src/core/actions.ts +134 -0
- package/src/core/cell.ts +202 -0
- package/src/core/debug.ts +23 -0
- package/src/core/define.ts +242 -0
- package/src/core/hooks.ts +314 -0
- package/src/core/hosts/collection.ts +325 -0
- package/src/core/hosts/switchable.ts +146 -0
- package/src/core/instance.ts +592 -0
- package/src/core/markers/clientonly.ts +13 -0
- package/src/core/markers/lazy.ts +40 -0
- package/src/core/markers/portal.ts +96 -0
- package/src/core/markers/suspense.ts +41 -0
- package/src/core/markers/transition.ts +70 -0
- package/src/core/registry.ts +25 -0
- package/src/core/runtime.ts +573 -0
- package/src/core/statics.ts +104 -0
- package/src/core/teardown.ts +60 -0
- package/src/core/view.ts +37 -0
- package/src/cycle/dom/DocumentDOMSource.ts +13 -8
- package/src/cycle/dom/ElementFinder.ts +7 -9
- package/src/cycle/dom/EventDelegator.ts +174 -223
- package/src/cycle/dom/IsolateModule.ts +65 -35
- package/src/cycle/dom/MainDOMSource.ts +13 -2
- package/src/cycle/dom/PriorityQueue.ts +4 -2
- package/src/cycle/dom/SymbolTree.ts +18 -47
- package/src/cycle/dom/classNameModule.ts +76 -0
- package/src/cycle/dom/controlledInputModule.ts +50 -0
- package/src/cycle/dom/enrichEventStream.ts +29 -45
- package/src/cycle/dom/fragment.ts +7 -0
- package/src/cycle/dom/isolate.ts +30 -27
- package/src/cycle/dom/makeDOMDriver.ts +115 -57
- package/src/cycle/dom/mockDOMSource.ts +83 -9
- package/src/cycle/dom/modules.ts +21 -4
- package/src/cycle/dom/propsModule.ts +65 -0
- package/src/cycle/dom/selectModule.ts +22 -17
- package/src/cycle/dom/snabbdom.ts +1 -6
- package/src/cycle/dom/thunk.ts +3 -0
- package/src/cycle/dom/utils.ts +98 -0
- package/src/cycle/dom/viewTransition.ts +43 -0
- package/src/cycle/state/StateSource.ts +20 -4
- package/src/cycle/state/objIsEqual.ts +50 -0
- package/src/cycle/state/types.ts +0 -10
- package/src/defineComponent.ts +34 -0
- package/src/devtools.d.ts +151 -0
- package/src/devtools.ts +34 -0
- package/src/element.d.ts +59 -0
- package/src/element.ts +235 -0
- package/src/extra/backoff.ts +9 -0
- package/src/extra/behaviors.ts +169 -0
- package/src/extra/browserSignals.ts +23 -0
- package/src/extra/browserSources.ts +326 -0
- package/src/extra/command.ts +3 -3
- package/src/extra/controls.ts +47 -0
- package/src/extra/copyAsTest.ts +286 -0
- package/src/extra/devtools.ts +109 -31
- package/src/extra/devtoolsActions.ts +379 -0
- package/src/extra/devtoolsHook.ts +9 -0
- package/src/extra/devtoolsNext.ts +83 -0
- package/src/extra/diagnostics/checks/actionLog.ts +108 -0
- package/src/extra/diagnostics/checks/behaviors.ts +49 -0
- package/src/extra/diagnostics/checks/browserSources.ts +96 -0
- package/src/extra/diagnostics/checks/collections.ts +67 -0
- package/src/extra/diagnostics/checks/controls.ts +148 -0
- package/src/extra/diagnostics/checks/dataset.ts +57 -0
- package/src/extra/diagnostics/checks/dom.ts +250 -0
- package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
- package/src/extra/diagnostics/checks/events.ts +83 -0
- package/src/extra/diagnostics/checks/fetch.ts +135 -0
- package/src/extra/diagnostics/checks/forms.ts +163 -0
- package/src/extra/diagnostics/checks/index.ts +202 -0
- package/src/extra/diagnostics/checks/inspect.ts +427 -0
- package/src/extra/diagnostics/checks/next.ts +417 -0
- package/src/extra/diagnostics/checks/persist.ts +54 -0
- package/src/extra/diagnostics/checks/props.ts +56 -0
- package/src/extra/diagnostics/checks/public.d.ts +302 -0
- package/src/extra/diagnostics/checks/replies.ts +128 -0
- package/src/extra/diagnostics/checks/router.ts +100 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +88 -0
- package/src/extra/diagnostics/checks/shared.ts +203 -0
- package/src/extra/diagnostics/checks/shorthand.ts +115 -0
- package/src/extra/diagnostics/checks/sortable.ts +129 -0
- package/src/extra/diagnostics/checks/state.ts +148 -0
- package/src/extra/diagnostics/checks/statics.ts +48 -0
- package/src/extra/diagnostics/checks/strict.ts +55 -0
- package/src/extra/diagnostics/checks/timers.ts +80 -0
- package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
- package/src/extra/diagnostics/checks/virtual.ts +73 -0
- package/src/extra/diagnostics/checks/widgets.ts +109 -0
- package/src/extra/diagnostics/checks/wiring.ts +121 -0
- package/src/extra/diagnostics/codes.ts +477 -0
- package/src/extra/diagnostics/index.ts +353 -0
- package/src/extra/diagnostics/legacy.ts +76 -0
- package/src/extra/driverFactories.ts +172 -60
- package/src/extra/elementCommands.ts +53 -0
- package/src/extra/eventDriver.ts +4 -0
- package/src/extra/fetchDriver.ts +519 -0
- package/src/extra/flatten.ts +75 -0
- package/src/extra/focusWithin.ts +27 -0
- package/src/extra/form.ts +268 -0
- package/src/extra/formHelpers.ts +139 -0
- package/src/extra/head.ts +106 -0
- package/src/extra/hmr.ts +2 -3
- package/src/extra/owned.ts +24 -0
- package/src/extra/pager.ts +50 -0
- package/src/extra/persist.ts +133 -0
- package/src/extra/pwa.ts +1 -1
- package/src/extra/queryCache.ts +107 -0
- package/src/extra/reducers.ts +13 -6
- package/src/extra/reduxDevtools.ts +92 -0
- package/src/extra/replies.ts +54 -0
- package/src/extra/router.ts +323 -0
- package/src/extra/run.ts +94 -210
- package/src/extra/selection.ts +76 -0
- package/src/extra/socketDriver.ts +265 -0
- package/src/extra/sortable.ts +377 -0
- package/src/extra/ssr.ts +427 -160
- package/src/extra/standardSchema.ts +27 -0
- package/src/extra/testing.ts +3158 -190
- package/src/extra/timers.ts +95 -0
- package/src/extra/undo.ts +261 -0
- package/src/extra/viewTransitions.ts +38 -0
- package/src/extra/virtual.ts +513 -0
- package/src/extra/widget.ts +201 -0
- package/src/extra/xstreamExtras.ts +269 -0
- package/src/index.d.ts +3012 -87
- package/src/index.ts +32 -10
- package/src/jsx-runtime.ts +15 -1
- package/src/jsx.ts +2 -1
- package/src/lazy.ts +50 -7
- package/src/portal.ts +3 -1
- package/src/pragma/index.ts +262 -134
- package/src/react-peers.d.ts +5 -0
- package/src/react.d.ts +43 -0
- package/src/react.ts +110 -0
- package/src/shared.ts +48 -0
- package/src/slot.ts +1 -1
- package/src/suspense.ts +3 -1
- package/src/switchable.ts +6 -119
- package/src/transition.ts +3 -1
- package/src/ui/accordion.ts +64 -0
- package/src/ui/dialog.ts +129 -0
- package/src/ui/disclosure.ts +35 -0
- package/src/ui/popover.ts +45 -0
- package/src/ui/shared.ts +94 -0
- package/src/ui/tabs.ts +82 -0
- package/src/ui/toaster.ts +198 -0
- package/src/ui/tooltip.ts +70 -0
- package/src/ui/zag/combobox.ts +115 -0
- package/src/ui/zag/menu.ts +40 -0
- package/src/ui/zag/select.ts +54 -0
- package/src/ui/zag/shared.ts +43 -0
- package/src/ui-combobox.d.ts +20 -0
- package/src/ui-combobox.ts +6 -0
- package/src/ui-menu.d.ts +30 -0
- package/src/ui-menu.ts +6 -0
- package/src/ui-select.d.ts +17 -0
- package/src/ui-select.ts +6 -0
- package/src/ui-zag-types.d.ts +42 -0
- package/src/ui.d.ts +208 -0
- package/src/ui.ts +15 -0
- package/src/vike/+config.ts +9 -1
- package/src/vike/ClientOnly.ts +1 -1
- package/src/vike/onRenderClient.ts +144 -96
- package/src/vike/onRenderHtml.ts +69 -26
- package/src/vike/types.ts +27 -4
- package/src/vite/globalthis-shim.ts +18 -0
- package/src/vite/globalthis.ts +56 -0
- package/src/vite/plugin.d.ts +114 -2
- package/src/vite/plugin.ts +920 -56
- package/src/zag.d.ts +64 -0
- package/src/zag.ts +238 -0
- package/dist/astro/astro/client.d.ts +0 -5
- package/dist/astro/astro/index.d.ts +0 -16
- package/dist/astro/astro/server.d.ts +0 -12
- package/dist/astro/client.d.ts +0 -5
- package/dist/astro/client.esm.js +0 -4665
- package/dist/astro/collection.d.ts +0 -12
- package/dist/astro/component.d.ts +0 -22
- package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/astro/cycle/dom/index.d.ts +0 -8
- package/dist/astro/cycle/dom/isolate.d.ts +0 -9
- package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/astro/cycle/dom/modules.d.ts +0 -5
- package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
- package/dist/astro/cycle/dom/thunk.d.ts +0 -11
- package/dist/astro/cycle/dom/utils.d.ts +0 -8
- package/dist/astro/cycle/isolate/index.d.ts +0 -42
- package/dist/astro/cycle/run/adapt.d.ts +0 -6
- package/dist/astro/cycle/run/index.d.ts +0 -36
- package/dist/astro/cycle/run/internals.d.ts +0 -8
- package/dist/astro/cycle/run/types.d.ts +0 -49
- package/dist/astro/cycle/state/Collection.d.ts +0 -29
- package/dist/astro/cycle/state/StateSource.d.ts +0 -20
- package/dist/astro/cycle/state/index.d.ts +0 -5
- package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
- package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
- package/dist/astro/cycle/state/types.d.ts +0 -18
- package/dist/astro/cycle/state/withState.d.ts +0 -17
- package/dist/astro/extra/classes.d.ts +0 -7
- package/dist/astro/extra/devtools.d.ts +0 -78
- package/dist/astro/extra/dragDriver.d.ts +0 -20
- package/dist/astro/extra/driverFactories.d.ts +0 -12
- package/dist/astro/extra/eventDriver.d.ts +0 -9
- package/dist/astro/extra/exactState.d.ts +0 -1
- package/dist/astro/extra/hmr.d.ts +0 -17
- package/dist/astro/extra/logDriver.d.ts +0 -2
- package/dist/astro/extra/processDrag.d.ts +0 -19
- package/dist/astro/extra/processForm.d.ts +0 -15
- package/dist/astro/extra/run.d.ts +0 -13
- package/dist/astro/extra/xstreamCompat.d.ts +0 -5
- package/dist/astro/index.d.ts +0 -19
- package/dist/astro/index.esm.js +0 -27
- package/dist/astro/jsx-dev-runtime.d.ts +0 -1
- package/dist/astro/jsx-runtime.d.ts +0 -4
- package/dist/astro/jsx.d.ts +0 -2
- package/dist/astro/pragma/fn.d.ts +0 -7
- package/dist/astro/pragma/index.d.ts +0 -7
- package/dist/astro/pragma/is.d.ts +0 -10
- package/dist/astro/server.d.ts +0 -12
- package/dist/astro/server.esm.js +0 -25
- package/dist/astro/switchable.d.ts +0 -7
- package/dist/collection.d.ts +0 -12
- package/dist/component.d.ts +0 -22
- package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/cycle/dom/index.d.ts +0 -8
- package/dist/cycle/dom/isolate.d.ts +0 -9
- package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/cycle/dom/modules.d.ts +0 -5
- package/dist/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/cycle/dom/styleModule.d.ts +0 -13
- package/dist/cycle/dom/thunk.d.ts +0 -11
- package/dist/cycle/dom/utils.d.ts +0 -8
- package/dist/cycle/isolate/index.d.ts +0 -42
- package/dist/cycle/run/adapt.d.ts +0 -6
- package/dist/cycle/run/index.d.ts +0 -36
- package/dist/cycle/run/internals.d.ts +0 -8
- package/dist/cycle/run/types.d.ts +0 -49
- package/dist/cycle/state/Collection.d.ts +0 -29
- package/dist/cycle/state/StateSource.d.ts +0 -20
- package/dist/cycle/state/index.d.ts +0 -5
- package/dist/cycle/state/pickCombine.d.ts +0 -3
- package/dist/cycle/state/pickMerge.d.ts +0 -3
- package/dist/cycle/state/types.d.ts +0 -18
- package/dist/cycle/state/withState.d.ts +0 -17
- package/dist/extra/classes.d.ts +0 -7
- package/dist/extra/devtools.d.ts +0 -78
- package/dist/extra/dragDriver.d.ts +0 -20
- package/dist/extra/driverFactories.d.ts +0 -12
- package/dist/extra/eventDriver.d.ts +0 -9
- package/dist/extra/exactState.d.ts +0 -1
- package/dist/extra/hmr.d.ts +0 -17
- package/dist/extra/logDriver.d.ts +0 -2
- package/dist/extra/processDrag.d.ts +0 -19
- package/dist/extra/processForm.d.ts +0 -15
- package/dist/extra/run.d.ts +0 -13
- package/dist/extra/xstreamCompat.d.ts +0 -5
- package/dist/jsx-dev-runtime.d.ts +0 -1
- package/dist/jsx-dev-runtime.js +0 -224
- package/dist/jsx-runtime.d.ts +0 -4
- package/dist/jsx-runtime.js +0 -224
- package/dist/jsx.d.ts +0 -2
- package/dist/pragma/fn.d.ts +0 -7
- package/dist/pragma/index.d.ts +0 -7
- package/dist/pragma/is.d.ts +0 -10
- package/dist/switchable.d.ts +0 -7
- package/dist/vike/+config.cjs.js +0 -65
- package/src/component.ts +0 -2256
- package/src/cycle/isolate/index.ts +0 -196
- package/src/cycle/run/index.ts +0 -151
- package/src/cycle/run/internals.ts +0 -143
- package/src/cycle/state/Collection.ts +0 -174
- package/src/cycle/state/index.ts +0 -5
- package/src/cycle/state/pickCombine.ts +0 -167
- package/src/cycle/state/pickMerge.ts +0 -122
- package/src/cycle/state/withState.ts +0 -47
- package/src/pragma/fn.ts +0 -55
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4 3-E (GS-10): the DevTools action log, in the dev-only 'sygnal/devtools' entry (0 B in
|
|
3
|
+
* apps: nothing in the core calls it).
|
|
4
|
+
*
|
|
5
|
+
* It records every action of every component instance through 2-C's recorder
|
|
6
|
+
* (./diagnostics/checks/actionLog: actionHooks), a hook layer every app reads from the core's
|
|
7
|
+
* diagnostics bridge (globalThis.__SYGNAL_DIAGNOSTICS__.layers), so it records with diagnostics
|
|
8
|
+
* off too. On top of 2-C's record it keeps:
|
|
9
|
+
* seq, at a sequence number, ms since the session started
|
|
10
|
+
* parent the parent instance id (null: a root)
|
|
11
|
+
* before, after the instance's state around the action's STATE reducer (onReducer), when it
|
|
12
|
+
* changed the state (`after` without calculated fields)
|
|
13
|
+
* replySink for a reply action (and RESOURCE): the source that delivered it, and
|
|
14
|
+
* replyKind 'fetch' when that source is makeFetchDriver's (renderComponent fakes it)
|
|
15
|
+
*
|
|
16
|
+
* A session runs from the recorder's start (or the last clearActions()) to now. getSession()
|
|
17
|
+
* turns it into a SessionRecording for one instance (./copyAsTest generates the test).
|
|
18
|
+
*/
|
|
19
|
+
import {clockNow, actionHooks} from './diagnostics/checks/actionLog'
|
|
20
|
+
import {addLayer, devFacade} from './devtoolsNext'
|
|
21
|
+
import type {ActionListener, ActionRecord, ActionCause} from './diagnostics/checks/actionLog'
|
|
22
|
+
|
|
23
|
+
export type {ActionCause}
|
|
24
|
+
|
|
25
|
+
export interface DevtoolsAction {
|
|
26
|
+
seq: number
|
|
27
|
+
type: string
|
|
28
|
+
data: any
|
|
29
|
+
component: string
|
|
30
|
+
instance: string
|
|
31
|
+
parent: string | null
|
|
32
|
+
/** live: fills in as the action's reducers run */
|
|
33
|
+
sinks: string[]
|
|
34
|
+
cause: ActionCause
|
|
35
|
+
/** ms since the session started */
|
|
36
|
+
at: number
|
|
37
|
+
before?: any
|
|
38
|
+
after?: any
|
|
39
|
+
replySink?: string
|
|
40
|
+
replyKind?: 'fetch' | 'other'
|
|
41
|
+
/** an intent action whose data is the instance's own state (an intent over STATE.stream) */
|
|
42
|
+
echo?: true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SessionAction {
|
|
46
|
+
type: string
|
|
47
|
+
data: any
|
|
48
|
+
cause: ActionCause
|
|
49
|
+
sinks: string[]
|
|
50
|
+
at: number
|
|
51
|
+
replySink?: string
|
|
52
|
+
replyKind?: 'fetch' | 'other'
|
|
53
|
+
echo?: true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One instance's session: what copyAsTest() needs (plain data, raw action data) */
|
|
57
|
+
export interface SessionRecording {
|
|
58
|
+
version: 1
|
|
59
|
+
component: string
|
|
60
|
+
instance: string
|
|
61
|
+
/** the state when the session started for this instance (undefined: unknown) */
|
|
62
|
+
initialState?: any
|
|
63
|
+
/** the component's own `initialState` (renderComponent's default) */
|
|
64
|
+
definitionInitialState?: any
|
|
65
|
+
finalState: any
|
|
66
|
+
/** the action names the component can be sent (model keys, behavior actions); undefined: any */
|
|
67
|
+
actionNames?: string[]
|
|
68
|
+
/** source names beyond DOM / EVENTS / STATE / LOG / CHILD / PARENT / READY */
|
|
69
|
+
drivers: string[]
|
|
70
|
+
/** those of `drivers` renderComponent fakes (makeFetchDriver sources: t.respond / t.fail) */
|
|
71
|
+
fakeable: string[]
|
|
72
|
+
/** the instance's own actions, in order */
|
|
73
|
+
actions: SessionAction[]
|
|
74
|
+
/** state changes in descendant instances a root-level replay can't reproduce */
|
|
75
|
+
foreign: Array<{type: string; component: string; instance: string; cause: ActionCause}>
|
|
76
|
+
/** the session lost its oldest actions (more than the recorder keeps) */
|
|
77
|
+
truncated?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ActionFilter {
|
|
81
|
+
component?: string
|
|
82
|
+
instance?: string | number
|
|
83
|
+
type?: string | RegExp
|
|
84
|
+
cause?: ActionCause | ActionCause[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface Inst {
|
|
88
|
+
c: any
|
|
89
|
+
id: string
|
|
90
|
+
name: string
|
|
91
|
+
parent: string | null
|
|
92
|
+
stateName: string
|
|
93
|
+
/** created after the session started */
|
|
94
|
+
inSession: boolean
|
|
95
|
+
hasInitial: boolean
|
|
96
|
+
initial?: any
|
|
97
|
+
disposed: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const MAX = 5000
|
|
101
|
+
const STANDARD = /^(DOM|EVENTS|LOG|CHILD|PARENT|READY|props\$|children\$|dispose\$)$/
|
|
102
|
+
|
|
103
|
+
let actions: DevtoolsAction[] = []
|
|
104
|
+
let seq = 0
|
|
105
|
+
let epoch = clockNow()
|
|
106
|
+
let truncated = false
|
|
107
|
+
let created = 0
|
|
108
|
+
const insts = new Map<string, Inst>()
|
|
109
|
+
const byRec = new WeakMap<ActionRecord, DevtoolsAction>()
|
|
110
|
+
/** instance -> the action whose STATE reducer just produced a value (its onReducer follows) */
|
|
111
|
+
const pendingState = new WeakMap<any, DevtoolsAction>()
|
|
112
|
+
const subscribers = new Set<(a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => void>()
|
|
113
|
+
|
|
114
|
+
const notify = (a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => {
|
|
115
|
+
for (const s of subscribers) try { s(a, kind) } catch (_) { /* a subscriber never breaks the app */ }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const idOf = (c: any) => String(c && c._componentNumber)
|
|
119
|
+
|
|
120
|
+
function ensure(c: any): Inst {
|
|
121
|
+
const id = idOf(c)
|
|
122
|
+
let i = insts.get(id)
|
|
123
|
+
if (!i || i.c !== c) {
|
|
124
|
+
const p = c && c.__next && c.__next.parentId
|
|
125
|
+
i = {c, id, name: c && c.name, parent: typeof p == 'number' ? String(p) : null, stateName: (c && c.stateSourceName) || 'STATE',
|
|
126
|
+
inSession: true, hasInitial: false, disposed: false}
|
|
127
|
+
;(i as any).order = ++created
|
|
128
|
+
insts.set(id, i)
|
|
129
|
+
}
|
|
130
|
+
return i
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function clean(c: any, s: any): any {
|
|
134
|
+
try { return typeof c.cleanupCalculated == 'function' ? c.cleanupCalculated(s) : s } catch (_) { return s }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const listener: ActionListener = {
|
|
138
|
+
action(r, c) {
|
|
139
|
+
const info = ensure(c)
|
|
140
|
+
// the instance's state when its session started: INITIALIZE's data (its initial state), else
|
|
141
|
+
// the state it had at its first action
|
|
142
|
+
if (r.type == 'INITIALIZE' && info.inSession) { info.initial = r.data; info.hasInitial = true }
|
|
143
|
+
else if (!info.hasInitial) { info.initial = c.currentState; info.hasInitial = true }
|
|
144
|
+
const a: DevtoolsAction = {seq: ++seq, type: r.type, data: r.data, component: r.component, instance: r.instance,
|
|
145
|
+
parent: info.parent, sinks: r.sinks, cause: r.cause, at: r.time - epoch}
|
|
146
|
+
const slot = (r as any).source
|
|
147
|
+
if (slot !== undefined && (r.cause == 'reply' || r.type == 'RESOURCE')) {
|
|
148
|
+
a.replySink = slot
|
|
149
|
+
a.replyKind = c.sources && c.sources[slot] && c.sources[slot].__sygnalStatic === 'resources' ? 'fetch' : 'other'
|
|
150
|
+
}
|
|
151
|
+
if (r.cause == 'intent' && isStateEcho(r.data, c.currentState)) a.echo = true
|
|
152
|
+
byRec.set(r, a)
|
|
153
|
+
actions.push(a)
|
|
154
|
+
if (actions.length > MAX) { actions.shift(); truncated = true }
|
|
155
|
+
notify(a, 'add')
|
|
156
|
+
},
|
|
157
|
+
sink(r, sink) {
|
|
158
|
+
const a = byRec.get(r)
|
|
159
|
+
if (!a) return
|
|
160
|
+
const info = insts.get(r.instance)
|
|
161
|
+
if (info && info.c && sink == info.stateName) pendingState.set(info.c, a)
|
|
162
|
+
notify(a, 'update')
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onReducer(c: any, name: string, prev: any, next: any): void {
|
|
167
|
+
const a = pendingState.get(c)
|
|
168
|
+
if (!a || a.type !== name || 'after' in a) return
|
|
169
|
+
pendingState.delete(c)
|
|
170
|
+
a.before = prev
|
|
171
|
+
a.after = clean(c, next)
|
|
172
|
+
notify(a, 'update')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onDispose(c: any): void {
|
|
176
|
+
const i = insts.get(idOf(c))
|
|
177
|
+
if (i && i.c === c) { i.disposed = true; i.c = undefined }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let unregister: (() => void) | undefined
|
|
181
|
+
|
|
182
|
+
/** PLAN-4.6 R4: the same recorder on the core, from its hooks (per app; a facade per instance) */
|
|
183
|
+
function nextLayer(api: any): any {
|
|
184
|
+
const L: ActionListener = {
|
|
185
|
+
action: (r, iv) => listener.action(r, devFacade(iv, api)),
|
|
186
|
+
sink: (r, sink, reducer) => listener.sink!(r, sink, reducer),
|
|
187
|
+
}
|
|
188
|
+
const log = actionHooks(L)
|
|
189
|
+
return {
|
|
190
|
+
...log,
|
|
191
|
+
onCreate(iv: any) { ensure(devFacade(iv, api)); log.onCreate(iv) },
|
|
192
|
+
onReducer(iv: any, name: string, prev: any, next: any) { onReducer(devFacade(iv, api), name, prev, next) },
|
|
193
|
+
onDispose(iv: any) { onDispose(devFacade(iv, api)) },
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Start recording actions (idempotent; 'sygnal/devtools' does it on import in a browser). Only
|
|
199
|
+
* components created afterwards are recorded. Returns a function that stops recording.
|
|
200
|
+
*/
|
|
201
|
+
export function recordActions(): () => void {
|
|
202
|
+
if (!unregister) {
|
|
203
|
+
const core = (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
204
|
+
if (!core) return () => {}
|
|
205
|
+
unregister = addLayer(nextLayer)
|
|
206
|
+
}
|
|
207
|
+
return stopRecording
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function stopRecording(): void {
|
|
211
|
+
if (unregister) { unregister(); unregister = undefined }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const isRecording = (): boolean => !!unregister
|
|
215
|
+
|
|
216
|
+
/** Start a new session: forget the recorded actions; each live instance's state is its new initial state. */
|
|
217
|
+
export function clearActions(): void {
|
|
218
|
+
actions = []
|
|
219
|
+
epoch = clockNow()
|
|
220
|
+
truncated = false
|
|
221
|
+
for (const [id, i] of insts) {
|
|
222
|
+
if (i.disposed) { insts.delete(id); continue }
|
|
223
|
+
i.inSession = false
|
|
224
|
+
i.initial = i.c && i.c.currentState
|
|
225
|
+
i.hasInitial = true
|
|
226
|
+
}
|
|
227
|
+
notify(null, 'reset')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const matches = (a: DevtoolsAction, f: ActionFilter) =>
|
|
231
|
+
(f.component === undefined || a.component === f.component) &&
|
|
232
|
+
(f.instance === undefined || a.instance === String(f.instance)) &&
|
|
233
|
+
(f.type === undefined || (typeof f.type == 'string' ? a.type === f.type : f.type.test(a.type))) &&
|
|
234
|
+
(f.cause === undefined || ([] as ActionCause[]).concat(f.cause).includes(a.cause))
|
|
235
|
+
|
|
236
|
+
/** The session's actions (oldest first), optionally filtered by component, instance, type or cause. */
|
|
237
|
+
export function getActions(filter?: ActionFilter): DevtoolsAction[] {
|
|
238
|
+
return filter ? actions.filter(a => matches(a, filter)) : actions.slice()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Subscribe to the log: (action, 'add' | 'update') and (null, 'reset'). Returns unsubscribe. */
|
|
242
|
+
export function onAction(fn: (a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => void): () => void {
|
|
243
|
+
subscribers.add(fn)
|
|
244
|
+
return () => { subscribers.delete(fn) }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** the live instances, newest first */
|
|
248
|
+
const live = () => [...insts.values()].filter(i => !i.disposed && i.c).sort((a, b) => (b as any).order - (a as any).order)
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The instance a session target names: undefined (the newest live root), an instance id, a
|
|
252
|
+
* component function (its newest live instance) or run()'s result (its root, by DOM source).
|
|
253
|
+
*/
|
|
254
|
+
export function resolveInstance(target?: any): Inst | undefined {
|
|
255
|
+
const all = live()
|
|
256
|
+
if (target === undefined || target === null) return all.find(i => i.parent === null)
|
|
257
|
+
if (typeof target == 'string' || typeof target == 'number') return insts.get(String(target))
|
|
258
|
+
if (typeof target == 'function') return all.find(i => i.c.view === target)
|
|
259
|
+
if (typeof target == 'object') {
|
|
260
|
+
const s = target.sources
|
|
261
|
+
if (s && typeof s == 'object') {
|
|
262
|
+
return all.find(i => i.parent === null && Object.keys(s).some(k => k != 'STATE' && s[k] && i.c.sources && i.c.sources[k] === s[k])) ||
|
|
263
|
+
all.find(i => i.parent === null)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function descendants(id: string): Set<string> {
|
|
270
|
+
const out = new Set<string>()
|
|
271
|
+
let grew = true
|
|
272
|
+
while (grew) {
|
|
273
|
+
grew = false
|
|
274
|
+
for (const i of insts.values()) if (i.parent !== null && (i.parent === id || out.has(i.parent)) && !out.has(i.id)) { out.add(i.id); grew = true }
|
|
275
|
+
}
|
|
276
|
+
// instances already pruned: their actions still name their parent
|
|
277
|
+
for (const a of actions) if (a.parent !== null && (a.parent === id || out.has(a.parent))) out.add(a.instance)
|
|
278
|
+
return out
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const REPLAYED = new Set<ActionCause>(['intent', 'simulateAction', 'behavior', 'reply'])
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The session of one instance (see resolveInstance for `target`) as plain data: its initial
|
|
285
|
+
* and final state, its own actions, and the state changes of its descendants (a replay at this
|
|
286
|
+
* instance can't reproduce them).
|
|
287
|
+
*/
|
|
288
|
+
export function getSession(target?: any): SessionRecording {
|
|
289
|
+
const info = resolveInstance(target)
|
|
290
|
+
if (!info) throw new Error('[Sygnal DevTools] getSession: no live component instance matches' + (target === undefined ? ' (no root recorded: import sygnal/devtools before run())' : ''))
|
|
291
|
+
const c = info.c
|
|
292
|
+
const desc = descendants(info.id)
|
|
293
|
+
const stateNameOf = (id: string) => insts.get(id)?.stateName || 'STATE'
|
|
294
|
+
const model = c.model && typeof c.model == 'object' ? Object.keys(c.model).map(k => k.split('|')[0].trim()) : undefined
|
|
295
|
+
const behaviors = c._behaviorActions && typeof c._behaviorActions == 'object' ? Object.keys(c._behaviorActions) : []
|
|
296
|
+
const sources = c.sources || {}
|
|
297
|
+
const drivers = (c.sourceNames || Object.keys(sources)).filter((n: string) =>
|
|
298
|
+
!STANDARD.test(n) && n !== info.stateName && n != 'STATE' && n != 'state' && !n.startsWith('__'))
|
|
299
|
+
return {
|
|
300
|
+
version: 1,
|
|
301
|
+
component: info.name,
|
|
302
|
+
instance: info.id,
|
|
303
|
+
initialState: info.hasInitial ? info.initial : undefined,
|
|
304
|
+
definitionInitialState: c.view ? c.view.initialState : undefined,
|
|
305
|
+
finalState: c.currentState,
|
|
306
|
+
actionNames: model ? [...model, ...behaviors] : undefined,
|
|
307
|
+
drivers,
|
|
308
|
+
fakeable: drivers.filter((n: string) => sources[n] && sources[n].__sygnalStatic === 'resources'),
|
|
309
|
+
actions: actions.filter(a => a.instance === info.id).map(a => ({
|
|
310
|
+
type: a.type, data: a.data, cause: a.cause, sinks: [...a.sinks], at: a.at,
|
|
311
|
+
...(a.replySink !== undefined ? {replySink: a.replySink, replyKind: a.replyKind} : {}),
|
|
312
|
+
...(a.echo ? {echo: true as const} : {}),
|
|
313
|
+
})),
|
|
314
|
+
foreign: actions.filter(a => desc.has(a.instance) && REPLAYED.has(a.cause) && a.sinks.includes(stateNameOf(a.instance)))
|
|
315
|
+
.map(a => ({type: a.type, component: a.component, instance: a.instance, cause: a.cause})),
|
|
316
|
+
...(truncated ? {truncated: true} : {}),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** A JSON-safe copy for the panel: DOM elements and events become short descriptions. */
|
|
321
|
+
export function preview(v: any): any {
|
|
322
|
+
if (v === undefined) return undefined
|
|
323
|
+
const seen = new WeakSet<object>()
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(JSON.stringify(v, (_k, x) => {
|
|
326
|
+
if (typeof x == 'function') return `[Function ${x.name || 'anonymous'}]`
|
|
327
|
+
if (typeof x == 'symbol') return String(x)
|
|
328
|
+
if (typeof x == 'bigint') return `${x}n`
|
|
329
|
+
if (x && typeof x == 'object') {
|
|
330
|
+
if (isElement(x)) return describeElement(x)
|
|
331
|
+
if (isEvent(x)) return `[${x.constructor && x.constructor.name || 'Event'} ${x.type}]`
|
|
332
|
+
if (x instanceof Map) return {'[Map]': [...x.entries()]}
|
|
333
|
+
if (x instanceof Set) return {'[Set]': [...x.values()]}
|
|
334
|
+
if (seen.has(x)) return '[Circular]'
|
|
335
|
+
seen.add(x)
|
|
336
|
+
}
|
|
337
|
+
return x
|
|
338
|
+
}))
|
|
339
|
+
} catch (_) {
|
|
340
|
+
return '[unserializable]'
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const isPlain = (x: any) => !!x && typeof x == 'object' && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null)
|
|
345
|
+
|
|
346
|
+
export function deepEqual(a: any, b: any): boolean {
|
|
347
|
+
if (Object.is(a, b)) return true
|
|
348
|
+
if (!a || !b || typeof a != 'object' || typeof b != 'object') return false
|
|
349
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
350
|
+
const ka = Object.keys(a).filter(k => a[k] !== undefined), kb = Object.keys(b).filter(k => b[k] !== undefined)
|
|
351
|
+
return ka.length == kb.length && ka.every(k => deepEqual(a[k], b[k]))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The data is the state itself (STATE.stream in the intent; calculated fields may be added): the
|
|
356
|
+
* replay sends it again by itself, so it isn't replayed.
|
|
357
|
+
*/
|
|
358
|
+
function isStateEcho(data: any, state: any): boolean {
|
|
359
|
+
if (!isPlain(data) || !isPlain(state)) return false
|
|
360
|
+
const keys = Object.keys(state)
|
|
361
|
+
return keys.length > 0 && (data === state || keys.every(k => deepEqual(data[k], state[k])))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export const isElement = (x: any): boolean => !!x && typeof x == 'object' && x.nodeType === 1 && typeof x.tagName == 'string'
|
|
365
|
+
export const isEvent = (x: any): boolean => !!x && typeof x == 'object' && typeof x.type == 'string' && typeof x.preventDefault == 'function' && 'target' in x
|
|
366
|
+
|
|
367
|
+
const describeElement = (el: any) => {
|
|
368
|
+
const cls = typeof el.className == 'string' && el.className.trim() ? '.' + el.className.trim().split(/\s+/).join('.') : ''
|
|
369
|
+
return `<${String(el.tagName).toLowerCase()}${el.id ? '#' + el.id : ''}${cls}>`
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** test seam: forget everything (sessions, instances); recording stays as it is */
|
|
373
|
+
export function _resetActionLog(): void {
|
|
374
|
+
actions = []
|
|
375
|
+
seq = 0
|
|
376
|
+
epoch = clockNow()
|
|
377
|
+
truncated = false
|
|
378
|
+
insts.clear()
|
|
379
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {SygnalDevTools} from './devtools';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The installed DevTools bridge, or undefined (D77: the bridge itself lives in the
|
|
5
|
+
* 'sygnal/devtools' dev entry, which sygnal/vite injects in dev).
|
|
6
|
+
*/
|
|
7
|
+
export function getDevTools(): SygnalDevTools | undefined {
|
|
8
|
+
return (globalThis as any).__SYGNAL_DEVTOOLS__;
|
|
9
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4.6 R4: 'sygnal/devtools' on the next component core (04 §3.5).
|
|
3
|
+
*
|
|
4
|
+
* The core calls no `window.__SYGNAL_DEVTOOLS__` method itself: each app reads the dev
|
|
5
|
+
* entries' hook layers once (`__SYGNAL_DIAGNOSTICS__.layers`), and this file's layer maps the
|
|
6
|
+
* hooks onto the bridge's existing methods (onComponentCreated, onStateChanged, ...). The bridge
|
|
7
|
+
* keeps a facade per instance with the fields it reads from today's instances (currentState,
|
|
8
|
+
* model, sourceNames, ...); time travel and the debug toggle go through the app's runtime API
|
|
9
|
+
* (`setState`, `setDebug`) instead of `sinks.STATE.shamefullySendNext` and `instance._debug`.
|
|
10
|
+
*/
|
|
11
|
+
const facades = new WeakMap<object, any>()
|
|
12
|
+
|
|
13
|
+
/** the bridge's view of a core instance (an InstanceView + its app's runtime API) */
|
|
14
|
+
export function devFacade(iv: any, api: any): any {
|
|
15
|
+
let f = facades.get(iv)
|
|
16
|
+
if (f) return f
|
|
17
|
+
f = {
|
|
18
|
+
__next: iv,
|
|
19
|
+
__api: api,
|
|
20
|
+
get _componentNumber() { return iv.id },
|
|
21
|
+
get name() { return iv.name },
|
|
22
|
+
get isSubComponent() { return !iv.isRoot },
|
|
23
|
+
get parentId() { return iv.parentId },
|
|
24
|
+
get view() { return iv.def.view },
|
|
25
|
+
get model() {
|
|
26
|
+
const m: Record<string, any> = {}
|
|
27
|
+
iv.def.handlers.forEach((hs: any[], a: string) => { const e: any = m[a] = {}; for (const [s, fn] of hs) e[s] = fn })
|
|
28
|
+
return m
|
|
29
|
+
},
|
|
30
|
+
get intent() { return iv.def.intent },
|
|
31
|
+
get context() { return iv.def.view.context },
|
|
32
|
+
get calculated() { return iv.def.view.calculated },
|
|
33
|
+
get _behaviorActions() { return iv.def.behaviorActions },
|
|
34
|
+
get currentState() { return iv.state },
|
|
35
|
+
get currentContext() { return iv.context },
|
|
36
|
+
get currentProps() { return iv.props },
|
|
37
|
+
get sources() { return iv.sources },
|
|
38
|
+
get sourceNames() { return Object.keys(iv.sources) },
|
|
39
|
+
stateSourceName: 'STATE',
|
|
40
|
+
get _debug() { return false },
|
|
41
|
+
/** the state without its calculated fields (as today's cleanupCalculated) */
|
|
42
|
+
cleanupCalculated(s: any) {
|
|
43
|
+
const c = iv.def.calcNames
|
|
44
|
+
if (!c || !s || typeof s != 'object' || Array.isArray(s)) return s
|
|
45
|
+
const out = {...s}
|
|
46
|
+
c.forEach((k: string) => { delete out[k] })
|
|
47
|
+
return out
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
facades.set(iv, f)
|
|
51
|
+
return f
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** the hook layer of the DevTools bridge `dt` for one app */
|
|
55
|
+
export function devtoolsHooks(dt: any, api: any): any {
|
|
56
|
+
const mounted = new WeakSet<object>()
|
|
57
|
+
return {
|
|
58
|
+
onCreate(iv: any) {
|
|
59
|
+
const f = devFacade(iv, api)
|
|
60
|
+
dt.onComponentCreated(iv.id, iv.name, f)
|
|
61
|
+
if (iv.parentId !== undefined) dt.onSubComponentRegistered(iv.parentId, iv.id)
|
|
62
|
+
// a Collection's first item (today's onCollectionMounted)
|
|
63
|
+
if (iv.kind == 'item' && iv.parentId !== undefined) {
|
|
64
|
+
const p = api.get(iv.parentId)
|
|
65
|
+
if (p && !mounted.has(p)) { mounted.add(p); dt.onCollectionMounted(p.id, p.name, iv.name, null) }
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
onStateChanged(iv: any, s: any) { dt.connected && dt.onStateChanged(iv.id, iv.name, s) },
|
|
69
|
+
onPropsChanged(iv: any, p: any) { dt.connected && dt.onPropsChanged(iv.id, iv.name, p) },
|
|
70
|
+
onContextChanged(iv: any, c: any) { dt.connected && dt.onContextChanged(iv.id, iv.name, c) },
|
|
71
|
+
onAction(iv: any, a: any) { dt.connected && dt.onActionDispatched(iv.id, iv.name, a.type, a.data) },
|
|
72
|
+
onReady(p: any, c: any, ready: boolean) { dt.connected && dt.onReadyChanged(p.id, p.name, c.name, ready) },
|
|
73
|
+
onDispose(iv: any) { dt.onComponentDisposed(iv.id, iv.name) },
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** register a hook layer factory with the core (read by each app at its start) */
|
|
78
|
+
export function addLayer(f: (api: any) => any): () => void {
|
|
79
|
+
const core = (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
80
|
+
if (!core) return () => {}
|
|
81
|
+
;(core.layers ||= new Set()).add(f)
|
|
82
|
+
return () => { core.layers?.delete(f) }
|
|
83
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4 2-C (GS-10): the action log. Records every action a component instance runs as
|
|
3
|
+
* { type, data, component, instance, sinks, cause, time }
|
|
4
|
+
* for renderComponent's t.actions / t.explain() (src/extra/testing.ts), inspect({ actions }) in
|
|
5
|
+
* the 'sygnal/diagnostics' dev entry (./inspect.ts) and the DevTools action log
|
|
6
|
+
* (src/extra/devtoolsActions.ts).
|
|
7
|
+
*
|
|
8
|
+
* 0 B in apps: nothing in the core calls this. actionHooks() is a hook layer of the core
|
|
9
|
+
* (04-hooks-contract §3.3): onAction opens the record (the core passes the cause: 'intent',
|
|
10
|
+
* 'next', 'reply', 'built-in', 'simulateAction'; an intent action a behavior owns is
|
|
11
|
+
* 'behavior'), wrapHandler sees which sinks produced a value. (The old core's version patched
|
|
12
|
+
* each instance; R5 removed it.)
|
|
13
|
+
*
|
|
14
|
+
* Sinks: a function reducer counts when it returns a value: not ABORT (or another symbol, which
|
|
15
|
+
* the core drops), and for STATE not the state it was given (GS-4: no change); EFFECT counts when
|
|
16
|
+
* the handler ran without ABORT. A constant / `true` entry always sends. A throwing reducer
|
|
17
|
+
* sends nothing.
|
|
18
|
+
*/
|
|
19
|
+
import {isAbort, ORIGINAL} from '../../../shared'
|
|
20
|
+
|
|
21
|
+
export type ActionCause = 'intent' | 'next' | 'reply' | 'built-in' | 'simulateAction' | 'behavior'
|
|
22
|
+
|
|
23
|
+
export interface ActionRecord {
|
|
24
|
+
type: string
|
|
25
|
+
data: any
|
|
26
|
+
/** the component's name */
|
|
27
|
+
component: string
|
|
28
|
+
/** the instance's id (its component number, as inspect()'s component ids) */
|
|
29
|
+
instance: string
|
|
30
|
+
/** sinks that produced a value for this action (live: STATE fills in when the reducer runs) */
|
|
31
|
+
sinks: string[]
|
|
32
|
+
cause: ActionCause
|
|
33
|
+
/** clock time (fake-timer aware) when the action reached the model */
|
|
34
|
+
time: number
|
|
35
|
+
/** a reply: the source that delivered it */
|
|
36
|
+
source?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ActionListener {
|
|
40
|
+
action(record: ActionRecord, component: any): void
|
|
41
|
+
/** a sink produced a value for `record` (reducer: the model's function, when there is one) */
|
|
42
|
+
sink?(record: ActionRecord, sink: string, reducer: any): void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const BUILT_IN = /^(BOOTSTRAP|INITIALIZE|DISPOSE|RESOURCE)$/
|
|
46
|
+
|
|
47
|
+
/** now on the test's fake clock when there is one (vi.useFakeTimers()), else Date.now() */
|
|
48
|
+
export const clockNow = (): number => {
|
|
49
|
+
const c = (setTimeout as any).clock
|
|
50
|
+
return c ? c.now : Date.now()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* PLAN-4.6 R4: the same log on the core, from its hooks (04 §3.3) instead of instance
|
|
55
|
+
* patches: onAction opens the record (the core passes the cause: 'intent', 'next', 'reply',
|
|
56
|
+
* 'built-in', 'simulateAction'), wrapHandler sees which sinks produced a value. Same records,
|
|
57
|
+
* same cause rules: a built-in type is 'built-in' unless simulated; an intent action a behavior
|
|
58
|
+
* owns is 'behavior'. `only(inst)`: the instances to record (renderComponent: its own tree).
|
|
59
|
+
*/
|
|
60
|
+
export function actionHooks(listener: ActionListener, only?: (inst: any) => boolean): any {
|
|
61
|
+
const cur = new WeakMap<object, [ActionRecord, string[]]>()
|
|
62
|
+
// INITIALIZE: the core writes the initial state at creation (no action, unless the model
|
|
63
|
+
// has an INITIALIZE entry); today's log has an INITIALIZE record with STATE, so it is opened here
|
|
64
|
+
const init = new WeakMap<object, ActionRecord>()
|
|
65
|
+
return {
|
|
66
|
+
onCreate(inst: any) {
|
|
67
|
+
const d = inst.def
|
|
68
|
+
if ((only && !only(inst)) || d.initialState === undefined || !(inst.isRoot || d.isolated)) return
|
|
69
|
+
const rec: ActionRecord = {type: 'INITIALIZE', data: d.initialState, component: inst.name, instance: String(inst.id), sinks: [], cause: 'built-in', time: clockNow()}
|
|
70
|
+
init.set(inst, rec)
|
|
71
|
+
try { listener.action(rec, inst) } catch (_) { /* ignore */ }
|
|
72
|
+
addSink(listener, rec, ['STATE'], 'STATE', undefined)
|
|
73
|
+
},
|
|
74
|
+
onAction(inst: any, a: any) {
|
|
75
|
+
if (only && !only(inst)) return
|
|
76
|
+
const type = String(a.type), owned = inst.def.behaviorActions
|
|
77
|
+
const hs = inst.def.handlers.get(type) || [], order = hs.map((h: any) => h[0])
|
|
78
|
+
const opened = type == 'INITIALIZE' && init.get(inst)
|
|
79
|
+
if (opened) { init.delete(inst); cur.set(inst, [opened, order]); return }
|
|
80
|
+
const cause: ActionCause = a.cause == 'simulateAction' ? a.cause : BUILT_IN.test(type) ? 'built-in'
|
|
81
|
+
: a.cause == 'next' || a.cause == 'reply' ? a.cause
|
|
82
|
+
: owned && Object.prototype.hasOwnProperty.call(owned, type) ? 'behavior' : 'intent'
|
|
83
|
+
const rec: ActionRecord = {type, data: a.data, component: inst.name, instance: String(inst.id), sinks: [], cause, time: clockNow(), ...(a.source !== undefined && {source: a.source})}
|
|
84
|
+
cur.set(inst, [rec, order])
|
|
85
|
+
try { listener.action(rec, inst) } catch (_) { /* a listener never breaks the app */ }
|
|
86
|
+
// a constant / `true` entry always sends (EFFECT runs functions only)
|
|
87
|
+
for (const [sink, h] of hs) if (typeof h != 'function' && sink != 'EFFECT') addSink(listener, rec, order, sink, undefined)
|
|
88
|
+
},
|
|
89
|
+
wrapHandler(inst: any, type: string, sink: string, fn: any) {
|
|
90
|
+
const c = cur.get(inst)
|
|
91
|
+
if (!c || c[0].type !== type || typeof fn != 'function') return
|
|
92
|
+
const [rec, order] = c, isState = sink == 'STATE', isEffect = sink == 'EFFECT'
|
|
93
|
+
return function (this: any, state: any) {
|
|
94
|
+
const r = fn.apply(this, arguments)
|
|
95
|
+
if (!isAbort(r) && (isEffect || typeof r != 'symbol') && !(isState && r === state)) addSink(listener, rec, order, sink, fn[ORIGINAL] || fn)
|
|
96
|
+
return r
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function addSink(l: ActionListener, rec: ActionRecord, order: string[], sink: string, reducer: any): void {
|
|
103
|
+
if (!rec.sinks.includes(sink)) {
|
|
104
|
+
rec.sinks.push(sink)
|
|
105
|
+
rec.sinks.sort((a, b) => order.indexOf(a) - order.indexOf(b))
|
|
106
|
+
}
|
|
107
|
+
try { l.sink?.(rec, sink, reducer) } catch (_) { /* ignore */ }
|
|
108
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4 GS-1, runtime behavior checks (dev entry only; the static `uses` resolution is 3-D's).
|
|
3
|
+
*
|
|
4
|
+
* SYG127 (error), once per component name (not per Collection item; again in each renderComponent):
|
|
5
|
+
* - a `uses` key that the host's own `initialState` static already has (the behavior's slice
|
|
6
|
+
* replaces it);
|
|
7
|
+
* - a `uses` value that isn't a defineBehavior() result (no `merge`): the core skips it, so
|
|
8
|
+
* state[key] and its actions never exist.
|
|
9
|
+
* A host intent or model entry named like a behavior action ('pager.NEXT') is an override,
|
|
10
|
+
* not a collision.
|
|
11
|
+
* Mechanism: onModel (every instance gets one, with or without a model) reads the static
|
|
12
|
+
* `view.uses` and `view.initialState`.
|
|
13
|
+
*/
|
|
14
|
+
import type {DiagnosticCheck} from '../index'
|
|
15
|
+
import {devReport, nameOf, once} from './shared'
|
|
16
|
+
|
|
17
|
+
const what = (v: any): string =>
|
|
18
|
+
v === null || v === undefined ? String(v)
|
|
19
|
+
: typeof v == 'function' ? 'a function (a defineBehavior factory not called?)'
|
|
20
|
+
: typeof v == 'object' ? 'a plain object'
|
|
21
|
+
: `a ${typeof v}`
|
|
22
|
+
|
|
23
|
+
export const behaviorsCheck: DiagnosticCheck = {
|
|
24
|
+
id: 'behaviors',
|
|
25
|
+
|
|
26
|
+
onModel(component) {
|
|
27
|
+
const view = component?.view, uses = view?.uses
|
|
28
|
+
if (!uses || typeof uses != 'object' || !once('SYG127:' + nameOf(component))) return
|
|
29
|
+
const init = view.initialState
|
|
30
|
+
for (const key of Object.keys(uses)) {
|
|
31
|
+
const b = uses[key]
|
|
32
|
+
if (typeof b?.merge != 'function') {
|
|
33
|
+
devReport('SYG127', {
|
|
34
|
+
component,
|
|
35
|
+
message: `uses entry '${key}' is ${what(b)}, not a defineBehavior() result; it was skipped, so state.${key} and its '${key}.*' actions don't exist`,
|
|
36
|
+
fix: `Use a defineBehavior() factory's result: uses = { ${key}: behavior(options) }`,
|
|
37
|
+
data: {key, reason: 'unresolvable'},
|
|
38
|
+
})
|
|
39
|
+
} else if (init && typeof init == 'object' && key in init) {
|
|
40
|
+
devReport('SYG127', {
|
|
41
|
+
component,
|
|
42
|
+
message: `uses key '${key}' is also a key of ${nameOf(component)}'s initialState; the behavior's slice replaces that value`,
|
|
43
|
+
fix: `Remove '${key}' from initialState (pass its values as the behavior's options), or rename the uses key`,
|
|
44
|
+
data: {key, reason: 'initialState'},
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}
|