@gyral/core 0.2.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 +87 -0
- package/README.md +7 -5
- 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 -327
- 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 -2
- 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 +7 -12
- package/dist/focus.js +13 -10
- 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 -17
- package/dist/index.js +9 -18
- 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 +11 -4
- 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/{template-whitespace.js → view/normalize/whitespace.js} +24 -34
- 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 +39 -4
- 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/invalid.d.ts +0 -21
- package/dist/invalid.js +0 -70
- package/dist/invalid.js.map +0 -1
- package/dist/lit-html-check.d.ts +0 -6
- package/dist/lit-html-check.js +0 -48
- package/dist/lit-html-check.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/template-whitespace.d.ts +0 -7
- package/dist/template-whitespace.js.map +0 -1
- package/dist/templates.d.ts +0 -5
- package/dist/templates.js +0 -15
- package/dist/templates.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 @@
|
|
|
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":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAK5C,MAAM,OAAO,YAAY;IACd,KAAK,CAAc;IACnB,IAAI,CAAS;IACb,SAAS,CAAgC;IACzC,SAAS,CAAW;IAC7B,kFAAkF;IACzE,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IACtD,YAAY,GAAmB,EAAE,CAAC;IAElC,YAAY,IAAiB,EAAE,GAAW,EAAE,QAA6B,EAAE,QAAkB;QAC3F,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED,SAAS,CAAC,KAAe;QACvB,OAAO,CACL,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAChC,CAAC;IACJ,CAAC;IAED,cAAc,CAAC,IAAY;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,IAAI,IAAI,CAAC,IAAI,iBAAiB,IAAI,8CAA8C;gBAC9E,YAAY,IAAI,2DAA2D,CAC9E,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oFAAoF;IACpF,IAAI,CAAI,GAAgB;QACtB,yFAAyF;QACzF,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,KAAU,CAAC;IAClE,CAAC;IAED,uFAAuF;IACvF,IAAI,CAAC,KAAqB;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAkC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC1E,CAAC;IAED,0FAA0F;IAC1F,OAAO;QACL,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;YAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YACtC,IAAI,CAAC,YAAY,CAAC,IAAI,CACpB,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;gBACjC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC,CAAC,CACH,CAAC;YACF,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,UAAU;QACR,KAAK,MAAM,WAAW,IAAI,IAAI,CAAC,YAAY;YAAE,WAAW,EAAE,CAAC;QAC3D,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;CACF;AAED,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,IAAiB,EACjB,GAAW,EACX,QAA6B,EAC7B,QAAkB,EACP,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","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":"AAAA,iFAAiF;AACjF,+FAA+F;AAC/F,oEAAoE;AACpE,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAG/B,MAAM,QAAQ,GAAY,QAAQ,CAAC;AAEnC,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,QAAQ;YACpB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,YAAY,CAAC,oBAAoB,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3E,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,gGAAgG;AAChG,MAAM,CAAC,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAOrD,2FAA2F;AAC3F,kGAAkG;AAClG,SAAS,cAAc,CAAC,IAAa;IACnC,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/F,OAAO,MAAM,CAAC,QAAQ,CAAC;AACzB,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,QAAQ,CAAC,IAAa,EAAE,GAAW,EAAE,KAAe;IAClE,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAC5C,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO,WAAW,CAAC;QAClD,MAAM,IAAI,KAAK,CACb,IAAI,GAAG,kBAAkB,KAAK,CAAC,IAAI,kDAAkD;YACnF,8FAA8F,CACjG,CAAC;IACJ,CAAC;IACD,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 { 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
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["// Shared state: stores as \"props from the side\" (docs/design-docs/0013-shared-state.md).\n// A store is MVI without a view: init + pure update, commands run by its own interpreter.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { isServer } from 'lit';\nimport { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';\nimport {\n splitNext,\n type AnyDriver,\n type Command,\n type DriverOverrides,\n type Next,\n} from './command.js';\nimport { makeInterpreter, type Interpreter } from './internal/interpreter.js';\nimport type { IntentRejected, Tagged } from './types.js';\n\nconst onServer: boolean = isServer;\n\ntype Variant<M extends Tagged, K extends M['_tag']> = Extract<M, { readonly _tag: K }>;\n\n/** One pure reducer per message tag, like a component's `update` but without context. */\nexport type StoreUpdate<S, M extends Tagged> = {\n readonly [K in M['_tag']]: (state: S, msg: Variant<M, K>) => Next<S, M>;\n};\n\nexport interface StoreSpec<S, M extends Tagged> {\n /** Initial state (and optional commands, e.g. load a saved cart). */\n readonly init: () => Next<S, M>;\n readonly update: StoreUpdate<S, M>;\n /** Driver substitutions by name for this store's commands. */\n readonly drivers?: DriverOverrides;\n /**\n * Validates the server's page seed before the client uses it (any synchronous Standard\n * Schema). An invalid seed is reported with console.error and the store starts from `init`.\n */\n readonly schema?: StandardSchemaV1<unknown, S>;\n}\n\n/** The result of checking a seed against a store's schema. */\nexport type SeedCheck<S> =\n | { readonly ok: true; readonly state: S }\n | { readonly ok: false; readonly issues: readonly string[] };\n\n/** What `ctx.read()` accepts: anything that names a store and carries its state type. */\nexport interface StoreRef<S> {\n readonly name: string;\n /** Type-only marker for the state type. Never set at runtime. */\n readonly stateType?: (state: S) => S;\n}\n\n/** A live store: one per page on the client, one per request on the server, one per test. */\nexport interface StoreInstance<S, M extends Tagged> {\n readonly store: Store<S, M>;\n readonly state: S;\n /** Feeds a message through the store's `update` and notifies subscribers on change. */\n send(msg: M): void;\n /** Called after every state change. Returns an unsubscribe function. */\n subscribe(listener: (state: S, prev: S) => void): () => void;\n /** Per-instance driver substitutions (test fakes). Checked before the spec's. */\n drivers: DriverOverrides;\n /** Stops running commands and drops subscribers. */\n dispose(): void;\n /** Set by the registry that holds the instance (store-to-store `send`). */\n bindScope(resolve: StoreResolver): void;\n}\n\nexport interface Store<S, M extends Tagged> extends StoreRef<S> {\n readonly spec: StoreSpec<S, M>;\n /**\n * A fresh, independent instance. `initial` skips `init`'s state (a server seed, a test\n * fixture); `init`'s commands still run on the client.\n */\n instance(initial?: S): StoreInstance<S, M>;\n /** Checks a seed with `spec.schema` (ok as-is without one). */\n checkSeed(seed: unknown): SeedCheck<S>;\n /** Type-only marker for the message type. Never set at runtime. */\n readonly messageType?: (msg: M) => M;\n}\n\n/** Any store, with its types erased (for declarations and registries). */\nexport interface AnyStore {\n readonly name: string;\n instance(initial?: never): AnyStoreInstance;\n checkSeed(seed: unknown): SeedCheck<unknown>;\n}\n\n/** Any store instance, with its types erased. */\nexport interface AnyStoreInstance {\n readonly store: AnyStore;\n readonly state: unknown;\n send(msg: never): void;\n subscribe(listener: (state: unknown, prev: unknown) => void): () => void;\n dispose(): void;\n /**\n * Called by the registry that holds this instance, so the store's `send(other, msg)`\n * commands reach `other`'s instance in the same scope (gyral-czi.20).\n */\n bindScope?(resolve: StoreResolver): void;\n}\n\n/** Finds a store's instance in a scope (a registry). */\nexport type StoreResolver = (store: AnyStore) => AnyStoreInstance;\n\n/** Store instances substituted by store name (tests, islands). */\nexport type StoreOverrides = Readonly<Record<string, AnyStoreInstance>>;\n\n/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */\nexport function defineStore<S, M extends Tagged>(name: string, spec: StoreSpec<S, M>): Store<S, M> {\n const store: Store<S, M> = {\n name,\n spec,\n instance: (initial?: S) => createInstance(store, initial),\n checkSeed: (seed) => checkSeed(spec.schema, seed),\n };\n return store;\n}\n\nconst issuePath = (issue: StandardSchemaV1.Issue): string =>\n (issue.path ?? []).map((s) => String(typeof s === 'object' ? s.key : s)).join('.');\n\nfunction checkSeed<S>(\n schema: StandardSchemaV1<unknown, S> | undefined,\n seed: unknown,\n): SeedCheck<S> {\n // Sound without a schema: the seed is this store's state, serialized by the server.\n if (schema === undefined) return { ok: true, state: seed as S };\n const result = schema['~standard'].validate(seed);\n if (result instanceof Promise) {\n return { ok: false, issues: ['the store schema is async; seeds need a synchronous schema'] };\n }\n if (result.issues === undefined) return { ok: true, state: result.value };\n return {\n ok: false,\n issues: result.issues.map((i) => `${issuePath(i) || '(root)'}: ${i.message}`),\n };\n}\n\nfunction createInstance<S, M extends Tagged>(\n store: Store<S, M>,\n initial: S | undefined,\n): StoreInstance<S, M> {\n // Sound: send() only calls the reducer whose key equals msg._tag.\n const reducers = store.spec.update as unknown as Readonly<\n Record<string, ((state: S, msg: M) => Next<S, M>) | undefined>\n >;\n const listeners = new Set<(state: S, prev: S) => void>();\n let interpreter: Interpreter<M | IntentRejected> | undefined;\n let disposed = false;\n\n const [initState, initCommands] = splitNext(store.spec.init());\n let state = initial === undefined ? initState : initial;\n\n const resolve = (driver: AnyDriver): AnyDriver =>\n instance.drivers[driver.name] ?? store.spec.drivers?.[driver.name] ?? driver;\n\n let scope: StoreResolver | undefined;\n\n // Store-to-store writes: delivered synchronously to the other store's instance in scope.\n const deliver = ({ store: target, msg }: StoreSendInput): void => {\n if (scope === undefined) {\n console.warn(\n `store \"${store.name}\" sends to \"${target.name}\" but is not held by a store scope ` +\n '(a registry, <gyral-stores> or the page default), so the message is dropped.',\n );\n return;\n }\n (scope(target) as { send(msg: unknown): void }).send(msg);\n };\n\n const run = (commands: ReadonlyArray<Command<M | IntentRejected>>): void => {\n if (onServer || disposed || commands.length === 0) return; // never on the server (ADR 0012)\n for (const cmd of commands) {\n if (cmd.driver === STORE_SEND) {\n deliver(cmd.input as StoreSendInput);\n continue;\n }\n interpreter ??= makeInterpreter<M | IntentRejected>(\n resolve,\n (msg) => {\n // Stores have no IntentRejected reducer; only their own messages apply.\n if (msg._tag !== 'IntentRejected') instance.send(msg as M);\n },\n DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined,\n );\n interpreter.run(cmd);\n }\n };\n\n const instance: StoreInstance<S, M> = {\n store,\n get state() {\n return state;\n },\n drivers: {},\n send(msg) {\n const reducer = reducers[msg._tag];\n if (reducer === undefined) {\n console.warn(`store \"${store.name}\" has no update for message \"${msg._tag}\".`);\n return;\n }\n const prev = state;\n const [next, commands] = splitNext(reducer(prev, msg));\n state = next;\n if (DEVTOOLS_ENABLED) devStore(store.name, msg, prev, next);\n if (!Object.is(next, prev)) for (const listener of [...listeners]) listener(next, prev);\n run(commands);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n dispose() {\n disposed = true;\n interpreter?.dispose();\n listeners.clear();\n },\n bindScope(resolver) {\n scope = resolver;\n },\n };\n\n // A seeded instance (hydration) starts init's commands after the hydrating renders flushed,\n // so a fast driver can't change state under them (same reason as ADR 0012 for components).\n if (initial === undefined) run(initCommands);\n else if (initCommands.length > 0)\n setTimeout(() => {\n run(initCommands);\n }, 0);\n return instance;\n}\n\n/** Marker driver for `send()`: `define()` delivers these to the resolved store instance. */\nexport const STORE_SEND = {\n name: '@gyral/store-send',\n run: () => undefined,\n} as const;\n\n/** The input of a `send()` command. */\nexport interface StoreSendInput {\n readonly store: AnyStore;\n readonly msg: Tagged;\n}\n\n/** A command that sends `msg` to the component's instance of `store`. Writes are commands. */\n// NoInfer: the store decides the message type, so a literal (`{ _tag: 'Track', … }`) is checked\n// against it instead of widening `_tag` to string (store-to-store sends have no other context).\nexport function send<S, M extends Tagged>(store: Store<S, M>, msg: NoInfer<M>): Command<never> {\n const input: StoreSendInput = { store, msg };\n return { driver: STORE_SEND, input, onSuccess: () => undefined };\n}\n\n/** Framework message: a store this component reads changed (ADR 0013). Optional reducer. */\nexport interface StoreChanged {\n readonly _tag: 'StoreChanged';\n /** The store's name. Narrow with `changed(store, msg)`. */\n readonly store: string;\n readonly state: unknown;\n readonly prev: unknown;\n}\n\n/** Narrows a `StoreChanged` message to one store's typed state, or `undefined`. */\nexport function changed<S>(\n store: StoreRef<S>,\n msg: StoreChanged,\n): { readonly state: S; readonly prev: S } | undefined {\n // Sound: the message was built from this store's instance (matched by unique name).\n return msg.store === store.name ? { state: msg.state as S, prev: msg.prev as S } : undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EACL,SAAS,GAKV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,eAAe,EAAoB,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhD,+EAA+E;AAC/E,MAAM,QAAQ,GAAG,OAAO,QAAQ,KAAK,WAAW,CAAC;AA0FjD,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAsB,IAAY,EAAE,IAAqB;IAClF,QAAQ,CAAC,MAAM,GAAG,UAAU,CAAC,CAAC,8CAA8C;IAC5E,MAAM,KAAK,GAAgB;QACzB,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,CAAC,OAAW,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC;QACzD,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;KAClD,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,KAA6B,EAAU,EAAE,CAC1D,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAErF,SAAS,SAAS,CAChB,MAAgD,EAChD,IAAa;IAEb,oFAAoF;IACpF,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAS,EAAE,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAClD,IAAI,MAAM,YAAY,OAAO,EAAE,CAAC;QAC9B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,4DAA4D,CAAC,EAAE,CAAC;IAC/F,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;IAC1E,OAAO;QACL,EAAE,EAAE,KAAK;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,QAAQ,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;KAC9E,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CACrB,KAAkB,EAClB,OAAsB;IAEtB,kEAAkE;IAClE,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,MAE3B,CAAC;IACF,MAAM,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAC;IACzD,IAAI,WAAwD,CAAC;IAC7D,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,IAAI,KAAK,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IAExD,MAAM,OAAO,GAAG,CAAC,MAAiB,EAAa,EAAE,CAC/C,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC;IAE/E,IAAI,KAAgC,CAAC;IAErC,yFAAyF;IACzF,MAAM,OAAO,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAkB,EAAQ,EAAE;QAC/D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CACV,UAAU,KAAK,CAAC,IAAI,eAAe,MAAM,CAAC,IAAI,qCAAqC;gBACjF,8EAA8E,CACjF,CAAC;YACF,OAAO;QACT,CAAC;QACA,KAAK,CAAC,MAAM,CAAkC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5D,CAAC,CAAC;IAEF,MAAM,GAAG,GAAG,CAAC,QAAoD,EAAQ,EAAE;QACzE,IAAI,QAAQ,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,iCAAiC;QAC5F,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;YAC3B,IAAI,GAAG,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,KAAuB,CAAC,CAAC;gBACrC,SAAS;YACX,CAAC;YACD,WAAW,KAAK,eAAe,CAC7B,OAAO,EACP,CAAC,GAAG,EAAE,EAAE;gBACN,wEAAwE;gBACxE,IAAI,GAAG,CAAC,IAAI,KAAK,gBAAgB;oBAAE,QAAQ,CAAC,IAAI,CAAC,GAAQ,CAAC,CAAC;YAC7D,CAAC,EACD,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CACxE,CAAC;YACF,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAwB;QACpC,KAAK;QACL,IAAI,KAAK;YACP,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,EAAE,EAAE;QACX,IAAI,CAAC,GAAG;YACN,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,OAAO,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,IAAI,gCAAgC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC;gBAC/E,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,KAAK,CAAC;YACnB,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;YACvD,KAAK,GAAG,IAAI,CAAC;YACb,IAAI,gBAAgB;gBAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;gBAAE,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;oBAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACxF,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChB,CAAC;QACD,SAAS,CAAC,QAAQ;YAChB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO;YACL,QAAQ,GAAG,IAAI,CAAC;YAChB,WAAW,EAAE,OAAO,EAAE,CAAC;YACvB,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;QACD,SAAS,CAAC,QAAQ;YAChB,KAAK,GAAG,QAAQ,CAAC;QACnB,CAAC;KACF,CAAC;IAEF,4FAA4F;IAC5F,2FAA2F;IAC3F,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,YAAY,CAAC,CAAC;SACxC,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAC9B,UAAU,CAAC,GAAG,EAAE;YACd,GAAG,CAAC,YAAY,CAAC,CAAC;QACpB,CAAC,EAAE,CAAC,CAAC,CAAC;IACR,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,IAAI,EAAE,mBAAmB;IACzB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;IACpB,gFAAgF;IAChF,KAAK,EAAE,CAAC,IAAe,EAAE,KAAc,EAAE,EAAE;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,KAAuB,CAAC,CAAC;IAC9C,CAAC;CACO,CAAC;AAQX,8FAA8F;AAC9F,gGAAgG;AAChG,gGAAgG;AAChG,MAAM,UAAU,IAAI,CAAsB,KAAkB,EAAE,GAAe;IAC3E,MAAM,KAAK,GAAmB,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IAC7C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;AACnE,CAAC;AAWD,mFAAmF;AACnF,MAAM,UAAU,OAAO,CACrB,KAAkB,EAClB,GAAiB;IAEjB,oFAAoF;IACpF,OAAO,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAU,EAAE,IAAI,EAAE,GAAG,CAAC,IAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/F,CAAC","sourcesContent":["// Shared state: stores as \"props from the side\" (docs/design-docs/0013-shared-state.md).\n// A store is MVI without a view: init + pure update, commands run by its own interpreter.\nimport type { StandardSchemaV1 } from '@standard-schema/spec';\nimport { DEVTOOLS_ENABLED, devCommands, devStore } from '#devtools';\nimport {\n splitNext,\n type AnyDriver,\n type Command,\n type DriverOverrides,\n type Next,\n} from './command.js';\nimport { features, type LocalHost } from './features.js';\nimport { makeInterpreter, type Interpreter } from './internal/interpreter.js';\nimport { bindStores } from './store-binding.js';\nimport type { IntentRejected, Tagged } from './types.js';\n\n/** No DOM means a server render: store commands never run there (ADR 0012). */\nconst onServer = typeof document === 'undefined';\n\ntype Variant<M extends Tagged, K extends M['_tag']> = Extract<M, { readonly _tag: K }>;\n\n/** One pure reducer per message tag, like a component's `update` but without context. */\nexport type StoreUpdate<S, M extends Tagged> = {\n readonly [K in M['_tag']]: (state: S, msg: Variant<M, K>) => Next<S, M>;\n};\n\nexport interface StoreSpec<S, M extends Tagged> {\n /** Initial state (and optional commands, e.g. load a saved cart). */\n readonly init: () => Next<S, M>;\n readonly update: StoreUpdate<S, M>;\n /** Driver substitutions by name for this store's commands. */\n readonly drivers?: DriverOverrides;\n /**\n * Validates the server's page seed before the client uses it (any synchronous Standard\n * Schema). An invalid seed is reported with console.error and the store starts from `init`.\n */\n readonly schema?: StandardSchemaV1<unknown, S>;\n}\n\n/** The result of checking a seed against a store's schema. */\nexport type SeedCheck<S> =\n | { readonly ok: true; readonly state: S }\n | { readonly ok: false; readonly issues: readonly string[] };\n\n/** What `ctx.read()` accepts: anything that names a store and carries its state type. */\nexport interface StoreRef<S> {\n readonly name: string;\n /** Type-only marker for the state type. Never set at runtime. */\n readonly stateType?: (state: S) => S;\n}\n\n/** A live store: one per page on the client, one per request on the server, one per test. */\nexport interface StoreInstance<S, M extends Tagged> {\n readonly store: Store<S, M>;\n readonly state: S;\n /** Feeds a message through the store's `update` and notifies subscribers on change. */\n send(msg: M): void;\n /** Called after every state change. Returns an unsubscribe function. */\n subscribe(listener: (state: S, prev: S) => void): () => void;\n /** Per-instance driver substitutions (test fakes). Checked before the spec's. */\n drivers: DriverOverrides;\n /** Stops running commands and drops subscribers. */\n dispose(): void;\n /** Set by the registry that holds the instance (store-to-store `send`). */\n bindScope(resolve: StoreResolver): void;\n}\n\nexport interface Store<S, M extends Tagged> extends StoreRef<S> {\n readonly spec: StoreSpec<S, M>;\n /**\n * A fresh, independent instance. `initial` skips `init`'s state (a server seed, a test\n * fixture); `init`'s commands still run on the client.\n */\n instance(initial?: S): StoreInstance<S, M>;\n /** Checks a seed with `spec.schema` (ok as-is without one). */\n checkSeed(seed: unknown): SeedCheck<S>;\n /** Type-only marker for the message type. Never set at runtime. */\n readonly messageType?: (msg: M) => M;\n}\n\n/** Any store, with its types erased (for declarations and registries). */\nexport interface AnyStore {\n readonly name: string;\n instance(initial?: never): AnyStoreInstance;\n checkSeed(seed: unknown): SeedCheck<unknown>;\n}\n\n/** Any store instance, with its types erased. */\nexport interface AnyStoreInstance {\n readonly store: AnyStore;\n readonly state: unknown;\n send(msg: never): void;\n subscribe(listener: (state: unknown, prev: unknown) => void): () => void;\n dispose(): void;\n /**\n * Called by the registry that holds this instance, so the store's `send(other, msg)`\n * commands reach `other`'s instance in the same scope (gyral-czi.20).\n */\n bindScope?(resolve: StoreResolver): void;\n}\n\n/** Finds a store's instance in a scope (a registry). */\nexport type StoreResolver = (store: AnyStore) => AnyStoreInstance;\n\n/** Store instances substituted by store name (tests, islands). */\nexport type StoreOverrides = Readonly<Record<string, AnyStoreInstance>>;\n\n/** Defines a store. `name` keys it in registries, seeds and overrides, so keep it unique. */\nexport function defineStore<S, M extends Tagged>(name: string, spec: StoreSpec<S, M>): Store<S, M> {\n features.stores = bindStores; // components can use stores now (features.ts)\n const store: Store<S, M> = {\n name,\n spec,\n instance: (initial?: S) => createInstance(store, initial),\n checkSeed: (seed) => checkSeed(spec.schema, seed),\n };\n return store;\n}\n\nconst issuePath = (issue: StandardSchemaV1.Issue): string =>\n (issue.path ?? []).map((s) => String(typeof s === 'object' ? s.key : s)).join('.');\n\nfunction checkSeed<S>(\n schema: StandardSchemaV1<unknown, S> | undefined,\n seed: unknown,\n): SeedCheck<S> {\n // Sound without a schema: the seed is this store's state, serialized by the server.\n if (schema === undefined) return { ok: true, state: seed as S };\n const result = schema['~standard'].validate(seed);\n if (result instanceof Promise) {\n return { ok: false, issues: ['the store schema is async; seeds need a synchronous schema'] };\n }\n if (result.issues === undefined) return { ok: true, state: result.value };\n return {\n ok: false,\n issues: result.issues.map((i) => `${issuePath(i) || '(root)'}: ${i.message}`),\n };\n}\n\nfunction createInstance<S, M extends Tagged>(\n store: Store<S, M>,\n initial: S | undefined,\n): StoreInstance<S, M> {\n // Sound: send() only calls the reducer whose key equals msg._tag.\n const reducers = store.spec.update as unknown as Readonly<\n Record<string, ((state: S, msg: M) => Next<S, M>) | undefined>\n >;\n const listeners = new Set<(state: S, prev: S) => void>();\n let interpreter: Interpreter<M | IntentRejected> | undefined;\n let disposed = false;\n\n const [initState, initCommands] = splitNext(store.spec.init());\n let state = initial === undefined ? initState : initial;\n\n const resolve = (driver: AnyDriver): AnyDriver =>\n instance.drivers[driver.name] ?? store.spec.drivers?.[driver.name] ?? driver;\n\n let scope: StoreResolver | undefined;\n\n // Store-to-store writes: delivered synchronously to the other store's instance in scope.\n const deliver = ({ store: target, msg }: StoreSendInput): void => {\n if (scope === undefined) {\n console.warn(\n `store \"${store.name}\" sends to \"${target.name}\" but is not held by a store scope ` +\n '(a registry, <gyral-stores> or the page default), so the message is dropped.',\n );\n return;\n }\n (scope(target) as { send(msg: unknown): void }).send(msg);\n };\n\n const run = (commands: ReadonlyArray<Command<M | IntentRejected>>): void => {\n if (onServer || disposed || commands.length === 0) return; // never on the server (ADR 0012)\n for (const cmd of commands) {\n if (cmd.driver === STORE_SEND) {\n deliver(cmd.input as StoreSendInput);\n continue;\n }\n interpreter ??= makeInterpreter<M | IntentRejected>(\n resolve,\n (msg) => {\n // Stores have no IntentRejected reducer; only their own messages apply.\n if (msg._tag !== 'IntentRejected') instance.send(msg as M);\n },\n DEVTOOLS_ENABLED ? devCommands(() => `store:${store.name}`) : undefined,\n );\n interpreter.run(cmd);\n }\n };\n\n const instance: StoreInstance<S, M> = {\n store,\n get state() {\n return state;\n },\n drivers: {},\n send(msg) {\n const reducer = reducers[msg._tag];\n if (reducer === undefined) {\n console.warn(`store \"${store.name}\" has no update for message \"${msg._tag}\".`);\n return;\n }\n const prev = state;\n const [next, commands] = splitNext(reducer(prev, msg));\n state = next;\n if (DEVTOOLS_ENABLED) devStore(store.name, msg, prev, next);\n if (!Object.is(next, prev)) for (const listener of [...listeners]) listener(next, prev);\n run(commands);\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n dispose() {\n disposed = true;\n interpreter?.dispose();\n listeners.clear();\n },\n bindScope(resolver) {\n scope = resolver;\n },\n };\n\n // A seeded instance (hydration) starts init's commands after the hydrating renders flushed,\n // so a fast driver can't change state under them (same reason as ADR 0012 for components).\n if (initial === undefined) run(initCommands);\n else if (initCommands.length > 0)\n setTimeout(() => {\n run(initCommands);\n }, 0);\n return instance;\n}\n\n/** Marker driver for `send()`: `define()` delivers these to the resolved store instance. */\nexport const STORE_SEND = {\n name: '@gyral/store-send',\n run: () => undefined,\n /** A component's send: delivered to its instance of the store (features.ts). */\n local: (host: LocalHost, input: unknown) => {\n host.stores().send(input as StoreSendInput);\n },\n} as const;\n\n/** The input of a `send()` command. */\nexport interface StoreSendInput {\n readonly store: AnyStore;\n readonly msg: Tagged;\n}\n\n/** A command that sends `msg` to the component's instance of `store`. Writes are commands. */\n// NoInfer: the store decides the message type, so a literal (`{ _tag: 'Track', … }`) is checked\n// against it instead of widening `_tag` to string (store-to-store sends have no other context).\nexport function send<S, M extends Tagged>(store: Store<S, M>, msg: NoInfer<M>): Command<never> {\n const input: StoreSendInput = { store, msg };\n return { driver: STORE_SEND, input, onSuccess: () => undefined };\n}\n\n/** Framework message: a store this component reads changed (ADR 0013). Optional reducer. */\nexport interface StoreChanged {\n readonly _tag: 'StoreChanged';\n /** The store's name. Narrow with `changed(store, msg)`. */\n readonly store: string;\n readonly state: unknown;\n readonly prev: unknown;\n}\n\n/** Narrows a `StoreChanged` message to one store's typed state, or `undefined`. */\nexport function changed<S>(\n store: StoreRef<S>,\n msg: StoreChanged,\n): { readonly state: S; readonly prev: S } | undefined {\n // Sound: the message was built from this store's instance (matched by unique name).\n return msg.store === store.name ? { state: msg.state as S, prev: msg.prev as S } : undefined;\n}\n"]}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* where a plain `<gyral-stores>` element already scopes stores and the server's shadow root
|
|
4
|
-
* (a `<slot>`) needs no JavaScript. Idempotent.
|
|
5
|
-
*/
|
|
6
|
-
export declare function defineStoresProvider(): void;
|
|
1
|
+
import { type ServerProvider } from './view/index.js';
|
|
2
|
+
export declare const storesProvider: ServerProvider;
|