@gyral/core 0.3.0 → 0.3.1
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 +42 -0
- package/NOTICE +19 -0
- package/README.md +30 -5
- package/dist/call.d.ts +32 -0
- package/dist/call.js +63 -0
- package/dist/call.js.map +1 -0
- package/dist/children.d.ts +25 -3
- package/dist/children.js +18 -1
- package/dist/children.js.map +1 -1
- package/dist/cli/gyral-types.d.ts +2 -0
- package/dist/cli/gyral-types.js +23 -0
- package/dist/cli/gyral-types.js.map +1 -0
- package/dist/command.d.ts +54 -12
- package/dist/command.js +25 -11
- package/dist/command.js.map +1 -1
- package/dist/compiler/ast.d.ts +2 -0
- package/dist/compiler/ast.js +5 -1
- package/dist/compiler/ast.js.map +1 -1
- package/dist/compiler/compile.d.ts +5 -2
- package/dist/compiler/compile.js +36 -17
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/component-entries.d.ts +7 -0
- package/dist/compiler/component-entries.js +42 -0
- package/dist/compiler/component-entries.js.map +1 -0
- package/dist/compiler/components.d.ts +4 -0
- package/dist/compiler/components.js +182 -0
- package/dist/compiler/components.js.map +1 -0
- package/dist/compiler/facts.d.ts +31 -0
- package/dist/compiler/facts.js +285 -0
- package/dist/compiler/facts.js.map +1 -0
- package/dist/compiler/features.d.ts +34 -0
- package/dist/compiler/features.js +158 -0
- package/dist/compiler/features.js.map +1 -0
- package/dist/compiler/hooks.d.ts +4 -0
- package/dist/compiler/hooks.js +144 -89
- package/dist/compiler/hooks.js.map +1 -1
- package/dist/compiler/loader-source.d.ts +14 -0
- package/dist/compiler/loader-source.js +86 -0
- package/dist/compiler/loader-source.js.map +1 -0
- package/dist/compiler/locate.d.ts +14 -0
- package/dist/compiler/locate.js +78 -0
- package/dist/compiler/locate.js.map +1 -0
- package/dist/compiler/parse5-check.d.ts +1 -1
- package/dist/compiler/parse5-check.js +8 -3
- package/dist/compiler/parse5-check.js.map +1 -1
- package/dist/compiler/prop-schemas.d.ts +4 -0
- package/dist/compiler/prop-schemas.js +108 -0
- package/dist/compiler/prop-schemas.js.map +1 -0
- package/dist/compiler/scan.d.ts +13 -3
- package/dist/compiler/scan.js +21 -13
- package/dist/compiler/scan.js.map +1 -1
- package/dist/compiler/scope.d.ts +8 -0
- package/dist/compiler/scope.js +116 -0
- package/dist/compiler/scope.js.map +1 -0
- package/dist/compiler/tag-map.d.ts +41 -0
- package/dist/compiler/tag-map.js +126 -0
- package/dist/compiler/tag-map.js.map +1 -0
- package/dist/components-ready.d.ts +7 -0
- package/dist/components-ready.js +19 -0
- package/dist/components-ready.js.map +1 -0
- package/dist/define.d.ts +13 -4
- package/dist/define.js +8 -12
- package/dist/define.js.map +1 -1
- package/dist/devtools-events.d.ts +8 -0
- package/dist/devtools-events.js.map +1 -1
- package/dist/devtools-off.d.ts +1 -0
- package/dist/devtools-off.js +1 -0
- package/dist/devtools-off.js.map +1 -1
- package/dist/devtools.d.ts +3 -0
- package/dist/devtools.js +14 -0
- package/dist/devtools.js.map +1 -1
- package/dist/drivers-scope.js.map +1 -1
- package/dist/element-internals.d.ts +1 -0
- package/dist/element-internals.js +11 -0
- package/dist/element-internals.js.map +1 -0
- package/dist/element-types.d.ts +6 -2
- package/dist/element-types.js.map +1 -1
- package/dist/element.js +80 -27
- package/dist/element.js.map +1 -1
- package/dist/error-view.d.ts +10 -0
- package/dist/error-view.js +30 -0
- package/dist/error-view.js.map +1 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -0
- package/dist/errors.json +289 -0
- package/dist/eslint/imports.d.ts +2 -0
- package/dist/eslint/imports.js +2 -2
- package/dist/eslint/imports.js.map +1 -1
- package/dist/eslint/index.d.ts +2 -1
- package/dist/eslint/index.js +11 -3
- package/dist/eslint/index.js.map +1 -1
- package/dist/eslint/template-rule.d.ts +1 -0
- package/dist/eslint/template-rule.js +15 -5
- package/dist/eslint/template-rule.js.map +1 -1
- package/dist/eslint/unused-intent-rule.d.ts +2 -0
- package/dist/eslint/unused-intent-rule.js +186 -0
- package/dist/eslint/unused-intent-rule.js.map +1 -0
- package/dist/features.d.ts +2 -2
- package/dist/features.js.map +1 -1
- package/dist/focus.d.ts +6 -0
- package/dist/focus.js +32 -2
- package/dist/focus.js.map +1 -1
- package/dist/form-associated.d.ts +46 -0
- package/dist/form-associated.js +77 -0
- package/dist/form-associated.js.map +1 -0
- package/dist/forms.d.ts +42 -30
- package/dist/forms.js +85 -41
- package/dist/forms.js.map +1 -1
- package/dist/frame-lane.d.ts +11 -0
- package/dist/frame-lane.js +28 -0
- package/dist/frame-lane.js.map +1 -0
- package/dist/head.d.ts +61 -0
- package/dist/head.js +85 -0
- package/dist/head.js.map +1 -0
- package/dist/host-model.d.ts +34 -5
- package/dist/host-model.js +146 -23
- package/dist/host-model.js.map +1 -1
- package/dist/hydration-client.js +2 -2
- package/dist/hydration-client.js.map +1 -1
- package/dist/hydration-loader.d.ts +13 -0
- package/dist/hydration-loader.js +58 -0
- package/dist/hydration-loader.js.map +1 -0
- package/dist/hydration-off.d.ts +8 -0
- package/dist/hydration-off.js +25 -0
- package/dist/hydration-off.js.map +1 -0
- package/dist/hydration.d.ts +0 -12
- package/dist/hydration.js +3 -52
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +20 -24
- package/dist/index.js +10 -19
- package/dist/index.js.map +1 -1
- package/dist/indexeddb-types.d.ts +97 -0
- package/dist/indexeddb-types.js +2 -0
- package/dist/indexeddb-types.js.map +1 -0
- package/dist/indexeddb.d.ts +5 -0
- package/dist/indexeddb.js +118 -0
- package/dist/indexeddb.js.map +1 -0
- package/dist/intent.d.ts +41 -19
- package/dist/intent.js +144 -106
- package/dist/intent.js.map +1 -1
- package/dist/internal/indexeddb-open.d.ts +8 -0
- package/dist/internal/indexeddb-open.js +96 -0
- package/dist/internal/indexeddb-open.js.map +1 -0
- package/dist/internal/indexeddb-transaction.d.ts +4 -0
- package/dist/internal/indexeddb-transaction.js +134 -0
- package/dist/internal/indexeddb-transaction.js.map +1 -0
- package/dist/internal/interpreter.d.ts +12 -4
- package/dist/internal/interpreter.js +36 -48
- package/dist/internal/interpreter.js.map +1 -1
- package/dist/internal/token.d.ts +1 -0
- package/dist/internal/token.js +7 -0
- package/dist/internal/token.js.map +1 -0
- package/dist/internal.d.ts +20 -0
- package/dist/internal.js +22 -0
- package/dist/internal.js.map +1 -0
- package/dist/invoker-fallback-used.d.ts +4 -0
- package/dist/invoker-fallback-used.js +10 -0
- package/dist/invoker-fallback-used.js.map +1 -0
- package/dist/invoker-fallback.d.ts +4 -0
- package/dist/invoker-fallback.js +15 -0
- package/dist/invoker-fallback.js.map +1 -0
- package/dist/light-dom.d.ts +1 -1
- package/dist/light-dom.js.map +1 -1
- package/dist/prop.d.ts +30 -7
- package/dist/prop.js +19 -0
- package/dist/prop.js.map +1 -1
- package/dist/props.d.ts +8 -3
- package/dist/props.js +22 -15
- package/dist/props.js.map +1 -1
- package/dist/retry.d.ts +15 -0
- package/dist/retry.js +48 -0
- package/dist/retry.js.map +1 -0
- package/dist/scheduler.d.ts +7 -3
- package/dist/scheduler.js +38 -56
- package/dist/scheduler.js.map +1 -1
- package/dist/script-json.d.ts +2 -0
- package/dist/script-json.js +13 -0
- package/dist/script-json.js.map +1 -0
- package/dist/server-component.d.ts +1 -1
- package/dist/server-component.js +45 -14
- package/dist/server-component.js.map +1 -1
- package/dist/server.d.ts +8 -2
- package/dist/server.js +11 -2
- package/dist/server.js.map +1 -1
- package/dist/settled.d.ts +4 -3
- package/dist/settled.js +48 -26
- package/dist/settled.js.map +1 -1
- package/dist/spec-features-used.d.ts +21 -0
- package/dist/spec-features-used.js +50 -0
- package/dist/spec-features-used.js.map +1 -0
- package/dist/spec-features.d.ts +28 -0
- package/dist/spec-features.js +42 -0
- package/dist/spec-features.js.map +1 -0
- package/dist/states.d.ts +3 -5
- package/dist/states.js +22 -8
- package/dist/states.js.map +1 -1
- package/dist/storage.d.ts +69 -0
- package/dist/storage.js +158 -0
- package/dist/storage.js.map +1 -0
- package/dist/store-binding.js +2 -2
- package/dist/store-binding.js.map +1 -1
- package/dist/store-scope.d.ts +2 -4
- package/dist/store-scope.js +6 -16
- package/dist/store-scope.js.map +1 -1
- package/dist/store.js +36 -8
- package/dist/store.js.map +1 -1
- package/dist/subscription.d.ts +35 -0
- package/dist/subscription.js +74 -0
- package/dist/subscription.js.map +1 -0
- package/dist/subscriptions.d.ts +26 -0
- package/dist/subscriptions.js +98 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/transitions.d.ts +4 -5
- package/dist/transitions.js +10 -4
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +124 -27
- package/dist/types.js.map +1 -1
- package/dist/use-dialogs.d.ts +2 -0
- package/dist/use-dialogs.js +9 -0
- package/dist/use-dialogs.js.map +1 -0
- package/dist/use-disposal.d.ts +2 -0
- package/dist/use-disposal.js +9 -0
- package/dist/use-disposal.js.map +1 -0
- package/dist/use-error-view.d.ts +2 -0
- package/dist/use-error-view.js +9 -0
- package/dist/use-error-view.js.map +1 -0
- package/dist/use-errored.d.ts +2 -0
- package/dist/use-errored.js +9 -0
- package/dist/use-errored.js.map +1 -0
- package/dist/use-form-associated.d.ts +2 -0
- package/dist/use-form-associated.js +7 -0
- package/dist/use-form-associated.js.map +1 -0
- package/dist/use-frame-lane.d.ts +2 -0
- package/dist/use-frame-lane.js +10 -0
- package/dist/use-frame-lane.js.map +1 -0
- package/dist/use-invokers.d.ts +2 -0
- package/dist/use-invokers.js +10 -0
- package/dist/use-invokers.js.map +1 -0
- package/dist/use-states.d.ts +2 -0
- package/dist/use-states.js +10 -0
- package/dist/use-states.js.map +1 -0
- package/dist/use-subscriptions.d.ts +2 -0
- package/dist/use-subscriptions.js +10 -0
- package/dist/use-subscriptions.js.map +1 -0
- package/dist/use-transitions.d.ts +2 -0
- package/dist/use-transitions.js +10 -0
- package/dist/use-transitions.js.map +1 -0
- package/dist/view/attributes.d.ts +5 -0
- package/dist/view/attributes.js +5 -0
- package/dist/view/attributes.js.map +1 -1
- package/dist/view/compiled.d.ts +1 -1
- package/dist/view/compiled.js +1 -1
- package/dist/view/compiled.js.map +1 -1
- package/dist/view/index.d.ts +9 -4
- package/dist/view/index.js +9 -3
- package/dist/view/index.js.map +1 -1
- package/dist/view/loc.d.ts +9 -0
- package/dist/view/loc.js +50 -0
- package/dist/view/loc.js.map +1 -0
- package/dist/view/message.d.ts +17 -0
- package/dist/view/message.js +40 -0
- package/dist/view/message.js.map +1 -0
- package/dist/view/messages.d.ts +43 -0
- package/dist/view/messages.js +84 -0
- package/dist/view/messages.js.map +1 -0
- package/dist/view/normalize/check.d.ts +5 -2
- package/dist/view/normalize/check.js +6 -3
- package/dist/view/normalize/check.js.map +1 -1
- package/dist/view/normalize/emit.d.ts +2 -0
- package/dist/view/normalize/emit.js +40 -11
- package/dist/view/normalize/emit.js.map +1 -1
- package/dist/view/normalize/errors.js +1 -1
- package/dist/view/normalize/errors.js.map +1 -1
- package/dist/view/normalize/id.d.ts +5 -2
- package/dist/view/normalize/id.js +9 -2
- package/dist/view/normalize/id.js.map +1 -1
- package/dist/view/normalize/normalize.d.ts +7 -4
- package/dist/view/normalize/normalize.js +15 -9
- package/dist/view/normalize/normalize.js.map +1 -1
- package/dist/view/normalize/rules.d.ts +9 -0
- package/dist/view/normalize/rules.js +35 -5
- package/dist/view/normalize/rules.js.map +1 -1
- package/dist/view/normalize/svg.d.ts +14 -0
- package/dist/view/normalize/svg.js +40 -0
- package/dist/view/normalize/svg.js.map +1 -0
- package/dist/view/normalize/tokens.d.ts +5 -1
- package/dist/view/normalize/tokens.js.map +1 -1
- package/dist/view/normalize/tree.d.ts +8 -2
- package/dist/view/normalize/tree.js +58 -13
- package/dist/view/normalize/tree.js.map +1 -1
- package/dist/view/normalize/types.d.ts +24 -4
- package/dist/view/normalize/types.js +4 -1
- package/dist/view/normalize/types.js.map +1 -1
- package/dist/view/normalize/whitespace.js +4 -3
- package/dist/view/normalize/whitespace.js.map +1 -1
- package/dist/view/prepare-stub.js +2 -2
- package/dist/view/prepare-stub.js.map +1 -1
- package/dist/view/prepare.d.ts +5 -2
- package/dist/view/prepare.js +9 -3
- package/dist/view/prepare.js.map +1 -1
- package/dist/view/registry.d.ts +15 -0
- package/dist/view/registry.js +10 -2
- package/dist/view/registry.js.map +1 -1
- package/dist/view/render/adopt-attr.js +7 -1
- package/dist/view/render/adopt-attr.js.map +1 -1
- package/dist/view/render/adopt.js +21 -4
- package/dist/view/render/adopt.js.map +1 -1
- package/dist/view/render/attr-parts.d.ts +12 -1
- package/dist/view/render/attr-parts.js +38 -5
- package/dist/view/render/attr-parts.js.map +1 -1
- package/dist/view/render/child-part.js +5 -2
- package/dist/view/render/child-part.js.map +1 -1
- package/dist/view/render/dispose.d.ts +3 -0
- package/dist/view/render/dispose.js +76 -0
- package/dist/view/render/dispose.js.map +1 -0
- package/dist/view/render/hook-part.d.ts +3 -1
- package/dist/view/render/hook-part.js +21 -5
- package/dist/view/render/hook-part.js.map +1 -1
- package/dist/view/render/hook-run.d.ts +2 -0
- package/dist/view/render/hook-run.js +72 -0
- package/dist/view/render/hook-run.js.map +1 -0
- package/dist/view/render/hooks.d.ts +73 -5
- package/dist/view/render/hooks.js +79 -17
- package/dist/view/render/hooks.js.map +1 -1
- package/dist/view/render/hydrate.js +2 -2
- package/dist/view/render/hydrate.js.map +1 -1
- package/dist/view/render/keyed.d.ts +6 -0
- package/dist/view/render/keyed.js +15 -0
- package/dist/view/render/keyed.js.map +1 -0
- package/dist/view/render/mismatch.js +5 -3
- package/dist/view/render/mismatch.js.map +1 -1
- package/dist/view/render/modal.d.ts +3 -0
- package/dist/view/render/modal.js +39 -0
- package/dist/view/render/modal.js.map +1 -0
- package/dist/view/render/raw.d.ts +4 -1
- package/dist/view/render/raw.js +6 -2
- package/dist/view/render/raw.js.map +1 -1
- package/dist/view/render/render.d.ts +4 -1
- package/dist/view/render/render.js +17 -12
- package/dist/view/render/render.js.map +1 -1
- package/dist/view/render/toggles.d.ts +6 -0
- package/dist/view/render/toggles.js +6 -0
- package/dist/view/render/toggles.js.map +1 -0
- package/dist/view/render/warn.d.ts +14 -0
- package/dist/view/render/warn.js +45 -0
- package/dist/view/render/warn.js.map +1 -1
- package/dist/view/server/component.d.ts +6 -3
- package/dist/view/server/component.js +29 -7
- package/dist/view/server/component.js.map +1 -1
- package/dist/view/server/index.d.ts +20 -0
- package/dist/view/server/index.js +12 -4
- package/dist/view/server/index.js.map +1 -1
- package/dist/view/server/tags.d.ts +13 -2
- package/dist/view/server/tags.js +13 -2
- package/dist/view/server/tags.js.map +1 -1
- package/dist/view/server/values.d.ts +2 -0
- package/dist/view/server/values.js +8 -0
- package/dist/view/server/values.js.map +1 -1
- package/dist/view/server/writer.d.ts +9 -4
- package/dist/view/server/writer.js +33 -18
- package/dist/view/server/writer.js.map +1 -1
- package/dist/view/template-element.d.ts +7 -0
- package/dist/view/template-element.js +25 -5
- package/dist/view/template-element.js.map +1 -1
- package/dist/view/template.d.ts +10 -2
- package/dist/view/template.js +62 -8
- package/dist/view/template.js.map +1 -1
- package/dist/view/trusted-html.d.ts +6 -0
- package/dist/view/trusted-html.js +34 -0
- package/dist/view/trusted-html.js.map +1 -0
- package/dist/vite.d.ts +38 -31
- package/dist/vite.js +198 -31
- package/dist/vite.js.map +1 -1
- package/package.json +34 -3
- package/dist/hooks/labelled-by.d.ts +0 -13
- package/dist/hooks/labelled-by.js +0 -51
- package/dist/hooks/labelled-by.js.map +0 -1
- package/dist/random.d.ts +0 -13
- package/dist/random.js +0 -19
- package/dist/random.js.map +0 -1
package/dist/states.js
CHANGED
|
@@ -1,20 +1,34 @@
|
|
|
1
1
|
// Model state → CSS custom states (`:state(loading)`), via ElementInternals (ADR 0001 addendum).
|
|
2
2
|
// An ADR 0003 enhancement: CustomStateSet is feature-detected; without it nothing is set.
|
|
3
|
+
// Reached through core's `#spec-features` (spec-features.ts): compiled builds bundle it only
|
|
4
|
+
// when a module names `states` (compiler/features.ts, gyral-c5d.12).
|
|
5
|
+
import { internalsOf } from './element-internals.js';
|
|
6
|
+
/** Each host's state set; `false` when the platform lacks custom states. */
|
|
7
|
+
const sets = new WeakMap();
|
|
3
8
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
9
|
+
* Mirrors a component's boolean states onto its CustomStateSet. ElementInternals is attached
|
|
10
|
+
* lazily, once per element, by the first sync (view/05-element.md "ElementInternals").
|
|
6
11
|
*/
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
export function syncStates(el, states) {
|
|
13
|
+
let set = sets.get(el);
|
|
14
|
+
if (set === undefined) {
|
|
15
|
+
const internals = internalsOf(el);
|
|
16
|
+
set = 'states' in internals ? internals.states : false;
|
|
17
|
+
sets.set(el, set);
|
|
18
|
+
}
|
|
19
|
+
if (set === false)
|
|
20
|
+
return;
|
|
21
|
+
try {
|
|
12
22
|
for (const [name, on] of Object.entries(states)) {
|
|
13
23
|
if (on)
|
|
14
24
|
set.add(name);
|
|
15
25
|
else
|
|
16
26
|
set.delete(name);
|
|
17
27
|
}
|
|
18
|
-
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Chromium 90–124 accept only `--`-prefixed names: no `:state()` support, so skip states.
|
|
31
|
+
sets.set(el, false);
|
|
32
|
+
}
|
|
19
33
|
}
|
|
20
34
|
//# sourceMappingURL=states.js.map
|
package/dist/states.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"states.js","sourceRoot":"","sources":["../src/states.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,0FAA0F;
|
|
1
|
+
{"version":3,"file":"states.js","sourceRoot":"","sources":["../src/states.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,0FAA0F;AAC1F,6FAA6F;AAC7F,qEAAqE;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAOrD,4EAA4E;AAC5E,MAAM,IAAI,GAAG,IAAI,OAAO,EAAqC,CAAC;AAE9D;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,EAAe,EAAE,MAAyC;IACnF,IAAI,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACvB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAClC,GAAG,GAAG,QAAQ,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QACvD,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,GAAG,KAAK,KAAK;QAAE,OAAO;IAC1B,IAAI,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAChD,IAAI,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;;gBACjB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0FAA0F;QAC1F,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC","sourcesContent":["// Model state → CSS custom states (`:state(loading)`), via ElementInternals (ADR 0001 addendum).\n// An ADR 0003 enhancement: CustomStateSet is feature-detected; without it nothing is set.\n// Reached through core's `#spec-features` (spec-features.ts): compiled builds bundle it only\n// when a module names `states` (compiler/features.ts, gyral-c5d.12).\n\nimport { internalsOf } from './element-internals.js';\n\ninterface StateSetLike {\n add(name: string): void;\n delete(name: string): boolean;\n}\n\n/** Each host's state set; `false` when the platform lacks custom states. */\nconst sets = new WeakMap<HTMLElement, StateSetLike | false>();\n\n/**\n * Mirrors a component's boolean states onto its CustomStateSet. ElementInternals is attached\n * lazily, once per element, by the first sync (view/05-element.md \"ElementInternals\").\n */\nexport function syncStates(el: HTMLElement, states: Readonly<Record<string, boolean>>): void {\n let set = sets.get(el);\n if (set === undefined) {\n const internals = internalsOf(el);\n set = 'states' in internals ? internals.states : false;\n sets.set(el, set);\n }\n if (set === false) return;\n try {\n for (const [name, on] of Object.entries(states)) {\n if (on) set.add(name);\n else set.delete(name);\n }\n } catch {\n // Chromium 90–124 accept only `--`-prefixed names: no `:state()` support, so skip states.\n sets.set(el, false);\n }\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import type { Driver } from './command.js';
|
|
3
|
+
export type StorageInput<T> = {
|
|
4
|
+
readonly _tag: 'Read';
|
|
5
|
+
readonly key: string;
|
|
6
|
+
} | {
|
|
7
|
+
readonly _tag: 'Write';
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly value: T;
|
|
10
|
+
} | {
|
|
11
|
+
readonly _tag: 'Remove';
|
|
12
|
+
readonly key: string;
|
|
13
|
+
} | {
|
|
14
|
+
readonly _tag: 'Clear';
|
|
15
|
+
} | {
|
|
16
|
+
readonly _tag: 'Watch';
|
|
17
|
+
readonly key: string;
|
|
18
|
+
};
|
|
19
|
+
/** Absence differs from a decoded value, even when the schema accepts null or undefined. */
|
|
20
|
+
export type StoredValue<T> = {
|
|
21
|
+
readonly found: false;
|
|
22
|
+
} | {
|
|
23
|
+
readonly found: true;
|
|
24
|
+
readonly value: T;
|
|
25
|
+
};
|
|
26
|
+
export type StorageChangeSource = 'initial' | 'driver' | 'tab';
|
|
27
|
+
export interface StorageDecodeError {
|
|
28
|
+
readonly _tag: 'StorageDecodeError';
|
|
29
|
+
readonly key: string;
|
|
30
|
+
readonly issues: readonly StandardSchemaV1.Issue[];
|
|
31
|
+
}
|
|
32
|
+
export type StorageError = StorageDecodeError | {
|
|
33
|
+
readonly _tag: 'StorageAccessError';
|
|
34
|
+
readonly operation: StorageInput<unknown>['_tag'];
|
|
35
|
+
readonly key: string | undefined;
|
|
36
|
+
readonly message: string;
|
|
37
|
+
};
|
|
38
|
+
export type StorageOutput<T> = {
|
|
39
|
+
readonly _tag: 'StorageValue';
|
|
40
|
+
readonly key: string;
|
|
41
|
+
readonly entry: StoredValue<T>;
|
|
42
|
+
} | {
|
|
43
|
+
readonly _tag: 'StorageWritten' | 'StorageRemoved';
|
|
44
|
+
readonly key: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly _tag: 'StorageCleared';
|
|
47
|
+
} | {
|
|
48
|
+
readonly _tag: 'StorageChanged';
|
|
49
|
+
readonly key: string;
|
|
50
|
+
readonly source: StorageChangeSource;
|
|
51
|
+
readonly entry: StoredValue<T>;
|
|
52
|
+
} | {
|
|
53
|
+
readonly _tag: 'StorageDecodeFailed';
|
|
54
|
+
readonly key: string;
|
|
55
|
+
readonly source: StorageChangeSource;
|
|
56
|
+
readonly error: StorageDecodeError;
|
|
57
|
+
};
|
|
58
|
+
export interface LocalStorageOptions<T> {
|
|
59
|
+
/** Name for normal driver substitution. Default localStorage. */
|
|
60
|
+
readonly name?: string;
|
|
61
|
+
/** Keys and Clear are restricted to this namespace. Default empty (all keys). */
|
|
62
|
+
readonly prefix?: string;
|
|
63
|
+
/** Accessed only when a command runs. Default native localStorage. */
|
|
64
|
+
readonly storage?: () => Storage;
|
|
65
|
+
/** A transformed schema may need to encode model values back into its input shape. */
|
|
66
|
+
readonly encode?: (value: T) => unknown;
|
|
67
|
+
}
|
|
68
|
+
/** JSON storage with typed reads/writes and cancellable native + own-write change streams. */
|
|
69
|
+
export declare function makeLocalStorageDriver<T>(schema: StandardSchemaV1<unknown, T>, options?: LocalStorageOptions<T>): Driver<StorageInput<T>, StorageOutput<T>, StorageError>;
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { subscription } from './subscription.js';
|
|
2
|
+
const listeners = new WeakMap();
|
|
3
|
+
const notify = (storage, change) => {
|
|
4
|
+
for (const listen of listeners.get(storage) ?? [])
|
|
5
|
+
listen(change);
|
|
6
|
+
};
|
|
7
|
+
class StorageFailure extends Error {
|
|
8
|
+
constructor(error) {
|
|
9
|
+
super(error._tag);
|
|
10
|
+
this.error = error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
14
|
+
const keyOf = (input) => 'key' in input ? input.key : undefined;
|
|
15
|
+
/** JSON storage with typed reads/writes and cancellable native + own-write change streams. */
|
|
16
|
+
export function makeLocalStorageDriver(schema, options = {}) {
|
|
17
|
+
const name = options.name ?? 'localStorage';
|
|
18
|
+
const prefix = options.prefix ?? '';
|
|
19
|
+
const storageOf = options.storage ??
|
|
20
|
+
(() => {
|
|
21
|
+
if (typeof localStorage === 'undefined')
|
|
22
|
+
throw new Error('localStorage is unavailable.');
|
|
23
|
+
return localStorage;
|
|
24
|
+
});
|
|
25
|
+
const decode = async (raw, key) => {
|
|
26
|
+
if (raw === null)
|
|
27
|
+
return { found: false };
|
|
28
|
+
try {
|
|
29
|
+
const value = JSON.parse(raw);
|
|
30
|
+
const result = await schema['~standard'].validate(value);
|
|
31
|
+
if (result.issues !== undefined)
|
|
32
|
+
throw new StorageFailure({ _tag: 'StorageDecodeError', key, issues: result.issues });
|
|
33
|
+
return { found: true, value: result.value };
|
|
34
|
+
}
|
|
35
|
+
catch (cause) {
|
|
36
|
+
if (cause instanceof StorageFailure)
|
|
37
|
+
throw cause;
|
|
38
|
+
throw new StorageFailure({
|
|
39
|
+
_tag: 'StorageDecodeError',
|
|
40
|
+
key,
|
|
41
|
+
issues: [{ message: messageOf(cause) }],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const watch = subscription(name, (emit, { input, signal }) => {
|
|
46
|
+
const storage = storageOf();
|
|
47
|
+
const key = prefix + input.key;
|
|
48
|
+
const initial = storage.getItem(key);
|
|
49
|
+
let chain = Promise.resolve();
|
|
50
|
+
// Re-read after each awaited decoder: cancellation can happen while it runs.
|
|
51
|
+
const active = () => !signal.aborted;
|
|
52
|
+
const publish = (raw, source) => {
|
|
53
|
+
chain = chain.then(async () => {
|
|
54
|
+
if (!active())
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const entry = await decode(raw, input.key);
|
|
58
|
+
if (active())
|
|
59
|
+
emit({ _tag: 'StorageChanged', key: input.key, source, entry });
|
|
60
|
+
}
|
|
61
|
+
catch (cause) {
|
|
62
|
+
if (active() &&
|
|
63
|
+
cause instanceof StorageFailure &&
|
|
64
|
+
cause.error._tag === 'StorageDecodeError') {
|
|
65
|
+
emit({ _tag: 'StorageDecodeFailed', key: input.key, source, error: cause.error });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
const own = (change) => {
|
|
71
|
+
if (change.key === null || change.key === key)
|
|
72
|
+
publish(change.value, 'driver');
|
|
73
|
+
};
|
|
74
|
+
const other = (event) => {
|
|
75
|
+
if (event.storageArea === storage && (event.key === null || event.key === key))
|
|
76
|
+
publish(event.newValue, 'tab');
|
|
77
|
+
};
|
|
78
|
+
const hub = listeners.get(storage) ?? new Set();
|
|
79
|
+
listeners.set(storage, hub);
|
|
80
|
+
hub.add(own);
|
|
81
|
+
if (typeof window !== 'undefined')
|
|
82
|
+
window.addEventListener('storage', other);
|
|
83
|
+
publish(initial, 'initial');
|
|
84
|
+
return () => {
|
|
85
|
+
hub.delete(own);
|
|
86
|
+
if (typeof window !== 'undefined')
|
|
87
|
+
window.removeEventListener('storage', other);
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
name,
|
|
92
|
+
concurrency: 'merge',
|
|
93
|
+
toError: (cause) => cause instanceof StorageFailure
|
|
94
|
+
? cause.error
|
|
95
|
+
: {
|
|
96
|
+
_tag: 'StorageAccessError',
|
|
97
|
+
operation: 'Read',
|
|
98
|
+
key: undefined,
|
|
99
|
+
message: messageOf(cause),
|
|
100
|
+
},
|
|
101
|
+
run: async (input, ctx) => {
|
|
102
|
+
ctx.signal.throwIfAborted();
|
|
103
|
+
try {
|
|
104
|
+
if (input._tag === 'Watch')
|
|
105
|
+
return await watch.run(input, ctx);
|
|
106
|
+
const storage = storageOf();
|
|
107
|
+
if (input._tag === 'Clear') {
|
|
108
|
+
if (prefix === '') {
|
|
109
|
+
storage.clear();
|
|
110
|
+
notify(storage, { key: null, value: null });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const keys = [];
|
|
114
|
+
for (let i = 0; i < storage.length; i += 1) {
|
|
115
|
+
const key = storage.key(i);
|
|
116
|
+
if (key?.startsWith(prefix))
|
|
117
|
+
keys.push(key);
|
|
118
|
+
}
|
|
119
|
+
for (const key of keys) {
|
|
120
|
+
storage.removeItem(key);
|
|
121
|
+
notify(storage, { key, value: null });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return { _tag: 'StorageCleared' };
|
|
125
|
+
}
|
|
126
|
+
const key = prefix + input.key;
|
|
127
|
+
if (input._tag === 'Read') {
|
|
128
|
+
const entry = await decode(storage.getItem(key), input.key);
|
|
129
|
+
ctx.signal.throwIfAborted();
|
|
130
|
+
return { _tag: 'StorageValue', key: input.key, entry };
|
|
131
|
+
}
|
|
132
|
+
if (input._tag === 'Write') {
|
|
133
|
+
const value = JSON.stringify(options.encode === undefined ? input.value : options.encode(input.value));
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.stringify(undefined) returns undefined despite its lib type
|
|
135
|
+
if (value === undefined)
|
|
136
|
+
throw new TypeError('Storage values must have a JSON encoding.');
|
|
137
|
+
storage.setItem(key, value);
|
|
138
|
+
notify(storage, { key, value });
|
|
139
|
+
return { _tag: 'StorageWritten', key: input.key };
|
|
140
|
+
}
|
|
141
|
+
storage.removeItem(key);
|
|
142
|
+
notify(storage, { key, value: null });
|
|
143
|
+
return { _tag: 'StorageRemoved', key: input.key };
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
if (ctx.signal.aborted || cause instanceof StorageFailure)
|
|
147
|
+
throw cause;
|
|
148
|
+
throw new StorageFailure({
|
|
149
|
+
_tag: 'StorageAccessError',
|
|
150
|
+
operation: input._tag,
|
|
151
|
+
key: keyOf(input),
|
|
152
|
+
message: messageOf(cause),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAyDjD,MAAM,SAAS,GAAG,IAAI,OAAO,EAA0C,CAAC;AACxE,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,MAAc,EAAQ,EAAE;IACxD,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE;QAAE,MAAM,CAAC,MAAM,CAAC,CAAC;AACpE,CAAC,CAAC;AACF,MAAM,cAAe,SAAQ,KAAK;IAChC,YAAqB,KAAmB;QACtC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QADC,UAAK,GAAL,KAAK,CAAc;IAExC,CAAC;CACF;AACD,MAAM,SAAS,GAAG,CAAC,KAAc,EAAU,EAAE,CAC3C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzD,MAAM,KAAK,GAAG,CAAC,KAA4B,EAAsB,EAAE,CACjE,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAEzC,8FAA8F;AAC9F,MAAM,UAAU,sBAAsB,CACpC,MAAoC,EACpC,UAAkC,EAAE;IAEpC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,cAAc,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;IACpC,MAAM,SAAS,GACb,OAAO,CAAC,OAAO;QACf,CAAC,GAAG,EAAE;YACJ,IAAI,OAAO,YAAY,KAAK,WAAW;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;YACzF,OAAO,YAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,MAAM,MAAM,GAAG,KAAK,EAAE,GAAkB,EAAE,GAAW,EAA2B,EAAE;QAChF,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,KAAK,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACzD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;gBAC7B,MAAM,IAAI,cAAc,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;YACvF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAc;gBAAE,MAAM,KAAK,CAAC;YACjD,MAAM,IAAI,cAAc,CAAC;gBACvB,IAAI,EAAE,oBAAoB;gBAC1B,GAAG;gBACH,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;aACxC,CAAC,CAAC;QACL,CAAC;IACH,CAAC,CAAC;IACF,MAAM,KAAK,GAAG,YAAY,CACxB,IAAI,EACJ,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE;QAC1B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC;QAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QAC9B,6EAA6E;QAC7E,MAAM,MAAM,GAAG,GAAY,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAC9C,MAAM,OAAO,GAAG,CAAC,GAAkB,EAAE,MAA2B,EAAQ,EAAE;YACxE,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;gBAC5B,IAAI,CAAC,MAAM,EAAE;oBAAE,OAAO;gBACtB,IAAI,CAAC;oBACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC3C,IAAI,MAAM,EAAE;wBAAE,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAChF,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IACE,MAAM,EAAE;wBACR,KAAK,YAAY,cAAc;wBAC/B,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,oBAAoB,EACzC,CAAC;wBACD,IAAI,CAAC,EAAE,IAAI,EAAE,qBAAqB,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;oBACpF,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QACF,MAAM,GAAG,GAAG,CAAC,MAAc,EAAQ,EAAE;YACnC,IAAI,MAAM,CAAC,GAAG,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,KAAK,GAAG;gBAAE,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACjF,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,CAAC,KAAmB,EAAQ,EAAE;YAC1C,IAAI,KAAK,CAAC,WAAW,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,CAAC;gBAC5E,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC,CAAC;QACF,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,GAAG,EAA4B,CAAC;QAC1E,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACb,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC7E,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC5B,OAAO,GAAG,EAAE;YACV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChB,IAAI,OAAO,MAAM,KAAK,WAAW;gBAAE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAClF,CAAC,CAAC;IACJ,CAAC,CACF,CAAC;IACF,OAAO;QACL,IAAI;QACJ,WAAW,EAAE,OAAO;QACpB,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,KAAK,YAAY,cAAc;YAC7B,CAAC,CAAC,KAAK,CAAC,KAAK;YACb,CAAC,CAAC;gBACE,IAAI,EAAE,oBAAoB;gBAC1B,SAAS,EAAE,MAAM;gBACjB,GAAG,EAAE,SAAS;gBACd,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC;aAC1B;QACP,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACxB,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;YAC5B,IAAI,CAAC;gBACH,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;oBAAE,OAAO,MAAM,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC/D,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;gBAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC3B,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;wBAClB,OAAO,CAAC,KAAK,EAAE,CAAC;wBAChB,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC9C,CAAC;yBAAM,CAAC;wBACN,MAAM,IAAI,GAAa,EAAE,CAAC;wBAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;4BAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;4BAC3B,IAAI,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC;gCAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;wBAC9C,CAAC;wBACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;4BACvB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;4BACxB,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;wBACxC,CAAC;oBACH,CAAC;oBACD,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;gBACpC,CAAC;gBACD,MAAM,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC;gBAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBAC1B,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC5D,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;oBAC5B,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC;gBACzD,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAC1B,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CACzE,CAAC;oBACF,2IAA2I;oBAC3I,IAAI,KAAK,KAAK,SAAS;wBAAE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;oBAC1F,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBAC5B,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;oBAChC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;gBACpD,CAAC;gBACD,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;gBACxB,MAAM,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBACtC,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;YACpD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,KAAK,YAAY,cAAc;oBAAE,MAAM,KAAK,CAAC;gBACvE,MAAM,IAAI,cAAc,CAAC;oBACvB,IAAI,EAAE,oBAAoB;oBAC1B,SAAS,EAAE,KAAK,CAAC,IAAI;oBACrB,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;oBACjB,OAAO,EAAE,SAAS,CAAC,KAAK,CAAC;iBAC1B,CAAC,CAAC;YACL,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// Optional native Web Storage commands (ADR 0028). Never touches storage at import/factory time.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport type { Driver } from './command.js';\nimport { subscription } from './subscription.js';\n\nexport type StorageInput<T> =\n | { readonly _tag: 'Read'; readonly key: string }\n | { readonly _tag: 'Write'; readonly key: string; readonly value: T }\n | { readonly _tag: 'Remove'; readonly key: string }\n | { readonly _tag: 'Clear' }\n | { readonly _tag: 'Watch'; readonly key: string };\n\n/** Absence differs from a decoded value, even when the schema accepts null or undefined. */\nexport type StoredValue<T> =\n { readonly found: false } | { readonly found: true; readonly value: T };\nexport type StorageChangeSource = 'initial' | 'driver' | 'tab';\nexport interface StorageDecodeError {\n readonly _tag: 'StorageDecodeError';\n readonly key: string;\n readonly issues: readonly StandardSchemaV1.Issue[];\n}\nexport type StorageError =\n | StorageDecodeError\n | {\n readonly _tag: 'StorageAccessError';\n readonly operation: StorageInput<unknown>['_tag'];\n readonly key: string | undefined;\n readonly message: string;\n };\nexport type StorageOutput<T> =\n | { readonly _tag: 'StorageValue'; readonly key: string; readonly entry: StoredValue<T> }\n | { readonly _tag: 'StorageWritten' | 'StorageRemoved'; readonly key: string }\n | { readonly _tag: 'StorageCleared' }\n | {\n readonly _tag: 'StorageChanged';\n readonly key: string;\n readonly source: StorageChangeSource;\n readonly entry: StoredValue<T>;\n }\n | {\n readonly _tag: 'StorageDecodeFailed';\n readonly key: string;\n readonly source: StorageChangeSource;\n readonly error: StorageDecodeError;\n };\nexport interface LocalStorageOptions<T> {\n /** Name for normal driver substitution. Default localStorage. */\n readonly name?: string;\n /** Keys and Clear are restricted to this namespace. Default empty (all keys). */\n readonly prefix?: string;\n /** Accessed only when a command runs. Default native localStorage. */\n readonly storage?: () => Storage;\n /** A transformed schema may need to encode model values back into its input shape. */\n readonly encode?: (value: T) => unknown;\n}\n\ninterface Change {\n readonly key: string | null;\n readonly value: string | null;\n}\nconst listeners = new WeakMap<Storage, Set<(change: Change) => void>>();\nconst notify = (storage: Storage, change: Change): void => {\n for (const listen of listeners.get(storage) ?? []) listen(change);\n};\nclass StorageFailure extends Error {\n constructor(readonly error: StorageError) {\n super(error._tag);\n }\n}\nconst messageOf = (cause: unknown): string =>\n cause instanceof Error ? cause.message : String(cause);\nconst keyOf = (input: StorageInput<unknown>): string | undefined =>\n 'key' in input ? input.key : undefined;\n\n/** JSON storage with typed reads/writes and cancellable native + own-write change streams. */\nexport function makeLocalStorageDriver<T>(\n schema: StandardSchemaV1<unknown, T>,\n options: LocalStorageOptions<T> = {},\n): Driver<StorageInput<T>, StorageOutput<T>, StorageError> {\n const name = options.name ?? 'localStorage';\n const prefix = options.prefix ?? '';\n const storageOf =\n options.storage ??\n (() => {\n if (typeof localStorage === 'undefined') throw new Error('localStorage is unavailable.');\n return localStorage;\n });\n const decode = async (raw: string | null, key: string): Promise<StoredValue<T>> => {\n if (raw === null) return { found: false };\n try {\n const value: unknown = JSON.parse(raw);\n const result = await schema['~standard'].validate(value);\n if (result.issues !== undefined)\n throw new StorageFailure({ _tag: 'StorageDecodeError', key, issues: result.issues });\n return { found: true, value: result.value };\n } catch (cause) {\n if (cause instanceof StorageFailure) throw cause;\n throw new StorageFailure({\n _tag: 'StorageDecodeError',\n key,\n issues: [{ message: messageOf(cause) }],\n });\n }\n };\n const watch = subscription<StorageOutput<T>, { readonly key: string }, StorageError>(\n name,\n (emit, { input, signal }) => {\n const storage = storageOf();\n const key = prefix + input.key;\n const initial = storage.getItem(key);\n let chain = Promise.resolve();\n // Re-read after each awaited decoder: cancellation can happen while it runs.\n const active = (): boolean => !signal.aborted;\n const publish = (raw: string | null, source: StorageChangeSource): void => {\n chain = chain.then(async () => {\n if (!active()) return;\n try {\n const entry = await decode(raw, input.key);\n if (active()) emit({ _tag: 'StorageChanged', key: input.key, source, entry });\n } catch (cause) {\n if (\n active() &&\n cause instanceof StorageFailure &&\n cause.error._tag === 'StorageDecodeError'\n ) {\n emit({ _tag: 'StorageDecodeFailed', key: input.key, source, error: cause.error });\n }\n }\n });\n };\n const own = (change: Change): void => {\n if (change.key === null || change.key === key) publish(change.value, 'driver');\n };\n const other = (event: StorageEvent): void => {\n if (event.storageArea === storage && (event.key === null || event.key === key))\n publish(event.newValue, 'tab');\n };\n const hub = listeners.get(storage) ?? new Set<(change: Change) => void>();\n listeners.set(storage, hub);\n hub.add(own);\n if (typeof window !== 'undefined') window.addEventListener('storage', other);\n publish(initial, 'initial');\n return () => {\n hub.delete(own);\n if (typeof window !== 'undefined') window.removeEventListener('storage', other);\n };\n },\n );\n return {\n name,\n concurrency: 'merge',\n toError: (cause) =>\n cause instanceof StorageFailure\n ? cause.error\n : {\n _tag: 'StorageAccessError',\n operation: 'Read',\n key: undefined,\n message: messageOf(cause),\n },\n run: async (input, ctx) => {\n ctx.signal.throwIfAborted();\n try {\n if (input._tag === 'Watch') return await watch.run(input, ctx);\n const storage = storageOf();\n if (input._tag === 'Clear') {\n if (prefix === '') {\n storage.clear();\n notify(storage, { key: null, value: null });\n } else {\n const keys: string[] = [];\n for (let i = 0; i < storage.length; i += 1) {\n const key = storage.key(i);\n if (key?.startsWith(prefix)) keys.push(key);\n }\n for (const key of keys) {\n storage.removeItem(key);\n notify(storage, { key, value: null });\n }\n }\n return { _tag: 'StorageCleared' };\n }\n const key = prefix + input.key;\n if (input._tag === 'Read') {\n const entry = await decode(storage.getItem(key), input.key);\n ctx.signal.throwIfAborted();\n return { _tag: 'StorageValue', key: input.key, entry };\n }\n if (input._tag === 'Write') {\n const value = JSON.stringify(\n options.encode === undefined ? input.value : options.encode(input.value),\n );\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JSON.stringify(undefined) returns undefined despite its lib type\n if (value === undefined) throw new TypeError('Storage values must have a JSON encoding.');\n storage.setItem(key, value);\n notify(storage, { key, value });\n return { _tag: 'StorageWritten', key: input.key };\n }\n storage.removeItem(key);\n notify(storage, { key, value: null });\n return { _tag: 'StorageRemoved', key: input.key };\n } catch (cause) {\n if (ctx.signal.aborted || cause instanceof StorageFailure) throw cause;\n throw new StorageFailure({\n _tag: 'StorageAccessError',\n operation: input._tag,\n key: keyOf(input),\n message: messageOf(cause),\n });\n }\n },\n };\n}\n"]}
|
package/dist/store-binding.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { scopeFor } from './store-scope.js';
|
|
2
|
+
import { message } from './view/index.js';
|
|
2
3
|
export class StoreBinding {
|
|
3
4
|
#host;
|
|
4
5
|
#tag;
|
|
@@ -21,8 +22,7 @@ export class StoreBinding {
|
|
|
21
22
|
#declaredStore(name) {
|
|
22
23
|
const store = this.#declared.get(name);
|
|
23
24
|
if (store === undefined) {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`stores: [${name}] (ADR 0013), so the component subscribes to its changes.`);
|
|
25
|
+
throw new Error(message(50, this.#tag, name));
|
|
26
26
|
}
|
|
27
27
|
return store;
|
|
28
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-binding.js","sourceRoot":"","sources":["../src/store-binding.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"store-binding.js","sourceRoot":"","sources":["../src/store-binding.ts"],"names":[],"mappings":"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","sourcesContent":["// A component's connection to the stores it declares (ADR 0013): resolve, read, subscribe, send.\n// `defineStore()` registers `bindStores` (features.ts), so apps without stores don't bundle it.\nimport type { FeatureHost, StoreLink } from './features.js';\nimport { scopeFor } from './store-scope.js';\nimport type { AnyStore, AnyStoreInstance, StoreRef, StoreSendInput } from './store.js';\nimport { message } from './view/index.js';\n\ntype OnChange = (store: AnyStore, state: unknown, prev: unknown) => void;\n\nexport class StoreBinding implements StoreLink {\n readonly #host: FeatureHost;\n readonly #tag: string;\n readonly #declared: ReadonlyMap<string, AnyStore>;\n readonly #onChange: OnChange;\n /** Resolved instances while connected (the scope can't change until it moves). */\n readonly #bound = new Map<string, AnyStoreInstance>();\n #unsubscribe: (() => void)[] = [];\n\n constructor(host: FeatureHost, tag: string, declared: readonly AnyStore[], onChange: OnChange) {\n this.#host = host;\n this.#tag = tag;\n this.#declared = new Map(declared.map((s) => [s.name, s]));\n this.#onChange = onChange;\n }\n\n #instance(store: AnyStore): AnyStoreInstance {\n return (\n this.#bound.get(store.name) ??\n this.#host.stores[store.name] ??\n scopeFor(this.#host).get(store)\n );\n }\n\n #declaredStore(name: string): AnyStore {\n const store = this.#declared.get(name);\n if (store === undefined) {\n throw new Error(message(50, this.#tag, name));\n }\n return store;\n }\n\n /** `ctx.read(store)`: the current state of this component's instance of `store`. */\n read<S>(ref: StoreRef<S>): S {\n // Sound: instances are keyed by store name, and the ref carries that store's state type.\n return this.#instance(this.#declaredStore(ref.name)).state as S;\n }\n\n /** Delivers a `send(store, msg)` command to this component's instance of the store. */\n send(input: StoreSendInput): void {\n const store = this.#declaredStore(input.store.name);\n (this.#instance(store) as { send(msg: unknown): void }).send(input.msg);\n }\n\n /** Resolves and subscribes every declared store. Returns true if any instance changed. */\n connect(): boolean {\n this.disconnect();\n let rebound = false;\n for (const store of this.#declared.values()) {\n const instance = this.#instance(store);\n this.#bound.set(store.name, instance);\n this.#unsubscribe.push(\n instance.subscribe((state, prev) => {\n this.#onChange(store, state, prev);\n }),\n );\n rebound = true;\n }\n return rebound;\n }\n\n disconnect(): void {\n for (const unsubscribe of this.#unsubscribe) unsubscribe();\n this.#unsubscribe = [];\n this.#bound.clear();\n }\n}\n\n/** The store feature (features.ts): a host's binding to its declared stores. */\nexport const bindStores = (\n host: FeatureHost,\n tag: string,\n declared: readonly AnyStore[],\n onChange: OnChange,\n): StoreLink => new StoreBinding(host, tag, declared, onChange);\n"]}
|
package/dist/store-scope.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AnyStore, AnyStoreInstance } from './store.js';
|
|
2
|
-
/** Page-level seed
|
|
2
|
+
/** Page-level seed from `@gyral/ssr`: `<script type="application/json" data-gyral-stores>`. */
|
|
3
3
|
export declare const STORE_SEED_ATTRIBUTE = "data-gyral-stores";
|
|
4
4
|
/** Provider element name. Set `.instances=${[cart.instance()]}`, or let it create its own. */
|
|
5
5
|
export declare const STORES_ELEMENT = "gyral-stores";
|
|
@@ -16,12 +16,10 @@ export declare class StoreRegistry {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* Runs `fn` with `registry` as the server's store scope. Server renders are synchronous per
|
|
19
|
-
* render step, so
|
|
19
|
+
* render step, so `@gyral/ssr` wraps every step: interleaved requests never see each other's
|
|
20
20
|
* stores, without AsyncLocalStorage (works on any runtime).
|
|
21
21
|
*/
|
|
22
22
|
export declare function withStoreScope<T>(registry: StoreRegistry, fn: () => T): T;
|
|
23
|
-
/** JSON for an inline `<script type="application/json">`: `</script>` and friends escaped. */
|
|
24
|
-
export declare function scriptSafeJson(value: unknown): string;
|
|
25
23
|
/**
|
|
26
24
|
* A `<gyral-stores>` provider's scope, created on first use. On the client a server-rendered
|
|
27
25
|
* provider carries its instances' states in `data-gyral-stores` (gyral-czi.20): seeded stores
|
package/dist/store-scope.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { message } from './view/index.js';
|
|
2
|
+
/** Page-level seed from `@gyral/ssr`: `<script type="application/json" data-gyral-stores>`. */
|
|
2
3
|
export const STORE_SEED_ATTRIBUTE = 'data-gyral-stores';
|
|
3
4
|
/** Provider element name. Set `.instances=${[cart.instance()]}`, or let it create its own. */
|
|
4
5
|
export const STORES_ELEMENT = 'gyral-stores';
|
|
@@ -28,8 +29,7 @@ export class StoreRegistry {
|
|
|
28
29
|
const check = store.checkSeed(this.#seeds[store.name]);
|
|
29
30
|
if (check.ok)
|
|
30
31
|
return store.instance(check.state);
|
|
31
|
-
console.error(
|
|
32
|
-
`Issues: ${check.issues.join('; ')}`);
|
|
32
|
+
console.error(message(51, store.name, check.issues.join('; ')));
|
|
33
33
|
return store.instance();
|
|
34
34
|
}
|
|
35
35
|
/** Every instance's state by store name (what the SSR seed carries). */
|
|
@@ -46,7 +46,7 @@ export class StoreRegistry {
|
|
|
46
46
|
let serverScope;
|
|
47
47
|
/**
|
|
48
48
|
* Runs `fn` with `registry` as the server's store scope. Server renders are synchronous per
|
|
49
|
-
* render step, so
|
|
49
|
+
* render step, so `@gyral/ssr` wraps every step: interleaved requests never see each other's
|
|
50
50
|
* stores, without AsyncLocalStorage (works on any runtime).
|
|
51
51
|
*/
|
|
52
52
|
export function withStoreScope(registry, fn) {
|
|
@@ -59,15 +59,6 @@ export function withStoreScope(registry, fn) {
|
|
|
59
59
|
serverScope = previous;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
/** JSON for an inline `<script type="application/json">`: `</script>` and friends escaped. */
|
|
63
|
-
export function scriptSafeJson(value) {
|
|
64
|
-
return JSON.stringify(value)
|
|
65
|
-
.replaceAll('<', '\\u003c')
|
|
66
|
-
.replaceAll('>', '\\u003e')
|
|
67
|
-
.replaceAll('&', '\\u0026')
|
|
68
|
-
.replaceAll('
', '\\u2028')
|
|
69
|
-
.replaceAll('
', '\\u2029');
|
|
70
|
-
}
|
|
71
62
|
let documentRegistry;
|
|
72
63
|
function parseSeeds(raw, where) {
|
|
73
64
|
if (raw == null)
|
|
@@ -77,7 +68,7 @@ function parseSeeds(raw, where) {
|
|
|
77
68
|
return parsed !== null && typeof parsed === 'object' ? parsed : {};
|
|
78
69
|
}
|
|
79
70
|
catch (error) {
|
|
80
|
-
console.error(
|
|
71
|
+
console.error(message(52, STORE_SEED_ATTRIBUTE, where), error);
|
|
81
72
|
return {};
|
|
82
73
|
}
|
|
83
74
|
}
|
|
@@ -111,8 +102,7 @@ export function providerScope(el) {
|
|
|
111
102
|
export function serverScopeFor(tag, store) {
|
|
112
103
|
if (serverScope !== undefined)
|
|
113
104
|
return serverScope;
|
|
114
|
-
throw new Error(
|
|
115
|
-
"Pass the request's store instances to page({ stores }) or renderToString(value, { stores }).");
|
|
105
|
+
throw new Error(message(53, tag, store.name));
|
|
116
106
|
}
|
|
117
107
|
/** The scope for a component: its nearest provider (across shadow roots) or the default. */
|
|
118
108
|
export function scopeFor(host) {
|
package/dist/store-scope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-scope.js","sourceRoot":"","sources":["../src/store-scope.ts"],"names":[],"mappings":"AAKA,mGAAmG;AACnG,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AAExD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAE7C,qFAAqF;AACrF,MAAM,OAAO,aAAa;IACf,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACjD,MAAM,CAAoC;IAEnD,YACE,YAAyC,EAAE,EAC3C,QAA2C,EAAE;QAE7C,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,kFAAkF;IAClF,GAAG,CAAC,KAAe;QACjB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,QAA0B;QAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACnD,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,oFAAoF;IACpF,OAAO,CAAC,KAAe;QACrB,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAc,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CACX,UAAU,KAAK,CAAC,IAAI,kEAAkE;YACpF,WAAW,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACvC,CAAC;QACF,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAED,wEAAwE;IACxE,QAAQ;QACN,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,sDAAsD;IACtD,OAAO;QACL,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACF;AAED,IAAI,WAAsC,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAI,QAAuB,EAAE,EAAW;IACpE,MAAM,QAAQ,GAAG,WAAW,CAAC;IAC7B,WAAW,GAAG,QAAQ,CAAC;IACvB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,WAAW,GAAG,QAAQ,CAAC;IACzB,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC,GAAG,EAAE,SAAS,CAAC;SAC1B,UAAU,CAAC;CACf,EAAE,SAAS,CAAC;SACR,UAAU,CAAC;CACf,EAAE,SAAS,CAAC,CAAC;AACd,CAAC;AAED,IAAI,gBAA2C,CAAC;AAEhD,SAAS,UAAU,CACjB,GAA8B,EAC9B,KAAa;IAEb,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,OAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,cAAc,oBAAoB,YAAY,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;QAC5E,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa;IACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,oBAAoB,GAAG,CAAC,CAAC;IACzE,gBAAgB,KAAK,IAAI,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IACxF,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,OAAO,EAA0B,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAW;IACvC,IAAI,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,EAAiD,CAAC;QAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;QACrF,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QACnE,QAAQ,GAAG,IAAI,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC9C,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,KAAe;IACzD,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAClD,MAAM,IAAI,KAAK,CACb,IAAI,GAAG,kBAAkB,KAAK,CAAC,IAAI,kDAAkD;QACnF,8FAA8F,CACjG,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,KAAK,IAAI,IAAI,GAAgB,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC;QAC7D,IAAI,IAAI,YAAY,OAAO,IAAI,IAAI,CAAC,SAAS,KAAK,cAAc;YAAE,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7F,IAAI,GAAG,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;IAClE,CAAC;IACD,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,mBAAmB;IACjC,gBAAgB,EAAE,OAAO,EAAE,CAAC;IAC5B,gBAAgB,GAAG,SAAS,CAAC;AAC/B,CAAC","sourcesContent":["// Which store instance a component uses (docs/design-docs/0013-shared-state.md):\n// el.stores override → nearest <gyral-stores> ancestor → the document default (client), or the\n// request scope set by @gyral/ssr around each render step (server).\nimport type { AnyStore, AnyStoreInstance } from './store.js';\n\n/** Page-level seed written by @gyral/ssr: `<script type=\"application/json\" data-gyral-stores>`. */\nexport const STORE_SEED_ATTRIBUTE = 'data-gyral-stores';\n\n/** Provider element name. Set `.instances=${[cart.instance()]}`, or let it create its own. */\nexport const STORES_ELEMENT = 'gyral-stores';\n\n/** A set of store instances by store name, created on demand from optional seeds. */\nexport class StoreRegistry {\n readonly #instances = new Map<string, AnyStoreInstance>();\n readonly #seeds: Readonly<Record<string, unknown>>;\n\n constructor(\n instances: readonly AnyStoreInstance[] = [],\n seeds: Readonly<Record<string, unknown>> = {},\n ) {\n for (const instance of instances) this.#hold(instance);\n this.#seeds = seeds;\n }\n\n /** The instance for `store`, creating it (from its seed, if any) on first use. */\n get(store: AnyStore): AnyStoreInstance {\n return this.#instances.get(store.name) ?? this.#hold(this.#create(store));\n }\n\n /** Keeps an instance and lets its `send(other, msg)` commands reach this scope. */\n #hold(instance: AnyStoreInstance): AnyStoreInstance {\n this.#instances.set(instance.store.name, instance);\n instance.bindScope?.((store) => this.get(store));\n return instance;\n }\n\n /** A new instance from its seed (checked by the store's schema), or from `init`. */\n #create(store: AnyStore): AnyStoreInstance {\n if (!(store.name in this.#seeds)) return store.instance();\n const check = store.checkSeed(this.#seeds[store.name]);\n if (check.ok) return store.instance(check.state as never);\n console.error(\n `store \"${store.name}\": the server's seed failed its schema, so it starts from init. ` +\n `Issues: ${check.issues.join('; ')}`,\n );\n return store.instance();\n }\n\n /** Every instance's state by store name (what the SSR seed carries). */\n snapshot(): Readonly<Record<string, unknown>> {\n return Object.fromEntries([...this.#instances].map(([name, i]) => [name, i.state]));\n }\n\n /** Disposes every instance (stops their commands). */\n dispose(): void {\n for (const instance of this.#instances.values()) instance.dispose();\n this.#instances.clear();\n }\n}\n\nlet serverScope: StoreRegistry | undefined;\n\n/**\n * Runs `fn` with `registry` as the server's store scope. Server renders are synchronous per\n * render step, so @gyral/ssr wraps every step: interleaved requests never see each other's\n * stores, without AsyncLocalStorage (works on any runtime).\n */\nexport function withStoreScope<T>(registry: StoreRegistry, fn: () => T): T {\n const previous = serverScope;\n serverScope = registry;\n try {\n return fn();\n } finally {\n serverScope = previous;\n }\n}\n\n/** JSON for an inline `<script type=\"application/json\">`: `</script>` and friends escaped. */\nexport function scriptSafeJson(value: unknown): string {\n return JSON.stringify(value)\n .replaceAll('<', '\\\\u003c')\n .replaceAll('>', '\\\\u003e')\n .replaceAll('&', '\\\\u0026')\n .replaceAll('
', '\\\\u2028')\n .replaceAll('
', '\\\\u2029');\n}\n\nlet documentRegistry: StoreRegistry | undefined;\n\nfunction parseSeeds(\n raw: string | null | undefined,\n where: string,\n): Readonly<Record<string, unknown>> {\n if (raw == null) return {};\n try {\n const parsed = JSON.parse(raw) as unknown;\n return parsed !== null && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};\n } catch (error) {\n console.error(`unreadable ${STORE_SEED_ATTRIBUTE} seed on ${where}`, error);\n return {};\n }\n}\n\n/** The client's default scope, restored from the server's page seed on first use. */\nfunction documentScope(): StoreRegistry {\n const script = document.querySelector(`script[${STORE_SEED_ATTRIBUTE}]`);\n documentRegistry ??= new StoreRegistry([], parseSeeds(script?.textContent, 'the page'));\n return documentRegistry;\n}\n\nconst providers = new WeakMap<Element, StoreRegistry>();\n\n/**\n * A `<gyral-stores>` provider's scope, created on first use. On the client a server-rendered\n * provider carries its instances' states in `data-gyral-stores` (gyral-czi.20): seeded stores\n * are restored from it (so hydration matches the server), and `.instances` supplies the rest.\n */\nexport function providerScope(el: Element): StoreRegistry {\n let registry = providers.get(el);\n if (registry === undefined) {\n const { instances = [] } = el as { instances?: readonly AnyStoreInstance[] };\n const seeds = parseSeeds(el.getAttribute(STORE_SEED_ATTRIBUTE), `<${el.localName}>`);\n const unseeded = instances.filter((i) => !(i.store.name in seeds));\n registry = new StoreRegistry(unseeded, seeds);\n providers.set(el, registry);\n }\n return registry;\n}\n\n/**\n * The scope for a server render: the request's, set by `withStoreScope`. Nested\n * `<gyral-stores>` providers on the server are the server renderer's job (Phase 4, gyral-g1r.9).\n */\nexport function serverScopeFor(tag: string, store: AnyStore): StoreRegistry {\n if (serverScope !== undefined) return serverScope;\n throw new Error(\n `<${tag}> reads store \"${store.name}\" during a server render without a store scope. ` +\n \"Pass the request's store instances to page({ stores }) or renderToString(value, { stores }).\",\n );\n}\n\n/** The scope for a component: its nearest provider (across shadow roots) or the default. */\nexport function scopeFor(host: Element): StoreRegistry {\n for (let node: Node | null = host.parentNode; node !== null;) {\n if (node instanceof Element && node.localName === STORES_ELEMENT) return providerScope(node);\n node = node instanceof ShadowRoot ? node.host : node.parentNode;\n }\n return documentScope();\n}\n\n/** Tests only: forget the document default so each test starts clean. */\nexport function resetDocumentStores(): void {\n documentRegistry?.dispose();\n documentRegistry = undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store-scope.js","sourceRoot":"","sources":["../src/store-scope.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,+FAA+F;AAC/F,MAAM,CAAC,MAAM,oBAAoB,GAAG,mBAAmB,CAAC;AAExD,8FAA8F;AAC9F,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAE7C,qFAAqF;AACrF,MAAM,OAAO,aAAa;IACf,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;IACjD,MAAM,CAAoC;IAEnD,YACE,YAAyC,EAAE,EAC3C,QAA2C,EAAE;QAE7C,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,kFAAkF;IAClF,GAAG,CAAC,KAAe;QACjB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,QAA0B;QAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACnD,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,oFAAoF;IACpF,OAAO,CAAC,KAAe;QACrB,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAc,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC1B,CAAC;IAED,wEAAwE;IACxE,QAAQ;QACN,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,sDAAsD;IACtD,OAAO;QACL,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACF;AAED,IAAI,WAAsC,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAI,QAAuB,EAAE,EAAW;IACpE,MAAM,QAAQ,GAAG,WAAW,CAAC;IAC7B,WAAW,GAAG,QAAQ,CAAC;IACvB,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,CAAC;IACd,CAAC;YAAS,CAAC;QACT,WAAW,GAAG,QAAQ,CAAC;IACzB,CAAC;AACH,CAAC;AAED,IAAI,gBAA2C,CAAC;AAEhD,SAAS,UAAU,CACjB,GAA8B,EAC9B,KAAa;IAEb,IAAI,GAAG,IAAI,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,OAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,oBAAoB,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/D,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,SAAS,aAAa;IACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,oBAAoB,GAAG,CAAC,CAAC;IACzE,gBAAgB,KAAK,IAAI,aAAa,CAAC,EAAE,EAAE,UAAU,CAAC,MAAM,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IACxF,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,SAAS,GAAG,IAAI,OAAO,EAA0B,CAAC;AAExD;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAAW;IACvC,IAAI,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACjC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,EAAE,SAAS,GAAG,EAAE,EAAE,GAAG,EAAiD,CAAC;QAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;QACrF,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QACnE,QAAQ,GAAG,IAAI,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC9C,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,KAAe;IACzD,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAClD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,KAAK,IAAI,IAAI,GAAgB,IAAI,CAAC,UAAU,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC;QAC7D,IAAI,IAAI,YAAY,OAAO,IAAI,IAAI,CAAC,SAAS,KAAK,cAAc;YAAE,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7F,IAAI,GAAG,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC;IAClE,CAAC;IACD,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,mBAAmB;IACjC,gBAAgB,EAAE,OAAO,EAAE,CAAC;IAC5B,gBAAgB,GAAG,SAAS,CAAC;AAC/B,CAAC","sourcesContent":["// Which store instance a component uses (docs/design-docs/0013-shared-state.md):\n// el.stores override → nearest <gyral-stores> ancestor → the document default (client), or the\n// request scope set by @gyral/ssr around each render step (server).\nimport type { AnyStore, AnyStoreInstance } from './store.js';\nimport { message } from './view/index.js';\n\n/** Page-level seed from `@gyral/ssr`: `<script type=\"application/json\" data-gyral-stores>`. */\nexport const STORE_SEED_ATTRIBUTE = 'data-gyral-stores';\n\n/** Provider element name. Set `.instances=${[cart.instance()]}`, or let it create its own. */\nexport const STORES_ELEMENT = 'gyral-stores';\n\n/** A set of store instances by store name, created on demand from optional seeds. */\nexport class StoreRegistry {\n readonly #instances = new Map<string, AnyStoreInstance>();\n readonly #seeds: Readonly<Record<string, unknown>>;\n\n constructor(\n instances: readonly AnyStoreInstance[] = [],\n seeds: Readonly<Record<string, unknown>> = {},\n ) {\n for (const instance of instances) this.#hold(instance);\n this.#seeds = seeds;\n }\n\n /** The instance for `store`, creating it (from its seed, if any) on first use. */\n get(store: AnyStore): AnyStoreInstance {\n return this.#instances.get(store.name) ?? this.#hold(this.#create(store));\n }\n\n /** Keeps an instance and lets its `send(other, msg)` commands reach this scope. */\n #hold(instance: AnyStoreInstance): AnyStoreInstance {\n this.#instances.set(instance.store.name, instance);\n instance.bindScope?.((store) => this.get(store));\n return instance;\n }\n\n /** A new instance from its seed (checked by the store's schema), or from `init`. */\n #create(store: AnyStore): AnyStoreInstance {\n if (!(store.name in this.#seeds)) return store.instance();\n const check = store.checkSeed(this.#seeds[store.name]);\n if (check.ok) return store.instance(check.state as never);\n console.error(message(51, store.name, check.issues.join('; ')));\n return store.instance();\n }\n\n /** Every instance's state by store name (what the SSR seed carries). */\n snapshot(): Readonly<Record<string, unknown>> {\n return Object.fromEntries([...this.#instances].map(([name, i]) => [name, i.state]));\n }\n\n /** Disposes every instance (stops their commands). */\n dispose(): void {\n for (const instance of this.#instances.values()) instance.dispose();\n this.#instances.clear();\n }\n}\n\nlet serverScope: StoreRegistry | undefined;\n\n/**\n * Runs `fn` with `registry` as the server's store scope. Server renders are synchronous per\n * render step, so `@gyral/ssr` wraps every step: interleaved requests never see each other's\n * stores, without AsyncLocalStorage (works on any runtime).\n */\nexport function withStoreScope<T>(registry: StoreRegistry, fn: () => T): T {\n const previous = serverScope;\n serverScope = registry;\n try {\n return fn();\n } finally {\n serverScope = previous;\n }\n}\n\nlet documentRegistry: StoreRegistry | undefined;\n\nfunction parseSeeds(\n raw: string | null | undefined,\n where: string,\n): Readonly<Record<string, unknown>> {\n if (raw == null) return {};\n try {\n const parsed = JSON.parse(raw) as unknown;\n return parsed !== null && typeof parsed === 'object' ? (parsed as Record<string, unknown>) : {};\n } catch (error) {\n console.error(message(52, STORE_SEED_ATTRIBUTE, where), error);\n return {};\n }\n}\n\n/** The client's default scope, restored from the server's page seed on first use. */\nfunction documentScope(): StoreRegistry {\n const script = document.querySelector(`script[${STORE_SEED_ATTRIBUTE}]`);\n documentRegistry ??= new StoreRegistry([], parseSeeds(script?.textContent, 'the page'));\n return documentRegistry;\n}\n\nconst providers = new WeakMap<Element, StoreRegistry>();\n\n/**\n * A `<gyral-stores>` provider's scope, created on first use. On the client a server-rendered\n * provider carries its instances' states in `data-gyral-stores` (gyral-czi.20): seeded stores\n * are restored from it (so hydration matches the server), and `.instances` supplies the rest.\n */\nexport function providerScope(el: Element): StoreRegistry {\n let registry = providers.get(el);\n if (registry === undefined) {\n const { instances = [] } = el as { instances?: readonly AnyStoreInstance[] };\n const seeds = parseSeeds(el.getAttribute(STORE_SEED_ATTRIBUTE), `<${el.localName}>`);\n const unseeded = instances.filter((i) => !(i.store.name in seeds));\n registry = new StoreRegistry(unseeded, seeds);\n providers.set(el, registry);\n }\n return registry;\n}\n\n/**\n * The scope for a server render: the request's, set by `withStoreScope`. Nested\n * `<gyral-stores>` providers on the server are the server renderer's job (Phase 4, gyral-g1r.9).\n */\nexport function serverScopeFor(tag: string, store: AnyStore): StoreRegistry {\n if (serverScope !== undefined) return serverScope;\n throw new Error(message(53, tag, store.name));\n}\n\n/** The scope for a component: its nearest provider (across shadow roots) or the default. */\nexport function scopeFor(host: Element): StoreRegistry {\n for (let node: Node | null = host.parentNode; node !== null;) {\n if (node instanceof Element && node.localName === STORES_ELEMENT) return providerScope(node);\n node = node instanceof ShadowRoot ? node.host : node.parentNode;\n }\n return documentScope();\n}\n\n/** Tests only: forget the document default so each test starts clean. */\nexport function resetDocumentStores(): void {\n documentRegistry?.dispose();\n documentRegistry = undefined;\n}\n"]}
|
package/dist/store.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';
|
|
2
2
|
import { splitNext, } from './command.js';
|
|
3
|
+
import { fail } from './errors.js';
|
|
3
4
|
import { features } from './features.js';
|
|
4
5
|
import { makeInterpreter } from './internal/interpreter.js';
|
|
6
|
+
import { noteActivity } from './scheduler.js';
|
|
5
7
|
import { bindStores } from './store-binding.js';
|
|
8
|
+
import { message } from './view/index.js';
|
|
6
9
|
/** No DOM means a server render: store commands never run there (ADR 0012). */
|
|
7
10
|
const onServer = typeof document === 'undefined';
|
|
8
11
|
/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */
|
|
@@ -45,8 +48,7 @@ function createInstance(store, initial) {
|
|
|
45
48
|
// Store-to-store writes: delivered synchronously to the other store's instance in scope.
|
|
46
49
|
const deliver = ({ store: target, msg }) => {
|
|
47
50
|
if (scope === undefined) {
|
|
48
|
-
console.warn(
|
|
49
|
-
'(a registry, <gyral-stores> or the page default), so the message is dropped.');
|
|
51
|
+
console.warn(message(54, store.name, target.name));
|
|
50
52
|
return;
|
|
51
53
|
}
|
|
52
54
|
scope(target).send(msg);
|
|
@@ -63,7 +65,15 @@ function createInstance(store, initial) {
|
|
|
63
65
|
// Stores have no IntentRejected reducer; only their own messages apply.
|
|
64
66
|
if (msg._tag !== 'IntentRejected')
|
|
65
67
|
instance.send(msg);
|
|
66
|
-
}, DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined)
|
|
68
|
+
}, DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined, (cause, code, driver) => {
|
|
69
|
+
const owner = `store "${store.name}"`;
|
|
70
|
+
const text = code === 40
|
|
71
|
+
? message(40, owner, driver)
|
|
72
|
+
: code === 41
|
|
73
|
+
? message(41, owner, driver)
|
|
74
|
+
: message(43, owner, driver);
|
|
75
|
+
fail(cause, 'command', text, { msg: driver });
|
|
76
|
+
});
|
|
67
77
|
interpreter.run(cmd);
|
|
68
78
|
}
|
|
69
79
|
};
|
|
@@ -74,19 +84,37 @@ function createInstance(store, initial) {
|
|
|
74
84
|
},
|
|
75
85
|
drivers: {},
|
|
76
86
|
send(msg) {
|
|
87
|
+
noteActivity(); // settled() keeps waiting while messages arrive (04)
|
|
77
88
|
const reducer = reducers[msg._tag];
|
|
78
89
|
if (reducer === undefined) {
|
|
79
|
-
console.warn(
|
|
90
|
+
console.warn(message(55, store.name, msg._tag));
|
|
80
91
|
return;
|
|
81
92
|
}
|
|
82
93
|
const prev = state;
|
|
83
|
-
|
|
94
|
+
let result;
|
|
95
|
+
try {
|
|
96
|
+
result = reducer(prev, msg);
|
|
97
|
+
}
|
|
98
|
+
catch (cause) {
|
|
99
|
+
// A store reducer that throws changes nothing (ADR 0024).
|
|
100
|
+
fail(cause, 'store', message(76, store.name, msg._tag), { msg: msg._tag });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const [next, commands] = splitNext(result);
|
|
84
104
|
state = next;
|
|
85
105
|
if (DEVTOOLS_ENABLED)
|
|
86
106
|
devStore(store.name, msg, prev, next);
|
|
87
|
-
if (!Object.is(next, prev))
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
if (!Object.is(next, prev)) {
|
|
108
|
+
// Each subscriber on its own: one that throws doesn't keep the others stale (ADR 0024).
|
|
109
|
+
for (const listener of [...listeners]) {
|
|
110
|
+
try {
|
|
111
|
+
listener(next, prev);
|
|
112
|
+
}
|
|
113
|
+
catch (cause) {
|
|
114
|
+
fail(cause, 'store', message(77, store.name), { msg: msg._tag });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
90
118
|
run(commands);
|
|
91
119
|
},
|
|
92
120
|
subscribe(listener) {
|