@yahoo/uds-create-config 2.45.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/dist/ai-lanes.d.ts +23 -0
- package/dist/ai-lanes.js +34 -0
- package/dist/authoring/define-component.d.ts +85 -0
- package/dist/configs/CanvasConfig.d.ts +3237 -0
- package/dist/configs/CanvasConfig.js +332 -0
- package/dist/configs/build-options.d.ts +10 -0
- package/dist/configs/build-options.js +68 -0
- package/dist/configs/platform.d.ts +15 -0
- package/dist/configs/platform.js +17 -0
- package/dist/configs/react-native-system.d.ts +3026 -0
- package/dist/configs/react-native-system.js +64 -0
- package/dist/configs/system.d.ts +2771 -0
- package/dist/configs/system.js +74 -0
- package/dist/css/properties.generated.d.ts +14 -0
- package/dist/css/properties.generated.js +6260 -0
- package/dist/css/value-type.d.ts +48 -0
- package/dist/css/value-type.js +131 -0
- package/dist/css/values.d.ts +33 -0
- package/dist/css/values.js +116 -0
- package/dist/entities/native/NativeModifier.d.ts +25 -0
- package/dist/entities/native/NativeModifier.js +41 -0
- package/dist/entities/native/NativeSettings.d.ts +44 -0
- package/dist/entities/native/NativeSettings.js +39 -0
- package/dist/entities/native/NativeStyleProperty.d.ts +355 -0
- package/dist/entities/native/NativeStyleProperty.js +28 -0
- package/dist/entities/native/NativeToken.d.ts +31 -0
- package/dist/entities/native/NativeToken.js +49 -0
- package/dist/entities/system/CanvasRole.d.ts +34 -0
- package/dist/entities/system/CanvasRole.js +45 -0
- package/dist/entities/system/Component.d.ts +1287 -0
- package/dist/entities/system/Component.js +1840 -0
- package/dist/entities/system/Composite.d.ts +12 -0
- package/dist/entities/system/Composite.js +26 -0
- package/dist/entities/system/Device.d.ts +11 -0
- package/dist/entities/system/Device.js +18 -0
- package/dist/entities/system/File.d.ts +72 -0
- package/dist/entities/system/File.js +136 -0
- package/dist/entities/system/Font.d.ts +48 -0
- package/dist/entities/system/Font.js +56 -0
- package/dist/entities/system/GlobalStyle.d.ts +26 -0
- package/dist/entities/system/GlobalStyle.js +54 -0
- package/dist/entities/system/Guidance.d.ts +85 -0
- package/dist/entities/system/Guidance.js +59 -0
- package/dist/entities/system/GuidanceStyle.d.ts +14 -0
- package/dist/entities/system/GuidanceStyle.js +8 -0
- package/dist/entities/system/Icon.d.ts +46 -0
- package/dist/entities/system/Icon.js +46 -0
- package/dist/entities/system/LinkedSystem.d.ts +504 -0
- package/dist/entities/system/LinkedSystem.js +814 -0
- package/dist/entities/system/Modifier.d.ts +92 -0
- package/dist/entities/system/Modifier.js +120 -0
- package/dist/entities/system/Motion.d.ts +28 -0
- package/dist/entities/system/Motion.js +48 -0
- package/dist/entities/system/Operation.d.ts +100 -0
- package/dist/entities/system/Operation.js +236 -0
- package/dist/entities/system/Package.d.ts +42 -0
- package/dist/entities/system/Package.js +75 -0
- package/dist/entities/system/Settings.d.ts +94 -0
- package/dist/entities/system/Settings.js +166 -0
- package/dist/entities/system/StyleProperty.d.ts +135 -0
- package/dist/entities/system/StyleProperty.js +438 -0
- package/dist/entities/system/Token.d.ts +67 -0
- package/dist/entities/system/Token.js +129 -0
- package/dist/entities/system/Tool.d.ts +134 -0
- package/dist/entities/system/Tool.js +124 -0
- package/dist/entities/system/color.d.ts +64 -0
- package/dist/entities/system/color.js +123 -0
- package/dist/entities/system/component-module.d.ts +63 -0
- package/dist/entities/system/component-module.js +258 -0
- package/dist/entities/system/copy-plan.d.ts +98 -0
- package/dist/entities/system/copy-plan.js +451 -0
- package/dist/entities/system/defineComponent.d.ts +428 -0
- package/dist/entities/system/defineComponent.js +337 -0
- package/dist/entities/system/element.d.ts +101 -0
- package/dist/entities/system/element.js +171 -0
- package/dist/entities/system/icon-library.d.ts +64 -0
- package/dist/entities/system/icon-library.js +112 -0
- package/dist/entities/system/icon-metadata.d.ts +72 -0
- package/dist/entities/system/icon-metadata.js +111 -0
- package/dist/entities/system/link-declaration.d.ts +27 -0
- package/dist/entities/system/link-declaration.js +64 -0
- package/dist/entities/system/link-plan.d.ts +160 -0
- package/dist/entities/system/link-plan.js +770 -0
- package/dist/entities/system/runtime-modules.d.ts +25 -0
- package/dist/entities/system/runtime-modules.js +38 -0
- package/dist/entities/system/style-bag.d.ts +20 -0
- package/dist/entities/system/style-bag.js +246 -0
- package/dist/entities/system/token-index.d.ts +89 -0
- package/dist/entities/system/token-index.js +112 -0
- package/dist/framework/Config.d.ts +2472 -0
- package/dist/framework/Config.js +5023 -0
- package/dist/framework/Entity.d.ts +127 -0
- package/dist/framework/Entity.js +125 -0
- package/dist/framework/authoring.d.ts +24 -0
- package/dist/framework/authoring.js +7 -0
- package/dist/framework/change-hooks.d.ts +119 -0
- package/dist/framework/change-hooks.js +131 -0
- package/dist/framework/changes.d.ts +88 -0
- package/dist/framework/changes.js +246 -0
- package/dist/framework/class-names.d.ts +260 -0
- package/dist/framework/class-names.js +630 -0
- package/dist/framework/coalesce.d.ts +25 -0
- package/dist/framework/coalesce.js +60 -0
- package/dist/framework/config-op.d.ts +76 -0
- package/dist/framework/config-op.js +43 -0
- package/dist/framework/config-operation-kind.d.ts +14 -0
- package/dist/framework/config-operation-kind.js +14 -0
- package/dist/framework/config-ops.d.ts +34 -0
- package/dist/framework/config-ops.js +187 -0
- package/dist/framework/defineConfig.d.ts +196 -0
- package/dist/framework/defineConfig.js +100 -0
- package/dist/framework/defineDerivedEntity.d.ts +74 -0
- package/dist/framework/defineDerivedEntity.js +83 -0
- package/dist/framework/defineEntity.d.ts +318 -0
- package/dist/framework/defineEntity.js +183 -0
- package/dist/framework/defineSubEntity.d.ts +148 -0
- package/dist/framework/defineSubEntity.js +121 -0
- package/dist/framework/derived-mutations.d.ts +20 -0
- package/dist/framework/derived-mutations.js +38 -0
- package/dist/framework/entity-search.d.ts +29 -0
- package/dist/framework/entity-search.js +117 -0
- package/dist/framework/inline-sources.d.ts +49 -0
- package/dist/framework/inline-sources.js +388 -0
- package/dist/framework/layer-styles.d.ts +25 -0
- package/dist/framework/layer-styles.js +49 -0
- package/dist/framework/memo.d.ts +48 -0
- package/dist/framework/memo.js +84 -0
- package/dist/framework/overlay.d.ts +49 -0
- package/dist/framework/overlay.js +17 -0
- package/dist/framework/plan.d.ts +49 -0
- package/dist/framework/plan.js +73 -0
- package/dist/framework/projections.d.ts +480 -0
- package/dist/framework/projections.js +974 -0
- package/dist/framework/prop-surface.d.ts +132 -0
- package/dist/framework/prop-surface.js +278 -0
- package/dist/framework/ref-graph.d.ts +71 -0
- package/dist/framework/ref-graph.js +158 -0
- package/dist/framework/ref-integrity.d.ts +55 -0
- package/dist/framework/ref-integrity.js +171 -0
- package/dist/framework/registered.d.ts +95 -0
- package/dist/framework/registry-dir.d.ts +42 -0
- package/dist/framework/registry-dir.js +48 -0
- package/dist/framework/registry.d.ts +300 -0
- package/dist/framework/registry.js +104 -0
- package/dist/framework/rejection.d.ts +58 -0
- package/dist/framework/rejection.js +111 -0
- package/dist/framework/rename-inference.d.ts +26 -0
- package/dist/framework/rename-inference.js +50 -0
- package/dist/framework/render-spec.d.ts +296 -0
- package/dist/framework/render-spec.js +809 -0
- package/dist/framework/schema-version.d.ts +140 -0
- package/dist/framework/schema-version.js +214 -0
- package/dist/framework/schemas.d.ts +109 -0
- package/dist/framework/schemas.js +615 -0
- package/dist/framework/session.d.ts +196 -0
- package/dist/framework/session.js +367 -0
- package/dist/framework/signature.d.ts +15 -0
- package/dist/framework/signature.js +83 -0
- package/dist/framework/snapshot.d.ts +115 -0
- package/dist/framework/snapshot.js +277 -0
- package/dist/framework/source-integrity.d.ts +226 -0
- package/dist/framework/source-integrity.js +478 -0
- package/dist/framework/sources.d.ts +211 -0
- package/dist/framework/sources.js +89 -0
- package/dist/framework/utils/compiled-schema.d.ts +23 -0
- package/dist/framework/utils/compiled-schema.js +46 -0
- package/dist/framework/utils/content-key.d.ts +15 -0
- package/dist/framework/utils/content-key.js +33 -0
- package/dist/framework/utils/enumerated.d.ts +13 -0
- package/dist/framework/utils/enumerated.js +19 -0
- package/dist/framework/utils/field-path.d.ts +95 -0
- package/dist/framework/utils/field-path.js +136 -0
- package/dist/framework/utils/field-schema.d.ts +55 -0
- package/dist/framework/utils/field-schema.js +183 -0
- package/dist/framework/utils/group.d.ts +37 -0
- package/dist/framework/utils/group.js +13 -0
- package/dist/framework/utils/package-path.d.ts +37 -0
- package/dist/framework/utils/package-path.js +61 -0
- package/dist/framework/utils/refs.d.ts +194 -0
- package/dist/framework/utils/refs.js +403 -0
- package/dist/framework/validate-spec.d.ts +27 -0
- package/dist/framework/validate-spec.js +45 -0
- package/dist/framework/value-domain.d.ts +26 -0
- package/dist/framework/value-domain.js +202 -0
- package/dist/framework/views-facade.d.ts +15 -0
- package/dist/framework/views-facade.js +19 -0
- package/dist/index.d.ts +90 -42
- package/dist/index.js +86 -37
- package/dist/migrations/2.0.0/raw-css.d.ts +11 -0
- package/dist/migrations/2.0.0/raw-css.js +72 -0
- package/dist/migrations/2.0.0/v1-artifact.d.ts +1559 -0
- package/dist/migrations/2.0.0/v1-artifact.js +1018 -0
- package/dist/migrations/20260908171448_settings_as_kind.d.ts +22 -0
- package/dist/migrations/20260908171448_settings_as_kind.js +89 -0
- package/dist/migrations/20260908194245_visible_state_pointer.d.ts +19 -0
- package/dist/migrations/20260908194245_visible_state_pointer.js +115 -0
- package/dist/migrations/20260909183001_icon_knockout_variants.d.ts +18 -0
- package/dist/migrations/20260909183001_icon_knockout_variants.js +8 -0
- package/dist/migrations/20260910212453_linked_system_link_ops.d.ts +13 -0
- package/dist/migrations/20260910212453_linked_system_link_ops.js +8 -0
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.js +2 -0
- package/dist/react-native/style-keys.generated.d.ts +10 -0
- package/dist/react-native/style-keys.generated.js +167 -0
- package/dist/renderer/RendererErrorBoundary.d.ts +32 -5
- package/dist/renderer/RendererErrorBoundary.js +59 -10
- package/dist/renderer/UdsRenderer.d.ts +11 -10
- package/dist/renderer/UdsRenderer.js +1 -1
- package/dist/renderer/assetEntries.d.ts +37 -0
- package/dist/renderer/assetEntries.js +39 -0
- package/dist/renderer/assetRenderable.d.ts +14 -6
- package/dist/renderer/assetRenderable.js +8 -2
- package/dist/renderer/componentEntry.d.ts +58 -0
- package/dist/renderer/componentEntry.js +71 -0
- package/dist/renderer/icon-library.d.ts +20 -0
- package/dist/renderer/index.d.ts +7 -7
- package/dist/renderer/index.js +5 -5
- package/dist/renderer/primitives/SlotRenderer.d.ts +1 -1
- package/dist/renderer/primitives/SlotRenderer.js +1 -1
- package/dist/renderer/runtimeComponent.d.ts +33 -0
- package/dist/renderer/runtimeComponent.js +113 -0
- package/dist/renderer/spec-content.d.ts +43 -0
- package/dist/renderer/spec-content.js +95 -0
- package/dist/renderer/wrapRegistry.d.ts +22 -37
- package/dist/renderer/wrapRegistry.js +10 -20
- package/dist/renderer/wrappers/event-bridge.js +6 -5
- package/dist/renderer/wrappers/hex-normalize.js +6 -5
- package/dist/renderer/wrappers/html-aliases.d.ts +7 -2
- package/dist/renderer/wrappers/html-aliases.js +36 -19
- package/dist/renderer/wrappers/inline-styles.js +7 -6
- package/dist/spec/asset-jsx.d.ts +18 -5
- package/dist/spec/asset-jsx.js +27 -16
- package/dist/spec/collapse-text-labels.d.ts +5 -15
- package/dist/spec/collapse-text-labels.js +26 -19
- package/dist/spec/empty-node-slots.d.ts +56 -34
- package/dist/spec/empty-node-slots.js +87 -47
- package/dist/spec/index.d.ts +21 -10
- package/dist/spec/index.js +21 -10
- package/dist/spec/jsxToSpec.d.ts +14 -9
- package/dist/spec/jsxToSpec.js +20 -3
- package/dist/spec/preview-elements.d.ts +63 -0
- package/dist/spec/preview-elements.js +89 -0
- package/dist/spec/rewrite-refs.d.ts +58 -0
- package/dist/spec/rewrite-refs.js +250 -0
- package/dist/spec/specToJsx.js +18 -29
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +39 -52
- package/dist/AssetGroup.d.ts +0 -77
- package/dist/AssetGroup.js +0 -125
- package/dist/Component.d.ts +0 -333
- package/dist/Component.js +0 -1061
- package/dist/ComponentGroup.d.ts +0 -22
- package/dist/ComponentGroup.js +0 -51
- package/dist/CompositeStyle.d.ts +0 -30
- package/dist/CompositeStyle.js +0 -59
- package/dist/Config.d.ts +0 -528
- package/dist/Config.js +0 -1712
- package/dist/Mode.d.ts +0 -41
- package/dist/Mode.js +0 -81
- package/dist/Modifier.d.ts +0 -51
- package/dist/Modifier.js +0 -97
- package/dist/MotionDef.d.ts +0 -81
- package/dist/MotionDef.js +0 -212
- package/dist/Props.d.ts +0 -332
- package/dist/Props.js +0 -35
- package/dist/Provider.d.ts +0 -20
- package/dist/Provider.js +0 -14
- package/dist/StyleProp.d.ts +0 -145
- package/dist/StyleProp.js +0 -239
- package/dist/Token.d.ts +0 -76
- package/dist/Token.js +0 -133
- package/dist/TokenGroup.d.ts +0 -33
- package/dist/TokenGroup.js +0 -69
- package/dist/asset-kind.d.ts +0 -55
- package/dist/asset-kind.js +0 -29
- package/dist/asset-value.d.ts +0 -23
- package/dist/asset-value.js +0 -40
- package/dist/brands.d.ts +0 -30
- package/dist/brands.js +0 -20
- package/dist/captureCallerPath.d.ts +0 -48
- package/dist/captureCallerPath.js +0 -95
- package/dist/colorExpressions.d.ts +0 -131
- package/dist/colorExpressions.js +0 -148
- package/dist/component-referrers.d.ts +0 -30
- package/dist/component-referrers.js +0 -40
- package/dist/config-diagnostics.d.ts +0 -21
- package/dist/config-diagnostics.js +0 -12
- package/dist/defineAssetGroup.d.ts +0 -200
- package/dist/defineAssetGroup.js +0 -323
- package/dist/defineProvider.d.ts +0 -29
- package/dist/defineProvider.js +0 -60
- package/dist/element-marker.d.ts +0 -63
- package/dist/element-marker.js +0 -113
- package/dist/entity-utils.d.ts +0 -56
- package/dist/entity-utils.js +0 -105
- package/dist/extension.d.ts +0 -55
- package/dist/extension.js +0 -214
- package/dist/factories.d.ts +0 -863
- package/dist/factories.js +0 -475
- package/dist/font-face.d.ts +0 -25
- package/dist/font-face.js +0 -72
- package/dist/foreign-component-name.d.ts +0 -21
- package/dist/foreign-component-name.js +0 -42
- package/dist/interpolate.d.ts +0 -20
- package/dist/interpolate.js +0 -10
- package/dist/jsx/__fixtures__/cross-component-preview.d.ts +0 -3
- package/dist/jsx/__fixtures__/cross-component-preview.js +0 -15
- package/dist/jsx/jsx-dev-runtime.d.ts +0 -15
- package/dist/jsx/jsx-dev-runtime.js +0 -11
- package/dist/jsx/jsx-runtime.d.ts +0 -48
- package/dist/jsx/jsx-runtime.js +0 -305
- package/dist/linked-system-pins.d.ts +0 -30
- package/dist/linked-system-pins.js +0 -45
- package/dist/linkedNodePrefixes.d.ts +0 -73
- package/dist/linkedNodePrefixes.js +0 -99
- package/dist/markers.d.ts +0 -235
- package/dist/markers.js +0 -70
- package/dist/motion-constants.d.ts +0 -37
- package/dist/motion-constants.js +0 -61
- package/dist/native/NativeConfig.d.ts +0 -56
- package/dist/native/NativeConfig.js +0 -162
- package/dist/native/assets.d.ts +0 -20
- package/dist/native/assets.js +0 -17
- package/dist/native/components.d.ts +0 -139
- package/dist/native/components.js +0 -67
- package/dist/native/index.d.ts +0 -18
- package/dist/native/index.js +0 -17
- package/dist/native/modes.d.ts +0 -10
- package/dist/native/modes.js +0 -11
- package/dist/native/types.d.ts +0 -95
- package/dist/native/types.js +0 -0
- package/dist/native/validation.d.ts +0 -8
- package/dist/native/validation.js +0 -885
- package/dist/native/values.d.ts +0 -7
- package/dist/native/values.js +0 -15
- package/dist/native.d.ts +0 -12
- package/dist/native.js +0 -10
- package/dist/refs.d.ts +0 -204
- package/dist/refs.js +0 -129
- package/dist/registry-aliases.d.ts +0 -33
- package/dist/registry-aliases.js +0 -35
- package/dist/registry-dir.d.ts +0 -22
- package/dist/registry-dir.js +0 -10
- package/dist/renderer/makeRegistry.d.ts +0 -34
- package/dist/renderer/makeRegistry.js +0 -52
- package/dist/renderer/makeUdsRenderer.d.ts +0 -13
- package/dist/renderer/makeUdsRenderer.js +0 -51
- package/dist/renderer/wrappers/component-slots.d.ts +0 -41
- package/dist/renderer/wrappers/component-slots.js +0 -66
- package/dist/renderer/wrappers/slot-resolution.d.ts +0 -25
- package/dist/renderer/wrappers/slot-resolution.js +0 -68
- package/dist/renderer/wrappers/void-elements.d.ts +0 -37
- package/dist/renderer/wrappers/void-elements.js +0 -40
- package/dist/spec/layer-props.d.ts +0 -52
- package/dist/spec/layer-props.js +0 -149
- package/dist/spec/preview-controls.d.ts +0 -44
- package/dist/spec/preview-controls.js +0 -139
- package/dist/spec/slot-refs.d.ts +0 -39
- package/dist/spec/slot-refs.js +0 -56
- package/dist/token-override-rows.d.ts +0 -68
- package/dist/token-override-rows.js +0 -233
- package/dist/tokenValueType.d.ts +0 -34
- package/dist/tokenValueType.js +0 -138
- package/dist/types/css-properties.d.ts +0 -233
- package/dist/types/css-properties.js +0 -14
- package/dist/types/css-property-keywords.d.ts +0 -156
- package/dist/types/css-property-keywords.js +0 -616
- package/dist/types/css-values.d.ts +0 -63
- package/dist/types/css-values.js +0 -16
- package/dist/types.d.ts +0 -1138
- package/dist/types.js +0 -12
- package/dist/units.d.ts +0 -14
- package/dist/units.js +0 -16
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/index.js +0 -4
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//#region src/framework/Entity.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* One flat, path-keyed entity — an item or a group node. The base owns hydration and
|
|
4
|
+
* serialization from the kind's declared field names, so a kind adds no per-entity ctor
|
|
5
|
+
* or `toJSON`. Identity IS the qualified path; the group is simply its prefix.
|
|
6
|
+
*/
|
|
7
|
+
declare class Entity {
|
|
8
|
+
#private;
|
|
9
|
+
readonly path: string;
|
|
10
|
+
private readonly fieldKeys;
|
|
11
|
+
/**
|
|
12
|
+
* VIRTUAL fields — read like any other, and deliberately absent from {@link toJSON}.
|
|
13
|
+
*
|
|
14
|
+
* The split is the whole point of having them separately. A computed field is a function of the
|
|
15
|
+
* entity and its config, so persisting one writes a derivation into the artifact: it goes stale the
|
|
16
|
+
* moment either input changes, and the stale copy wins on the next hydrate. Provenance is the
|
|
17
|
+
* clearest case — whether an item borrows is read off the source qualifier in its own fields, so a
|
|
18
|
+
* stored answer could contradict the value sitting next to it.
|
|
19
|
+
*/
|
|
20
|
+
private readonly computedKeys;
|
|
21
|
+
readonly label?: string;
|
|
22
|
+
readonly description?: string;
|
|
23
|
+
/** Where a copied entity was taken from, as `<path>@<slug>`; `undefined` for one authored here. */
|
|
24
|
+
readonly copiedFrom?: string;
|
|
25
|
+
/** Kept and resolving, but not offered by authoring surfaces. This entity's own flag only; the
|
|
26
|
+
* answer that also reads its folders and its pin is `Config.hidden`. */
|
|
27
|
+
readonly hidden?: boolean;
|
|
28
|
+
/** Withdrawn from new work: hidden, and every existing use flagged. This entity's own flag only;
|
|
29
|
+
* the answer that also reads its folders is `Config.deprecated`. */
|
|
30
|
+
readonly deprecated?: boolean;
|
|
31
|
+
/** How to use this entity, as prose for a model. Collected into the guidance document per kind. */
|
|
32
|
+
readonly guidance?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The DEPENDENCIES this item's own fields point through, deduped and sorted — the framework installs
|
|
35
|
+
* this computed on every kind, so it is declared here rather than per kind.
|
|
36
|
+
*
|
|
37
|
+
* `declare` because the value arrives through the computed pass like any other field; this only says
|
|
38
|
+
* that every entity has it, which is what lets a kind-agnostic reader (`config.resolve(kind, path)`)
|
|
39
|
+
* ask without a cast.
|
|
40
|
+
*
|
|
41
|
+
* Empty for an item the config owns. Reads never need it — `resolveFieldValue` chases a local and a
|
|
42
|
+
* sourced ref identically — so a caller asking whether this is non-empty is a WRITER, gating an edit
|
|
43
|
+
* that would turn someone else's value into a copy of itself. A caller wanting to NAME where it comes
|
|
44
|
+
* from asks {@link source}.
|
|
45
|
+
*/
|
|
46
|
+
readonly sources: readonly string[];
|
|
47
|
+
/**
|
|
48
|
+
* The dependency this item is borrowed FROM — `undefined` for one this config authored, and that
|
|
49
|
+
* includes one whose own fields point through a link. The framework installs this computed on every
|
|
50
|
+
* kind too, so it is declared here beside {@link sources}.
|
|
51
|
+
*
|
|
52
|
+
* The pair is easy to conflate and gates opposite things. `sources` is about the VALUE: it says a
|
|
53
|
+
* badge should name another system, and it is set for `{ value: token:accent@ds }` — a pointer this
|
|
54
|
+
* config wrote, into someone else's token. This is about the ENTITY: it is set only when the item is
|
|
55
|
+
* stored AS a pointer (`Token.ref('accent@ds').extend({ … })`), which makes the item the source's
|
|
56
|
+
* with our fields laid over it.
|
|
57
|
+
*
|
|
58
|
+
* So a WRITER asks this one. Gating on `sources` locks the first case, where nothing but our own
|
|
59
|
+
* artifact changes when the ref is retargeted — which is how repointing a local alias came to be
|
|
60
|
+
* impossible in the token rail. See `borrowedSlug`.
|
|
61
|
+
*/
|
|
62
|
+
readonly borrowedFrom: string | undefined;
|
|
63
|
+
constructor(path: string, raw: Record<string, unknown>, fieldKeys: readonly string[],
|
|
64
|
+
/**
|
|
65
|
+
* VIRTUAL fields — read like any other, and deliberately absent from {@link toJSON}.
|
|
66
|
+
*
|
|
67
|
+
* The split is the whole point of having them separately. A computed field is a function of the
|
|
68
|
+
* entity and its config, so persisting one writes a derivation into the artifact: it goes stale the
|
|
69
|
+
* moment either input changes, and the stale copy wins on the next hydrate. Provenance is the
|
|
70
|
+
* clearest case — whether an item borrows is read off the source qualifier in its own fields, so a
|
|
71
|
+
* stored answer could contradict the value sitting next to it.
|
|
72
|
+
*/
|
|
73
|
+
computedKeys?: readonly string[]);
|
|
74
|
+
/**
|
|
75
|
+
* The ONE source this item comes from, or `undefined` when it comes from none — what a surface names
|
|
76
|
+
* when it says where something came from.
|
|
77
|
+
*
|
|
78
|
+
* Folds BOTH ways an item can involve another system, because a provenance surface does not care
|
|
79
|
+
* which: an item borrowed and locally extended came from its source, and an item we authored whose
|
|
80
|
+
* value points into a source draws from it. Reading only {@link sources} left a borrowed COMPONENT
|
|
81
|
+
* unbadged, because resolving an extension merges the source's own literal fields in and there is no
|
|
82
|
+
* qualified ref left to find.
|
|
83
|
+
*
|
|
84
|
+
* `undefined` when the answer is not unanimous, which is the case worth stating: an item involving two
|
|
85
|
+
* systems has no single answer, and naming either would label the other wrongly. That is the same rule
|
|
86
|
+
* a group follows — see `Config.groupSource`.
|
|
87
|
+
*
|
|
88
|
+
* NOT the write gate. Whose the item is has one answer and it is {@link borrowedFrom}.
|
|
89
|
+
*
|
|
90
|
+
* A getter rather than a computed field so there is one walk and one stored answer; this is a read of
|
|
91
|
+
* it.
|
|
92
|
+
*/
|
|
93
|
+
get source(): string | undefined;
|
|
94
|
+
/** The pin a copied entity came through — the `@<slug>` half of {@link copiedFrom}. */
|
|
95
|
+
get copiedThrough(): string | undefined;
|
|
96
|
+
/** The path a copied entity was taken from, as the source spells it — the half of {@link copiedFrom}
|
|
97
|
+
* before the `@`. */
|
|
98
|
+
get copiedPath(): string | undefined;
|
|
99
|
+
/** Identity is the whole qualified path (`color/brand`). */
|
|
100
|
+
get identity(): string;
|
|
101
|
+
/** The emergent group — everything before the last segment (`color/brand` → `color`);
|
|
102
|
+
* `undefined` for a top-level item. Membership is never stored, only derived. */
|
|
103
|
+
get group(): string | undefined;
|
|
104
|
+
/** The bare last segment (`color/brand` → `brand`) — what a UI renders. */
|
|
105
|
+
get leaf(): string;
|
|
106
|
+
/**
|
|
107
|
+
* What a UI calls this item: its authored `label` field, else the bare
|
|
108
|
+
* {@link leaf}. Never identity — a label is metadata and two items may share
|
|
109
|
+
* one — and never a `kind:` marker, which no person should read.
|
|
110
|
+
*/
|
|
111
|
+
get displayLabel(): string;
|
|
112
|
+
/**
|
|
113
|
+
* This item's declared fields as a plain bag.
|
|
114
|
+
*
|
|
115
|
+
* For a reader whose KIND is only known at runtime — a prompt catalogue walking every kind, an op
|
|
116
|
+
* form rendering whatever collection its tool names. A reader that knows the kind asks the entity
|
|
117
|
+
* class instead (`Component.get(config, path).styles`), which answers typed.
|
|
118
|
+
*
|
|
119
|
+
* Deliberately not `toJSON`: serialization is for storage and the wire, and a display surface
|
|
120
|
+
* reaching for it is how a raw-shape cast gets in. (No kind declares a field called `body`; one
|
|
121
|
+
* that did would shadow this, since fields are assigned as own properties.)
|
|
122
|
+
*/
|
|
123
|
+
get body(): Record<string, unknown>;
|
|
124
|
+
toJSON(): Record<string, unknown>;
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { Entity };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
//#region src/framework/Entity.ts
|
|
2
|
+
/**
|
|
3
|
+
* One flat, path-keyed entity — an item or a group node. The base owns hydration and
|
|
4
|
+
* serialization from the kind's declared field names, so a kind adds no per-entity ctor
|
|
5
|
+
* or `toJSON`. Identity IS the qualified path; the group is simply its prefix.
|
|
6
|
+
*/
|
|
7
|
+
var Entity = class {
|
|
8
|
+
label;
|
|
9
|
+
description;
|
|
10
|
+
/** Where a copied entity was taken from, as `<path>@<slug>`; `undefined` for one authored here. */
|
|
11
|
+
copiedFrom;
|
|
12
|
+
/** Kept and resolving, but not offered by authoring surfaces. This entity's own flag only; the
|
|
13
|
+
* answer that also reads its folders and its pin is `Config.hidden`. */
|
|
14
|
+
hidden;
|
|
15
|
+
/** Withdrawn from new work: hidden, and every existing use flagged. This entity's own flag only;
|
|
16
|
+
* the answer that also reads its folders is `Config.deprecated`. */
|
|
17
|
+
deprecated;
|
|
18
|
+
/** How to use this entity, as prose for a model. Collected into the guidance document per kind. */
|
|
19
|
+
guidance;
|
|
20
|
+
constructor(path, raw, fieldKeys, computedKeys = []) {
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.fieldKeys = fieldKeys;
|
|
23
|
+
this.computedKeys = computedKeys;
|
|
24
|
+
const hydrated = {};
|
|
25
|
+
for (const key of [...fieldKeys, ...computedKeys]) if (raw[key] !== void 0) hydrated[key] = raw[key];
|
|
26
|
+
Object.assign(this, hydrated);
|
|
27
|
+
this.label = raw.label;
|
|
28
|
+
this.description = raw.description;
|
|
29
|
+
this.copiedFrom = raw.copiedFrom;
|
|
30
|
+
this.hidden = raw.hidden;
|
|
31
|
+
this.deprecated = raw.deprecated;
|
|
32
|
+
this.guidance = raw.guidance;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The ONE source this item comes from, or `undefined` when it comes from none — what a surface names
|
|
36
|
+
* when it says where something came from.
|
|
37
|
+
*
|
|
38
|
+
* Folds BOTH ways an item can involve another system, because a provenance surface does not care
|
|
39
|
+
* which: an item borrowed and locally extended came from its source, and an item we authored whose
|
|
40
|
+
* value points into a source draws from it. Reading only {@link sources} left a borrowed COMPONENT
|
|
41
|
+
* unbadged, because resolving an extension merges the source's own literal fields in and there is no
|
|
42
|
+
* qualified ref left to find.
|
|
43
|
+
*
|
|
44
|
+
* `undefined` when the answer is not unanimous, which is the case worth stating: an item involving two
|
|
45
|
+
* systems has no single answer, and naming either would label the other wrongly. That is the same rule
|
|
46
|
+
* a group follows — see `Config.groupSource`.
|
|
47
|
+
*
|
|
48
|
+
* NOT the write gate. Whose the item is has one answer and it is {@link borrowedFrom}.
|
|
49
|
+
*
|
|
50
|
+
* A getter rather than a computed field so there is one walk and one stored answer; this is a read of
|
|
51
|
+
* it.
|
|
52
|
+
*/
|
|
53
|
+
get source() {
|
|
54
|
+
if (this.borrowedFrom !== void 0) return this.borrowedFrom;
|
|
55
|
+
return this.sources.length === 1 ? this.sources[0] : void 0;
|
|
56
|
+
}
|
|
57
|
+
/** The pin a copied entity came through — the `@<slug>` half of {@link copiedFrom}. */
|
|
58
|
+
get copiedThrough() {
|
|
59
|
+
const at = this.copiedFrom?.lastIndexOf("@") ?? -1;
|
|
60
|
+
return at === -1 ? void 0 : this.copiedFrom?.slice(at + 1);
|
|
61
|
+
}
|
|
62
|
+
/** The path a copied entity was taken from, as the source spells it — the half of {@link copiedFrom}
|
|
63
|
+
* before the `@`. */
|
|
64
|
+
get copiedPath() {
|
|
65
|
+
const at = this.copiedFrom?.lastIndexOf("@") ?? -1;
|
|
66
|
+
return at === -1 ? void 0 : this.copiedFrom?.slice(0, at);
|
|
67
|
+
}
|
|
68
|
+
/** Identity is the whole qualified path (`color/brand`). */
|
|
69
|
+
get identity() {
|
|
70
|
+
return this.path;
|
|
71
|
+
}
|
|
72
|
+
/** The emergent group — everything before the last segment (`color/brand` → `color`);
|
|
73
|
+
* `undefined` for a top-level item. Membership is never stored, only derived. */
|
|
74
|
+
get group() {
|
|
75
|
+
const slash = this.path.lastIndexOf("/");
|
|
76
|
+
return slash === -1 ? void 0 : this.path.slice(0, slash);
|
|
77
|
+
}
|
|
78
|
+
/** The bare last segment (`color/brand` → `brand`) — what a UI renders. */
|
|
79
|
+
get leaf() {
|
|
80
|
+
const slash = this.path.lastIndexOf("/");
|
|
81
|
+
return slash === -1 ? this.path : this.path.slice(slash + 1);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* What a UI calls this item: its authored `label` field, else the bare
|
|
85
|
+
* {@link leaf}. Never identity — a label is metadata and two items may share
|
|
86
|
+
* one — and never a `kind:` marker, which no person should read.
|
|
87
|
+
*/
|
|
88
|
+
get displayLabel() {
|
|
89
|
+
const label = this.label?.trim();
|
|
90
|
+
return label ? label : this.leaf;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* This item's declared fields as a plain bag.
|
|
94
|
+
*
|
|
95
|
+
* For a reader whose KIND is only known at runtime — a prompt catalogue walking every kind, an op
|
|
96
|
+
* form rendering whatever collection its tool names. A reader that knows the kind asks the entity
|
|
97
|
+
* class instead (`Component.get(config, path).styles`), which answers typed.
|
|
98
|
+
*
|
|
99
|
+
* Deliberately not `toJSON`: serialization is for storage and the wire, and a display surface
|
|
100
|
+
* reaching for it is how a raw-shape cast gets in. (No kind declares a field called `body`; one
|
|
101
|
+
* that did would shadow this, since fields are assigned as own properties.)
|
|
102
|
+
*/
|
|
103
|
+
get body() {
|
|
104
|
+
return this.#bag([...this.fieldKeys, ...this.computedKeys]);
|
|
105
|
+
}
|
|
106
|
+
toJSON() {
|
|
107
|
+
return this.#bag(this.fieldKeys);
|
|
108
|
+
}
|
|
109
|
+
/** The fields named by `keys`, as a plain bag. `body` and `toJSON` differ only in which keys they
|
|
110
|
+
* ask for — the READ shape includes computeds, the WIRE shape cannot. */
|
|
111
|
+
#bag(keys) {
|
|
112
|
+
const self = this;
|
|
113
|
+
const out = {};
|
|
114
|
+
for (const key of keys) if (self[key] !== void 0) out[key] = self[key];
|
|
115
|
+
if (this.label !== void 0) out.label = this.label;
|
|
116
|
+
if (this.description !== void 0) out.description = this.description;
|
|
117
|
+
if (this.copiedFrom !== void 0) out.copiedFrom = this.copiedFrom;
|
|
118
|
+
if (this.hidden !== void 0) out.hidden = this.hidden;
|
|
119
|
+
if (this.deprecated !== void 0) out.deprecated = this.deprecated;
|
|
120
|
+
if (this.guidance !== void 0) out.guidance = this.guidance;
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
//#endregion
|
|
125
|
+
export { Entity };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/framework/authoring.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The bridge between any typed authoring surface and the live `Config`.
|
|
4
|
+
*
|
|
5
|
+
* An authoring API (like `defineComponent`) produces sugar — markers, builders, handles —
|
|
6
|
+
* that isn't the entity's stored shape. `Authored` is the one contract that lets `Config`
|
|
7
|
+
* ingest ANY such output without knowing its specifics: it exposes the target `kind`, a
|
|
8
|
+
* `toStored()` that normalizes the sugar into the entity's wire def (validated on the way
|
|
9
|
+
* in like any create), and an optional `assignPath` so a ref-handle can adopt its
|
|
10
|
+
* qualified identity at registration (enabling cross-references).
|
|
11
|
+
*
|
|
12
|
+
* New authoring APIs need only return an `Authored`; `Config.author` (see Config.ts) and
|
|
13
|
+
* the rest of the system stay unchanged.
|
|
14
|
+
*/
|
|
15
|
+
interface Authored {
|
|
16
|
+
readonly kind: string;
|
|
17
|
+
/** Normalize the authoring sugar into the entity's stored/wire def. */
|
|
18
|
+
toStored(): Record<string, unknown>;
|
|
19
|
+
/** Adopt the qualified path assigned at registration (so refs to this resolve). */
|
|
20
|
+
assignPath?(path: string): void;
|
|
21
|
+
}
|
|
22
|
+
declare function isAuthored(value: unknown): value is Authored;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { Authored, isAuthored };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/framework/authoring.ts
|
|
2
|
+
function isAuthored(value) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") return false;
|
|
4
|
+
return value !== null && "kind" in value && typeof value.toStored === "function";
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { isAuthored };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Config } from "./Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/framework/change-hooks.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The previous result, and the config it came from.
|
|
6
|
+
*
|
|
7
|
+
* `from` is what makes a cheap path expressible at all — a delta needs the state it is a delta from.
|
|
8
|
+
* A hook that keeps a partial result must keep the config that part came from with it, not accept
|
|
9
|
+
* `from` as the base: after one cheap run, `from` is the config the cheap run answered, which is no
|
|
10
|
+
* longer the state the kept part describes. (`stylesOnChange` carries a `base` in its value for
|
|
11
|
+
* exactly this reason — the overlay is always a delta from the sheet's own config, never from the
|
|
12
|
+
* previous edit.)
|
|
13
|
+
*/
|
|
14
|
+
interface LastDerived<T> {
|
|
15
|
+
readonly from: Config;
|
|
16
|
+
readonly value: T;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* When a scheduled derivation actually runs.
|
|
20
|
+
*
|
|
21
|
+
* `'idle'` is the editor answer: after the frame the edit landed on, so the click paints first.
|
|
22
|
+
* `'sync'` runs it immediately, which is what a test and a server want. A function is anything else.
|
|
23
|
+
*/
|
|
24
|
+
type ChangeSchedule = 'sync' | 'idle' | ((fire: () => void) => void);
|
|
25
|
+
/** One derived value that follows the config. */
|
|
26
|
+
interface ChangeHook<C extends Config = Config, T = unknown> {
|
|
27
|
+
/**
|
|
28
|
+
* Every kind the derivation can reach, including ones it only touches by chasing refs. A change
|
|
29
|
+
* that moves none of them is skipped, so under-declaring means a paint that never happens —
|
|
30
|
+
* declare the same list the memo inside `run` declares.
|
|
31
|
+
*/
|
|
32
|
+
readonly kinds: readonly string[];
|
|
33
|
+
/**
|
|
34
|
+
* Declared as a method rather than as a function property on purpose: method parameters are checked
|
|
35
|
+
* bivariantly, which is what lets a `ChangeHook<C, LiveStyles>` sit in a map of
|
|
36
|
+
* `ChangeHook<C, unknown>` without an `any` to paper over the variance.
|
|
37
|
+
*/
|
|
38
|
+
run(config: C, last: LastDerived<T> | undefined): T | Promise<T>;
|
|
39
|
+
/** Default `'sync'`: a caller that wants the derivation off the interaction path asks for it. */
|
|
40
|
+
readonly schedule?: ChangeSchedule;
|
|
41
|
+
}
|
|
42
|
+
/** Where the live config comes from — a session, a store, anything that can be read and watched. */
|
|
43
|
+
interface ConfigFeed<C extends Config = Config> {
|
|
44
|
+
current(): C | null;
|
|
45
|
+
subscribe(listener: () => void): () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A set of hooks, keyed by the name their result is read under.
|
|
49
|
+
*
|
|
50
|
+
* The entries are heterogeneous — each produces its own type — so the map is stated at `unknown` and
|
|
51
|
+
* the real type is recovered at the read by {@link DerivedOf}, off the literal type of what the caller
|
|
52
|
+
* passed. That is why `run` is a method: it makes this widening legal without an `any`.
|
|
53
|
+
*/
|
|
54
|
+
type ChangeHookMap<C extends Config> = Record<string, ChangeHook<C, unknown>>;
|
|
55
|
+
/** The result type an entry of the map produces, so `derived('styles')` is typed without a cast. */
|
|
56
|
+
type DerivedOf<H> = H extends ChangeHook<Config, infer T> ? T : never;
|
|
57
|
+
/** What registering a hook set hands back: the values, a way to watch them, and the teardown. */
|
|
58
|
+
interface ChangeHookRun<H> {
|
|
59
|
+
/** The current value, or `undefined` before the first run has published one. */
|
|
60
|
+
derived<K extends keyof H>(name: K): DerivedOf<H[K]> | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Called on every publish, and immediately with the current value if there already is one.
|
|
63
|
+
*
|
|
64
|
+
* The replay is what makes this safe to use from an effect: a sink that only heard about future
|
|
65
|
+
* publishes would miss the derivation that ran before it mounted, and the miss looks exactly like
|
|
66
|
+
* a value that never derived. Returns the disposer.
|
|
67
|
+
*/
|
|
68
|
+
onDerived<K extends keyof H>(name: K, listener: (value: DerivedOf<H[K]>) => void): () => void;
|
|
69
|
+
/** Stop watching and drop anything scheduled. Safe to call twice. */
|
|
70
|
+
dispose(): void;
|
|
71
|
+
}
|
|
72
|
+
interface ChangeHookOptions {
|
|
73
|
+
/**
|
|
74
|
+
* A derivation that threw. Never swallowed silently: the surface waiting on the value is showing
|
|
75
|
+
* something stale, and only the caller knows how to say so.
|
|
76
|
+
*/
|
|
77
|
+
readonly onError?: (name: string, error: unknown) => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A hook must not move what it reads — the one way to use this that does not terminate.
|
|
81
|
+
*
|
|
82
|
+
* The skip test below keys on {@link sliceKeys}, which includes every attached source by identity —
|
|
83
|
+
* load-bearing, since a config that swapped a source has the same slices and different data. A hook
|
|
84
|
+
* whose published value is applied back onto the feed, and that changes one of those keys, re-triggers
|
|
85
|
+
* itself: the driver cannot tell that cycle from ordinary progress, since each run sees genuinely
|
|
86
|
+
* different keys and is genuinely not skippable. A hook that fetches sources and attaches them, for
|
|
87
|
+
* instance, mints a fresh `Config` per answer — an unbounded loop of requests, paced only by network
|
|
88
|
+
* latency.
|
|
89
|
+
*
|
|
90
|
+
* So a hook that derives a value something else writes back into the same feed owns the fixed point:
|
|
91
|
+
* it must compare against what is already there and write nothing when the answer hasn't moved.
|
|
92
|
+
* There's no guard here, because "the value changed" and "the value changed because I changed it" are
|
|
93
|
+
* the same event from this side.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* Run `hooks` against `feed` until disposed.
|
|
97
|
+
*
|
|
98
|
+
* Each entry gets its own coalescer, so a slow derivation cannot hold up a fast one, and each is
|
|
99
|
+
* driven by the key of what it declares it reads rather than by config identity. That distinction is
|
|
100
|
+
* the whole correctness argument:
|
|
101
|
+
*
|
|
102
|
+
* - **Skipping.** A config change that moves none of a hook's kinds cannot change its result, so
|
|
103
|
+
* there is nothing to run.
|
|
104
|
+
* - **Discarding.** A result computed for a config the feed has moved past is only stale if the
|
|
105
|
+
* newer config reads differently — and if it does, the change that superseded it will run. Keyed
|
|
106
|
+
* on config identity instead, a result would be discarded by an edit its own kinds never saw
|
|
107
|
+
* (a label, a description) and then never recomputed, because the follow-up run would be skipped
|
|
108
|
+
* by the rule above. That combination is a canvas that silently stops repainting.
|
|
109
|
+
*/
|
|
110
|
+
declare function runChangeHooks<C extends Config, H extends ChangeHookMap<C>>({
|
|
111
|
+
feed,
|
|
112
|
+
hooks,
|
|
113
|
+
onError
|
|
114
|
+
}: {
|
|
115
|
+
readonly feed: ConfigFeed<C>;
|
|
116
|
+
readonly hooks: H;
|
|
117
|
+
} & ChangeHookOptions): ChangeHookRun<H>;
|
|
118
|
+
//#endregion
|
|
119
|
+
export { ChangeHook, ChangeHookMap, ChangeHookRun, runChangeHooks };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { sameSliceKeys, sliceKeys } from "./memo.js";
|
|
2
|
+
import { coalesceLatest } from "./coalesce.js";
|
|
3
|
+
//#region src/framework/change-hooks.ts
|
|
4
|
+
/** How long an idle derivation can be held back — one frame's worth, so the canvas repaints in the
|
|
5
|
+
* same beat as the panel rather than whenever the tab next goes quiet. */
|
|
6
|
+
const IDLE_TIMEOUT_MS = 48;
|
|
7
|
+
function scheduler(schedule) {
|
|
8
|
+
if (typeof schedule === "function") return schedule;
|
|
9
|
+
if (schedule === "sync") return (fire) => fire();
|
|
10
|
+
return (fire) => {
|
|
11
|
+
if (typeof requestIdleCallback === "function") {
|
|
12
|
+
requestIdleCallback(fire, { timeout: IDLE_TIMEOUT_MS });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
setTimeout(fire, 0);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A hook must not move what it reads — the one way to use this that does not terminate.
|
|
20
|
+
*
|
|
21
|
+
* The skip test below keys on {@link sliceKeys}, which includes every attached source by identity —
|
|
22
|
+
* load-bearing, since a config that swapped a source has the same slices and different data. A hook
|
|
23
|
+
* whose published value is applied back onto the feed, and that changes one of those keys, re-triggers
|
|
24
|
+
* itself: the driver cannot tell that cycle from ordinary progress, since each run sees genuinely
|
|
25
|
+
* different keys and is genuinely not skippable. A hook that fetches sources and attaches them, for
|
|
26
|
+
* instance, mints a fresh `Config` per answer — an unbounded loop of requests, paced only by network
|
|
27
|
+
* latency.
|
|
28
|
+
*
|
|
29
|
+
* So a hook that derives a value something else writes back into the same feed owns the fixed point:
|
|
30
|
+
* it must compare against what is already there and write nothing when the answer hasn't moved.
|
|
31
|
+
* There's no guard here, because "the value changed" and "the value changed because I changed it" are
|
|
32
|
+
* the same event from this side.
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* Run `hooks` against `feed` until disposed.
|
|
36
|
+
*
|
|
37
|
+
* Each entry gets its own coalescer, so a slow derivation cannot hold up a fast one, and each is
|
|
38
|
+
* driven by the key of what it declares it reads rather than by config identity. That distinction is
|
|
39
|
+
* the whole correctness argument:
|
|
40
|
+
*
|
|
41
|
+
* - **Skipping.** A config change that moves none of a hook's kinds cannot change its result, so
|
|
42
|
+
* there is nothing to run.
|
|
43
|
+
* - **Discarding.** A result computed for a config the feed has moved past is only stale if the
|
|
44
|
+
* newer config reads differently — and if it does, the change that superseded it will run. Keyed
|
|
45
|
+
* on config identity instead, a result would be discarded by an edit its own kinds never saw
|
|
46
|
+
* (a label, a description) and then never recomputed, because the follow-up run would be skipped
|
|
47
|
+
* by the rule above. That combination is a canvas that silently stops repainting.
|
|
48
|
+
*/
|
|
49
|
+
function runChangeHooks({ feed, hooks, onError }) {
|
|
50
|
+
const values = /* @__PURE__ */ new Map();
|
|
51
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
52
|
+
const requests = [];
|
|
53
|
+
const cancels = [];
|
|
54
|
+
let disposed = false;
|
|
55
|
+
for (const [name, hook] of Object.entries(hooks)) {
|
|
56
|
+
let last;
|
|
57
|
+
let lastKeys;
|
|
58
|
+
const publish = (value, from) => {
|
|
59
|
+
last = {
|
|
60
|
+
from,
|
|
61
|
+
value
|
|
62
|
+
};
|
|
63
|
+
values.set(name, value);
|
|
64
|
+
for (const listener of listeners.get(name) ?? []) listener(value);
|
|
65
|
+
};
|
|
66
|
+
const derive = coalesceLatest({
|
|
67
|
+
schedule: scheduler(hook.schedule ?? "sync"),
|
|
68
|
+
run: async (config) => {
|
|
69
|
+
const keys = sliceKeys({
|
|
70
|
+
config,
|
|
71
|
+
kinds: hook.kinds
|
|
72
|
+
});
|
|
73
|
+
if (lastKeys && sameSliceKeys({
|
|
74
|
+
before: lastKeys,
|
|
75
|
+
after: keys
|
|
76
|
+
})) return;
|
|
77
|
+
let value;
|
|
78
|
+
try {
|
|
79
|
+
value = await hook.run(config, last);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
onError?.(name, error);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const live = feed.current();
|
|
85
|
+
if (disposed || !live || !sameSliceKeys({
|
|
86
|
+
before: keys,
|
|
87
|
+
after: sliceKeys({
|
|
88
|
+
config: live,
|
|
89
|
+
kinds: hook.kinds
|
|
90
|
+
})
|
|
91
|
+
})) return;
|
|
92
|
+
lastKeys = keys;
|
|
93
|
+
publish(value, config);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
requests.push(derive);
|
|
97
|
+
cancels.push(derive.cancel);
|
|
98
|
+
}
|
|
99
|
+
const request = () => {
|
|
100
|
+
const config = feed.current();
|
|
101
|
+
if (!config || disposed) return;
|
|
102
|
+
for (const derive of requests) derive(config);
|
|
103
|
+
};
|
|
104
|
+
request();
|
|
105
|
+
const unsubscribe = feed.subscribe(request);
|
|
106
|
+
return {
|
|
107
|
+
derived: (name) => values.get(String(name)),
|
|
108
|
+
onDerived: (name, listener) => {
|
|
109
|
+
const key = String(name);
|
|
110
|
+
let set = listeners.get(key);
|
|
111
|
+
if (!set) {
|
|
112
|
+
set = /* @__PURE__ */ new Set();
|
|
113
|
+
listeners.set(key, set);
|
|
114
|
+
}
|
|
115
|
+
set.add(listener);
|
|
116
|
+
if (values.has(key)) listener(values.get(key));
|
|
117
|
+
return () => {
|
|
118
|
+
set?.delete(listener);
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
dispose: () => {
|
|
122
|
+
if (disposed) return;
|
|
123
|
+
disposed = true;
|
|
124
|
+
unsubscribe();
|
|
125
|
+
for (const cancel of cancels) cancel();
|
|
126
|
+
listeners.clear();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { runChangeHooks };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { StampedPatch } from "./overlay.js";
|
|
2
|
+
import { Config } from "./Config.js";
|
|
3
|
+
|
|
4
|
+
//#region src/framework/changes.d.ts
|
|
5
|
+
/** What a patch addresses: one item, one group node, or a kind's order. */
|
|
6
|
+
type ChangeTarget = 'item' | 'group' | 'order';
|
|
7
|
+
interface Change {
|
|
8
|
+
/** The pending patch's id — what `ConfigSession.drop` takes. */
|
|
9
|
+
readonly id: string;
|
|
10
|
+
/** The gesture that wrote this patch alongside others, when it was one of several. */
|
|
11
|
+
readonly changeset?: string;
|
|
12
|
+
readonly kind: string;
|
|
13
|
+
readonly operation: string;
|
|
14
|
+
readonly target: ChangeTarget;
|
|
15
|
+
/** The item or group path the patch addresses. Absent for a reorder, which names no one path. */
|
|
16
|
+
readonly path?: string;
|
|
17
|
+
/** Where a rename moved the path. */
|
|
18
|
+
readonly to?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The dotted path within the body the patch wrote, when it wrote one place: a partial write's
|
|
21
|
+
* single key (`modifiers.dark`), or a sub-op's member (`layers.root`). Absent when the patch wrote
|
|
22
|
+
* the whole body or several fields, in which case `before` and `after` are bodies.
|
|
23
|
+
*/
|
|
24
|
+
readonly field?: string;
|
|
25
|
+
/** The value on the base. `undefined` when the path did not exist there. */
|
|
26
|
+
readonly before: unknown;
|
|
27
|
+
/** The value on the draft. `undefined` when the path no longer exists. */
|
|
28
|
+
readonly after: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** The base as a config of its own — the branch with no draft applied. */
|
|
31
|
+
declare function baseOf(config: Config): Config;
|
|
32
|
+
/** The value a change addresses in one config — the body, or the field within it. */
|
|
33
|
+
declare function changeValueAt({
|
|
34
|
+
config,
|
|
35
|
+
change,
|
|
36
|
+
side
|
|
37
|
+
}: {
|
|
38
|
+
config: Config;
|
|
39
|
+
change: Pick<Change, 'kind' | 'path' | 'field' | 'target'>;
|
|
40
|
+
side: 'before' | 'after';
|
|
41
|
+
}): unknown;
|
|
42
|
+
/** One pending patch as a change, with its values read off `base` and `draft`. */
|
|
43
|
+
declare function changeOf(entry: StampedPatch, {
|
|
44
|
+
base,
|
|
45
|
+
draft
|
|
46
|
+
}: {
|
|
47
|
+
base: Config;
|
|
48
|
+
draft: Config;
|
|
49
|
+
}): Change;
|
|
50
|
+
/** Every pending patch of `config` as a change, in draft order. */
|
|
51
|
+
declare function changesOf(config: Config, base?: Config): Change[];
|
|
52
|
+
/**
|
|
53
|
+
* What a set of changes on one entity did, in words — the line a card carries under the entity's
|
|
54
|
+
* name. `entity` is the path the changes are grouped under, which may be a prefix of what each
|
|
55
|
+
* change addresses (a token group over its tokens) or the item itself (a component).
|
|
56
|
+
*/
|
|
57
|
+
declare function summarizeChanges({
|
|
58
|
+
config,
|
|
59
|
+
kind,
|
|
60
|
+
entity,
|
|
61
|
+
changes
|
|
62
|
+
}: {
|
|
63
|
+
config: Config;
|
|
64
|
+
kind: string; /** The grouping path; absent when the changes are a kind's order. */
|
|
65
|
+
entity: string | undefined;
|
|
66
|
+
changes: readonly Change[];
|
|
67
|
+
}): string;
|
|
68
|
+
/**
|
|
69
|
+
* A body summarized as what it contains — the cell for a created or deleted container, where the
|
|
70
|
+
* body itself is too much to show. `undefined` for a value that is not a body.
|
|
71
|
+
*
|
|
72
|
+
* `config` is the one the body was read from, since a group's members are counted there.
|
|
73
|
+
*/
|
|
74
|
+
declare function describeBody({
|
|
75
|
+
config,
|
|
76
|
+
kind,
|
|
77
|
+
path,
|
|
78
|
+
target,
|
|
79
|
+
value
|
|
80
|
+
}: {
|
|
81
|
+
config: Config;
|
|
82
|
+
kind: string;
|
|
83
|
+
path: string;
|
|
84
|
+
target: ChangeTarget;
|
|
85
|
+
value: unknown;
|
|
86
|
+
}): string | undefined;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { Change, ChangeTarget, baseOf, changeOf, changeValueAt, changesOf, describeBody, summarizeChanges };
|