@gyral/core 0.1.0 → 0.3.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 +112 -0
- package/LICENSE +1 -1
- package/NOTICE +1 -1
- package/README.md +9 -7
- package/dist/children.d.ts +5 -2
- package/dist/children.js +8 -3
- package/dist/children.js.map +1 -1
- package/dist/command.js +6 -0
- package/dist/command.js.map +1 -1
- package/dist/compiler/ast.d.ts +21 -0
- package/dist/compiler/ast.js +145 -0
- package/dist/compiler/ast.js.map +1 -0
- package/dist/compiler/compile.d.ts +38 -0
- package/dist/compiler/compile.js +129 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/hooks.d.ts +18 -0
- package/dist/compiler/hooks.js +186 -0
- package/dist/compiler/hooks.js.map +1 -0
- package/dist/compiler/parse5-check.d.ts +21 -0
- package/dist/compiler/parse5-check.js +55 -0
- package/dist/compiler/parse5-check.js.map +1 -0
- package/dist/compiler/scan.d.ts +31 -0
- package/dist/compiler/scan.js +152 -0
- package/dist/compiler/scan.js.map +1 -0
- package/dist/compiler/source.d.ts +34 -0
- package/dist/compiler/source.js +114 -0
- package/dist/compiler/source.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.js +35 -334
- package/dist/define.js.map +1 -1
- package/dist/devtools-events.d.ts +5 -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 +1 -0
- package/dist/devtools.js +3 -0
- package/dist/devtools.js.map +1 -1
- package/dist/drivers-scope.d.ts +1 -1
- package/dist/drivers-scope.js +1 -8
- package/dist/drivers-scope.js.map +1 -1
- package/dist/element-types.d.ts +5 -3
- package/dist/element-types.js.map +1 -1
- package/dist/element.d.ts +3 -0
- package/dist/element.js +269 -0
- package/dist/element.js.map +1 -0
- package/dist/eslint/imports.d.ts +24 -0
- package/dist/eslint/imports.js +78 -0
- package/dist/eslint/imports.js.map +1 -0
- package/dist/eslint/index.d.ts +15 -0
- package/dist/eslint/index.js +15 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/locate.d.ts +33 -0
- package/dist/eslint/locate.js +132 -0
- package/dist/eslint/locate.js.map +1 -0
- package/dist/eslint/row-purity-rule.d.ts +2 -0
- package/dist/eslint/row-purity-rule.js +103 -0
- package/dist/eslint/row-purity-rule.js.map +1 -0
- package/dist/eslint/template-rule.d.ts +5 -0
- package/dist/eslint/template-rule.js +66 -0
- package/dist/eslint/template-rule.js.map +1 -0
- package/dist/features.d.ts +39 -0
- package/dist/features.js +2 -0
- package/dist/features.js.map +1 -0
- package/dist/focus.d.ts +9 -2
- package/dist/focus.js +17 -2
- package/dist/focus.js.map +1 -1
- package/dist/hooks/invalid.d.ts +14 -0
- package/dist/hooks/invalid.js +72 -0
- package/dist/hooks/invalid.js.map +1 -0
- package/dist/hooks/labelled-by.d.ts +13 -0
- package/dist/hooks/labelled-by.js +51 -0
- package/dist/hooks/labelled-by.js.map +1 -0
- package/dist/host-model.d.ts +48 -0
- package/dist/host-model.js +155 -0
- package/dist/host-model.js.map +1 -0
- package/dist/hydration-client.d.ts +9 -0
- package/dist/hydration-client.js +35 -0
- package/dist/hydration-client.js.map +1 -0
- package/dist/hydration.d.ts +13 -19
- package/dist/hydration.js +45 -45
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +9 -16
- package/dist/index.js +9 -16
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +31 -8
- package/dist/intent.js +82 -27
- package/dist/intent.js.map +1 -1
- package/dist/internal/interpreter.d.ts +7 -1
- package/dist/internal/interpreter.js +118 -73
- package/dist/internal/interpreter.js.map +1 -1
- package/dist/invokers-shim.d.ts +6 -0
- package/dist/invokers-shim.js +32 -0
- package/dist/invokers-shim.js.map +1 -0
- package/dist/invokers.d.ts +3 -5
- package/dist/invokers.js +11 -51
- package/dist/invokers.js.map +1 -1
- package/dist/islands.d.ts +0 -2
- package/dist/islands.js +4 -11
- package/dist/islands.js.map +1 -1
- package/dist/light-dom.d.ts +3 -52
- package/dist/light-dom.js +5 -100
- package/dist/light-dom.js.map +1 -1
- package/dist/prop.d.ts +66 -0
- package/dist/prop.js +22 -0
- package/dist/prop.js.map +1 -0
- package/dist/props.d.ts +52 -21
- package/dist/props.js +150 -34
- package/dist/props.js.map +1 -1
- package/dist/scheduler.d.ts +39 -0
- package/dist/scheduler.js +230 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/server-component.d.ts +10 -0
- package/dist/server-component.js +102 -0
- package/dist/server-component.js.map +1 -0
- package/dist/server-specs.d.ts +6 -0
- package/dist/server-specs.js +13 -0
- package/dist/server-specs.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -0
- package/dist/settled.d.ts +10 -0
- package/dist/settled.js +40 -0
- package/dist/settled.js.map +1 -0
- package/dist/states.d.ts +3 -3
- package/dist/states.js +4 -7
- package/dist/states.js.map +1 -1
- package/dist/store-binding.d.ts +6 -3
- package/dist/store-binding.js +5 -6
- package/dist/store-binding.js.map +1 -1
- package/dist/store-scope.d.ts +6 -7
- package/dist/store-scope.js +11 -26
- package/dist/store-scope.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.js +9 -2
- package/dist/store.js.map +1 -1
- package/dist/stores-provider.d.ts +2 -6
- package/dist/stores-provider.js +21 -55
- package/dist/stores-provider.js.map +1 -1
- package/dist/transitions.d.ts +5 -6
- package/dist/transitions.js +11 -20
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +19 -23
- package/dist/types.js.map +1 -1
- package/dist/view/attributes.d.ts +6 -0
- package/dist/view/attributes.js +10 -0
- package/dist/view/attributes.js.map +1 -0
- package/dist/view/compiled.d.ts +1 -0
- package/dist/view/compiled.js +6 -0
- package/dist/view/compiled.js.map +1 -0
- package/dist/view/dev-off.d.ts +1 -0
- package/dist/view/dev-off.js +4 -0
- package/dist/view/dev-off.js.map +1 -0
- package/dist/view/dev.d.ts +1 -0
- package/dist/view/dev.js +5 -0
- package/dist/view/dev.js.map +1 -0
- package/dist/view/flags.d.ts +1 -0
- package/dist/view/flags.js +4 -0
- package/dist/view/flags.js.map +1 -0
- package/dist/view/index.d.ts +21 -0
- package/dist/view/index.js +21 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/normalize/check.d.ts +20 -0
- package/dist/view/normalize/check.js +78 -0
- package/dist/view/normalize/check.js.map +1 -0
- package/dist/view/normalize/emit.d.ts +13 -0
- package/dist/view/normalize/emit.js +136 -0
- package/dist/view/normalize/emit.js.map +1 -0
- package/dist/view/normalize/entities.d.ts +4 -0
- package/dist/view/normalize/entities.js +38 -0
- package/dist/view/normalize/entities.js.map +1 -0
- package/dist/view/normalize/errors.d.ts +9 -0
- package/dist/view/normalize/errors.js +19 -0
- package/dist/view/normalize/errors.js.map +1 -0
- package/dist/view/normalize/id.d.ts +7 -0
- package/dist/view/normalize/id.js +48 -0
- package/dist/view/normalize/id.js.map +1 -0
- package/dist/view/normalize/normalize.d.ts +15 -0
- package/dist/view/normalize/normalize.js +36 -0
- package/dist/view/normalize/normalize.js.map +1 -0
- package/dist/view/normalize/repairs.d.ts +24 -0
- package/dist/view/normalize/repairs.js +166 -0
- package/dist/view/normalize/repairs.js.map +1 -0
- package/dist/view/normalize/rules.d.ts +30 -0
- package/dist/view/normalize/rules.js +90 -0
- package/dist/view/normalize/rules.js.map +1 -0
- package/dist/view/normalize/shape.d.ts +5 -0
- package/dist/view/normalize/shape.js +38 -0
- package/dist/view/normalize/shape.js.map +1 -0
- package/dist/view/normalize/source.d.ts +13 -0
- package/dist/view/normalize/source.js +34 -0
- package/dist/view/normalize/source.js.map +1 -0
- package/dist/view/normalize/tag-states.d.ts +23 -0
- package/dist/view/normalize/tag-states.js +192 -0
- package/dist/view/normalize/tag-states.js.map +1 -0
- package/dist/view/normalize/tokenizer.d.ts +5 -0
- package/dist/view/normalize/tokenizer.js +233 -0
- package/dist/view/normalize/tokenizer.js.map +1 -0
- package/dist/view/normalize/tokens.d.ts +51 -0
- package/dist/view/normalize/tokens.js +24 -0
- package/dist/view/normalize/tokens.js.map +1 -0
- package/dist/view/normalize/tree.d.ts +59 -0
- package/dist/view/normalize/tree.js +190 -0
- package/dist/view/normalize/tree.js.map +1 -0
- package/dist/view/normalize/types.d.ts +126 -0
- package/dist/view/normalize/types.js +26 -0
- package/dist/view/normalize/types.js.map +1 -0
- package/dist/view/normalize/whitespace.d.ts +2 -0
- package/dist/view/normalize/whitespace.js +182 -0
- package/dist/view/normalize/whitespace.js.map +1 -0
- package/dist/view/prepare-stub.d.ts +3 -0
- package/dist/view/prepare-stub.js +7 -0
- package/dist/view/prepare-stub.js.map +1 -0
- package/dist/view/prepare.d.ts +8 -0
- package/dist/view/prepare.js +57 -0
- package/dist/view/prepare.js.map +1 -0
- package/dist/view/registry.d.ts +63 -0
- package/dist/view/registry.js +19 -0
- package/dist/view/registry.js.map +1 -0
- package/dist/view/render/adopt-attr.d.ts +14 -0
- package/dist/view/render/adopt-attr.js +77 -0
- package/dist/view/render/adopt-attr.js.map +1 -0
- package/dist/view/render/adopt-plan.d.ts +10 -0
- package/dist/view/render/adopt-plan.js +26 -0
- package/dist/view/render/adopt-plan.js.map +1 -0
- package/dist/view/render/adopt.d.ts +3 -0
- package/dist/view/render/adopt.js +269 -0
- package/dist/view/render/adopt.js.map +1 -0
- package/dist/view/render/attr-parts.d.ts +61 -0
- package/dist/view/render/attr-parts.js +229 -0
- package/dist/view/render/attr-parts.js.map +1 -0
- package/dist/view/render/child-part.d.ts +51 -0
- package/dist/view/render/child-part.js +170 -0
- package/dist/view/render/child-part.js.map +1 -0
- package/dist/view/render/dev-check.d.ts +12 -0
- package/dist/view/render/dev-check.js +122 -0
- package/dist/view/render/dev-check.js.map +1 -0
- package/dist/view/render/hook-part.d.ts +3 -0
- package/dist/view/render/hook-part.js +20 -0
- package/dist/view/render/hook-part.js.map +1 -0
- package/dist/view/render/hooks.d.ts +40 -0
- package/dist/view/render/hooks.js +46 -0
- package/dist/view/render/hooks.js.map +1 -0
- package/dist/view/render/hydrate.d.ts +10 -0
- package/dist/view/render/hydrate.js +33 -0
- package/dist/view/render/hydrate.js.map +1 -0
- package/dist/view/render/instance.d.ts +33 -0
- package/dist/view/render/instance.js +107 -0
- package/dist/view/render/instance.js.map +1 -0
- package/dist/view/render/list.d.ts +20 -0
- package/dist/view/render/list.js +156 -0
- package/dist/view/render/list.js.map +1 -0
- package/dist/view/render/mismatch.d.ts +10 -0
- package/dist/view/render/mismatch.js +53 -0
- package/dist/view/render/mismatch.js.map +1 -0
- package/dist/view/render/nodes.d.ts +10 -0
- package/dist/view/render/nodes.js +37 -0
- package/dist/view/render/nodes.js.map +1 -0
- package/dist/view/render/place.d.ts +18 -0
- package/dist/view/render/place.js +69 -0
- package/dist/view/render/place.js.map +1 -0
- package/dist/view/render/plan.d.ts +26 -0
- package/dist/view/render/plan.js +124 -0
- package/dist/view/render/plan.js.map +1 -0
- package/dist/view/render/raw.d.ts +18 -0
- package/dist/view/render/raw.js +68 -0
- package/dist/view/render/raw.js.map +1 -0
- package/dist/view/render/render.d.ts +12 -0
- package/dist/view/render/render.js +60 -0
- package/dist/view/render/render.js.map +1 -0
- package/dist/view/render/reorder.d.ts +4 -0
- package/dist/view/render/reorder.js +101 -0
- package/dist/view/render/reorder.js.map +1 -0
- package/dist/view/render/rows.d.ts +23 -0
- package/dist/view/render/rows.js +95 -0
- package/dist/view/render/rows.js.map +1 -0
- package/dist/view/render/seen.d.ts +11 -0
- package/dist/view/render/seen.js +17 -0
- package/dist/view/render/seen.js.map +1 -0
- package/dist/view/render/values.d.ts +29 -0
- package/dist/view/render/values.js +13 -0
- package/dist/view/render/values.js.map +1 -0
- package/dist/view/render/warn.d.ts +14 -0
- package/dist/view/render/warn.js +69 -0
- package/dist/view/render/warn.js.map +1 -0
- package/dist/view/server/component.d.ts +9 -0
- package/dist/view/server/component.js +42 -0
- package/dist/view/server/component.js.map +1 -0
- package/dist/view/server/escape.d.ts +10 -0
- package/dist/view/server/escape.js +34 -0
- package/dist/view/server/escape.js.map +1 -0
- package/dist/view/server/index.d.ts +28 -0
- package/dist/view/server/index.js +72 -0
- package/dist/view/server/index.js.map +1 -0
- package/dist/view/server/sha256.d.ts +2 -0
- package/dist/view/server/sha256.js +75 -0
- package/dist/view/server/sha256.js.map +1 -0
- package/dist/view/server/tags.d.ts +57 -0
- package/dist/view/server/tags.js +121 -0
- package/dist/view/server/tags.js.map +1 -0
- package/dist/view/server/values.d.ts +16 -0
- package/dist/view/server/values.js +86 -0
- package/dist/view/server/values.js.map +1 -0
- package/dist/view/server/writer.d.ts +16 -0
- package/dist/view/server/writer.js +155 -0
- package/dist/view/server/writer.js.map +1 -0
- package/dist/view/styles.d.ts +25 -0
- package/dist/view/styles.js +58 -0
- package/dist/view/styles.js.map +1 -0
- package/dist/view/template-element.d.ts +3 -0
- package/dist/view/template-element.js +25 -0
- package/dist/view/template-element.js.map +1 -0
- package/dist/view/template.d.ts +21 -0
- package/dist/view/template.js +30 -0
- package/dist/view/template.js.map +1 -0
- package/dist/vite.d.ts +45 -6
- package/dist/vite.js +150 -25
- package/dist/vite.js.map +1 -1
- package/package.json +41 -7
- package/dist/accessible-name.d.ts +0 -19
- package/dist/accessible-name.js +0 -56
- package/dist/accessible-name.js.map +0 -1
- package/dist/element-directive.d.ts +0 -20
- package/dist/element-directive.js +0 -31
- package/dist/element-directive.js.map +0 -1
- package/dist/internal/runtime.d.ts +0 -7
- package/dist/internal/runtime.js +0 -8
- package/dist/internal/runtime.js.map +0 -1
- package/dist/invalid.d.ts +0 -21
- package/dist/invalid.js +0 -70
- package/dist/invalid.js.map +0 -1
- package/dist/live-boolean.d.ts +0 -22
- package/dist/live-boolean.js +0 -34
- package/dist/live-boolean.js.map +0 -1
- package/dist/styles.d.ts +0 -12
- package/dist/styles.js +0 -11
- package/dist/styles.js.map +0 -1
- package/dist/textarea.d.ts +0 -26
- package/dist/textarea.js +0 -85
- package/dist/textarea.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// A spec as the server renderer sees it (view/05-element.md "Registration", view/06-server.md
|
|
2
|
+
// "Components"). Outside the browser `define()` records the spec (server-specs.ts) and
|
|
3
|
+
// `@gyral/core/server` registers one of these per spec in view/'s server registry. Rendering is
|
|
4
|
+
// `view(init(props))`: props parsed from the start tag's attributes (always validated) and
|
|
5
|
+
// property holes (validated in development), `init`'s commands dropped (the client's `init`
|
|
6
|
+
// starts them after hydration, ADR 0012), then `initialMessages` through their reducers.
|
|
7
|
+
// `ctx.read` uses the nearest `<gyral-stores>` provider's registry, else the request's.
|
|
8
|
+
import { splitNext } from './command.js';
|
|
9
|
+
import { makeSeed } from './hydration.js';
|
|
10
|
+
import { runInit } from './init.js';
|
|
11
|
+
import { intentNames } from './intent.js';
|
|
12
|
+
import { isLight } from './light-dom.js';
|
|
13
|
+
import { attributeOf, checkValue, missingRequired, parseAttribute, readProps, reportInvalid, } from './props.js';
|
|
14
|
+
import { recordedSpecs } from './server-specs.js';
|
|
15
|
+
import { serverScopeFor, StoreRegistry } from './store-scope.js';
|
|
16
|
+
import { DEV, registerServerComponent, serverComponent as registered, styleTexts, } from './view/index.js';
|
|
17
|
+
/** Resolves props from a start tag: attributes always validated, properties in development. */
|
|
18
|
+
function propsFrom(tag, table, input) {
|
|
19
|
+
const values = {};
|
|
20
|
+
const carried = {};
|
|
21
|
+
for (const [name, def] of Object.entries(table)) {
|
|
22
|
+
if (name in input.properties) {
|
|
23
|
+
const value = input.properties[name];
|
|
24
|
+
if (value === undefined)
|
|
25
|
+
continue;
|
|
26
|
+
const checked = DEV ? checkValue(tag, name, def, value) : { ok: true, value };
|
|
27
|
+
if (checked.ok)
|
|
28
|
+
values[name] = value;
|
|
29
|
+
else
|
|
30
|
+
reportInvalid(tag, name, 'a property', checked.issues);
|
|
31
|
+
carried[name] = value; // no attribute carries it: the seed does
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const attr = attributeOf(name, def);
|
|
35
|
+
if (attr === undefined)
|
|
36
|
+
continue;
|
|
37
|
+
const raw = input.attributes[attr];
|
|
38
|
+
const parsed = parseAttribute(def, raw ?? null);
|
|
39
|
+
if (parsed === undefined)
|
|
40
|
+
continue;
|
|
41
|
+
const checked = parsed.ok ? checkValue(tag, name, def, parsed.value) : parsed;
|
|
42
|
+
if (checked.ok)
|
|
43
|
+
values[name] = checked.value;
|
|
44
|
+
else
|
|
45
|
+
reportInvalid(tag, name, `the attribute ${attr}`, checked.issues);
|
|
46
|
+
}
|
|
47
|
+
return { values, carried };
|
|
48
|
+
}
|
|
49
|
+
/** The server registry entry for `spec`. */
|
|
50
|
+
export function serverComponent(tag, spec) {
|
|
51
|
+
const table = (spec.props ?? {});
|
|
52
|
+
const reducers = spec.update;
|
|
53
|
+
const declared = new Set((spec.stores ?? []).map((store) => store.name));
|
|
54
|
+
const reader = (scope) => (store) => {
|
|
55
|
+
if (!declared.has(store.name)) {
|
|
56
|
+
throw new Error(`<${tag}> uses store "${store.name}" without declaring it in spec.stores.`);
|
|
57
|
+
}
|
|
58
|
+
const any = store;
|
|
59
|
+
const registry = scope instanceof StoreRegistry ? scope : serverScopeFor(tag, any);
|
|
60
|
+
return registry.get(any).state; // Sound: the ref carries this store's state type.
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
tag,
|
|
64
|
+
light: isLight(spec),
|
|
65
|
+
styles: isLight(spec) ? [] : styleTexts(spec.styles),
|
|
66
|
+
hydrate: spec.hydrate ?? 'load',
|
|
67
|
+
render(input) {
|
|
68
|
+
const { values, carried } = propsFrom(tag, table, input);
|
|
69
|
+
const missing = missingRequired(values, table);
|
|
70
|
+
if (missing.length > 0) {
|
|
71
|
+
console.warn(`<${tag}> is missing required prop(s): ${missing.join(', ')}.`);
|
|
72
|
+
}
|
|
73
|
+
const props = readProps(values, table); // Sound: exactly the declared props.
|
|
74
|
+
const ctx = { props, read: reader(input.scope) };
|
|
75
|
+
const [initial] = splitNext(runInit(spec, props));
|
|
76
|
+
let state = initial;
|
|
77
|
+
for (const msg of (input.initialMessages ?? [])) {
|
|
78
|
+
const reducer = reducers[msg._tag];
|
|
79
|
+
if (reducer !== undefined)
|
|
80
|
+
[state] = splitNext(reducer(state, msg, ctx));
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
view: spec.view(state, intentNames, ctx),
|
|
84
|
+
seed: makeSeed(tag, state, initial, carried),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Registers every spec define() recorded outside the browser (and not registered yet) in
|
|
91
|
+
* view/'s server registry. `@gyral/core/server` calls it before each render; client code never
|
|
92
|
+
* imports this module.
|
|
93
|
+
*/
|
|
94
|
+
export function registerRecordedSpecs() {
|
|
95
|
+
for (const [tag, spec] of recordedSpecs()) {
|
|
96
|
+
if (registered(tag) !== undefined)
|
|
97
|
+
continue;
|
|
98
|
+
// Sound: define() recorded exactly this tag's ComponentSpec.
|
|
99
|
+
registerServerComponent(serverComponent(tag, spec));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=server-component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-component.js","sourceRoot":"","sources":["../src/server-component.ts"],"names":[],"mappings":"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","sourcesContent":["// A spec as the server renderer sees it (view/05-element.md \"Registration\", view/06-server.md\n// \"Components\"). Outside the browser `define()` records the spec (server-specs.ts) and\n// `@gyral/core/server` registers one of these per spec in view/'s server registry. Rendering is\n// `view(init(props))`: props parsed from the start tag's attributes (always validated) and\n// property holes (validated in development), `init`'s commands dropped (the client's `init`\n// starts them after hydration, ADR 0012), then `initialMessages` through their reducers.\n// `ctx.read` uses the nearest `<gyral-stores>` provider's registry, else the request's.\nimport { splitNext, type Next } from './command.js';\nimport { makeSeed } from './hydration.js';\nimport { runInit } from './init.js';\nimport { intentNames } from './intent.js';\nimport { isLight } from './light-dom.js';\nimport {\n attributeOf,\n checkValue,\n missingRequired,\n parseAttribute,\n readProps,\n reportInvalid,\n type PropTable,\n} from './props.js';\nimport { recordedSpecs } from './server-specs.js';\nimport { serverScopeFor, StoreRegistry } from './store-scope.js';\nimport type { AnyStore, StoreRef } from './store.js';\nimport type { ComponentSpec, Ctx, IntentNames, Tagged } from './types.js';\nimport {\n DEV,\n registerServerComponent,\n serverComponent as registered,\n styleTexts,\n type ServerComponent,\n type ServerRenderInput,\n type ServerRendering,\n} from './view/index.js';\n\ntype Reducers<S> = Readonly<\n Record<string, ((s: S, m: Tagged, ctx: Ctx<object>) => Next<S, Tagged>) | undefined>\n>;\n\n/** Resolves props from a start tag: attributes always validated, properties in development. */\nfunction propsFrom(tag: string, table: PropTable, input: ServerRenderInput) {\n const values: Record<string, unknown> = {};\n const carried: Record<string, unknown> = {};\n for (const [name, def] of Object.entries(table)) {\n if (name in input.properties) {\n const value = input.properties[name];\n if (value === undefined) continue;\n const checked = DEV ? checkValue(tag, name, def, value) : { ok: true as const, value };\n if (checked.ok) values[name] = value;\n else reportInvalid(tag, name, 'a property', checked.issues);\n carried[name] = value; // no attribute carries it: the seed does\n continue;\n }\n const attr = attributeOf(name, def);\n if (attr === undefined) continue;\n const raw = input.attributes[attr];\n const parsed = parseAttribute(def, raw ?? null);\n if (parsed === undefined) continue;\n const checked = parsed.ok ? checkValue(tag, name, def, parsed.value) : parsed;\n if (checked.ok) values[name] = checked.value;\n else reportInvalid(tag, name, `the attribute ${attr}`, checked.issues);\n }\n return { values, carried };\n}\n\n/** The server registry entry for `spec`. */\nexport function serverComponent<S, M extends Tagged, P>(\n tag: string,\n spec: ComponentSpec<S, M, P>,\n): ServerComponent {\n const table = (spec.props ?? {}) as PropTable;\n const reducers = spec.update as unknown as Reducers<S>;\n const declared = new Set((spec.stores ?? []).map((store: AnyStore) => store.name));\n const reader =\n (scope: unknown) =>\n <T>(store: StoreRef<T>): T => {\n if (!declared.has(store.name)) {\n throw new Error(`<${tag}> uses store \"${store.name}\" without declaring it in spec.stores.`);\n }\n const any = store as unknown as AnyStore;\n const registry = scope instanceof StoreRegistry ? scope : serverScopeFor(tag, any);\n return registry.get(any).state as T; // Sound: the ref carries this store's state type.\n };\n return {\n tag,\n light: isLight(spec),\n styles: isLight(spec) ? [] : styleTexts(spec.styles),\n hydrate: spec.hydrate ?? 'load',\n render(input): ServerRendering {\n const { values, carried } = propsFrom(tag, table, input);\n const missing = missingRequired(values, table);\n if (missing.length > 0) {\n console.warn(`<${tag}> is missing required prop(s): ${missing.join(', ')}.`);\n }\n const props = readProps(values, table) as P; // Sound: exactly the declared props.\n const ctx = { props, read: reader(input.scope) } as Ctx<object>;\n const [initial] = splitNext(runInit(spec, props));\n let state = initial;\n for (const msg of (input.initialMessages ?? []) as Tagged[]) {\n const reducer = reducers[msg._tag];\n if (reducer !== undefined) [state] = splitNext(reducer(state, msg, ctx));\n }\n return {\n view: spec.view(state, intentNames as IntentNames<M>, ctx as Ctx<P>),\n seed: makeSeed(tag, state, initial, carried),\n };\n },\n };\n}\n\n/**\n * Registers every spec define() recorded outside the browser (and not registered yet) in\n * view/'s server registry. `@gyral/core/server` calls it before each render; client code never\n * imports this module.\n */\nexport function registerRecordedSpecs(): void {\n for (const [tag, spec] of recordedSpecs()) {\n if (registered(tag) !== undefined) continue;\n // Sound: define() recorded exactly this tag's ComponentSpec.\n registerServerComponent(serverComponent(tag, spec as ComponentSpec<unknown, Tagged, object>));\n }\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** A recorded spec, typed loosely: the server component builder re-types it. */
|
|
2
|
+
export type RecordedSpec = object;
|
|
3
|
+
/** define() without a DOM: remember `spec` under `tag` (first definition wins). */
|
|
4
|
+
export declare function recordSpec(tag: string, spec: RecordedSpec): void;
|
|
5
|
+
/** Every recorded spec, by tag. */
|
|
6
|
+
export declare const recordedSpecs: () => ReadonlyMap<string, RecordedSpec>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Specs recorded by define() outside the browser (view/05-element.md "Registration"). Only a
|
|
2
|
+
// map, so client bundles carry a few bytes: the server registry entries (server-component.ts,
|
|
3
|
+
// view/registry.ts) are built from these by the server entry (Phase 4, gyral-g1r.9) through
|
|
4
|
+
// `registerRecordedSpecs()`, which client code never imports.
|
|
5
|
+
const specs = new Map();
|
|
6
|
+
/** define() without a DOM: remember `spec` under `tag` (first definition wins). */
|
|
7
|
+
export function recordSpec(tag, spec) {
|
|
8
|
+
if (!specs.has(tag))
|
|
9
|
+
specs.set(tag, spec);
|
|
10
|
+
}
|
|
11
|
+
/** Every recorded spec, by tag. */
|
|
12
|
+
export const recordedSpecs = () => specs;
|
|
13
|
+
//# sourceMappingURL=server-specs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-specs.js","sourceRoot":"","sources":["../src/server-specs.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,8FAA8F;AAC9F,4FAA4F;AAC5F,8DAA8D;AAK9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE9C,mFAAmF;AACnF,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,IAAkB;IACxD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,mCAAmC;AACnC,MAAM,CAAC,MAAM,aAAa,GAAG,GAAsC,EAAE,CAAC,KAAK,CAAC","sourcesContent":["// Specs recorded by define() outside the browser (view/05-element.md \"Registration\"). Only a\n// map, so client bundles carry a few bytes: the server registry entries (server-component.ts,\n// view/registry.ts) are built from these by the server entry (Phase 4, gyral-g1r.9) through\n// `registerRecordedSpecs()`, which client code never imports.\n\n/** A recorded spec, typed loosely: the server component builder re-types it. */\nexport type RecordedSpec = object;\n\nconst specs = new Map<string, RecordedSpec>();\n\n/** define() without a DOM: remember `spec` under `tag` (first definition wins). */\nexport function recordSpec(tag: string, spec: RecordedSpec): void {\n if (!specs.has(tag)) specs.set(tag, spec);\n}\n\n/** Every recorded spec, by tag. */\nexport const recordedSpecs = (): ReadonlyMap<string, RecordedSpec> => specs;\n"]}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ChildValue } from './view/index.js';
|
|
2
|
+
import { styleHash, styleHashSync, type ServerRenderOptions } from './view/server/index.js';
|
|
3
|
+
export type { ServerRenderOptions };
|
|
4
|
+
export { styleHash, styleHashSync };
|
|
5
|
+
/**
|
|
6
|
+
* Whether `@gyral/core` resolved with the `development` condition (Vite's dev server and its
|
|
7
|
+
* SSR, Vitest): the default of `render`'s `dev` option (view/06-server.md "Development markers").
|
|
8
|
+
*/
|
|
9
|
+
export declare const development: boolean;
|
|
10
|
+
export { StoreRegistry, withStoreScope } from './store-scope.js';
|
|
11
|
+
/**
|
|
12
|
+
* Renders `value` to HTML chunks, synchronously and lazily: each step of the iterator writes up
|
|
13
|
+
* to the next component boundary, running that component's `init` and view. Wrap each step in
|
|
14
|
+
* `withStoreScope(registry, …)` to give components the request's stores (ADR 0013).
|
|
15
|
+
*/
|
|
16
|
+
export declare function render(value: ChildValue, options?: ServerRenderOptions): Iterable<string>;
|
|
17
|
+
/** Renders `value` to one HTML string. */
|
|
18
|
+
export declare function renderToString(value: ChildValue, options?: ServerRenderOptions): string;
|
|
19
|
+
/**
|
|
20
|
+
* `'sha256-…'` hashes of every registered shadow component's declarative-shadow-root `<style>`,
|
|
21
|
+
* for a `Content-Security-Policy` `style-src` (view/08-styles.md "Server").
|
|
22
|
+
*/
|
|
23
|
+
export declare function styleHashes(): Promise<readonly string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Tag → declarative-shadow-root `<style>` text of every registered shadow component with CSS
|
|
26
|
+
* (recorded specs registered first), for checking or building a CSP at render time.
|
|
27
|
+
*/
|
|
28
|
+
export declare function componentStyles(): ReadonlyMap<string, string>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// `@gyral/core/server` (docs/design-docs/view/06-server.md): renders template results and Gyral
|
|
2
|
+
// components to HTML without a DOM. Server-only: never import it from client code. Every
|
|
3
|
+
// render first registers the specs define() recorded outside the browser, and the
|
|
4
|
+
// `<gyral-stores>` provider.
|
|
5
|
+
import { registerRecordedSpecs } from './server-component.js';
|
|
6
|
+
import { storesProvider } from './stores-provider.js';
|
|
7
|
+
import { DEV, registerServerProvider } from './view/index.js';
|
|
8
|
+
import { componentStyles as styles, render as renderChunks, renderToString as renderString, styleHash, styleHashes as hashes, styleHashSync, } from './view/server/index.js';
|
|
9
|
+
export { styleHash, styleHashSync };
|
|
10
|
+
/**
|
|
11
|
+
* Whether `@gyral/core` resolved with the `development` condition (Vite's dev server and its
|
|
12
|
+
* SSR, Vitest): the default of `render`'s `dev` option (view/06-server.md "Development markers").
|
|
13
|
+
*/
|
|
14
|
+
export const development = DEV;
|
|
15
|
+
export { StoreRegistry, withStoreScope } from './store-scope.js';
|
|
16
|
+
function prepare() {
|
|
17
|
+
registerServerProvider(storesProvider);
|
|
18
|
+
registerRecordedSpecs();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Renders `value` to HTML chunks, synchronously and lazily: each step of the iterator writes up
|
|
22
|
+
* to the next component boundary, running that component's `init` and view. Wrap each step in
|
|
23
|
+
* `withStoreScope(registry, …)` to give components the request's stores (ADR 0013).
|
|
24
|
+
*/
|
|
25
|
+
export function render(value, options) {
|
|
26
|
+
prepare();
|
|
27
|
+
return renderChunks(value, options);
|
|
28
|
+
}
|
|
29
|
+
/** Renders `value` to one HTML string. */
|
|
30
|
+
export function renderToString(value, options) {
|
|
31
|
+
prepare();
|
|
32
|
+
return renderString(value, options);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* `'sha256-…'` hashes of every registered shadow component's declarative-shadow-root `<style>`,
|
|
36
|
+
* for a `Content-Security-Policy` `style-src` (view/08-styles.md "Server").
|
|
37
|
+
*/
|
|
38
|
+
export function styleHashes() {
|
|
39
|
+
prepare();
|
|
40
|
+
return hashes();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Tag → declarative-shadow-root `<style>` text of every registered shadow component with CSS
|
|
44
|
+
* (recorded specs registered first), for checking or building a CSP at render time.
|
|
45
|
+
*/
|
|
46
|
+
export function componentStyles() {
|
|
47
|
+
prepare();
|
|
48
|
+
return styles();
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,yFAAyF;AACzF,kFAAkF;AAClF,6BAA6B;AAC7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,GAAG,EAAE,sBAAsB,EAAmB,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EACL,eAAe,IAAI,MAAM,EACzB,MAAM,IAAI,YAAY,EACtB,cAAc,IAAI,YAAY,EAC9B,SAAS,EACT,WAAW,IAAI,MAAM,EACrB,aAAa,GAEd,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC;AAEpC;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAY,GAAG,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEjE,SAAS,OAAO;IACd,sBAAsB,CAAC,cAAc,CAAC,CAAC;IACvC,qBAAqB,EAAE,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,KAAiB,EAAE,OAA6B;IACrE,OAAO,EAAE,CAAC;IACV,OAAO,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,cAAc,CAAC,KAAiB,EAAE,OAA6B;IAC7E,OAAO,EAAE,CAAC;IACV,OAAO,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,EAAE,CAAC;IACV,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,EAAE,CAAC;IACV,OAAO,MAAM,EAAE,CAAC;AAClB,CAAC","sourcesContent":["// `@gyral/core/server` (docs/design-docs/view/06-server.md): renders template results and Gyral\n// components to HTML without a DOM. Server-only: never import it from client code. Every\n// render first registers the specs define() recorded outside the browser, and the\n// `<gyral-stores>` provider.\nimport { registerRecordedSpecs } from './server-component.js';\nimport { storesProvider } from './stores-provider.js';\nimport { DEV, registerServerProvider, type ChildValue } from './view/index.js';\nimport {\n componentStyles as styles,\n render as renderChunks,\n renderToString as renderString,\n styleHash,\n styleHashes as hashes,\n styleHashSync,\n type ServerRenderOptions,\n} from './view/server/index.js';\n\nexport type { ServerRenderOptions };\nexport { styleHash, styleHashSync };\n\n/**\n * Whether `@gyral/core` resolved with the `development` condition (Vite's dev server and its\n * SSR, Vitest): the default of `render`'s `dev` option (view/06-server.md \"Development markers\").\n */\nexport const development: boolean = DEV;\nexport { StoreRegistry, withStoreScope } from './store-scope.js';\n\nfunction prepare(): void {\n registerServerProvider(storesProvider);\n registerRecordedSpecs();\n}\n\n/**\n * Renders `value` to HTML chunks, synchronously and lazily: each step of the iterator writes up\n * to the next component boundary, running that component's `init` and view. Wrap each step in\n * `withStoreScope(registry, …)` to give components the request's stores (ADR 0013).\n */\nexport function render(value: ChildValue, options?: ServerRenderOptions): Iterable<string> {\n prepare();\n return renderChunks(value, options);\n}\n\n/** Renders `value` to one HTML string. */\nexport function renderToString(value: ChildValue, options?: ServerRenderOptions): string {\n prepare();\n return renderString(value, options);\n}\n\n/**\n * `'sha256-…'` hashes of every registered shadow component's declarative-shadow-root `<style>`,\n * for a `Content-Security-Policy` `style-src` (view/08-styles.md \"Server\").\n */\nexport function styleHashes(): Promise<readonly string[]> {\n prepare();\n return hashes();\n}\n\n/**\n * Tag → declarative-shadow-root `<style>` text of every registered shadow component with CSS\n * (recorded specs registered first), for checking or building a CSP at render time.\n */\nexport function componentStyles(): ReadonlyMap<string, string> {\n prepare();\n return styles();\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves once rendering is quiet: every connected Gyral host has rendered its latest state,
|
|
3
|
+
* view-transition updates have run, and focus commands and outputs have been delivered (an
|
|
4
|
+
* output that makes a parent render is waited for too). Rejects with the loop guard's error
|
|
5
|
+
* when rendering never settles in development (components feeding each other).
|
|
6
|
+
*
|
|
7
|
+
* el.send({ _tag: 'Increment' });
|
|
8
|
+
* await settled();
|
|
9
|
+
*/
|
|
10
|
+
export declare function settled(): Promise<void>;
|
package/dist/settled.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// `settled()` (docs/design-docs/view/04-scheduler.md "`settled()`"): wait until rendering is
|
|
2
|
+
// quiet. Implemented on the global scheduler: no host is dirty, no flush is scheduled, no
|
|
3
|
+
// view-transition update is pending, the post-render queue has run, and outputs sent to
|
|
4
|
+
// parents have been delivered. It covers rendering only: driver work (HTTP, timers) is outside
|
|
5
|
+
// it. Tests use it instead of the 0.2 `el.updateComplete`.
|
|
6
|
+
import { isQuiet, whenQuiet } from './scheduler.js';
|
|
7
|
+
/** Rounds of "wait for the next quiet flush" before settled() gives up: a cycle. */
|
|
8
|
+
const MAX_ROUNDS = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Microtask turns first, so follow-ups that are already resolving (a driver that answered at
|
|
11
|
+
* once, an output on its way to a parent) reach the scheduler before quiet is judged.
|
|
12
|
+
*/
|
|
13
|
+
const DRAIN_TURNS = 4;
|
|
14
|
+
async function drain() {
|
|
15
|
+
for (let turn = 0; turn < DRAIN_TURNS; turn += 1)
|
|
16
|
+
await Promise.resolve();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves once rendering is quiet: every connected Gyral host has rendered its latest state,
|
|
20
|
+
* view-transition updates have run, and focus commands and outputs have been delivered (an
|
|
21
|
+
* output that makes a parent render is waited for too). Rejects with the loop guard's error
|
|
22
|
+
* when rendering never settles in development (components feeding each other).
|
|
23
|
+
*
|
|
24
|
+
* el.send({ _tag: 'Increment' });
|
|
25
|
+
* await settled();
|
|
26
|
+
*/
|
|
27
|
+
export async function settled() {
|
|
28
|
+
for (let round = 0; round < MAX_ROUNDS; round += 1) {
|
|
29
|
+
// Wait for a pending flush before draining, so its outcome (a loop-guard error) reaches us.
|
|
30
|
+
const pending = isQuiet() ? undefined : whenQuiet();
|
|
31
|
+
await drain();
|
|
32
|
+
if (pending !== undefined)
|
|
33
|
+
await pending;
|
|
34
|
+
else if (isQuiet())
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`settled(): rendering did not settle after ${String(MAX_ROUNDS)} flushes. ` +
|
|
38
|
+
'Components are probably feeding each other props or messages in a cycle.');
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=settled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settled.js","sourceRoot":"","sources":["../src/settled.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,0FAA0F;AAC1F,wFAAwF;AACxF,+FAA+F;AAC/F,2DAA2D;AAC3D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEpD,oFAAoF;AACpF,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB;;;GAGG;AACH,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,KAAK,UAAU,KAAK;IAClB,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,WAAW,EAAE,IAAI,IAAI,CAAC;QAAE,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;AAC5E,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACnD,4FAA4F;QAC5F,MAAM,OAAO,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;QACpD,MAAM,KAAK,EAAE,CAAC;QACd,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,OAAO,CAAC;aACpC,IAAI,OAAO,EAAE;YAAE,OAAO;IAC7B,CAAC;IACD,MAAM,IAAI,KAAK,CACb,6CAA6C,MAAM,CAAC,UAAU,CAAC,YAAY;QACzE,0EAA0E,CAC7E,CAAC;AACJ,CAAC","sourcesContent":["// `settled()` (docs/design-docs/view/04-scheduler.md \"`settled()`\"): wait until rendering is\n// quiet. Implemented on the global scheduler: no host is dirty, no flush is scheduled, no\n// view-transition update is pending, the post-render queue has run, and outputs sent to\n// parents have been delivered. It covers rendering only: driver work (HTTP, timers) is outside\n// it. Tests use it instead of the 0.2 `el.updateComplete`.\nimport { isQuiet, whenQuiet } from './scheduler.js';\n\n/** Rounds of \"wait for the next quiet flush\" before settled() gives up: a cycle. */\nconst MAX_ROUNDS = 100;\n\n/**\n * Microtask turns first, so follow-ups that are already resolving (a driver that answered at\n * once, an output on its way to a parent) reach the scheduler before quiet is judged.\n */\nconst DRAIN_TURNS = 4;\n\nasync function drain(): Promise<void> {\n for (let turn = 0; turn < DRAIN_TURNS; turn += 1) await Promise.resolve();\n}\n\n/**\n * Resolves once rendering is quiet: every connected Gyral host has rendered its latest state,\n * view-transition updates have run, and focus commands and outputs have been delivered (an\n * output that makes a parent render is waited for too). Rejects with the loop guard's error\n * when rendering never settles in development (components feeding each other).\n *\n * el.send({ _tag: 'Increment' });\n * await settled();\n */\nexport async function settled(): Promise<void> {\n for (let round = 0; round < MAX_ROUNDS; round += 1) {\n // Wait for a pending flush before draining, so its outcome (a loop-guard error) reaches us.\n const pending = isQuiet() ? undefined : whenQuiet();\n await drain();\n if (pending !== undefined) await pending;\n else if (isQuiet()) return;\n }\n throw new Error(\n `settled(): rendering did not settle after ${String(MAX_ROUNDS)} flushes. ` +\n 'Components are probably feeding each other props or messages in a cycle.',\n );\n}\n"]}
|
package/dist/states.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Writes a component's boolean states to its CustomStateSet. */
|
|
2
2
|
export type StateSync = (states: Readonly<Record<string, boolean>>) => void;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A function that mirrors boolean states onto `internals.states`, or `false` when the platform
|
|
5
|
+
* lacks custom states.
|
|
6
6
|
*/
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function stateSync(internals: ElementInternals): StateSync | false;
|
package/dist/states.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
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
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* A function that mirrors boolean states onto `internals.states`, or `false` when the platform
|
|
5
|
+
* lacks custom states.
|
|
6
6
|
*/
|
|
7
|
-
export function
|
|
8
|
-
if (typeof host.attachInternals !== 'function')
|
|
9
|
-
return undefined;
|
|
10
|
-
const internals = host.attachInternals();
|
|
7
|
+
export function stateSync(internals) {
|
|
11
8
|
if (!('states' in internals))
|
|
12
|
-
return
|
|
9
|
+
return false;
|
|
13
10
|
const set = internals.states;
|
|
14
11
|
return (states) => {
|
|
15
12
|
for (const [name, on] of Object.entries(states)) {
|
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;AAU1F;;;GAGG;AACH,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"states.js","sourceRoot":"","sources":["../src/states.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,0FAA0F;AAU1F;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,SAA2B;IACnD,IAAI,CAAC,CAAC,QAAQ,IAAI,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,GAAG,GAAG,SAAS,CAAC,MAAsB,CAAC;IAC7C,OAAO,CAAC,MAAM,EAAE,EAAE;QAChB,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,CAAC;AACJ,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\ninterface StateSetLike {\n add(name: string): void;\n delete(name: string): boolean;\n}\n\n/** Writes a component's boolean states to its CustomStateSet. */\nexport type StateSync = (states: Readonly<Record<string, boolean>>) => void;\n\n/**\n * A function that mirrors boolean states onto `internals.states`, or `false` when the platform\n * lacks custom states.\n */\nexport function stateSync(internals: ElementInternals): StateSync | false {\n if (!('states' in internals)) return false;\n const set = internals.states as StateSetLike;\n return (states) => {\n for (const [name, on] of Object.entries(states)) {\n if (on) set.add(name);\n else set.delete(name);\n }\n };\n}\n"]}
|
package/dist/store-binding.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FeatureHost, StoreLink } from './features.js';
|
|
2
|
+
import type { AnyStore, StoreRef, StoreSendInput } from './store.js';
|
|
2
3
|
type OnChange = (store: AnyStore, state: unknown, prev: unknown) => void;
|
|
3
|
-
export declare class StoreBinding {
|
|
4
|
+
export declare class StoreBinding implements StoreLink {
|
|
4
5
|
#private;
|
|
5
|
-
constructor(host:
|
|
6
|
+
constructor(host: FeatureHost, tag: string, declared: readonly AnyStore[], onChange: OnChange);
|
|
6
7
|
/** `ctx.read(store)`: the current state of this component's instance of `store`. */
|
|
7
8
|
read<S>(ref: StoreRef<S>): S;
|
|
8
9
|
/** Delivers a `send(store, msg)` command to this component's instance of the store. */
|
|
@@ -11,4 +12,6 @@ export declare class StoreBinding {
|
|
|
11
12
|
connect(): boolean;
|
|
12
13
|
disconnect(): void;
|
|
13
14
|
}
|
|
15
|
+
/** The store feature (features.ts): a host's binding to its declared stores. */
|
|
16
|
+
export declare const bindStores: (host: FeatureHost, tag: string, declared: readonly AnyStore[], onChange: OnChange) => StoreLink;
|
|
14
17
|
export {};
|
package/dist/store-binding.js
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
// A component's connection to the stores it declares (ADR 0013): resolve, read, subscribe, send.
|
|
2
1
|
import { scopeFor } from './store-scope.js';
|
|
3
2
|
export class StoreBinding {
|
|
4
3
|
#host;
|
|
5
4
|
#tag;
|
|
6
5
|
#declared;
|
|
7
|
-
#overrides;
|
|
8
6
|
#onChange;
|
|
9
7
|
/** Resolved instances while connected (the scope can't change until it moves). */
|
|
10
8
|
#bound = new Map();
|
|
11
9
|
#unsubscribe = [];
|
|
12
|
-
constructor(host, tag, declared,
|
|
10
|
+
constructor(host, tag, declared, onChange) {
|
|
13
11
|
this.#host = host;
|
|
14
12
|
this.#tag = tag;
|
|
15
13
|
this.#declared = new Map(declared.map((s) => [s.name, s]));
|
|
16
|
-
this.#overrides = overrides;
|
|
17
14
|
this.#onChange = onChange;
|
|
18
15
|
}
|
|
19
16
|
#instance(store) {
|
|
20
17
|
return (this.#bound.get(store.name) ??
|
|
21
|
-
this.#
|
|
22
|
-
scopeFor(this.#host
|
|
18
|
+
this.#host.stores[store.name] ??
|
|
19
|
+
scopeFor(this.#host).get(store));
|
|
23
20
|
}
|
|
24
21
|
#declaredStore(name) {
|
|
25
22
|
const store = this.#declared.get(name);
|
|
@@ -60,4 +57,6 @@ export class StoreBinding {
|
|
|
60
57
|
this.#bound.clear();
|
|
61
58
|
}
|
|
62
59
|
}
|
|
60
|
+
/** The store feature (features.ts): a host's binding to its declared stores. */
|
|
61
|
+
export const bindStores = (host, tag, declared, onChange) => new StoreBinding(host, tag, declared, onChange);
|
|
63
62
|
//# sourceMappingURL=store-binding.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-binding.js","sourceRoot":"","sources":["../src/store-binding.ts"],"names":[],"mappings":"
|
|
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';\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(\n `<${this.#tag}> uses store \"${name}\" without declaring it. Add it to the spec: ` +\n `stores: [${name}] (ADR 0013), so the component subscribes to its changes.`,\n );\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
|
@@ -28,13 +28,12 @@ export declare function scriptSafeJson(value: unknown): string;
|
|
|
28
28
|
* are restored from it (so hydration matches the server), and `.instances` supplies the rest.
|
|
29
29
|
*/
|
|
30
30
|
export declare function providerScope(el: Element): StoreRegistry;
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
31
|
+
/**
|
|
32
|
+
* The scope for a server render: the request's, set by `withStoreScope`. Nested
|
|
33
|
+
* `<gyral-stores>` providers on the server are the server renderer's job (Phase 4, gyral-g1r.9).
|
|
34
|
+
*/
|
|
35
|
+
export declare function serverScopeFor(tag: string, store: AnyStore): StoreRegistry;
|
|
37
36
|
/** The scope for a component: its nearest provider (across shadow roots) or the default. */
|
|
38
|
-
export declare function scopeFor(host: Element
|
|
37
|
+
export declare function scopeFor(host: Element): StoreRegistry;
|
|
39
38
|
/** Tests only: forget the document default so each test starts clean. */
|
|
40
39
|
export declare function resetDocumentStores(): void;
|
package/dist/store-scope.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// Which store instance a component uses (docs/design-docs/0013-shared-state.md):
|
|
2
|
-
// el.stores override → nearest <gyral-stores> ancestor → the document default (client), or the
|
|
3
|
-
// request scope set by @gyral/ssr around each render step (server).
|
|
4
|
-
import { isServer } from 'lit';
|
|
5
|
-
const onServer = isServer;
|
|
6
1
|
/** Page-level seed written by @gyral/ssr: `<script type="application/json" data-gyral-stores>`. */
|
|
7
2
|
export const STORE_SEED_ATTRIBUTE = 'data-gyral-stores';
|
|
8
3
|
/** Provider element name. Set `.instances=${[cart.instance()]}`, or let it create its own. */
|
|
@@ -102,35 +97,25 @@ export function providerScope(el) {
|
|
|
102
97
|
let registry = providers.get(el);
|
|
103
98
|
if (registry === undefined) {
|
|
104
99
|
const { instances = [] } = el;
|
|
105
|
-
const seeds =
|
|
106
|
-
? {}
|
|
107
|
-
: parseSeeds(el.getAttribute(STORE_SEED_ATTRIBUTE), `<${el.localName}>`);
|
|
100
|
+
const seeds = parseSeeds(el.getAttribute(STORE_SEED_ATTRIBUTE), `<${el.localName}>`);
|
|
108
101
|
const unseeded = instances.filter((i) => !(i.store.name in seeds));
|
|
109
102
|
registry = new StoreRegistry(unseeded, seeds);
|
|
110
103
|
providers.set(el, registry);
|
|
111
104
|
}
|
|
112
105
|
return registry;
|
|
113
106
|
}
|
|
114
|
-
/**
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
107
|
+
/**
|
|
108
|
+
* The scope for a server render: the request's, set by `withStoreScope`. Nested
|
|
109
|
+
* `<gyral-stores>` providers on the server are the server renderer's job (Phase 4, gyral-g1r.9).
|
|
110
|
+
*/
|
|
111
|
+
export function serverScopeFor(tag, store) {
|
|
112
|
+
if (serverScope !== undefined)
|
|
113
|
+
return serverScope;
|
|
114
|
+
throw new Error(`<${tag}> reads store "${store.name}" during a server render without a store scope. ` +
|
|
115
|
+
"Pass the request's store instances to page({ stores }) or renderToString(value, { stores }).");
|
|
122
116
|
}
|
|
123
117
|
/** The scope for a component: its nearest provider (across shadow roots) or the default. */
|
|
124
|
-
export function scopeFor(host
|
|
125
|
-
if (onServer) {
|
|
126
|
-
const provided = serverProvider(host);
|
|
127
|
-
if (provided !== undefined)
|
|
128
|
-
return provided;
|
|
129
|
-
if (serverScope !== undefined)
|
|
130
|
-
return serverScope;
|
|
131
|
-
throw new Error(`<${tag}> reads store "${store.name}" during a server render without a store scope. ` +
|
|
132
|
-
"Pass the request's store instances to page({ stores }) or renderToString(value, { stores }).");
|
|
133
|
-
}
|
|
118
|
+
export function scopeFor(host) {
|
|
134
119
|
for (let node = host.parentNode; node !== null;) {
|
|
135
120
|
if (node instanceof Element && node.localName === STORES_ELEMENT)
|
|
136
121
|
return providerScope(node);
|
package/dist/store-scope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-scope.js","sourceRoot":"","sources":["../src/store-scope.ts"],"names":[],"mappings":"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","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 { isServer } from 'lit';\nimport type { AnyStore, AnyStoreInstance } from './store.js';\n\nconst onServer: boolean = isServer;\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 = onServer\n ? {}\n : 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/** Event a component dispatches on the server to find its nearest `<gyral-stores>` provider. */\nexport const STORES_REQUEST = 'gyral-stores-request';\n\n/** The detail of a STORES_REQUEST event; the provider fills in `registry`. */\nexport interface StoresRequest {\n registry?: StoreRegistry;\n}\n\n// The server has no DOM ancestry, but Lit's SSR DOM shim bubbles events through the custom\n// elements being rendered (the mechanism @lit/context uses), so a registered provider can answer.\nfunction serverProvider(host: Element): StoreRegistry | undefined {\n const detail: StoresRequest = {};\n host.dispatchEvent(new CustomEvent(STORES_REQUEST, { bubbles: true, composed: true, detail }));\n return detail.registry;\n}\n\n/** The scope for a component: its nearest provider (across shadow roots) or the default. */\nexport function scopeFor(host: Element, tag: string, store: AnyStore): StoreRegistry {\n if (onServer) {\n const provided = serverProvider(host);\n if (provided !== undefined) return provided;\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 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":"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","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"]}
|
package/dist/store.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
2
|
import { type Command, type DriverOverrides, type Next } from './command.js';
|
|
3
|
+
import { type LocalHost } from './features.js';
|
|
3
4
|
import type { Tagged } from './types.js';
|
|
4
5
|
type Variant<M extends Tagged, K extends M['_tag']> = Extract<M, {
|
|
5
6
|
readonly _tag: K;
|
|
@@ -90,6 +91,8 @@ export declare function defineStore<S, M extends Tagged>(name: string, spec: Sto
|
|
|
90
91
|
export declare const STORE_SEND: {
|
|
91
92
|
readonly name: "@gyral/store-send";
|
|
92
93
|
readonly run: () => undefined;
|
|
94
|
+
/** A component's send: delivered to its instance of the store (features.ts). */
|
|
95
|
+
readonly local: (host: LocalHost, input: unknown) => void;
|
|
93
96
|
};
|
|
94
97
|
/** The input of a `send()` command. */
|
|
95
98
|
export interface StoreSendInput {
|
package/dist/store.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { isServer } from 'lit';
|
|
2
1
|
import { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';
|
|
3
2
|
import { splitNext, } from './command.js';
|
|
3
|
+
import { features } from './features.js';
|
|
4
4
|
import { makeInterpreter } from './internal/interpreter.js';
|
|
5
|
-
|
|
5
|
+
import { bindStores } from './store-binding.js';
|
|
6
|
+
/** No DOM means a server render: store commands never run there (ADR 0012). */
|
|
7
|
+
const onServer = typeof document === 'undefined';
|
|
6
8
|
/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */
|
|
7
9
|
export function defineStore(name, spec) {
|
|
10
|
+
features.stores = bindStores; // components can use stores now (features.ts)
|
|
8
11
|
const store = {
|
|
9
12
|
name,
|
|
10
13
|
spec,
|
|
@@ -113,6 +116,10 @@ function createInstance(store, initial) {
|
|
|
113
116
|
export const STORE_SEND = {
|
|
114
117
|
name: '@gyral/store-send',
|
|
115
118
|
run: () => undefined,
|
|
119
|
+
/** A component's send: delivered to its instance of the store (features.ts). */
|
|
120
|
+
local: (host, input) => {
|
|
121
|
+
host.stores().send(input);
|
|
122
|
+
},
|
|
116
123
|
};
|
|
117
124
|
/** A command that sends `msg` to the component's instance of `store`. Writes are commands. */
|
|
118
125
|
// NoInfer: the store decides the message type, so a literal (`{ _tag: 'Track', … }`) is checked
|