@yahoo/uds-create-config 2.45.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,2472 @@
|
|
|
1
|
+
import { CopyPlan } from "../entities/system/copy-plan.js";
|
|
2
|
+
import { ConfigEdit, ConfigOp } from "./config-op.js";
|
|
3
|
+
import { RebaseResult, StampedPatch } from "./overlay.js";
|
|
4
|
+
import { DeletePlan, Plan, PlanOp, RenamePlan } from "./plan.js";
|
|
5
|
+
import { Entity } from "./Entity.js";
|
|
6
|
+
import { Ref } from "./utils/refs.js";
|
|
7
|
+
import { RefGraph, TraverseOptions } from "./ref-graph.js";
|
|
8
|
+
import { FieldsSchema } from "./utils/field-schema.js";
|
|
9
|
+
import { SubEntityClass } from "./defineSubEntity.js";
|
|
10
|
+
import { LocalOverlay, SourceAnswer, SourceOverlay, SourceResolver, SourceState } from "./sources.js";
|
|
11
|
+
import { AuthoredEntry } from "./utils/group.js";
|
|
12
|
+
import { EntityClass } from "./defineEntity.js";
|
|
13
|
+
import { BreakPlan, LinkPlan, SourceRowState, UnlinkPlan } from "../entities/system/link-plan.js";
|
|
14
|
+
import { InlineOptions } from "./inline-sources.js";
|
|
15
|
+
import { Snapshot } from "./snapshot.js";
|
|
16
|
+
import { EntityKind } from "./registry.js";
|
|
17
|
+
|
|
18
|
+
//#region src/framework/Config.d.ts
|
|
19
|
+
/**
|
|
20
|
+
* One source this config recorded — the closure it reaches through a link, as an ordinary artifact.
|
|
21
|
+
*
|
|
22
|
+
* Recorded because only a build ever has a source attached, and every reader afterwards wanted the
|
|
23
|
+
* twelve entities a system uses rather than the four hundred it can see. Stored as a config rather
|
|
24
|
+
* than as a bag of values so nothing about reading it is special: entity bodies validate against their
|
|
25
|
+
* own kinds' schemas, group nodes and settings ride along, and `sourceFor` hands back something the
|
|
26
|
+
* ordinary chase already knows how to walk.
|
|
27
|
+
*/
|
|
28
|
+
interface RecordedSource {
|
|
29
|
+
/**
|
|
30
|
+
* What the source resolved to when this was recorded — concrete, and an immutable snapshot. Absent
|
|
31
|
+
* when nothing resolved it: a config assembled by `registerSources` rather than loaded has attached
|
|
32
|
+
* sources and no answer about where they came from, which is the same third state
|
|
33
|
+
* {@link Config.sources} reports.
|
|
34
|
+
*
|
|
35
|
+
* `unknown`, because the shape is the source kind's. It is written straight from the `attached`
|
|
36
|
+
* state's `resolved`, already validated against that kind's own `sourceResolution.resolved` schema at
|
|
37
|
+
* the resolver seam — so a reader that knows the kind reads it as that kind's type
|
|
38
|
+
* (`ResolvedSource` for a linked system) and nothing here restates it. Naming
|
|
39
|
+
* `{ systemId, version }` was the framework naming a kind, and a repo-local source could not fill it
|
|
40
|
+
* anyway: identity on one is its `configPath`.
|
|
41
|
+
*/
|
|
42
|
+
readonly resolved?: unknown;
|
|
43
|
+
/** The reached closure. Recursive by nature: a source is a config. */
|
|
44
|
+
readonly config: SerializedConfig;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A config's wire form.
|
|
48
|
+
*
|
|
49
|
+
* Declared rather than inferred off `toJSON`. Inference is the nicer shape — a field added to the
|
|
50
|
+
* writer is in the type for free — and it is not available here: the wire form contains itself
|
|
51
|
+
* ({@link RecordedSource} holds a `SerializedConfig`), so reading the type off a writer that
|
|
52
|
+
* references the type is circular and TypeScript answers `any`.
|
|
53
|
+
*
|
|
54
|
+
* Most of the guarantee the inference bought is kept by annotating `toJSON` with this type: a required
|
|
55
|
+
* field declared here and not emitted is a compile error, and a field added to the literal outside a
|
|
56
|
+
* spread is an excess-property error.
|
|
57
|
+
*
|
|
58
|
+
* What is genuinely weaker: an optional field added as a conditional spread
|
|
59
|
+
* (`...(cond ? { k } : {})`) is not excess-checked, so it can be emitted without being declared here
|
|
60
|
+
* and nothing complains. Every optional field below arrives that way. There is no type-level fix that
|
|
61
|
+
* keeps the recursion, so this is the cost of the wire form containing itself — when adding an
|
|
62
|
+
* optional field to `toJSON`, add it here too.
|
|
63
|
+
*/
|
|
64
|
+
/**
|
|
65
|
+
* The version of {@link SerializedConfig} this build writes and reads — an alias for
|
|
66
|
+
* `CURRENT_SCHEMA_VERSION` (`schema-version.ts`), kept under the name the wire-form constructors
|
|
67
|
+
* across the repo already spell. The version is a mint timestamp and moves with every schema change;
|
|
68
|
+
* an artifact written under an older one comes up through the registered migration chain on
|
|
69
|
+
* hydrate, and one written under a newer one is refused with the remedy named.
|
|
70
|
+
*
|
|
71
|
+
* An artifact with no version at all is the corpus written before versioning existed — version 1.
|
|
72
|
+
* The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
|
|
73
|
+
* that replaced it (see `detectedWireVersion`).
|
|
74
|
+
*/
|
|
75
|
+
declare const SERIALIZED_CONFIG_VERSION = 20260910212453;
|
|
76
|
+
interface SerializedConfig {
|
|
77
|
+
/** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
|
|
78
|
+
readonly version: number;
|
|
79
|
+
/** The config's namespace name — preserved so a round-tripped config keeps its tool prefix. */
|
|
80
|
+
readonly name?: string;
|
|
81
|
+
/** The kinds this config owns — preserved so a kind registered without items (an AI sandbox that
|
|
82
|
+
* may create the first one) survives the round-trip and still exposes its ops. */
|
|
83
|
+
readonly ownedKinds: string[];
|
|
84
|
+
readonly items: Record<string, Record<string, unknown>>;
|
|
85
|
+
readonly groups: Record<string, Record<string, unknown>>;
|
|
86
|
+
/** Every source this config reaches into, by slug — see {@link RecordedSource}. Absent when
|
|
87
|
+
* nothing is borrowed. */
|
|
88
|
+
readonly sources?: Record<string, RecordedSource>;
|
|
89
|
+
/** Self-sourced derived kinds, re-attached (source = the loaded config) on `fromJSON`. An
|
|
90
|
+
* externally-sourced one re-attaches via `rederiveFrom`, not through the wire. */
|
|
91
|
+
readonly derivedKinds?: string[];
|
|
92
|
+
/** Sparse per-member overrides of a derived kind's fields. The base always re-derives. */
|
|
93
|
+
readonly overrides?: Record<string, Record<string, Record<string, unknown>>>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A stored artifact as read, before this build has checked its version: written under some version or
|
|
97
|
+
* none, and possibly carrying the `options` slot a pre-version artifact kept its config type's settings
|
|
98
|
+
* in. What `hydrateFrom` takes in and what a migration's `up` reads and answers.
|
|
99
|
+
*/
|
|
100
|
+
type StoredConfig = Omit<SerializedConfig, 'version' | 'sources'> & {
|
|
101
|
+
readonly version?: number;
|
|
102
|
+
readonly options?: unknown; /** The recorded closures, each an artifact of the same age as the one holding it. */
|
|
103
|
+
readonly sources?: Record<string, {
|
|
104
|
+
readonly resolved?: unknown;
|
|
105
|
+
readonly config: StoredConfig;
|
|
106
|
+
}>;
|
|
107
|
+
};
|
|
108
|
+
/** A CRUD mutation — the patch shape the pipeline speaks. `data` is validated against the
|
|
109
|
+
* kind's schema-derived create/update shape. */
|
|
110
|
+
/** An op a tolerant batch left out: where it sat in the batch, and the refusal it got. */
|
|
111
|
+
interface SkippedOp {
|
|
112
|
+
readonly opIndex: number;
|
|
113
|
+
readonly message: string;
|
|
114
|
+
}
|
|
115
|
+
type Patch = {
|
|
116
|
+
kind: string;
|
|
117
|
+
operation: 'create';
|
|
118
|
+
path: string;
|
|
119
|
+
data: unknown;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A partial over the item body, addressed by field path — `{ 'layers.root.bg': 'hover' }`. The value
|
|
123
|
+
* replaces whatever the key names and `null` removes it, so one verb writes at every depth
|
|
124
|
+
* ({@link applyPathDelta}).
|
|
125
|
+
*
|
|
126
|
+
* Depth is the caller's to state, which is what makes this one op rather than two. It also composes:
|
|
127
|
+
* two entries against one target apply in order rather than clobbering, so a batch needs no grouping
|
|
128
|
+
* by target and no caller does read-merge-write to set a nested value.
|
|
129
|
+
*/
|
|
130
|
+
| {
|
|
131
|
+
kind: string;
|
|
132
|
+
operation: 'update';
|
|
133
|
+
path: string;
|
|
134
|
+
data: unknown;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Replace an occupied path's whole stored body — the write a partial can't express.
|
|
138
|
+
*
|
|
139
|
+
* `update` is a merge, and on a borrowed item it merges into the envelope by design: `{ value: … }`
|
|
140
|
+
* over `Token.ref('x@ds').extend({ … })` records a local override, which is what makes an extension
|
|
141
|
+
* useful and why `update` can never remove one. `create` refuses an occupied path, and
|
|
142
|
+
* `delete`-then-`create` is refused by any referrer and drops the item to the end of its collection,
|
|
143
|
+
* because order is key order.
|
|
144
|
+
*
|
|
145
|
+
* So dissolving a borrow into a body of this config's own has no other spelling. Writing the snapshot
|
|
146
|
+
* directly produces no patch, which is invisible to replay: an undo or a rebase re-applies the
|
|
147
|
+
* flattening `update` onto the envelope it was meant to remove, puts the value back inside `extend`,
|
|
148
|
+
* then refuses to drop the pin because the extension still references it. Both directions of the
|
|
149
|
+
* borrow boundary — a `copy` landing on a pointer row, and a pointer written over a local body — are
|
|
150
|
+
* ordinary recorded edits through this verb.
|
|
151
|
+
*
|
|
152
|
+
* It writes in place, so the item keeps its position. `data` is a complete body, the same shape
|
|
153
|
+
* `create` takes, including the two shorthands: a bare ref lands in the kind's primary field, and an
|
|
154
|
+
* `{ __ref, extend }` body is stored as an extension.
|
|
155
|
+
*/
|
|
156
|
+
| {
|
|
157
|
+
kind: string;
|
|
158
|
+
operation: 'replace';
|
|
159
|
+
path: string;
|
|
160
|
+
data: unknown;
|
|
161
|
+
} | {
|
|
162
|
+
kind: string;
|
|
163
|
+
operation: 'delete';
|
|
164
|
+
path: string;
|
|
165
|
+
} | {
|
|
166
|
+
kind: string;
|
|
167
|
+
operation: 'rename';
|
|
168
|
+
path: string;
|
|
169
|
+
to: string;
|
|
170
|
+
}
|
|
171
|
+
/** A group node's own body (`path` = the group prefix) — its kind's `groupFields` plus the
|
|
172
|
+
* universal label/description. Separate from item CRUD because a group isn't an item: it lives in
|
|
173
|
+
* its own store, and a folder can be rendered with no node behind it. */
|
|
174
|
+
| {
|
|
175
|
+
kind: string;
|
|
176
|
+
operation: 'group-update';
|
|
177
|
+
path: string;
|
|
178
|
+
data: unknown;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Move a whole group — its stored body and every member whose path extends it.
|
|
182
|
+
*
|
|
183
|
+
* Its own operation rather than a fan-out of member renames, because a group is two things at once:
|
|
184
|
+
* membership is emergent from paths, but the body (label, icon) is stored under the group path. A
|
|
185
|
+
* caller renaming the members would move half of it, orphaning the body under the old name and
|
|
186
|
+
* leaving the new name unlabelled. It is also one patch — one draft row, one undo step, one line in
|
|
187
|
+
* the changes panel — where the fan-out was one per member.
|
|
188
|
+
*/
|
|
189
|
+
| {
|
|
190
|
+
kind: string;
|
|
191
|
+
operation: 'group-rename';
|
|
192
|
+
path: string;
|
|
193
|
+
to: string;
|
|
194
|
+
} /** Delete a group: its body and its members. Nothing is left for a group to be derived from. */ | {
|
|
195
|
+
kind: string;
|
|
196
|
+
operation: 'group-delete';
|
|
197
|
+
path: string;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Rearrange a kind's items — and, separately below, its group nodes.
|
|
201
|
+
*
|
|
202
|
+
* Order is key order — the order of the authored object literal, of the serialized artifact, and of
|
|
203
|
+
* the emitted stylesheet — so this rewrites that rather than writing a rank or an index anywhere. An
|
|
204
|
+
* authored config therefore says nothing about ordering; it just has its keys in an order.
|
|
205
|
+
*
|
|
206
|
+
* `order` is the full intended sequence rather than a relative move, so replay never depends on a
|
|
207
|
+
* neighbour still existing.
|
|
208
|
+
*
|
|
209
|
+
* Two operations rather than one carrying `target: 'item' | 'group'`, for the same reason a sub-op
|
|
210
|
+
* spells its collection (`sub-create/layers`): what is being ordered is part of which operation this
|
|
211
|
+
* is, not an argument to a shared one. Every other group operation is already its own verb
|
|
212
|
+
* (`group-update`, `group-rename`, `group-delete`), ordering was the one that wasn't, and the cost
|
|
213
|
+
* showed up downstream — one op can't be both `scope: 'collection'` and `scope: 'group'`, so its
|
|
214
|
+
* derived tool advertised a single overloaded call where every other group edit has its own.
|
|
215
|
+
*/
|
|
216
|
+
| {
|
|
217
|
+
kind: string;
|
|
218
|
+
operation: 'reorder';
|
|
219
|
+
order: readonly string[];
|
|
220
|
+
} /** Rearrange a kind's group nodes — the folders, not the items inside them. */ | {
|
|
221
|
+
kind: string;
|
|
222
|
+
operation: 'group-reorder';
|
|
223
|
+
order: readonly string[];
|
|
224
|
+
} | SubPatch;
|
|
225
|
+
/**
|
|
226
|
+
* A mutation on a member inside an item's keyed collection field (a component's `layers`/`props`) —
|
|
227
|
+
* `field` names the collection, `key` the member.
|
|
228
|
+
*
|
|
229
|
+
* A nested collection is addressed by the same two plus `owner`: `field` is the declared path
|
|
230
|
+
* (`props/value`, a variant prop's values) and `owner` is the member of the outer collection it
|
|
231
|
+
* sits in (`variant`). The field stays the declaration rather than absorbing the owner key, so the
|
|
232
|
+
* verb it produces (`sub-create/props/value`) is the same string the op catalog declares.
|
|
233
|
+
*/
|
|
234
|
+
type SubPatch = {
|
|
235
|
+
kind: string;
|
|
236
|
+
operation: 'sub-create';
|
|
237
|
+
path: string;
|
|
238
|
+
field: string;
|
|
239
|
+
owner?: string; /** Left out on a collection that derives its members' keys. */
|
|
240
|
+
key?: string;
|
|
241
|
+
data: unknown;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Write one member's whole value, whether or not it is already there.
|
|
245
|
+
*
|
|
246
|
+
* Deliberately not `sub-create` relaxed into an upsert. A create's `ALREADY_EXISTS` refusal is
|
|
247
|
+
* load-bearing: the draft is a replayed list, so a create landing on a base that already holds the
|
|
248
|
+
* member is a collision someone has to be told about, and an "add" tool that silently clobbered a
|
|
249
|
+
* member a collaborator just added would make that invisible. Those are two different intents, so
|
|
250
|
+
* they are two operations — `sub-create` still means "this is new", and a surface that only knows
|
|
251
|
+
* what the member should be says so instead of reading the collection first to find out which
|
|
252
|
+
* verb it is allowed to use.
|
|
253
|
+
*/
|
|
254
|
+
| {
|
|
255
|
+
kind: string;
|
|
256
|
+
operation: 'sub-upsert';
|
|
257
|
+
path: string;
|
|
258
|
+
field: string;
|
|
259
|
+
owner?: string; /** Left out on a collection that derives its members' keys — an override's, a style rule's. */
|
|
260
|
+
key?: string;
|
|
261
|
+
data: unknown;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* A partial over one member, addressed by field path — `{ 'layers.root.bg': 'hover' }` changes one
|
|
265
|
+
* leaf of a style rule's bag and leaves every sibling alone, where `sub-upsert` would replace the
|
|
266
|
+
* member whole. `null` at an address removes what is there.
|
|
267
|
+
*
|
|
268
|
+
* **Upserting on purpose**: a missing member is written from `{}` rather than refused. Two reasons,
|
|
269
|
+
* and they point the same way. A derived key (`defineSubEntity({ key })`) is a function of the
|
|
270
|
+
* content, so "the rule at this condition" names the same thing whether or not it is stored yet —
|
|
271
|
+
* refusing would mean a component could only be styled where it already was. And a member whose key
|
|
272
|
+
* is the caller's may still be a part the config only projects until someone edits it: a node inside
|
|
273
|
+
* an inserted instance has no stored anatomy entry until its first prop is written, and that write is
|
|
274
|
+
* what stores it. `sub-create` stays the strict form for a caller that means "this must be new".
|
|
275
|
+
*
|
|
276
|
+
* The result is validated against the field's create schema, never the delta against a derived
|
|
277
|
+
* partial — so `null` never reaches a stored value type, per-owner strictness still applies, and
|
|
278
|
+
* every declared coercion and `.default()` still runs.
|
|
279
|
+
*/
|
|
280
|
+
| {
|
|
281
|
+
kind: string;
|
|
282
|
+
operation: 'sub-update';
|
|
283
|
+
path: string;
|
|
284
|
+
field: string;
|
|
285
|
+
owner?: string;
|
|
286
|
+
key: string;
|
|
287
|
+
data: unknown;
|
|
288
|
+
} | {
|
|
289
|
+
kind: string;
|
|
290
|
+
operation: 'sub-delete';
|
|
291
|
+
path: string;
|
|
292
|
+
field: string;
|
|
293
|
+
owner?: string;
|
|
294
|
+
key: string;
|
|
295
|
+
} | {
|
|
296
|
+
kind: string;
|
|
297
|
+
operation: 'sub-rename';
|
|
298
|
+
path: string;
|
|
299
|
+
field: string;
|
|
300
|
+
owner?: string;
|
|
301
|
+
key: string;
|
|
302
|
+
to: string;
|
|
303
|
+
} | {
|
|
304
|
+
kind: string;
|
|
305
|
+
operation: 'sub-reorder';
|
|
306
|
+
path: string;
|
|
307
|
+
field: string;
|
|
308
|
+
owner?: string;
|
|
309
|
+
order: string[];
|
|
310
|
+
};
|
|
311
|
+
/** A node in a kind's folder/file tree — a `folder` (an emergent group prefix, with its
|
|
312
|
+
* optional metadata) or an `item` (a leaf entity). */
|
|
313
|
+
interface TreeNode {
|
|
314
|
+
name: string;
|
|
315
|
+
path: string;
|
|
316
|
+
type: 'folder' | 'item';
|
|
317
|
+
meta?: Record<string, unknown>;
|
|
318
|
+
item?: Entity;
|
|
319
|
+
children: TreeNode[];
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Somewhere a current config can be read from — what a link points at.
|
|
323
|
+
*
|
|
324
|
+
* Two immutable values can't be "live" with respect to each other: once a link captures a `Config`,
|
|
325
|
+
* that config never changes again, and `sys = sys.apply(…)` produces a value the link has never
|
|
326
|
+
* heard of. So a link stores this instead, and reads resolve through it every time.
|
|
327
|
+
*
|
|
328
|
+
* `ConfigSession` is the real implementation — a mutable handle whose `config` advances as edits
|
|
329
|
+
* land. A plain config supplies {@link Config.liveSource}, which resolves to itself: correct for the
|
|
330
|
+
* build-time case, where the source is authored once and never edited.
|
|
331
|
+
*/
|
|
332
|
+
interface LiveConfig {
|
|
333
|
+
readonly config: Config;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* One source kind's loaded sources, by the slug every ref through them spells — a live `Config`, or
|
|
337
|
+
* the serialized form straight off the wire.
|
|
338
|
+
*
|
|
339
|
+
* Accepting the serialized form is what keeps `fromJSON` the only hydration API. A caller holding a
|
|
340
|
+
* payload would otherwise need somewhere to turn each source into a `Config` first, and "somewhere"
|
|
341
|
+
* becomes a helper, a name for the argument, and a second vocabulary sitting next to `fromJSON` — with
|
|
342
|
+
* every call site remembering to use it. There is nothing to remember if the option takes what the
|
|
343
|
+
* caller has.
|
|
344
|
+
*/
|
|
345
|
+
type SourcesBySlug = Record<string, Config | LiveConfig | SerializedConfig>;
|
|
346
|
+
/**
|
|
347
|
+
* Loaded sources ready to attach, keyed by the source kind's accessor name and then by the item's
|
|
348
|
+
* slug — `{ linkedSystems: { yos: yahooOs } }`.
|
|
349
|
+
*
|
|
350
|
+
* The same two levels the declaration uses (`register({ linkedSystems: { yos: LinkedSystem.item(…) } })`),
|
|
351
|
+
* so declaring a source and handing over what it resolved to are spelled the same way. See
|
|
352
|
+
* {@link Config.registerSources}.
|
|
353
|
+
*/
|
|
354
|
+
type AttachedSources = Readonly<Record<string, SourcesBySlug>>;
|
|
355
|
+
/**
|
|
356
|
+
* What a serialized config needs from outside itself in order to hydrate whole.
|
|
357
|
+
*
|
|
358
|
+
* `sources` attaches each source under the slug every ref through it spells, and is not required: an
|
|
359
|
+
* artifact whose sources are absent is a legitimate, readable config whose borrowed values simply do not
|
|
360
|
+
* resolve, which is what Create shows as "linked, unresolved".
|
|
361
|
+
*
|
|
362
|
+
* Attaching at hydration rather than after it is the point. `fromJSON(json)` followed by
|
|
363
|
+
* `.registerSources(…)` leaves a window in which every sourced ref dangles, and anything that reads in that
|
|
364
|
+
* window — an emitter, an integrity check, a memo that caches — gets a wrong answer that looks like data.
|
|
365
|
+
*/
|
|
366
|
+
interface HydrationOptions {
|
|
367
|
+
/**
|
|
368
|
+
* The loaded sources to attach — {@link AttachedSources}, so `{ linkedSystems: { yos: … } }`.
|
|
369
|
+
*
|
|
370
|
+
* Nested under its own key rather than spread across this interface, because the accessor names are
|
|
371
|
+
* the config type's to choose. A `linkedSystems?:` field here would be the framework naming a kind
|
|
372
|
+
* (see `.claude/rules/config-framework-kind-agnostic.md`), and a config declaring `linkedPackages`
|
|
373
|
+
* would have no way to hydrate at all. `patches` shares this level, which is why the sources cannot
|
|
374
|
+
* simply be this interface.
|
|
375
|
+
*/
|
|
376
|
+
readonly sources?: AttachedSources;
|
|
377
|
+
/**
|
|
378
|
+
* What those sources resolved to, by slug — the receipt beside the attachment.
|
|
379
|
+
*
|
|
380
|
+
* `sources` and this are two halves of one answer, and {@link Config.load} sets both: attaching says
|
|
381
|
+
* a source is readable, and the state says how that turned out and, for one that couldn't be met,
|
|
382
|
+
* why. A surface reads the second through {@link Config.sourceStatus}, so hydrating with sources but
|
|
383
|
+
* no states leaves the linked-system rail unable to tell "nobody looked" from "looked and could not".
|
|
384
|
+
*
|
|
385
|
+
* Here rather than only inside `load`, because resolving is what makes `load` async and attaching is
|
|
386
|
+
* not. A client handed pre-resolved answers — the editor, whose server resolved them at page load —
|
|
387
|
+
* has nothing to await, and going through `load` would mean an async hydrate for a synchronous fact.
|
|
388
|
+
*
|
|
389
|
+
* Keyed by slug alone, like every other source lookup: a ref spells `@ds` with no room for a
|
|
390
|
+
* collection. The payloads inside are the kind's own, so this names no kind.
|
|
391
|
+
*/
|
|
392
|
+
readonly sourceStates?: ReadonlyMap<string, SourceState>;
|
|
393
|
+
/**
|
|
394
|
+
* The draft this snapshot already includes, recorded rather than replayed — see
|
|
395
|
+
* {@link Config.withResolvedDraft}.
|
|
396
|
+
*
|
|
397
|
+
* Here because a client's hydration is one step conceptually and was two in practice: the server sends
|
|
398
|
+
* base-plus-draft applied, so the browser must record the log without performing it, and a `fromJSON`
|
|
399
|
+
* that couldn't do that left every caller to remember a second call. `config.patches` empty on mount is
|
|
400
|
+
* not a smaller version of the truth — it is a config that cannot answer "is this new in this draft".
|
|
401
|
+
*
|
|
402
|
+
* **Pass the ID with a patch that has one.** A stored patch is already known by an id, and that id is
|
|
403
|
+
* what the changes panel deletes by, what `drop` takes, and what an optimistic caller staged under; a
|
|
404
|
+
* fresh one would name nothing on either side. So an entry may be a bare `Patch` — stamped with a new id,
|
|
405
|
+
* which is right for a draft assembled locally — or `{ id, patch }` when the id already exists.
|
|
406
|
+
*
|
|
407
|
+
* Its twin, {@link Config.withDraft}, stays a method: it replays, and reports which patches would not
|
|
408
|
+
* apply, so it answers with more than a config.
|
|
409
|
+
*/
|
|
410
|
+
readonly patches?: readonly (Patch | StampedPatch)[];
|
|
411
|
+
}
|
|
412
|
+
/** How a derived kind gets its members. `source: undefined` means self, resolved at read time
|
|
413
|
+
* rather than captured in a closure — so a derived config rebinds for free instead of having to
|
|
414
|
+
* rebuild every forward. */
|
|
415
|
+
interface DerivedBinding {
|
|
416
|
+
readonly deriveMembers: (source: Config) => Record<string, Record<string, unknown>>;
|
|
417
|
+
/** `undefined` = self. An external source is a {@link LiveConfig} for the same reason a link is:
|
|
418
|
+
* a derived kind computed from another config has to track it, not a frozen copy of it. */
|
|
419
|
+
readonly source: LiveConfig | undefined;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* A config's whole state, in one object — so deriving a config is a single property write and the
|
|
423
|
+
* clone stays O(1) however many kinds are registered.
|
|
424
|
+
*
|
|
425
|
+
* The metadata half (`entities` / `ownedKinds` / `links` / `derived`) is shared by
|
|
426
|
+
* reference across every config `apply` produces, and copied only by the authoring methods, which
|
|
427
|
+
* run once at build time.
|
|
428
|
+
*/
|
|
429
|
+
/**
|
|
430
|
+
* What a config has written: its base and its pending list.
|
|
431
|
+
*
|
|
432
|
+
* One `Edition` value is shared by every config derived from the same edits, so two configs with the
|
|
433
|
+
* same `edition` differ only in what was read into them — an attached source, derived rows, an owner.
|
|
434
|
+
* A holder deciding whether someone else wrote compares editions; comparing configs would count every
|
|
435
|
+
* late read as a write.
|
|
436
|
+
*/
|
|
437
|
+
interface Edition {
|
|
438
|
+
readonly base: Snapshot;
|
|
439
|
+
readonly pending: readonly StampedPatch[];
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* A judgement about CSS text the config can't make on its own: whether `value` is one the CSS
|
|
443
|
+
* `property` takes, against the property's real grammar. `true` and `false` are verdicts; `undefined`
|
|
444
|
+
* is no verdict, for a property or a value the grammar can't judge (a custom property, a `var()`).
|
|
445
|
+
*
|
|
446
|
+
* The generated CSS table answers keywords and single-class literals itself and puts to this only what
|
|
447
|
+
* it can't judge (a shorthand, a function). A write lane supplies one through
|
|
448
|
+
* {@link Config.withCssGrammar}; the package never loads a grammar itself, since it is bundled into
|
|
449
|
+
* the Studio client.
|
|
450
|
+
*/
|
|
451
|
+
type CssGrammar = (input: {
|
|
452
|
+
readonly property: string;
|
|
453
|
+
readonly value: string;
|
|
454
|
+
}) => boolean | undefined;
|
|
455
|
+
interface ConfigState {
|
|
456
|
+
readonly name: string;
|
|
457
|
+
/** What this config type holds and how one is authored — prose the config type declares. */
|
|
458
|
+
readonly description?: string;
|
|
459
|
+
/** The grammar a write lane armed this config with — see {@link CssGrammar}. */
|
|
460
|
+
readonly cssGrammar?: CssGrammar;
|
|
461
|
+
/** What reads see: the base with every pending patch applied. */
|
|
462
|
+
readonly snapshot: Snapshot;
|
|
463
|
+
/** The last state seen from the source — what `rebase` replays the pending patches onto. */
|
|
464
|
+
readonly base: Snapshot;
|
|
465
|
+
/** Local edits the source hasn't confirmed yet. */
|
|
466
|
+
readonly pending: readonly StampedPatch[];
|
|
467
|
+
/** Shared by every config holding the same `base` and `pending` — see {@link Edition}. */
|
|
468
|
+
readonly edition: Edition;
|
|
469
|
+
/** Bumps on every derivation — the coarse cache key kept for compatibility. New code keys on
|
|
470
|
+
* Snapshot slice identity instead (see `memo.ts`), which doesn't over-invalidate. */
|
|
471
|
+
readonly generation: number;
|
|
472
|
+
readonly entities: ReadonlyMap<string, EntityKind>;
|
|
473
|
+
readonly ownedKinds: ReadonlySet<string>;
|
|
474
|
+
/**
|
|
475
|
+
* The loaded linked systems, keyed by the link's local slug — what a `@source` ref resolves
|
|
476
|
+
* through.
|
|
477
|
+
*
|
|
478
|
+
* Distinct from `links`, which is keyed by kind and borrows one kind wholesale (a design file's
|
|
479
|
+
* view of its system's components). This borrows nothing: a source sits here so refs into it can
|
|
480
|
+
* be followed, and using none of it is the normal case. A {@link LiveConfig} for the same reason a
|
|
481
|
+
* link is — a source that advances has to be seen advancing.
|
|
482
|
+
*/
|
|
483
|
+
readonly sources: ReadonlyMap<string, LiveConfig>;
|
|
484
|
+
/**
|
|
485
|
+
* The resolved values of the borrowed entities this config actually reaches, keyed by the qualified ref
|
|
486
|
+
* that reaches them — computed as the config serializes, which is the only moment they matter.
|
|
487
|
+
*
|
|
488
|
+
* Why record what the sources could compute: a source is a whole config, and only the build ever has one
|
|
489
|
+
* attached. Every reader afterwards — Create painting a swatch, the server regenerating a stylesheet —
|
|
490
|
+
* wanted the twelve values a system uses, not the four hundred it can see, and was being handed the
|
|
491
|
+
* problem of fetching and materializing entire artifacts to get them. Recording them where they are
|
|
492
|
+
* already computed (the CSS emitter walks exactly this set) makes every later read a lookup.
|
|
493
|
+
*
|
|
494
|
+
* Sound because a pin is exact: a published `{systemId, version}` is immutable, so its values cannot
|
|
495
|
+
* drift, and a repo-local pin is recomputed on every local build. Stale values would need a source to
|
|
496
|
+
* change under a fixed pin, which is the one thing a pin prevents.
|
|
497
|
+
*/
|
|
498
|
+
/**
|
|
499
|
+
* Sources this config recorded, by slug — the closure it reaches, each as an ordinary artifact.
|
|
500
|
+
*
|
|
501
|
+
* Distinct from {@link sources} above, which holds what is attached right now. A reader asking "can
|
|
502
|
+
* I read this value" wants either ({@link Config.sourceFor}); a reader asking "is a real source
|
|
503
|
+
* attached" wants only the first ({@link Config.liveSourceFor}), and four of them do.
|
|
504
|
+
*
|
|
505
|
+
* Carried by reference through `derive`, which is load-bearing: the reconstructed configs are
|
|
506
|
+
* memoized on this map's identity, and rebuilding it per `apply` would rebuild them too — throwing
|
|
507
|
+
* away every memo keyed on a source (`memo.ts`) on every keystroke, with nothing failing.
|
|
508
|
+
*/
|
|
509
|
+
readonly recorded: ReadonlyMap<string, RecordedSource>;
|
|
510
|
+
/**
|
|
511
|
+
* What each declared source resolved to, recorded per slug by {@link Config.load} — the same
|
|
512
|
+
* tagged value the resolver answered with, so nothing translates between them.
|
|
513
|
+
*
|
|
514
|
+
* Absent is a third state, and a meaningful one: a config hydrated by plain `fromJSON` asked nobody,
|
|
515
|
+
* which is different from a resolver that looked and could not. Those two plus `attached` are the
|
|
516
|
+
* three cases that are all the same `undefined` without this.
|
|
517
|
+
*
|
|
518
|
+
* Carried by reference through `derive` like {@link recorded} — an edit does not change what a
|
|
519
|
+
* source resolved to.
|
|
520
|
+
*/
|
|
521
|
+
readonly sourceStates: ReadonlyMap<string, SourceState>;
|
|
522
|
+
readonly derived: ReadonlyMap<string, DerivedBinding>;
|
|
523
|
+
/** The mutable handle that owns this config's lineage, when one does (a `ConfigSession`). A link
|
|
524
|
+
* taken off this config reads through it, so the window stays live as the session advances.
|
|
525
|
+
* `undefined` for a standalone value, which links as itself. */
|
|
526
|
+
readonly owner: LiveConfig | undefined;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Everything a config derives from its snapshot and its live linked sources, in one bucket.
|
|
530
|
+
*
|
|
531
|
+
* Reference stability for a resolved extension, a kind's list and a group's members all want the
|
|
532
|
+
* same key, and none of them can be keyed on the snapshot alone: an extended linked item takes its
|
|
533
|
+
* base from the current source, so a config that swapped one — hydrating with `{ sources }`,
|
|
534
|
+
* re-attaching after a republish — has the same slices and different data. `list` inlines those
|
|
535
|
+
* resolved entities into an array, so a slug-only key would keep handing back the pre-republish ones
|
|
536
|
+
* even though `resolveExtension` would now build new ones.
|
|
537
|
+
*
|
|
538
|
+
* Keyed by the current source Config identities, a live source edit invalidates naturally without
|
|
539
|
+
* serializing or hashing either config.
|
|
540
|
+
*/
|
|
541
|
+
/** What a collection read may leave out. `retired: false` drops every entity {@link Config.retired}
|
|
542
|
+
* answers true for; anything else is the total list. */
|
|
543
|
+
type ListOptions = {
|
|
544
|
+
readonly retired?: boolean;
|
|
545
|
+
};
|
|
546
|
+
declare class Config {
|
|
547
|
+
#private;
|
|
548
|
+
/** All of this config's state. `protected`, not a `#private` field, so a derived config can be
|
|
549
|
+
* built with `Object.create` — JS private fields are installed by a constructor, and an O(1)
|
|
550
|
+
* clone is precisely the thing that skips the constructor. */
|
|
551
|
+
protected state: ConfigState;
|
|
552
|
+
/** A config's name is its namespace (`'system-config'`, `'design-file'`) — the segment tool names
|
|
553
|
+
* carry (`uds_<name>_<verb>_<kind>`) so two configs' tools are distinct. Defaults to
|
|
554
|
+
* `'system-config'` for a bare `new Config()`; a config type (`defineConfig({ name })`) passes its
|
|
555
|
+
* own. */
|
|
556
|
+
constructor(name?: string, description?: string);
|
|
557
|
+
get name(): string;
|
|
558
|
+
/** What this config type holds and how one is authored, when the type says. */
|
|
559
|
+
get description(): string | undefined;
|
|
560
|
+
/** The stored state this config reads through — the value the view caches key on (`memo.ts`). */
|
|
561
|
+
get snapshot(): Snapshot;
|
|
562
|
+
/** The last state seen from the source, without the local pending patches. */
|
|
563
|
+
get base(): Snapshot;
|
|
564
|
+
/** Local edits the source hasn't confirmed yet, oldest first. */
|
|
565
|
+
get patches(): readonly Patch[];
|
|
566
|
+
/** This config as A link source — what another config's `registerLinked` should point at.
|
|
567
|
+
* Resolves through the owning session when there is one, so the link tracks it; otherwise it
|
|
568
|
+
* resolves to this exact value, which is what a build-time link wants. */
|
|
569
|
+
get liveSource(): LiveConfig;
|
|
570
|
+
/** Bind this config's lineage to the handle that owns it — called by `ConfigSession`, so links
|
|
571
|
+
* taken off any config it produces resolve through the session rather than freezing. */
|
|
572
|
+
withOwner(owner: LiveConfig): this;
|
|
573
|
+
/** The pending edits with their ids — what a sink persists and `merge` names. */
|
|
574
|
+
get pending(): readonly StampedPatch[];
|
|
575
|
+
/**
|
|
576
|
+
* What this config has written, as one value shared with every config derived from the same edits.
|
|
577
|
+
*
|
|
578
|
+
* A write — `apply`, `rebase`, `withDraft`, `merge`, `drop` — produces a new edition. A read that
|
|
579
|
+
* arrives late — a source attaching, rows deriving, an owner binding — produces a new config over
|
|
580
|
+
* the same one. A holder asking "did someone else write while my edit was out" compares this.
|
|
581
|
+
*/
|
|
582
|
+
get edition(): Edition;
|
|
583
|
+
/**
|
|
584
|
+
* The members of one collection that the unmerged draft created — "which of these elements are new
|
|
585
|
+
* in this draft?"
|
|
586
|
+
*
|
|
587
|
+
* The one class of question a snapshot cannot answer. A component's anatomy says an element is
|
|
588
|
+
* there, not who put it there, and an editor that routes a dropped instance to a different panel
|
|
589
|
+
* than a baked one has to know which it is. It is asked of the config because the config is what
|
|
590
|
+
* holds the draft; the alternative every surface reached for was scanning stored rows, which sees
|
|
591
|
+
* only what has been persisted — an edit still in flight is invisible to it, and one that has
|
|
592
|
+
* settled is invisible in the other direction, because the row a client stages and the rows a
|
|
593
|
+
* commit writes are not the same shape.
|
|
594
|
+
*
|
|
595
|
+
* Looks for the create, not for the op that produced it. A custom op decomposes into several
|
|
596
|
+
* patches and replay never records that one ran, so `element-insert` leaves a `sub-create` on the
|
|
597
|
+
* collection its element landed in and nothing that names the insert. That also means a subtree
|
|
598
|
+
* insert answers with every node it grafted, not just the root: each one is equally new, and a
|
|
599
|
+
* caller that wants only the root has a containment question to ask of the anatomy, not of the
|
|
600
|
+
* draft.
|
|
601
|
+
*/
|
|
602
|
+
draftCreatedKeys(kind: string, path: string, field: string): ReadonlySet<string>;
|
|
603
|
+
/**
|
|
604
|
+
* This config's bucket in {@link cachesFor}, resolved once.
|
|
605
|
+
*
|
|
606
|
+
* `cachesFor` allocates a sorted `[slug, Config]` list and scans the snapshot's buckets for a match,
|
|
607
|
+
* and its callers include `withComputed`, which the value-domain expansion behind one CSS emit reaches
|
|
608
|
+
* hundreds of thousands of times. The pair it keys on — this config's snapshot and its attached
|
|
609
|
+
* sources — is fixed for the instance's lifetime, because every change goes through {@link derive} and
|
|
610
|
+
* produces a new one, so holding the answer here is a memo that can't go stale.
|
|
611
|
+
*
|
|
612
|
+
* Safe on a derived config for the same reason: `derive` builds the clone with `Object.create` and
|
|
613
|
+
* assigns only `state`, so an instance field is never carried across.
|
|
614
|
+
*/
|
|
615
|
+
private get caches();
|
|
616
|
+
private buckets?;
|
|
617
|
+
/**
|
|
618
|
+
* The next config: same identity and metadata, new state. One `Object.create` plus one property
|
|
619
|
+
* write — no re-registration, no rehydration, and the receiver is untouched.
|
|
620
|
+
*/
|
|
621
|
+
private derive;
|
|
622
|
+
/**
|
|
623
|
+
* This config over another snapshot — what a guard inside {@link computeApply} reads.
|
|
624
|
+
*
|
|
625
|
+
* `computeApply` is pure in its `snapshot` and reads metadata off `this`, and during a replay those
|
|
626
|
+
* are two different states: `this` is the stale local config, while the patch has to be judged against
|
|
627
|
+
* the base it is landing on. So a guard whose question is about stored rows — a pin's `link` field,
|
|
628
|
+
* a folder's pointer — must not ask `this`. The reads that answer it are `Config` methods rather than
|
|
629
|
+
* `Snapshot` ones, because they cross kinds and consult the entity registry, and this is the view they
|
|
630
|
+
* take instead.
|
|
631
|
+
*
|
|
632
|
+
* One `Object.create` (see {@link derive}), and every snapshot-keyed cache is shared with the
|
|
633
|
+
* receiver, so a guard pays nothing for asking the right config.
|
|
634
|
+
*/
|
|
635
|
+
private at;
|
|
636
|
+
/** A mutable copy of the metadata half — taken by the authoring methods, which are pure but rare.
|
|
637
|
+
* `apply` shares the metadata by reference and never pays for this. */
|
|
638
|
+
private metaDraft;
|
|
639
|
+
/** The state a completed registration produces. Build content is confirmed by definition, so the
|
|
640
|
+
* base advances with it — nothing authored is ever "pending". */
|
|
641
|
+
private registered;
|
|
642
|
+
/** This config's metadata for a kind — the `EntityKind` copied off the entity class when the kind
|
|
643
|
+
* was registered into this instance. `undefined` for a kind this config doesn't know. The single
|
|
644
|
+
* accessor every internal read routes through. */
|
|
645
|
+
entityOf(kind: string): EntityKind | undefined;
|
|
646
|
+
/**
|
|
647
|
+
* Run one of a kind's operations — the input parsed through the op's own schema, then handed to
|
|
648
|
+
* its handler.
|
|
649
|
+
*
|
|
650
|
+
* The parse is the point. An op declares what it takes, and that declaration is what the AI tools
|
|
651
|
+
* advertise, what the docs print, and what an example is filled from — but a caller reaching
|
|
652
|
+
* `schemas[verb].handler` directly skipped every word of it, so a missing field arrived as
|
|
653
|
+
* `undefined` and a declared `.default()` never applied at all. Callers were left supplying the
|
|
654
|
+
* defaults themselves, which put a fact about the op (an unstated parent means `root`) in whichever
|
|
655
|
+
* consumer happened to need it, spelled slightly differently each time.
|
|
656
|
+
*
|
|
657
|
+
* Throws the same rejection any other refused write throws, so a bad tool call and a bad patch
|
|
658
|
+
* report identically.
|
|
659
|
+
*
|
|
660
|
+
* The single-op half of {@link run}, which is the door a caller uses.
|
|
661
|
+
*/
|
|
662
|
+
private dispatch;
|
|
663
|
+
/**
|
|
664
|
+
* A whole-body op's input with a bare ref put in the kind's primary field, so an op takes the same
|
|
665
|
+
* shorthand `apply` does (`wholeBodyChange`): `Token.ref('x@ds')` is a token body at either door.
|
|
666
|
+
* Anything else is handed back as it came.
|
|
667
|
+
*/
|
|
668
|
+
private wholeBodyShorthand;
|
|
669
|
+
/**
|
|
670
|
+
* Run an operation: one op, or a list as one changeset.
|
|
671
|
+
*
|
|
672
|
+
* One op is looked up and run, read or write alike, and typed by its verb where the framework
|
|
673
|
+
* declares the verb: a standard write (`create`, `update`, `rename`, `sub-update`, …) produces a
|
|
674
|
+
* config, and a standard read (`list`, `get`, `delete-plan`, …) produces its answer. A custom op is
|
|
675
|
+
* declared by a kind, which the type system cannot enumerate, so it comes back as `unknown`; a caller
|
|
676
|
+
* that wants the config from a custom op runs it as a one-item list.
|
|
677
|
+
*
|
|
678
|
+
* `id` on the single form names the first patch the op produces, for the same reason `apply` takes
|
|
679
|
+
* one — see {@link named}. A custom op applies several patches internally, and the caller cannot hand
|
|
680
|
+
* any of them over, so this is the only place its own id can reach them.
|
|
681
|
+
*/
|
|
682
|
+
run(op: ConfigOp & {
|
|
683
|
+
readonly operation: Patch['operation'];
|
|
684
|
+
}, id?: string): this;
|
|
685
|
+
run(op: ConfigOp & {
|
|
686
|
+
readonly operation: 'list';
|
|
687
|
+
}): Entity[];
|
|
688
|
+
run(op: ConfigOp & {
|
|
689
|
+
readonly operation: 'get';
|
|
690
|
+
}): Entity | undefined;
|
|
691
|
+
run(op: ConfigOp & {
|
|
692
|
+
readonly operation: 'dependents';
|
|
693
|
+
}): string[];
|
|
694
|
+
run(op: ConfigOp & {
|
|
695
|
+
readonly operation: 'rename-plan';
|
|
696
|
+
}): RenamePlan;
|
|
697
|
+
run(op: ConfigOp & {
|
|
698
|
+
readonly operation: 'delete-plan';
|
|
699
|
+
}): DeletePlan;
|
|
700
|
+
run(op: ConfigOp & {
|
|
701
|
+
readonly operation: 'copy-plan';
|
|
702
|
+
}): Omit<CopyPlan, 'patches'>;
|
|
703
|
+
run(op: ConfigOp & {
|
|
704
|
+
readonly operation: 'link-plan';
|
|
705
|
+
}): Omit<LinkPlan, 'patches'>;
|
|
706
|
+
run(op: ConfigOp & {
|
|
707
|
+
readonly operation: 'unlink-plan';
|
|
708
|
+
}): Omit<UnlinkPlan, 'patches'>;
|
|
709
|
+
run(op: ConfigOp & {
|
|
710
|
+
readonly operation: 'break-plan';
|
|
711
|
+
}): Omit<BreakPlan, 'patches'>;
|
|
712
|
+
run(op: ConfigOp & {
|
|
713
|
+
readonly operation: 'rows';
|
|
714
|
+
}): SourceRowState[];
|
|
715
|
+
run(op: ConfigOp & {
|
|
716
|
+
readonly operation: 'group-list';
|
|
717
|
+
}): ReturnType<Config['groups']>;
|
|
718
|
+
run(op: ConfigOp & {
|
|
719
|
+
readonly operation: 'group-get';
|
|
720
|
+
}): {
|
|
721
|
+
path: string;
|
|
722
|
+
meta: Record<string, unknown> | undefined;
|
|
723
|
+
members: string[];
|
|
724
|
+
};
|
|
725
|
+
run(op: ConfigOp, id?: string): unknown;
|
|
726
|
+
run(ops: readonly ConfigEdit[], changeset?: {
|
|
727
|
+
id?: string;
|
|
728
|
+
}): this;
|
|
729
|
+
/**
|
|
730
|
+
* Several ops as one changeset.
|
|
731
|
+
*
|
|
732
|
+
* Each runs against the config the previous one produced, so a refused op refuses the call and
|
|
733
|
+
* nothing lands, and every patch they wrote is stamped with the call's changeset — one undo step and one
|
|
734
|
+
* line in a changes list for what a caller did once. An op that stamped a changeset of its own is
|
|
735
|
+
* folded into the call's: a changeset is flat, and the call is the gesture.
|
|
736
|
+
*
|
|
737
|
+
* An item may name the id its first patch lands under ({@link IdentifiedOp}); the rest are minted.
|
|
738
|
+
*
|
|
739
|
+
* A refusal is the op's own `ConfigRejection` with `opIndex` set to the position of the op that
|
|
740
|
+
* was refused, so a caller reporting on a batch can name the item without running them one by one.
|
|
741
|
+
*/
|
|
742
|
+
private runBatch;
|
|
743
|
+
/**
|
|
744
|
+
* The batch {@link run} would apply, keeping every op this config takes and naming the ones it
|
|
745
|
+
* refuses, by position and reason. An op that depended on a refused one is refused in turn and
|
|
746
|
+
* named the same way. What landed shares one changeset, as a run does; nothing lands when
|
|
747
|
+
* everything was refused.
|
|
748
|
+
*/
|
|
749
|
+
runSkippingRefused(ops: readonly ConfigEdit[], changeset?: {
|
|
750
|
+
id?: string;
|
|
751
|
+
}): {
|
|
752
|
+
config: Config;
|
|
753
|
+
skipped: readonly SkippedOp[];
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* What running `ops` would do, without doing it.
|
|
757
|
+
*
|
|
758
|
+
* Every op is config in, config out, so the ops run against this config as {@link run} would run a list of
|
|
759
|
+
* them, and the patches they appended are read off the result and returned with it dropped. Nothing
|
|
760
|
+
* is stored and nothing reaches a sink; the receiver is untouched. Refuses exactly what the ops
|
|
761
|
+
* refuse, since it runs them, and refuses a read, since a read writes nothing to plan.
|
|
762
|
+
*
|
|
763
|
+
* A patch list cannot show everything an op does. A rename is one patch whose cascade rewrites every
|
|
764
|
+
* referrer; a delete is refused by dependents; a link break resolves every borrowed ref. A kind says
|
|
765
|
+
* so by declaring a `<verb>-plan` read beside the op, and the plan runs that read for each op, against
|
|
766
|
+
* the config as it stands when the op would run, so one plan is the whole account.
|
|
767
|
+
*/
|
|
768
|
+
plan(ops: PlanOp | readonly PlanOp[]): Plan;
|
|
769
|
+
/** This config carrying a CSS grammar for what the generated table leaves unjudged, and every config
|
|
770
|
+
* derived from it carries it too. `undefined` disarms. */
|
|
771
|
+
withCssGrammar(grammar: CssGrammar | undefined): this;
|
|
772
|
+
/** The grammar a write lane armed this config with, if any — see {@link CssGrammar}. */
|
|
773
|
+
get cssGrammar(): CssGrammar | undefined;
|
|
774
|
+
/**
|
|
775
|
+
* The pending patches from `since` onward, as one changeset — the call's, over whatever the ops that
|
|
776
|
+
* produced them stamped, since a changeset is flat and the call is the gesture.
|
|
777
|
+
*
|
|
778
|
+
* What {@link run} on a list ends with, and what a caller that applied a batch entry by entry (to report
|
|
779
|
+
* which entry failed) calls afterwards. The first patch takes `id` when given, and that id is the
|
|
780
|
+
* changeset's; a tail of one patch carries no changeset.
|
|
781
|
+
*/
|
|
782
|
+
withChangeset({
|
|
783
|
+
since,
|
|
784
|
+
id
|
|
785
|
+
}: {
|
|
786
|
+
since: number;
|
|
787
|
+
id?: string;
|
|
788
|
+
}): this;
|
|
789
|
+
/**
|
|
790
|
+
* The patches an op produced, as one changeset.
|
|
791
|
+
*
|
|
792
|
+
* A custom op is one changeset that decomposes into several patches, and the handler mints an id for
|
|
793
|
+
* each — so an optimistic caller, which staged a row under an id of its own before the op ran, gets
|
|
794
|
+
* back a pending log naming patches it has never heard of and can never settle its row. The first
|
|
795
|
+
* patch takes the caller's id, exactly as passing an id to `apply` does for the single-patch case,
|
|
796
|
+
* and that id is also the changeset's, so what the caller staged, what undo drops and what a list groups
|
|
797
|
+
* under are one identifier.
|
|
798
|
+
*
|
|
799
|
+
* A single patch carries no changeset. Several carry one, and a caller whose call is the gesture
|
|
800
|
+
* stamps over what the ops stamped with {@link withChangeset}.
|
|
801
|
+
*
|
|
802
|
+
* Anything that isn't a config passes straight through — a read op (`info`, `api`) returns a value,
|
|
803
|
+
* and there is no patch to name.
|
|
804
|
+
*/
|
|
805
|
+
private named;
|
|
806
|
+
/** The sub-entity a kind's collection field is declared with (`component`/`layers` → `Layer`), or
|
|
807
|
+
* `undefined` when the field isn't a collection. Takes a field path, so a nested collection
|
|
808
|
+
* (`props/value`) resolves too. The accessor a consumer asks for a member's labels or its own
|
|
809
|
+
* `list`/`get`, so nothing has to reach into `subEntities` or fall back to title-casing the key. */
|
|
810
|
+
subEntityOf(kind: string, field: string): SubEntityClass | undefined;
|
|
811
|
+
/** The human label for one item of a kind — the registered `label`, or a title-cased fallback for
|
|
812
|
+
* a kind this config doesn't know (e.g. a synthetic UI group). */
|
|
813
|
+
entityLabel(kind: string): string;
|
|
814
|
+
/** The human label for a collection of a kind — the registered `labelPlural`, or a naive `<Label>s`
|
|
815
|
+
* fallback. */
|
|
816
|
+
entityLabelPlural(kind: string): string;
|
|
817
|
+
/** Spin up a read-view facade for this config — `config.views.components()`,
|
|
818
|
+
* `config.views.token(path)`, `config.views.render(spec)`. */
|
|
819
|
+
get views(): {
|
|
820
|
+
token: (path: string) => string | number | undefined;
|
|
821
|
+
componentProps: (path: string) => Record<string, unknown>;
|
|
822
|
+
stylePropValues: (path: string) => string[];
|
|
823
|
+
styleValue: (value: unknown) => string;
|
|
824
|
+
styleDeclarations: (bag: Record<string, unknown>) => Record<string, string>;
|
|
825
|
+
cssVar: (kind: string, path: string) => string;
|
|
826
|
+
cssVarRef: (kind: string, path: string) => string;
|
|
827
|
+
className: (kind: string, path: string) => string;
|
|
828
|
+
};
|
|
829
|
+
/** Every ref in this config, indexed both ways — `config.refs.dependents(ref)`,
|
|
830
|
+
* `.dependencies(ref)` (each with `{ transitive }`), and `.path(from, to)` for the chain
|
|
831
|
+
* between two entities. Carried on the snapshot and updated per mutation for the entities that
|
|
832
|
+
* actually changed, so a delete-safety check or an impact panel is a lookup, not a re-scan. */
|
|
833
|
+
get refs(): RefGraph;
|
|
834
|
+
/** Bumps on every derivation — the coarse cache key a display layer memoizes against. Includes
|
|
835
|
+
* linked sources' versions, so a change in a linked config bumps this too. Prefer keying on
|
|
836
|
+
* Snapshot slice identity (`memo.ts`), which doesn't invalidate unrelated kinds. */
|
|
837
|
+
get version(): number;
|
|
838
|
+
/** The low-level registration primitive — seed a kind's metadata into a new config (from the
|
|
839
|
+
* Entity class's `kindDef`) and wire its membership: Derive (members computed from a source —
|
|
840
|
+
* self by default), link (a read-only window onto a source's items), or own (load the items in
|
|
841
|
+
* `record`). `protected`: authoring goes through a config type (`defineConfig`), whose typed
|
|
842
|
+
* `register`/`registerLinked` call this per entry — there is no public single-entity register.
|
|
843
|
+
*
|
|
844
|
+
* A value in `record` is a group node (`Token.group(...)`), a raw item (`Token.item(...)` / bare
|
|
845
|
+
* shorthand), or a typed authoring handle (`defineComponent` → any `Authored`, which adopts its
|
|
846
|
+
* path and normalizes via `toStored()`). Entries register in order, so one may reference another. */
|
|
847
|
+
protected registerEntity<F extends FieldsSchema, G extends FieldsSchema>(entityClass: EntityClass<F, G> | {
|
|
848
|
+
readonly kind: string;
|
|
849
|
+
readonly isDerived: true;
|
|
850
|
+
readonly kindDef: EntityKind;
|
|
851
|
+
}, record?: Record<string, AuthoredEntry<F, G>>): this;
|
|
852
|
+
/**
|
|
853
|
+
* Every item of `kind`, with its factory sub-entity members coerced — what makes an authored
|
|
854
|
+
* collection store what the same write through a sub-op would have stored.
|
|
855
|
+
*
|
|
856
|
+
* Without it a component authored with its `styles` inline holds a condition exactly as typed
|
|
857
|
+
* while one edited through `sub-create` holds the ref its schema transforms it into, and every
|
|
858
|
+
* reader has to accept both forever. A no-op for a kind with no factory members, which is most.
|
|
859
|
+
*/
|
|
860
|
+
/**
|
|
861
|
+
* Re-coerce every owned kind — run after a `register` call has loaded all of its records.
|
|
862
|
+
*
|
|
863
|
+
* A member's schema is built from the config, so a value that names another kind's item can only
|
|
864
|
+
* bind once that item exists. `registerEntity` coerces the kind it just wrote, which makes forward
|
|
865
|
+
* depend on the order the keys happen to appear in one `register({ … })` literal: a component listed
|
|
866
|
+
* before the motion presets it defaults to saw no presets yet and stored a bare name, silently, with
|
|
867
|
+
* the reference missing from the graph. A second pass over everything removes the ordering from the
|
|
868
|
+
* answer.
|
|
869
|
+
*
|
|
870
|
+
* Idempotent, which is what makes a second pass safe: forward turns a leaf into a ref, and a ref is
|
|
871
|
+
* not a leaf, so re-coercing already-bound data is a no-op that allocates nothing.
|
|
872
|
+
*/
|
|
873
|
+
protected coerceOwned(): this;
|
|
874
|
+
/**
|
|
875
|
+
* Refuse two source kinds claiming one slug.
|
|
876
|
+
*
|
|
877
|
+
* A ref spells `@ds` and nothing more, so a slug resolves against the config rather than against a
|
|
878
|
+
* collection: {@link sourceFor}, the attached sources, the recorded closures and the resolved states
|
|
879
|
+
* are every one of them keyed by slug alone. Two kinds declaring `ds` therefore cannot both be
|
|
880
|
+
* reachable — one silently wins every read, and which one depends on the order they were registered
|
|
881
|
+
* in.
|
|
882
|
+
*
|
|
883
|
+
* Checked rather than assumed. "The slug namespace is flat" is a property of the ref grammar, and
|
|
884
|
+
* nothing was making the data honour it: a config declaring `linkedSystems: { ds }` and
|
|
885
|
+
* `linkedPackages: { ds }` registered clean, `sourceSlugs()` answered `['ds', 'ds']`, and every read
|
|
886
|
+
* through `@ds` picked a winner. Unreachable while a config declares one source kind, and the first
|
|
887
|
+
* thing to go wrong the day it declares two — which is exactly what `sources` exists to allow.
|
|
888
|
+
*
|
|
889
|
+
* A slug equal to this config's own {@link name} is refused for the same reason: {@link configFor}
|
|
890
|
+
* answers a ref qualified with the name locally, so a source declared under it would shadow this
|
|
891
|
+
* config's own entities for every reader holding such a ref.
|
|
892
|
+
*
|
|
893
|
+
* The cross-kind check is free for a config with fewer than two source kinds, which is every config
|
|
894
|
+
* today.
|
|
895
|
+
*/
|
|
896
|
+
private assertDistinctSourceSlugs;
|
|
897
|
+
private coerceKind;
|
|
898
|
+
/**
|
|
899
|
+
* Seed a kind in place — legal only from a constructor, where the object being built isn't
|
|
900
|
+
* observable to anyone yet, so this isn't mutation in the sense the rest of the class avoids.
|
|
901
|
+
* `defineConfig` uses it to install a config type's manifest without a clone per kind.
|
|
902
|
+
*/
|
|
903
|
+
protected seedEntityInPlace(entityClass: {
|
|
904
|
+
readonly kind: string;
|
|
905
|
+
readonly kindDef: EntityKind;
|
|
906
|
+
}): void;
|
|
907
|
+
/** Apply one patch — the recorded unit of change, in the shape a draft stores and replays.
|
|
908
|
+
*
|
|
909
|
+
* `apply` and {@link run} are two doors on one wall. A {@link Patch} is one of the framework's own
|
|
910
|
+
* verbs (`create`, `update`, `rename`, `sub-update`, …) with its body already in stored shape, and
|
|
911
|
+
* `apply` performs exactly that one and records it. A {@link ConfigOp} is anything the catalog
|
|
912
|
+
* addresses — those same verbs, and custom ops such as `linked-system/copy` that decompose into
|
|
913
|
+
* several patches — and `run` parses its input against the op's own schema and hands it to the op's
|
|
914
|
+
* handler, which for a standard verb is a call to `apply`. So `apply` is what a handler and a
|
|
915
|
+
* replay call, and `run` is what a caller calls: a tool, an editor gesture, a commit. A caller
|
|
916
|
+
* reaching for `apply` directly skips the op's input parse (its defaults, its refinements) and
|
|
917
|
+
* cannot spell a custom op at all.
|
|
918
|
+
*
|
|
919
|
+
* `create`/`update` bodies are validated against the schema-derived shapes; `update`/`delete`
|
|
920
|
+
* require an existing target. Throws on an invalid body or missing target.
|
|
921
|
+
*
|
|
922
|
+
* Returns a new config; the receiver is unchanged. A caller that drops the return has applied
|
|
923
|
+
* nothing.
|
|
924
|
+
*
|
|
925
|
+
* `id` lets a caller name the patch it just made. A client that paints optimistically stages a row
|
|
926
|
+
* under an id of its own and has to settle that row when the write lands, so it needs the pending
|
|
927
|
+
* entry and its own staging to agree — and an id minted here would be a second source that
|
|
928
|
+
* silently disagrees with it. Omitted, one is minted, which is what every non-optimistic caller
|
|
929
|
+
* wants. */
|
|
930
|
+
apply(patch: Patch, id?: string): this;
|
|
931
|
+
/**
|
|
932
|
+
* Several patches as one step: each validates against the state the previous one produced, as
|
|
933
|
+
* {@link apply} would, and the config is derived once at the end rather than once per patch.
|
|
934
|
+
*
|
|
935
|
+
* What a handler that writes a row per entity uses. Deriving is the larger share of an apply, so
|
|
936
|
+
* a copy of 2,600 entities takes a third of the time it would one patch at a time, and every
|
|
937
|
+
* patch still lands in `pending` with its own stamp, so the sink and a replay see the same list.
|
|
938
|
+
*
|
|
939
|
+
* A patch on a source kind goes through {@link apply} on its own, since that is where a pin that
|
|
940
|
+
* moves retires what was held under its slug, and where the rows its declaration mints re-derive.
|
|
941
|
+
*
|
|
942
|
+
* Several patches are one gesture, so they share one changeset; a single patch carries none.
|
|
943
|
+
*/
|
|
944
|
+
applyAll(patches: readonly Patch[]): this;
|
|
945
|
+
/**
|
|
946
|
+
* Replay the pending patches onto a new base — what a remote change calls.
|
|
947
|
+
*
|
|
948
|
+
* Each patch re-validates against the state the previous one produced, so a patch whose target
|
|
949
|
+
* the remote change removed (or whose derived member key it re-keyed) fails exactly as a fresh
|
|
950
|
+
* apply would. Those are dropped and reported rather than silently skipped: the local edit really
|
|
951
|
+
* is gone, and the surface that made it needs to be able to say so.
|
|
952
|
+
*
|
|
953
|
+
* A replayed pin patch does not re-derive rows the way {@link apply} does. A draft stored before
|
|
954
|
+
* pin patches re-derived carries a `delete` per minted row after its `link: null`, and those have
|
|
955
|
+
* to replay whole; the session derives once after the replay instead.
|
|
956
|
+
*/
|
|
957
|
+
rebase(base: Snapshot): RebaseResult<this>;
|
|
958
|
+
/**
|
|
959
|
+
* One patch of a changeset onto a scratch snapshot, with the same second try a lone patch gets: a
|
|
960
|
+
* patch may fail because the rows it needs had not been derived yet (the pin it depends on is in
|
|
961
|
+
* this draft), so the replay derives against what it has built so far and goes once more.
|
|
962
|
+
*/
|
|
963
|
+
private replayOne;
|
|
964
|
+
/**
|
|
965
|
+
* Attach the draft log to a snapshot that already includes it.
|
|
966
|
+
*
|
|
967
|
+
* The client's counterpart to {@link withDraft}, and deliberately not the same thing. A browser
|
|
968
|
+
* receives the config the server already resolved — base plus draft, applied — so replaying the
|
|
969
|
+
* draft over it would apply every edit twice. But `fromJSON` seeds `pending: []`, so what arrives
|
|
970
|
+
* is a config that cannot say which patches compose it: `config.patches` is empty on mount and
|
|
971
|
+
* after every true-up, and every question of the form "is this element new in this draft" has to
|
|
972
|
+
* be re-derived by scanning stored rows instead.
|
|
973
|
+
*
|
|
974
|
+
* So the patches are recorded, not performed. The snapshot is untouched.
|
|
975
|
+
*
|
|
976
|
+
* The consequence to know: `base` stays where it is, which for a client is the resolved state —
|
|
977
|
+
* so this config knows its draft but cannot rewind it. {@link rebase} and {@link merge} are
|
|
978
|
+
* therefore not meaningful on one built this way, and a client doesn't call them: it re-hydrates
|
|
979
|
+
* wholesale from the server instead.
|
|
980
|
+
*/
|
|
981
|
+
withResolvedDraft(patches: readonly StampedPatch[]): this;
|
|
982
|
+
/**
|
|
983
|
+
* Drop patches the base now contains — what a merge produces.
|
|
984
|
+
*
|
|
985
|
+
* Merging a branch's draft publishes a new artifact that already includes these edits, so keeping
|
|
986
|
+
* them pending would apply them twice. Note this is not about persistence: a patch written to
|
|
987
|
+
* `draft_patches` is durable but still part of the draft, and still belongs here.
|
|
988
|
+
*
|
|
989
|
+
* Unknown ids are ignored, since a merge may be observed more than once.
|
|
990
|
+
*/
|
|
991
|
+
merge(patchIds: readonly string[]): this;
|
|
992
|
+
/**
|
|
993
|
+
* Adopt a shared draft — take `patches` as the pending list and replay them onto the current base.
|
|
994
|
+
*
|
|
995
|
+
* The collaborative case. A branch's draft is one list every client can see, so a client takes it
|
|
996
|
+
* whole rather than reconciling it entry by entry. Replacing rather than appending is what makes
|
|
997
|
+
* a client's own echo harmless: its patch comes back inside a list it is already part of, so it
|
|
998
|
+
* is replayed exactly once.
|
|
999
|
+
*
|
|
1000
|
+
* The base does not move here — only the draft over it does. A patch the current base can no
|
|
1001
|
+
* longer take is dropped and reported, exactly as in `rebase`.
|
|
1002
|
+
*/
|
|
1003
|
+
withDraft(patches: readonly StampedPatch[]): RebaseResult<this>;
|
|
1004
|
+
/**
|
|
1005
|
+
* One complete item body as a change — the shared half of `create` and `replace`, which differ only
|
|
1006
|
+
* in whether the path has to be free or has to be taken.
|
|
1007
|
+
*
|
|
1008
|
+
* Three body shapes, all of which a config file already authors, which is why they're accepted here
|
|
1009
|
+
* rather than only by `register`:
|
|
1010
|
+
*
|
|
1011
|
+
* • a field bag — the ordinary case, validated against the kind's create schema.
|
|
1012
|
+
* • a bare ref — `{ __ref: 'token:color/brand@ds' }` borrows through a source, and `primaryField`
|
|
1013
|
+
* is the field the kind puts it in. That is why `Token.ref(…)` reads as a token in a config file.
|
|
1014
|
+
* `itemChange` does the wrap, but it runs after the parse, so without this a patch would be the
|
|
1015
|
+
* one door refusing a shorthand every other door takes.
|
|
1016
|
+
* • an extension — `{ __ref, extend }`, stored as framework data rather than parsed as a body,
|
|
1017
|
+
* because its delta is sparse and a complete-body schema would refuse it.
|
|
1018
|
+
*/
|
|
1019
|
+
private wholeBodyChange;
|
|
1020
|
+
/** The next snapshot for one patch, validated. Pure in `snapshot`; reads metadata off `this`. */
|
|
1021
|
+
private computeApply;
|
|
1022
|
+
/**
|
|
1023
|
+
* This config's reads over another snapshot, for a check that wants nothing else: a plain clone
|
|
1024
|
+
* with the snapshot swapped in. Unlike {@link at}, it records no parent link for the snapshot, so a
|
|
1025
|
+
* batch or a replay that checks every intermediate step keeps none of them alive.
|
|
1026
|
+
*/
|
|
1027
|
+
private viewOf;
|
|
1028
|
+
/**
|
|
1029
|
+
* Refuse a state the kind's {@link EntityKind.invariants} reject, or one where a kind declaring
|
|
1030
|
+
* {@link EntityKind.uniqueLeaves} holds a leaf twice. One kind when a patch names it, every kind
|
|
1031
|
+
* after a registration. Never called on hydration.
|
|
1032
|
+
*/
|
|
1033
|
+
private assertInvariants;
|
|
1034
|
+
/** One sentence per leaf that two items of `kind` hold, since a leaf has to name one item. */
|
|
1035
|
+
private duplicateLeaves;
|
|
1036
|
+
/** `patch` with a bare leaf resolved to the path it names — see {@link leafPath}. */
|
|
1037
|
+
private leafAddressed;
|
|
1038
|
+
private computeApplyPatch;
|
|
1039
|
+
/** Apply a mutation to a derived kind — only `update`, which merges a sparse override of the
|
|
1040
|
+
* `overridable` fields (the base always re-derives). Create/delete/rename don't exist. */
|
|
1041
|
+
private applyDerived;
|
|
1042
|
+
/** Sub-entity CRUD — mutate one member inside an item's keyed collection field
|
|
1043
|
+
* (`layers`/`props`): create/update/delete/rename a member, or reorder the collection.
|
|
1044
|
+
* The item re-hydrates so refs inside the member flow into the dependency graph. */
|
|
1045
|
+
private applySub;
|
|
1046
|
+
/** Which names a member's delta may address as fields — read off the collection's own member
|
|
1047
|
+
* schema, resolved for this owner, so a per-owner shape (a style rule's `layers`, keyed by the
|
|
1048
|
+
* Component's own layers) answers for the member actually being written. */
|
|
1049
|
+
private memberFields;
|
|
1050
|
+
/**
|
|
1051
|
+
* The key a member of `field` on `kind` is stored under when the collection derives it from the
|
|
1052
|
+
* member itself — an override's from its `on`. `undefined` where the caller's key stands, and for a
|
|
1053
|
+
* member the derivation cannot read.
|
|
1054
|
+
*/
|
|
1055
|
+
memberKeyOf({
|
|
1056
|
+
kind,
|
|
1057
|
+
field,
|
|
1058
|
+
member
|
|
1059
|
+
}: {
|
|
1060
|
+
kind: string;
|
|
1061
|
+
field: string;
|
|
1062
|
+
member: unknown;
|
|
1063
|
+
}): string | undefined;
|
|
1064
|
+
/** A member's derived key, when its collection declares that members are identified by their
|
|
1065
|
+
* value (`defineSubEntity({ key })`). `undefined` = the caller's key stands. */
|
|
1066
|
+
private memberKey;
|
|
1067
|
+
/** Validate a sub-entity member body against the field's sub-op data schema (`sub-create/<field>`
|
|
1068
|
+
* or `sub-update/<field>` in `schemas`), returning the parsed (coerced) output. A field with no
|
|
1069
|
+
* derived member schema stores as-is. */
|
|
1070
|
+
private validateMember;
|
|
1071
|
+
/**
|
|
1072
|
+
* One member through its per-owner schema, for its coercions only — whatever doesn't fit is
|
|
1073
|
+
* returned exactly as authored, field by field ({@link coerceFieldwise}).
|
|
1074
|
+
*
|
|
1075
|
+
* Lenient on purpose. This runs on paths that never validated factory members at all (an authored
|
|
1076
|
+
* Config's inline collections, an item create), so rejecting here would turn a coercion into a new
|
|
1077
|
+
* gate and fail configs that load today. Validation stays where it already is: the sub-ops, which
|
|
1078
|
+
* refuse through {@link validateMember}.
|
|
1079
|
+
*/
|
|
1080
|
+
private coerceMember;
|
|
1081
|
+
/**
|
|
1082
|
+
* Run every member of every sub-entity collection through its own member schema.
|
|
1083
|
+
*
|
|
1084
|
+
* An authored config never meets that schema otherwise: `register` stores the body as written, so
|
|
1085
|
+
* a component authored with its `styles` inline stored a condition exactly as typed while the same
|
|
1086
|
+
* rule written through `sub-create` stored the ref its schema transforms it into. Two storage
|
|
1087
|
+
* forms for one statement, decided by which door the write came through — and every reader
|
|
1088
|
+
* downstream then has to accept both forever.
|
|
1089
|
+
*
|
|
1090
|
+
* Running it here is what makes the authored path and the op path agree, which is why it covers
|
|
1091
|
+
* static members too and not only the per-owner factories: a variant prop's values are a keyed
|
|
1092
|
+
* collection whichever way they were authored, and the list an author writes is coerced by the
|
|
1093
|
+
* member's own schema. Members are re-keyed afterwards for the same reason `cascadeRename` does
|
|
1094
|
+
* it: a coercion can change a derived key.
|
|
1095
|
+
*
|
|
1096
|
+
* `written` scopes it to the fields a write actually carried. Coercion belongs to the write that
|
|
1097
|
+
* stores a value, and a collection the patch didn't touch was coerced by the write that did store
|
|
1098
|
+
* it — so an update setting one field has no reason to re-derive the per-owner schema of every
|
|
1099
|
+
* other collection on the item. Omitted (create, registration) means the whole body was written.
|
|
1100
|
+
*
|
|
1101
|
+
* `strict` is a whole new body — a create or a replace — which refuses a member its per-owner
|
|
1102
|
+
* schema refuses, exactly as the member's own sub-op would; otherwise a component created whole
|
|
1103
|
+
* could carry a style rule no `sub-create/styles` takes, and the first later edit of that rule,
|
|
1104
|
+
* which re-validates it merged, would be refused for a key the create let in. An update stays
|
|
1105
|
+
* lenient, as registration and hydration do ({@link coerceMember}): the build records the anatomy
|
|
1106
|
+
* a render actually has through `update`, and a stored body has to load so it can be fixed.
|
|
1107
|
+
*/
|
|
1108
|
+
private normalizeMembers;
|
|
1109
|
+
/**
|
|
1110
|
+
* A body's refs to a kind with unique leaves, spelled by path. An author writes
|
|
1111
|
+
* `Modifier.ref('dark')`; the graph holds `modifier:colorMode/dark`, so one spelling is what the
|
|
1112
|
+
* index sees and what a rename cascade finds. A ref that already spells a path, or that reaches
|
|
1113
|
+
* through a source, is left alone.
|
|
1114
|
+
*/
|
|
1115
|
+
private canonicalLeafRefs;
|
|
1116
|
+
/**
|
|
1117
|
+
* Every reference to `from` rewritten to name `to` — the entities at either address untouched.
|
|
1118
|
+
*
|
|
1119
|
+
* The third answer to a link collision, and the one that makes a design spec portable: a spec stores
|
|
1120
|
+
* paths, so a system that repoints its refs at the source's qualified address starts speaking the
|
|
1121
|
+
* source's vocabulary and its specs travel. Re-aliasing is the other answer — keep your path, point
|
|
1122
|
+
* its body at theirs — and the two are not reversible into one another for free, which is why the
|
|
1123
|
+
* `link` op asks rather than defaulting.
|
|
1124
|
+
*
|
|
1125
|
+
* Only the dependents the ref index names are visited, the same as a rename.
|
|
1126
|
+
*
|
|
1127
|
+
* **A name held as a key is not moved, and this refuses rather than half-moving it.** A style
|
|
1128
|
+
* property and a modifier appear in a styles bag as keys rather than refs, and `to` here is a
|
|
1129
|
+
* qualified address (`style-property:bg@ds`), which is not a thing a bag key can be. So after the
|
|
1130
|
+
* refs are rewritten anything still depending on `from` is depending by name, and the caller is told
|
|
1131
|
+
* which — a repoint that silently left those keys naming a row it had just deleted would break the
|
|
1132
|
+
* styles that use them with nothing to read.
|
|
1133
|
+
*/
|
|
1134
|
+
repointRefs({
|
|
1135
|
+
from,
|
|
1136
|
+
to
|
|
1137
|
+
}: {
|
|
1138
|
+
from: string;
|
|
1139
|
+
to: string;
|
|
1140
|
+
}): this;
|
|
1141
|
+
/**
|
|
1142
|
+
* Rewrite every ref `from` → `to`, across items and group nodes alike.
|
|
1143
|
+
*
|
|
1144
|
+
* Only the entities the ref index says actually point at `from` are visited. The previous version
|
|
1145
|
+
* re-serialized every item of every kind to find the few that matched, which made a rename cost
|
|
1146
|
+
* the whole config; the index already knows the answer.
|
|
1147
|
+
*/
|
|
1148
|
+
private cascadeRename;
|
|
1149
|
+
/**
|
|
1150
|
+
* Rename one key inside every record that is keyed by the renamed collection — a style rule's
|
|
1151
|
+
* `when`, which names the props it conditions on.
|
|
1152
|
+
*
|
|
1153
|
+
* The counterpart to a ref rewrite, for the one place a rename can't be expressed as one. Refs
|
|
1154
|
+
* carry identity in a value, where {@link rewriteRefs} can find them; a record carries it in a
|
|
1155
|
+
* key, where nothing generic can — so the sub-entity declares which of its fields work that way
|
|
1156
|
+
* (`namesMembersOf`) and this walks exactly those.
|
|
1157
|
+
*
|
|
1158
|
+
* Key order is preserved: `when`'s order feeds `canonicalWhen`, and a rebuilt record that reorders
|
|
1159
|
+
* would change the derived key of a rule nothing asked to move.
|
|
1160
|
+
*/
|
|
1161
|
+
private rekeyRecords;
|
|
1162
|
+
/** Re-derive every derived member key in `def`. The key function is the single source of a
|
|
1163
|
+
* member's identity (`memberKey` already runs it on sub-create/sub-update), so re-running it
|
|
1164
|
+
* after any change to the member's data keeps key and value from drifting. Collections with
|
|
1165
|
+
* caller-chosen keys (layers, props) have no key function and pass through untouched. */
|
|
1166
|
+
private rekeyMembers;
|
|
1167
|
+
/** Merge a partial over a group node's body, creating the node when the folder is emergent — a
|
|
1168
|
+
* group exists as soon as an item path implies it, so requiring a node first would make a folder
|
|
1169
|
+
* the UI already renders uneditable. `null` clears an optional field, as on an item update. */
|
|
1170
|
+
private applyGroupUpdate;
|
|
1171
|
+
/**
|
|
1172
|
+
* A group's members — every item whose path extends the prefix.
|
|
1173
|
+
*
|
|
1174
|
+
* The separator is part of the match, so `color/` never catches `colorScheme/fg`, and a member in a
|
|
1175
|
+
* subgroup (`color/brand/primary`) belongs to `color` too. Getting either wrong is silent: one drags
|
|
1176
|
+
* an unrelated group along, the other skips half of what the user sees under the heading.
|
|
1177
|
+
*/
|
|
1178
|
+
private groupMembers;
|
|
1179
|
+
/**
|
|
1180
|
+
* The declared group nodes nested under `group`, at any depth. Sub-groups are stored apart from a
|
|
1181
|
+
* group's items, so a group op has to move or remove them explicitly. Emergent sub-groups exist
|
|
1182
|
+
* because an item is named into them and follow the items by construction.
|
|
1183
|
+
*/
|
|
1184
|
+
private descendantGroups;
|
|
1185
|
+
private applyGroupRename;
|
|
1186
|
+
private applyGroupDelete;
|
|
1187
|
+
/** A derived kind's members, memoized against the source's snapshot (recompute only on change). */
|
|
1188
|
+
private derivedMembers;
|
|
1189
|
+
/** Resolve one derived member — base ⊕ sparse override, with `computed` fields evaluated on read
|
|
1190
|
+
* (never stored). Returns `undefined` for a path that doesn't derive. */
|
|
1191
|
+
private resolveDerived;
|
|
1192
|
+
resolve(kind: string, path: string): Entity | undefined;
|
|
1193
|
+
/**
|
|
1194
|
+
* Whether this config holds `target`'s entity, at the path the source spells it, through `slug`.
|
|
1195
|
+
*
|
|
1196
|
+
* The narrow question, and the narrowness is the point. A row merely occupying that path is not
|
|
1197
|
+
* enough — a borrower with its own `primitives/Box` has a different entity that happens to share a
|
|
1198
|
+
* name, and reading the source's render against it would swap what the render was written for
|
|
1199
|
+
* without anything looking broken. This asks whether the row is a pointer at the same entity, so
|
|
1200
|
+
* that answering yes means the two spellings cannot disagree.
|
|
1201
|
+
*
|
|
1202
|
+
* Entity-level pointers only. A value borrow leaves the entity the borrower's own, holding what
|
|
1203
|
+
* theirs is worth, so it is the first case rather than this one.
|
|
1204
|
+
*/
|
|
1205
|
+
private borrowsExactly;
|
|
1206
|
+
/**
|
|
1207
|
+
* The body a linked extension resolves to before this config's kind parses it — the pinned source's
|
|
1208
|
+
* own fields, qualified into this namespace, with the locally authored delta laid over them.
|
|
1209
|
+
*
|
|
1210
|
+
* Split out of {@link resolveExtension} so {@link canBorrow} can ask whether a borrow would parse
|
|
1211
|
+
* here against the same merge a resolve performs. Two merges would disagree about the one case either
|
|
1212
|
+
* exists for, the record-valued fields that merge a level deep, and the disagreement would surface
|
|
1213
|
+
* only as a borrow the picker offered and the read then refused.
|
|
1214
|
+
*/
|
|
1215
|
+
private extensionBody;
|
|
1216
|
+
/**
|
|
1217
|
+
* Resolve one locally extended linked item: the pinned source body, shallowly overlaid by the fields
|
|
1218
|
+
* authored here, then parsed through this config's kind. Parsing on this side is what lets a React
|
|
1219
|
+
* Native kind transform or reject a web value rather than trusting the source kind's rules.
|
|
1220
|
+
*
|
|
1221
|
+
* `strict` says what to do when the kind refuses it, and the two callers want different things:
|
|
1222
|
+
*
|
|
1223
|
+
* `true` a write is going through this body — a sub-op editing the inherited bag, or `materialize`
|
|
1224
|
+
* sealing it into a local item. A body the kind refuses can't be written, so it throws and
|
|
1225
|
+
* names the field.
|
|
1226
|
+
* `false` a read. A borrow whose body this kind refuses is one this config can't show, which is the
|
|
1227
|
+
* same answer as a source that never resolved: nothing. It must not throw, because the row is
|
|
1228
|
+
* stored, so `list`, `sourceClosure` and `toJSON` all reach it on their way past, and a throw
|
|
1229
|
+
* from inside serialization has no caller in a position to catch it.
|
|
1230
|
+
*
|
|
1231
|
+
* {@link canBorrow} is the door that keeps such a row from being written; this is what a config
|
|
1232
|
+
*/
|
|
1233
|
+
private resolveExtension;
|
|
1234
|
+
/**
|
|
1235
|
+
* Whether an entity in the catalogue of the source `slug` names can be borrowed into this config —
|
|
1236
|
+
* asked before the ref is written, which is the only moment anything can be done about the answer.
|
|
1237
|
+
*
|
|
1238
|
+
* Two ways it can be no, and they're different failures:
|
|
1239
|
+
*
|
|
1240
|
+
* - **This config has no such kind.** Two config types need not declare the same ones — a native
|
|
1241
|
+
* System declares no `motion` or `global-style` — so a source offering them is offering something
|
|
1242
|
+
* with nowhere to land.
|
|
1243
|
+
* - **The kind exists and the body doesn't fit it.** Same kind string, different value domain: a
|
|
1244
|
+
* Native modifier's `{ state: 'pressed' }` selector has no arm in the web `Modifier`'s union.
|
|
1245
|
+
* Writing that borrow leaves a row nothing here can read.
|
|
1246
|
+
*
|
|
1247
|
+
* Judged by the merge a resolve would perform ({@link extensionBody}), so what a surface offers and
|
|
1248
|
+
* what the read produces can't drift. A slug with nothing resolved is `ok` rather than refused: the
|
|
1249
|
+
* catalogue is what this judges against, and "not loaded here" isn't a verdict about the entity.
|
|
1250
|
+
*/
|
|
1251
|
+
canBorrow({
|
|
1252
|
+
kind,
|
|
1253
|
+
path,
|
|
1254
|
+
slug,
|
|
1255
|
+
extend
|
|
1256
|
+
}: {
|
|
1257
|
+
kind: string;
|
|
1258
|
+
path: string;
|
|
1259
|
+
slug: string;
|
|
1260
|
+
/** The delta the borrow would carry, if any — a local answer is exactly how a body that would not
|
|
1261
|
+
* otherwise fit is brought into range, so judging without it would refuse a legal borrow. */
|
|
1262
|
+
extend?: Record<string, unknown>;
|
|
1263
|
+
}): {
|
|
1264
|
+
ok: true;
|
|
1265
|
+
} | {
|
|
1266
|
+
ok: false;
|
|
1267
|
+
error: string;
|
|
1268
|
+
};
|
|
1269
|
+
/**
|
|
1270
|
+
* The kinds this config could take from `slug` at all — what the source declares, narrowed to what
|
|
1271
|
+
* this config also declares.
|
|
1272
|
+
*
|
|
1273
|
+
* The intersection is the whole content, and it's a real question because the two sides need not
|
|
1274
|
+
* share a platform: a native system declares no `motion` or `global-style`, and a web source offering
|
|
1275
|
+
* both is offering kinds with nowhere to land. A surface enumerating the source's kinds instead would
|
|
1276
|
+
* send an op per kind for things this config can't hold.
|
|
1277
|
+
*
|
|
1278
|
+
* Capability only. Whether a kind is one a person should take wholesale — their pins, their editor
|
|
1279
|
+
* configuration, their sealed files — is a judgement about what the kind means, and belongs to the
|
|
1280
|
+
* surface offering the gesture.
|
|
1281
|
+
*/
|
|
1282
|
+
borrowableKinds(slug: string): string[];
|
|
1283
|
+
/**
|
|
1284
|
+
* Which config answers for `kind`/`path` — this one, or the source of a linked kind.
|
|
1285
|
+
*
|
|
1286
|
+
* Provenance, resolved per item rather than per kind, and that is the whole point. A borrowed
|
|
1287
|
+
* Entity's own fields were written in its source's namespace: its unqualified `token:color/brand`
|
|
1288
|
+
* means the source's `color/brand`, and its var name carries the source's `cssPrefix`. Interpreting
|
|
1289
|
+
* it against the borrowing config names a custom property nothing sets, and the borrowed component
|
|
1290
|
+
* paints its default with nothing thrown.
|
|
1291
|
+
*
|
|
1292
|
+
* Asking per item is also what makes two links bringing the same kind unambiguous: the answer is the
|
|
1293
|
+
* Config that has the item, so there is no precedence to define and no first-match to depend on.
|
|
1294
|
+
*
|
|
1295
|
+
* Routing only — no entity is built. Every ref rendered into a stylesheet goes through here
|
|
1296
|
+
* ({@link configFor} → `renderStyleValue`), so it walks the snapshot and the declared links and
|
|
1297
|
+
* stops; {@link resolvedIn} is the pair for a caller that wants the item too.
|
|
1298
|
+
*
|
|
1299
|
+
* Recursive, because a source may itself link.
|
|
1300
|
+
*/
|
|
1301
|
+
ownerOf(kind: string, path: string): Config | undefined;
|
|
1302
|
+
/**
|
|
1303
|
+
* The item at `kind`/`path` and the config that answered for it.
|
|
1304
|
+
*
|
|
1305
|
+
* Every read that cares where an item came from goes through here, because the two halves must not be
|
|
1306
|
+
* chosen separately: a caller that resolved the item here and then interpreted it against a config of
|
|
1307
|
+
* its own choosing gets a plausible wrong answer whenever those differ — a borrowed token carries the
|
|
1308
|
+
* source's `cssPrefix`, and a borrowed entity's unqualified refs name the source's vocabulary.
|
|
1309
|
+
*
|
|
1310
|
+
* {@link ownerOf} picks the config; this builds the item there. Split so the routing can be asked for
|
|
1311
|
+
* on its own, and so the two can never disagree about who answers.
|
|
1312
|
+
*/
|
|
1313
|
+
private resolvedIn;
|
|
1314
|
+
/**
|
|
1315
|
+
* The path a bare leaf names, for a kind that declares {@link EntityKind.uniqueLeaves}. `undefined`
|
|
1316
|
+
* for any other kind, for a leaf nothing holds, or for a name that already carries a slash.
|
|
1317
|
+
*/
|
|
1318
|
+
leafPath(kind: string, leaf: string): string | undefined;
|
|
1319
|
+
/**
|
|
1320
|
+
* Every spelling an edge to `kind:path` may carry: the path itself, and for a kind with unique
|
|
1321
|
+
* leaves the bare leaf, since a name-borne edge (`modifier:dark`) is minted from a bag key that
|
|
1322
|
+
* cannot spell the path. Read wherever the index is asked who points at an entity.
|
|
1323
|
+
*/
|
|
1324
|
+
private refAliases;
|
|
1325
|
+
/** Who points at `kind:path`, under every spelling {@link refAliases} admits. */
|
|
1326
|
+
private referrersOf;
|
|
1327
|
+
/** The path an item read addresses. A singleton has one item, at the kind's own name, so any path
|
|
1328
|
+
* a ref spells is a path INTO it and the item is what resolves; {@link valueAt} reads the rest. */
|
|
1329
|
+
private itemPath;
|
|
1330
|
+
/**
|
|
1331
|
+
* The value a ref addresses, member path included: an item's whole body, one member of a keyed
|
|
1332
|
+
* collection (`component:Button#props/gap`), or a field of a singleton's one item spelled as the path
|
|
1333
|
+
* (`settings:css/prefix`, `settings:css`). A qualified ref reads through its source. `undefined` when
|
|
1334
|
+
* nothing is there.
|
|
1335
|
+
*/
|
|
1336
|
+
valueAt(target: string | Ref): unknown;
|
|
1337
|
+
/** The item this config stores or derives at `kind`/`path`, with no routing — {@link ownerOf} has
|
|
1338
|
+
* already chosen who answers, so this must not delegate or the two would recurse into each other. */
|
|
1339
|
+
private storedEntity;
|
|
1340
|
+
/**
|
|
1341
|
+
* A stored entity, with its kind's {@link ComputedFields} evaluated against this config.
|
|
1342
|
+
*
|
|
1343
|
+
* A stored `Entity` is built when the snapshot changes, so it holds no config and can't compute
|
|
1344
|
+
* anything itself. The config it lives in is only known at read time, which is here.
|
|
1345
|
+
*
|
|
1346
|
+
* **Memoized, because entity reads are reference-stable and code depends on it.** `Component.get`
|
|
1347
|
+
* returning a fresh instance per call would put a new value in every dep array that holds one, which
|
|
1348
|
+
* is the same failure a derivation inside a zustand selector causes, arriving from the other
|
|
1349
|
+
* direction. Keyed on the snapshot rather than the config instance: a config is a value, so a cache
|
|
1350
|
+
* living on one would be discarded by the next `apply`, where a snapshot survives every derivation
|
|
1351
|
+
* that didn't change the data.
|
|
1352
|
+
*
|
|
1353
|
+
* The snapshot alone isn't the whole key, because two configs can share one and still disagree on a
|
|
1354
|
+
* computed — attaching a linked source derives a new config over the same data, which is what flips a
|
|
1355
|
+
* Token's `link` from unresolved to resolved. So it shares {@link cachesFor}'s bucket, which adds the
|
|
1356
|
+
* attached sources by identity. Keyed on slug names, re-pointing a slug at a newer source under the
|
|
1357
|
+
* same name would look like nothing changed, and a computed reading through that source would serve
|
|
1358
|
+
* the pre-swap answer for the rest of the session.
|
|
1359
|
+
*
|
|
1360
|
+
* A kind that declares no computeds is handed straight back, so the common path allocates nothing.
|
|
1361
|
+
*/
|
|
1362
|
+
private withComputed;
|
|
1363
|
+
/**
|
|
1364
|
+
* Attach loaded sources, in the shape everything else names them in — keyed by the source kind's
|
|
1365
|
+
* accessor name, then by the item's slug:
|
|
1366
|
+
*
|
|
1367
|
+
* ```ts
|
|
1368
|
+
* Config.registerSources({ linkedSystems: { yos: yahooOs } })
|
|
1369
|
+
* ```
|
|
1370
|
+
*
|
|
1371
|
+
* The same two levels `register` takes (`{ linkedSystems: { yos: LinkedSystem.item(…) } }`) and the
|
|
1372
|
+
* same two `fromJSON(json, { sources })` takes, so one gesture — declare a source, then hand
|
|
1373
|
+
* over what it resolved to — reads identically at all three. A flat `{ yos }` said nothing about
|
|
1374
|
+
* which collection `yos` came from, which is fine while `linked-system` is the only source kind and
|
|
1375
|
+
* wrong the day there is a second.
|
|
1376
|
+
*
|
|
1377
|
+
* Immutable like every other authoring step: the sources ride on the derived config, and every later
|
|
1378
|
+
* `apply` carries them along.
|
|
1379
|
+
*
|
|
1380
|
+
* Prefer `fromJSON(json, { sources })` when hydrating: attaching after hydration leaves a window
|
|
1381
|
+
* in which every sourced ref dangles, and the emitters and integrity checks read exactly there.
|
|
1382
|
+
*/
|
|
1383
|
+
registerSources(sources: AttachedSources): this;
|
|
1384
|
+
private assertSlugIsNotOwnName;
|
|
1385
|
+
/**
|
|
1386
|
+
* Every attached source, ordered by slug — the memo key for any derivation that can reach a borrowed
|
|
1387
|
+
* value (`memo.ts`). A source's entities are in the source's snapshot, so nothing about swapping one
|
|
1388
|
+
* shows up in this config's slices.
|
|
1389
|
+
*/
|
|
1390
|
+
attachedSources(): readonly Config[];
|
|
1391
|
+
/**
|
|
1392
|
+
* The source behind a link slug — attached if one is, otherwise the closure this config recorded.
|
|
1393
|
+
*
|
|
1394
|
+
* What a reader asking "can I read this value" wants, and it should not have to care which it got:
|
|
1395
|
+
* a recorded closure is a config, so the ordinary chase walks it and a borrowed alias resolves
|
|
1396
|
+
* inside it exactly as it would inside the live source.
|
|
1397
|
+
*
|
|
1398
|
+
* A reader asking "is a real source attached right now" wants {@link liveSourceFor} instead, and
|
|
1399
|
+
* four do — the incremental CSS delta (which compares by reference), the emitted path unions (which
|
|
1400
|
+
* must not narrow to paths already used), the dangling-ref check (whose whole subject is the seam
|
|
1401
|
+
* where the other side can change without us), and the Create badges.
|
|
1402
|
+
*/
|
|
1403
|
+
sourceFor(slug: string): Config | undefined;
|
|
1404
|
+
/** This config's own answer for a slug — attached, else the closure it recorded. No hop, which is
|
|
1405
|
+
* what makes it the base case {@link sourceThrough} recurses over. */
|
|
1406
|
+
private ownSourceFor;
|
|
1407
|
+
/**
|
|
1408
|
+
* A slug this config doesn't declare, answered by a source that does.
|
|
1409
|
+
*
|
|
1410
|
+
* It exists for a published chain two deep. Materializing keeps a ref whose target has no value to
|
|
1411
|
+
* collapse into — a `style-property`, a component's `file` — so a component borrowed from `mid`
|
|
1412
|
+
* arrives in `top` still spelling `style-property:bg@base`, where `base` is a pin only `mid` declared.
|
|
1413
|
+
* `top` has no way to name it, because the slug belongs to the system that wrote the ref. Answering
|
|
1414
|
+
* `undefined` makes the ref unresolvable, which surfaces as `Cannot inline "style-property:bg@base"`
|
|
1415
|
+
* and takes the whole publish down. Depth one works only because there the slug is the consumer's own.
|
|
1416
|
+
*
|
|
1417
|
+
* Read the way {@link configFor} already describes resolution: it hops configs, each answering for
|
|
1418
|
+
* the links it declares. This is the same hop for the source address rather than the entity.
|
|
1419
|
+
*
|
|
1420
|
+
* Local first, always. {@link sourceFor} tries {@link ownSourceFor} before reaching here, so a config
|
|
1421
|
+
* that declares `base` itself keeps meaning its own, and a diamond resolves through the pin its owner
|
|
1422
|
+
* wrote.
|
|
1423
|
+
*
|
|
1424
|
+
* Memoized per instance including the misses: a dangling sourced ref is read repeatedly, since
|
|
1425
|
+
* `list`, `sourceClosure` and `toJSON` all pass over it, and each miss would otherwise re-walk every
|
|
1426
|
+
* source.
|
|
1427
|
+
*/
|
|
1428
|
+
private sourceThrough;
|
|
1429
|
+
private throughCache?;
|
|
1430
|
+
/**
|
|
1431
|
+
* One level of {@link sourceThrough}'s search, then the same again through whatever answered.
|
|
1432
|
+
*
|
|
1433
|
+
* `seen` guards against a walk that never terminates: a derived kind can be sourced from the config
|
|
1434
|
+
* that owns it, so a Config is reachable from itself. It carries configs rather than slugs, because
|
|
1435
|
+
* one slug legitimately names different sources at different depths.
|
|
1436
|
+
*
|
|
1437
|
+
* Keyed off the attachment maps rather than `sourceSlugs()` — a recorded closure holds the entities
|
|
1438
|
+
* its refs reached and no pin items at all, so asking it what it declares answers nothing while its
|
|
1439
|
+
* own recorded closures are right there.
|
|
1440
|
+
*/
|
|
1441
|
+
private hopFor;
|
|
1442
|
+
/**
|
|
1443
|
+
* Record what each source resolved to. Immutable like every other authoring step, and carried by
|
|
1444
|
+
* reference through `derive` — an edit does not change what a source resolved to.
|
|
1445
|
+
*
|
|
1446
|
+
* Internal to {@link Config.load}: a state is an answer a resolver gave, so there is no legitimate
|
|
1447
|
+
* reason for a caller to assert one without having resolved it.
|
|
1448
|
+
*/
|
|
1449
|
+
protected withSourceStates(states: ReadonlyMap<string, SourceState>): this;
|
|
1450
|
+
/**
|
|
1451
|
+
* What every resolved source resolved to, by slug.
|
|
1452
|
+
*
|
|
1453
|
+
* Empty for a config hydrated by plain `fromJSON`, which asked nobody — and that is a real third
|
|
1454
|
+
* answer rather than a gap. "Nothing resolved this" is not "a resolver looked and could not", and the
|
|
1455
|
+
* two being the same `undefined` is why one rail says a value "isn't loaded here" when it is loaded
|
|
1456
|
+
* and the ref is simply broken.
|
|
1457
|
+
*
|
|
1458
|
+
* Distinct from the config type's `sources` declaration, which says what it draws on; this says how
|
|
1459
|
+
* that turned out. Distinct again from {@link attachedSources}, which hands back the configs
|
|
1460
|
+
* themselves and answers nothing about the ones that failed to resolve.
|
|
1461
|
+
*/
|
|
1462
|
+
sourceStates(): readonly SourceState[];
|
|
1463
|
+
/**
|
|
1464
|
+
* One source's state — the same tagged value its resolver answered with, so nothing maps between
|
|
1465
|
+
* what a resolver said and what a surface reads.
|
|
1466
|
+
*
|
|
1467
|
+
* By slug alone, like {@link sourceFor} and {@link liveSourceFor}, even though the state itself
|
|
1468
|
+
* carries which declared source it came from. A ref spells `@ds` with no room for a collection, so a
|
|
1469
|
+
* second address level here would be a key nothing that resolves a ref could supply — and
|
|
1470
|
+
* {@link assertDistinctSourceSlugs} is what makes one level sufficient rather than merely usual.
|
|
1471
|
+
*
|
|
1472
|
+
* `undefined` means nothing has resolved this slug. See {@link sources} for why that is a third
|
|
1473
|
+
* state and not a missing one.
|
|
1474
|
+
*/
|
|
1475
|
+
sourceStatus(slug: string): SourceState | undefined;
|
|
1476
|
+
/**
|
|
1477
|
+
* The one source slug every member of a group draws through — `undefined` when they disagree, and
|
|
1478
|
+
* `undefined` for an empty group.
|
|
1479
|
+
*
|
|
1480
|
+
* The same rule {@link Entity.source} applies across an item's fields, applied across a group's
|
|
1481
|
+
* members: a group is a display convenience over member paths, not a thing that can itself be
|
|
1482
|
+
* borrowed, so "this group is borrowed" can only mean "all of it is". One local member makes it
|
|
1483
|
+
* mixed, and a mixed group answers `undefined` — its editor stays editable, because one of its rows
|
|
1484
|
+
* is. Members split across two sources answer `undefined` too, because naming either would label the
|
|
1485
|
+
* other's rows wrongly.
|
|
1486
|
+
*
|
|
1487
|
+
* Answers the slug, which is the kind-agnostic half and therefore the half that belongs here. Turning
|
|
1488
|
+
* a slug into the typed pin behind it is the source kind's own business (`sourceOfGroup` beside
|
|
1489
|
+
* `LinkedSystem`) — a `Config` method could only hand back a bare `Entity`, and the rule above would
|
|
1490
|
+
* then be stated once per source kind instead of once.
|
|
1491
|
+
*/
|
|
1492
|
+
groupSource({
|
|
1493
|
+
kind,
|
|
1494
|
+
groupPath
|
|
1495
|
+
}: {
|
|
1496
|
+
kind: string;
|
|
1497
|
+
groupPath: string;
|
|
1498
|
+
}): string | undefined;
|
|
1499
|
+
/**
|
|
1500
|
+
* The one source every member of a group is borrowed from — `undefined` when they disagree, when any
|
|
1501
|
+
* member is this config's own, and for an empty group.
|
|
1502
|
+
*
|
|
1503
|
+
* {@link groupSource} applied to the other provenance question, so the agreement rule is stated once
|
|
1504
|
+
* for both. The pair splits the way `Entity.source` and `Entity.borrowedFrom` do: a folder whose
|
|
1505
|
+
* members all point at another system is worth adorning, while a folder whose members all are another
|
|
1506
|
+
* System's is the one where adding, renaming and reordering belong to somebody else.
|
|
1507
|
+
*/
|
|
1508
|
+
groupBorrowedFrom({
|
|
1509
|
+
kind,
|
|
1510
|
+
groupPath
|
|
1511
|
+
}: {
|
|
1512
|
+
kind: string;
|
|
1513
|
+
groupPath: string;
|
|
1514
|
+
}): string | undefined;
|
|
1515
|
+
/** The slug a linked folder records on its own body (`ref`), or `undefined` for a folder of this
|
|
1516
|
+
* config's own. */
|
|
1517
|
+
private recordedGroupSource;
|
|
1518
|
+
/** The rule both group-level provenance reads follow — one dissenting or local member and there is
|
|
1519
|
+
* no answer. Private because the two public readers above are the questions worth asking. */
|
|
1520
|
+
private agreedAcrossMembers;
|
|
1521
|
+
/**
|
|
1522
|
+
* Every item this config stores for a kind, as authored — the body before resolution.
|
|
1523
|
+
*
|
|
1524
|
+
* The counterpart to {@link list}, and the right subject for anything asking what this config claims
|
|
1525
|
+
* rather than what it currently reads as. Resolution is lossy in both directions for that question:
|
|
1526
|
+
* an extension whose base cannot be found resolves to nothing and drops out of `list` entirely, while
|
|
1527
|
+
* one that can be found resolves to its source's fields merged in — so a resolved body carries refs
|
|
1528
|
+
* this config never wrote, through slugs it never declared.
|
|
1529
|
+
*
|
|
1530
|
+
* Groups are not included. A group node holds metadata, not values, and every caller here is asking
|
|
1531
|
+
* about the things that point somewhere.
|
|
1532
|
+
*/
|
|
1533
|
+
authoredItems(kind: string): {
|
|
1534
|
+
path: string;
|
|
1535
|
+
body: Record<string, unknown>;
|
|
1536
|
+
}[];
|
|
1537
|
+
/**
|
|
1538
|
+
* The slug whose bulk-take declaration still covers this path — `undefined` when none does.
|
|
1539
|
+
*
|
|
1540
|
+
* Two questions, and both have to answer yes. The entity has to be a borrow, stored as a pointer into
|
|
1541
|
+
* a source ({@link borrowedFrom}): a declaration says what was taken from someone else and reaches
|
|
1542
|
+
* nothing this config authored, so gating on the kind alone would refuse you your own tokens the
|
|
1543
|
+
* moment you took that kind wholesale. And the source's pin has to still cover the path the pointer
|
|
1544
|
+
* names, which only the pin's kind can say — where it records a take, how it keys it, and which kinds
|
|
1545
|
+
* a whole-source take doesn't extend to are all facts about that kind
|
|
1546
|
+
* ({@link EntityKind.bulkAdopts}).
|
|
1547
|
+
*
|
|
1548
|
+
* Matched on the path the source spells rather than the local one, because that's what a declaration
|
|
1549
|
+
* names: `to` lets a borrow sit under a name of your own, and a list matched against the local name
|
|
1550
|
+
* would cover nothing for exactly those.
|
|
1551
|
+
*
|
|
1552
|
+
* A pointer is the only borrow shape this can see. A `scope: 'value'` borrow stores the source's ref
|
|
1553
|
+
* in the primary field, which is byte-for-byte what a local entity pointing at a borrowed value
|
|
1554
|
+
* stores, so refusing on that shape would lock a pointer we declared. See `borrowedSlug`.
|
|
1555
|
+
*/
|
|
1556
|
+
/**
|
|
1557
|
+
* The pin whose `link` declaration covers `kind:path` and whose source has a row there, so the build
|
|
1558
|
+
* mints the source's row at the path on its next pass. `undefined` when no declaration covers the
|
|
1559
|
+
* path, or the declaring source is attached and lacks it. A source that isn't attached is taken at
|
|
1560
|
+
* its declaration's word.
|
|
1561
|
+
*/
|
|
1562
|
+
private declaringSource;
|
|
1563
|
+
/** Whether the pin at `slug` declares the source's `kind:path`, read off the stored pin alone. */
|
|
1564
|
+
private declarationAdopts;
|
|
1565
|
+
private adoptingSource;
|
|
1566
|
+
/**
|
|
1567
|
+
* What the declared source at `slug` lays over one of its entities, or `undefined` where it says
|
|
1568
|
+
* nothing about that path. The narrow half of {@link Config.sourceDirective}, for the readers whose
|
|
1569
|
+
* subject is the overlay rather than whether a row is minted at all.
|
|
1570
|
+
*/
|
|
1571
|
+
sourceOverlay(slug: string, kind: string, path: string): SourceOverlay | undefined;
|
|
1572
|
+
/**
|
|
1573
|
+
* The field-path delta that records `overlay` as the pin's directive for one path, or `null` when
|
|
1574
|
+
* there is nothing to write.
|
|
1575
|
+
*
|
|
1576
|
+
* An overlay with nothing in it is cleared rather than written: the pin refuses an empty entry, and
|
|
1577
|
+
* a path it says nothing about is the plain adopted row this edit has just made it again. Clearing
|
|
1578
|
+
* the last entry the pin holds removes `options` altogether, so the pin reads as it was authored,
|
|
1579
|
+
* and clearing an entry that was never there is a no-op rather than a recorded patch.
|
|
1580
|
+
*/
|
|
1581
|
+
private directiveWrite;
|
|
1582
|
+
/**
|
|
1583
|
+
* Every path a declared source's declaration says something about, with the accessor resolved to the
|
|
1584
|
+
* kind it addresses — `kind: undefined` where this config has no kind under that accessor.
|
|
1585
|
+
*
|
|
1586
|
+
* The inverse of every other read here. Those answer for a path you already hold, so they are asked
|
|
1587
|
+
* while walking something that exists; this enumerates what was written, which is the only direction
|
|
1588
|
+
* that can see an entry aimed at a path or an accessor that is not there. Asked of the kind
|
|
1589
|
+
* ({@link EntityKind.bulkDirectives}), for the reason the rest are: the field is the kind's.
|
|
1590
|
+
*/
|
|
1591
|
+
sourceDirectives(slug: string): {
|
|
1592
|
+
kind: string | undefined;
|
|
1593
|
+
accessor: string;
|
|
1594
|
+
path: string;
|
|
1595
|
+
}[];
|
|
1596
|
+
/**
|
|
1597
|
+
* Whether one of a declared source's paths is adopted here — covered by the pin's `link`.
|
|
1598
|
+
*
|
|
1599
|
+
* The question a spelling turns on: an adopted path is addressed bare, because the build mints a
|
|
1600
|
+
* row for it at the path the source spells, and everything else is addressed qualified. The two are
|
|
1601
|
+
* exclusive, which is what keeps one entity from having two names.
|
|
1602
|
+
*/
|
|
1603
|
+
sourceStance(slug: string, kind: string, path: string): {
|
|
1604
|
+
adopted: boolean;
|
|
1605
|
+
};
|
|
1606
|
+
/**
|
|
1607
|
+
* Whether the entity at `path` — an item or a group node — is kept but no longer offered.
|
|
1608
|
+
*
|
|
1609
|
+
* An authoring question, not a resolution one. The entity is still here and still resolves, because
|
|
1610
|
+
* references already going through it have to keep working — so nothing that emits a *value* reads
|
|
1611
|
+
* this. What does: a surface that lists paths for someone to pick from, and the emitted type union,
|
|
1612
|
+
* which is exactly such a list.
|
|
1613
|
+
*
|
|
1614
|
+
* True when the entity's own body says `hidden`, when any folder above it does, or when the pin a
|
|
1615
|
+
* borrowed row came through hides its path. A folder's flag reaches every member without the members
|
|
1616
|
+
* being rewritten, so un-hiding the folder is one write. Asked by local path, which for an inherited
|
|
1617
|
+
* row is the path the source spells.
|
|
1618
|
+
*/
|
|
1619
|
+
hidden(kind: string, path: string): boolean;
|
|
1620
|
+
/**
|
|
1621
|
+
* Whether the entity at `path` — an item or a group node — is withdrawn from new work.
|
|
1622
|
+
*
|
|
1623
|
+
* Everything {@link hidden} is, and then every existing use is something to flag: a value bound to a
|
|
1624
|
+
* deprecated token, an instance of a deprecated component, a build warning per reference. Same shape
|
|
1625
|
+
* as `hidden` — the body, or any folder above it — minus the pin case, since deprecation is a fact
|
|
1626
|
+
* about the entity rather than about a borrow. A borrowed row reads its source's flag through its
|
|
1627
|
+
* resolved body.
|
|
1628
|
+
*/
|
|
1629
|
+
deprecated(kind: string, path: string): boolean;
|
|
1630
|
+
/**
|
|
1631
|
+
* Whether the entity at `path` is retired short of deletion — {@link hidden} or {@link deprecated}.
|
|
1632
|
+
* The one read a surface choosing a NEW value filters on: a picker, an autocomplete, a prompt
|
|
1633
|
+
* catalogue. A surface showing an EXISTING value does not ask, since a value already bound keeps
|
|
1634
|
+
* rendering whatever its entity's state.
|
|
1635
|
+
*/
|
|
1636
|
+
retired(kind: string, path: string): boolean;
|
|
1637
|
+
/** {@link hidden} and {@link deprecated} share one walk: the node at the path, then each folder
|
|
1638
|
+
* above it, nearest first. */
|
|
1639
|
+
private flagged;
|
|
1640
|
+
/**
|
|
1641
|
+
* One node's own answer, with no walk: its resolved body, and for `hidden` the directive the pin lays
|
|
1642
|
+
* over a borrowed row or a borrowed group. A path that is both an item and a folder (a component
|
|
1643
|
+
* holding its parts) answers as the item; its group node has no body of its own.
|
|
1644
|
+
*/
|
|
1645
|
+
private flagAt;
|
|
1646
|
+
/** {@link groups} keyed by path, cached beside it. */
|
|
1647
|
+
private groupIndex;
|
|
1648
|
+
/**
|
|
1649
|
+
* What the declared source at `slug` says about one of the source's paths: whether its `link` claims
|
|
1650
|
+
* it, and what it lays over the entity.
|
|
1651
|
+
*
|
|
1652
|
+
* Asked of the kind ({@link EntityKind.bulkAdopts} / {@link EntityKind.bulkOverlay}) rather than read
|
|
1653
|
+
* off a field here: the framework does not know the pin's vocabulary, and a second reader of the
|
|
1654
|
+
* same field is a second thing to keep in step.
|
|
1655
|
+
*/
|
|
1656
|
+
private sourceDirective;
|
|
1657
|
+
/**
|
|
1658
|
+
* One of a source's entity bodies as it reads from here — their fields with their refs qualified
|
|
1659
|
+
* into this namespace, and `undefined` for a path they have nothing at.
|
|
1660
|
+
*
|
|
1661
|
+
* Qualified for the reason {@link extensionBody} qualifies: their body spells its refs the way they
|
|
1662
|
+
* do, locally, and a value spliced into a row of ours without that would name whatever this config
|
|
1663
|
+
* happens to have at the same path. The overlay merge splices exactly one thing — the entries an
|
|
1664
|
+
* `extend` unions onto their array.
|
|
1665
|
+
*/
|
|
1666
|
+
private theirBody;
|
|
1667
|
+
/**
|
|
1668
|
+
* The source's entity at `kind:path`, as a body this config could author — what a copy stores.
|
|
1669
|
+
*
|
|
1670
|
+
* Refs into the source are qualified `@slug`, except a target this config names bare: one it
|
|
1671
|
+
* already borrows at the source's own path, or one `alongside` says will be here once the edit
|
|
1672
|
+
* lands — arriving with it, or held already. So a copied token that points at a sibling copied
|
|
1673
|
+
* with it points at the local copy, and one that points at something not taken keeps drawing
|
|
1674
|
+
* through the pin.
|
|
1675
|
+
*
|
|
1676
|
+
* `extend` is the delta a pointer at the path carried, laid over the source's body by the same rule
|
|
1677
|
+
* a derived row's `options` are — so copying a linked row keeps what was written over it here.
|
|
1678
|
+
*
|
|
1679
|
+
* `undefined` when nothing has resolved the slug, or the source has no such entity.
|
|
1680
|
+
*/
|
|
1681
|
+
copyOf({
|
|
1682
|
+
slug,
|
|
1683
|
+
kind,
|
|
1684
|
+
path,
|
|
1685
|
+
alongside,
|
|
1686
|
+
extend
|
|
1687
|
+
}: {
|
|
1688
|
+
slug: string;
|
|
1689
|
+
kind: string;
|
|
1690
|
+
path: string; /** `kind:path` targets landing as this config's own in the same edit. */
|
|
1691
|
+
alongside?: ReadonlySet<string>;
|
|
1692
|
+
extend?: Record<string, unknown>;
|
|
1693
|
+
}): Record<string, unknown> | undefined;
|
|
1694
|
+
/**
|
|
1695
|
+
* Whether a copied row no longer matches what its source offers at the path it was copied from.
|
|
1696
|
+
*
|
|
1697
|
+
* `undefined` when the row is not a copy, or its source is not attached — there is nothing to compare
|
|
1698
|
+
* against, which is not the same as matching. `true` when the source has moved on since the copy, or
|
|
1699
|
+
* no longer has the path; also `true` when the copy was edited here, since the row itself cannot say
|
|
1700
|
+
* which side moved. A surface phrases it as "differs from the source" for that reason.
|
|
1701
|
+
*/
|
|
1702
|
+
copyDiffers({
|
|
1703
|
+
kind,
|
|
1704
|
+
path
|
|
1705
|
+
}: {
|
|
1706
|
+
kind: string;
|
|
1707
|
+
path: string;
|
|
1708
|
+
}): boolean | undefined;
|
|
1709
|
+
/**
|
|
1710
|
+
* Whether what this config shows at `path` reads the same as what `slug` offers there — what a
|
|
1711
|
+
* source page asks about a row that is yours, or another pin's, before it calls the two a conflict.
|
|
1712
|
+
*
|
|
1713
|
+
* The whole of what shows, not the one body: a component reads through its style properties and
|
|
1714
|
+
* tokens, so every entity the source's row reaches by ref is compared on both sides too, a ref at a
|
|
1715
|
+
* folder by that folder's members. Taking theirs would bring those along, and a match here means
|
|
1716
|
+
* nothing that renders would change.
|
|
1717
|
+
*
|
|
1718
|
+
* `undefined` when nothing is held at the path, the source has no such path, or the source is not
|
|
1719
|
+
* attached. Compares resolved bodies, so a row minted through another pin is read as the values it
|
|
1720
|
+
* shows rather than as the pointer it stores. Refs are compared bare: `token:color/brand@f` and
|
|
1721
|
+
* `token:color/brand@ds` name one path through two pins. A copy's `copiedFrom` mark is provenance,
|
|
1722
|
+
* not a value, and is left out. A dependency this config doesn't hold is a difference: what shows
|
|
1723
|
+
* here can't read the same without it.
|
|
1724
|
+
*/
|
|
1725
|
+
matchesSource({
|
|
1726
|
+
slug,
|
|
1727
|
+
kind,
|
|
1728
|
+
path
|
|
1729
|
+
}: {
|
|
1730
|
+
slug: string;
|
|
1731
|
+
kind: string;
|
|
1732
|
+
path: string;
|
|
1733
|
+
}): boolean | undefined;
|
|
1734
|
+
/** The source attached for a slug — `undefined` for one that was only recorded. The narrow question,
|
|
1735
|
+
* for the readers whose subject is the live link rather than the value behind it. */
|
|
1736
|
+
liveSourceFor(slug: string): Config | undefined;
|
|
1737
|
+
/**
|
|
1738
|
+
* The source an item is borrowed from — set only when this config stores it as a pointer into one,
|
|
1739
|
+
* and `undefined` for an item it authored, however far that item's own fields point.
|
|
1740
|
+
*
|
|
1741
|
+
* Backs the `borrowedFrom` computed the framework installs on every kind, which is how callers read
|
|
1742
|
+
* it (`config.resolve(kind, path)?.borrowedFrom`). A method here because the answer is in the
|
|
1743
|
+
* snapshot rather than in the resolved entity — resolving an extension merges its source's fields in
|
|
1744
|
+
* over the pointer, so by the time a computed sees `member` there is nothing left to read.
|
|
1745
|
+
*/
|
|
1746
|
+
borrowedFrom(kind: string, path: string): string | undefined;
|
|
1747
|
+
/**
|
|
1748
|
+
* What this config lays over a borrowed item, beside the source's own body it lays it on —
|
|
1749
|
+
* `undefined` for an item this config authored.
|
|
1750
|
+
*
|
|
1751
|
+
* One answer for both ways an item is borrowed. A row the build minted from a `link` carries the
|
|
1752
|
+
* pin's directive folded into its `extend`, and a row extended by hand carries the delta the author
|
|
1753
|
+
* wrote there; either way the stored body is the pointer plus our fields. The source body is theirs
|
|
1754
|
+
* as it reads from here, refs qualified into this namespace, and `undefined` while the source is
|
|
1755
|
+
* unavailable — every field is then an extension, since nothing says otherwise.
|
|
1756
|
+
*/
|
|
1757
|
+
localOverlay(kind: string, path: string): LocalOverlay | undefined;
|
|
1758
|
+
/**
|
|
1759
|
+
* One item's body as stored — the pointer still visible, nothing merged in, and `undefined` for a path
|
|
1760
|
+
* this config doesn't hold.
|
|
1761
|
+
*
|
|
1762
|
+
* The pair to {@link resolve}, and the difference is why both exist. `resolve` answers what the item
|
|
1763
|
+
* is, so an extension comes back as the source's fields under ours, which is what a renderer wants
|
|
1764
|
+
* and is the merge that erases the pointer. This answers what this config wrote, which is what a
|
|
1765
|
+
* caller deciding whether an item is a borrow, and of which shape, has to read. {@link borrowedFrom}
|
|
1766
|
+
* is the narrow question over the same body; this is for a caller that needs the rest of it.
|
|
1767
|
+
*
|
|
1768
|
+
* `groupMeta` is the group half. Named for the stored form rather than "raw", because the distinction
|
|
1769
|
+
* is about the merge and not about parsing: the body has been through the kind's schema.
|
|
1770
|
+
*/
|
|
1771
|
+
storedBody(kind: string, path: string): Record<string, unknown> | undefined;
|
|
1772
|
+
/**
|
|
1773
|
+
* This config with every cross-source ref replaced by the value it points at — self-contained, so it
|
|
1774
|
+
* loads with no sources attached.
|
|
1775
|
+
*
|
|
1776
|
+
* What the materialize route runs before handing a published version to a consumer: their artifact may
|
|
1777
|
+
* draw on systems the consumer never pinned and cannot see, and resolving here (with the publisher's
|
|
1778
|
+
* sources) is what makes one entry answer for itself. A chained link resolves all the way to a literal,
|
|
1779
|
+
* and a ref that cannot resolve throws rather than leaving a hole — a partial artifact breaks builds
|
|
1780
|
+
* confusingly later.
|
|
1781
|
+
*
|
|
1782
|
+
* The pins stay. They are not values that inline, and a consumer's tooling still wants them for
|
|
1783
|
+
* attribution and upgrade detection. Breaking a link is deleting the pin, once nothing refers
|
|
1784
|
+
* through it.
|
|
1785
|
+
*
|
|
1786
|
+
* Local refs are untouched — the point is to remove dependence on other systems, not to flatten this
|
|
1787
|
+
* one into literals.
|
|
1788
|
+
*/
|
|
1789
|
+
withInlinedSources(options?: InlineOptions): this;
|
|
1790
|
+
/** @internal Turn extensions through the selected source into ordinary complete local items.
|
|
1791
|
+
*
|
|
1792
|
+
* Used by source materialization before it walks nested refs. A component cannot
|
|
1793
|
+
* collapse to one scalar value the way a token ref can, so its extension is flattened as the
|
|
1794
|
+
* complete resolved entity instead. That makes a published B→A component extension self-contained
|
|
1795
|
+
* when C installs B: C receives B's component and render source, and never needs permission to A.
|
|
1796
|
+
*
|
|
1797
|
+
* `only` narrows to one item. It has to come through here rather than being done by the caller,
|
|
1798
|
+
* because the enumeration is over stored bodies — an extension is only visible before it resolves,
|
|
1799
|
+
* and `list` hands back the merge.
|
|
1800
|
+
*
|
|
1801
|
+
* Each one goes through `apply` as a `replace`. Writing the snapshot directly would produce no
|
|
1802
|
+
* patch, so the dissolve would be invisible to replay: an undo or a rebase would re-apply a
|
|
1803
|
+
* flattening `update` onto the envelope it was supposed to have removed, put the value back inside
|
|
1804
|
+
* `extend`, and then refuse to drop the pin because the extension still referenced it. `replace`
|
|
1805
|
+
* makes a dissolve an ordinary recorded edit, so it survives being replayed. */
|
|
1806
|
+
withMaterializedExtensions({
|
|
1807
|
+
slug,
|
|
1808
|
+
only
|
|
1809
|
+
}?: {
|
|
1810
|
+
slug?: string;
|
|
1811
|
+
only?: {
|
|
1812
|
+
kind: string;
|
|
1813
|
+
path: string;
|
|
1814
|
+
};
|
|
1815
|
+
}): this;
|
|
1816
|
+
/**
|
|
1817
|
+
* The config a ref resolves in — this one for a local ref or a ref qualified with this config's own
|
|
1818
|
+
* {@link name}, the linked source for any other qualified one.
|
|
1819
|
+
*
|
|
1820
|
+
* `undefined` means the ref names a source this config can't reach: either the link isn't declared
|
|
1821
|
+
* or it was declared and never loaded. That is deliberately not the same as falling back to
|
|
1822
|
+
* ourselves. Resolving `token:color/brand@yahoo-os` against our own `color/brand` would hand back
|
|
1823
|
+
* a plausible wrong value with nothing to signal it — the exact confusion between your key and
|
|
1824
|
+
* their key that qualified refs exist to prevent.
|
|
1825
|
+
*
|
|
1826
|
+
* The own-name case is the one exception, because it is not a fallback: a canvas attaches the one
|
|
1827
|
+
* system it draws from under that system's `name`, so the refs it mints
|
|
1828
|
+
* (`component:actions/Button@system-config`) are addressed to whichever system is attached. The
|
|
1829
|
+
* name is the config type's, shared by every `System`, which is why "the attached one" is all it
|
|
1830
|
+
* can mean. Handed to the system itself, such a ref resolves there. A source that was hydrated
|
|
1831
|
+
* under that slug still answers first, so an artifact written before the name was reserved keeps
|
|
1832
|
+
* its meaning; {@link assertDistinctSourceSlugs} refuses authoring a new one.
|
|
1833
|
+
*/
|
|
1834
|
+
configFor(target: string | Ref): Config | undefined;
|
|
1835
|
+
/** One ref, resolved wherever it points — the source hop plus an ordinary `resolve`, so a caller
|
|
1836
|
+
* following a ref never splits one apart or decides which config to ask. */
|
|
1837
|
+
resolveRef(target: string | Ref): Entity | undefined;
|
|
1838
|
+
/**
|
|
1839
|
+
* What a person calls whatever `target` names: the resolved entity's
|
|
1840
|
+
* {@link Entity.displayLabel} (its authored `label`, else its leaf). A
|
|
1841
|
+
* `#member` reads as the member's own leaf (`gap` for `props/gap`), and a
|
|
1842
|
+
* name that resolves to nothing — a bare intrinsic tag, a glyph the config
|
|
1843
|
+
* never registered, a deleted component — as the leaf of its path. Never a
|
|
1844
|
+
* `kind:` marker, which no person should read.
|
|
1845
|
+
*/
|
|
1846
|
+
displayLabelOf(target: string | Ref): string;
|
|
1847
|
+
/**
|
|
1848
|
+
* The `data` schema for a mutation `verb`, narrowed to one target entity — the same per-owner
|
|
1849
|
+
* schema `apply` validates against (a factory input like a style rule's `when`/`layers` builds
|
|
1850
|
+
* itself from the resolved parent), emitted as JSON Schema for a form to render. A tool's
|
|
1851
|
+
* advertised input stays owner-agnostic (no `path` chosen yet); this is for a form that already
|
|
1852
|
+
* knows its target. `undefined` when the verb carries no `data`, or the target doesn't resolve.
|
|
1853
|
+
*/
|
|
1854
|
+
contextualDataSchema(kind: string, path: string, verb: string): Record<string, unknown> | undefined;
|
|
1855
|
+
/**
|
|
1856
|
+
* Validate a `data` payload against that same per-owner schema — the write-time check `apply`
|
|
1857
|
+
* runs, minus the mutation. `[]` = valid; otherwise `path: message` lines. Lets a preview form
|
|
1858
|
+
* flag exactly what `apply` would reject, before anything is sent.
|
|
1859
|
+
*/
|
|
1860
|
+
validateData(kind: string, path: string, verb: string, data: unknown): string[];
|
|
1861
|
+
/** What a body's collection members would be refused for, as `field.key.path: message` lines —
|
|
1862
|
+
* the per-owner check a strict {@link normalizeMembers} throws, collected instead. */
|
|
1863
|
+
private memberIssues;
|
|
1864
|
+
/** Emergent membership: the items whose path sits under a group prefix. Never stored. Reads
|
|
1865
|
+
* through `list`, so a linked kind's members come from its source. */
|
|
1866
|
+
members(kind: string, groupPath: string): Entity[];
|
|
1867
|
+
/**
|
|
1868
|
+
* A kind's groups — the union of the two ways one can exist.
|
|
1869
|
+
*
|
|
1870
|
+
* A group's membership is emergent: `spacing/sm` puts `spacing` on this list whether or not anything
|
|
1871
|
+
* wrote a body for it. A group's body is stored, so a group someone just made and hasn't filled is on
|
|
1872
|
+
* this list too. Listing only the stored bodies hides every group nobody has labelled, including —
|
|
1873
|
+
* after a rename moved its members — the group they moved to.
|
|
1874
|
+
*
|
|
1875
|
+
* Every prefix level with members counts, so `spectrum/brand` is a group in its own right next to
|
|
1876
|
+
* `spectrum`, **unless there is an item at that path**. That exception keeps a component's parts from
|
|
1877
|
+
* turning their owner into a folder: `Select/Trigger` is a part of `Select` because it is named that
|
|
1878
|
+
* way, and `Select` is a component rather than a group containing one. A prefix is a group only when
|
|
1879
|
+
* nothing occupies it.
|
|
1880
|
+
*
|
|
1881
|
+
* **The stored record is the authority for order.** Bodied groups come first, in its key order, which
|
|
1882
|
+
* is what `reorder` rewrites, and body-less ones follow, positioned by where their first member sits
|
|
1883
|
+
* in the item record. So a body-less group can't be reordered until something gives it a body, which
|
|
1884
|
+
* is a gap rather than an intended rule.
|
|
1885
|
+
*/
|
|
1886
|
+
/**
|
|
1887
|
+
* A group's metadata, with what the source says about the group inherited from it and any
|
|
1888
|
+
* directive the pin lays over this path folded in.
|
|
1889
|
+
*
|
|
1890
|
+
* The source's group node arrives whole, minus the fields the kind declares as its own
|
|
1891
|
+
* ({@link EntityKind.localGroupFields}). The split is what a field is ABOUT: `variants` and `sizes`
|
|
1892
|
+
* describe the icon library, so a consumer that borrows it needs them — `primitives/Icon` declares
|
|
1893
|
+
* its `variant` prop's domain as `icon:<slug>#variants`, which is read off this node, and without
|
|
1894
|
+
* it the ref resolves to nothing and the build refuses. `cssPrefix` describes how a config emits,
|
|
1895
|
+
* and a borrowed component's classes carry the consumer's prefix, so inheriting the source's would
|
|
1896
|
+
* name classes this stylesheet doesn't contain.
|
|
1897
|
+
*
|
|
1898
|
+
* Asked of the kind rather than named here, so a kind whose group node carries facts about the
|
|
1899
|
+
* group is described by this rather than flattened to the two fields every kind happens to share.
|
|
1900
|
+
*
|
|
1901
|
+
* A directive at this path — from `options`, or from `applyGroupUpdate`'s redirect off a minted
|
|
1902
|
+
* body — lays over what inherits, the same order an item's `extend` lays over its source fields.
|
|
1903
|
+
* Anything the stored body itself still sets wins over both: a hand-linked group (no `link`
|
|
1904
|
+
* declaration behind it) keeps writing its own body, and this is where that read wins.
|
|
1905
|
+
*/
|
|
1906
|
+
private withInheritedGroupMeta;
|
|
1907
|
+
/** Every group node of a kind, total by default; `{ retired: false }` leaves out retired folders,
|
|
1908
|
+
* the same way {@link list} leaves out retired items. */
|
|
1909
|
+
groups(kind: string, opts?: ListOptions): {
|
|
1910
|
+
path: string;
|
|
1911
|
+
meta: Record<string, unknown>;
|
|
1912
|
+
}[];
|
|
1913
|
+
/** The total list behind {@link groups}. */
|
|
1914
|
+
private allGroupsOf;
|
|
1915
|
+
/**
|
|
1916
|
+
* The walk behind {@link groups}. Per call it reads every stored group's inherited meta through its
|
|
1917
|
+
* source and splits every item's path for emergent folders, so a consumer with borrowed groups pays
|
|
1918
|
+
* the source's own walk once per group — which is why the answer is cached per snapshot above.
|
|
1919
|
+
*/
|
|
1920
|
+
private computeGroups;
|
|
1921
|
+
groupMeta(kind: string, groupPath: string): Record<string, unknown> | undefined;
|
|
1922
|
+
/** The kinds this config surfaces for reading — its own kinds with items, plus its derived ones.
|
|
1923
|
+
* The top level of a UI tree. A source's kinds are not here: they are read through a qualified
|
|
1924
|
+
* ref, so they belong to the config that answers for them. */
|
|
1925
|
+
kinds(): string[];
|
|
1926
|
+
/** The kinds this config derives (computed, read-only) via `defineDerivedEntity`. */
|
|
1927
|
+
derivedKinds(): string[];
|
|
1928
|
+
/** Re-attach `other`'s derived kinds onto this config — the derived twin of `relinkFrom`. A
|
|
1929
|
+
* self-sourced kind rebinds to this config (so a clone's members reflect the clone); an
|
|
1930
|
+
* externally-sourced one keeps pointing at its original source. Used after `fromJSON` on a clone. */
|
|
1931
|
+
rederiveFrom(other: Config): this;
|
|
1932
|
+
/**
|
|
1933
|
+
* The accessor each kind is registered under (`tokens` → the Token kind) — the one mapping a write
|
|
1934
|
+
* back into authored source needs, because a patch names the kind while the source names the
|
|
1935
|
+
* accessor.
|
|
1936
|
+
*
|
|
1937
|
+
* Read off the config type's own `entities` manifest, which `defineConfig` installs as a static.
|
|
1938
|
+
* Here rather than at each call site: every writer needs it, and reaching for the constructor is a
|
|
1939
|
+
* cast each of them would otherwise repeat.
|
|
1940
|
+
*/
|
|
1941
|
+
accessors(): Record<string, {
|
|
1942
|
+
kind: string;
|
|
1943
|
+
}>;
|
|
1944
|
+
/** The kinds registered into this config — what it owns, whether or not they hold items yet.
|
|
1945
|
+
* Tool derivation (reads + mutations) scopes to these so a config only exposes its own kinds
|
|
1946
|
+
* (a source's kinds are read through their own config, so they derive none). */
|
|
1947
|
+
ownedKinds(): string[];
|
|
1948
|
+
/**
|
|
1949
|
+
* What this config reaches inside `slug`'s source, transitively — as qualified `kind:path` targets.
|
|
1950
|
+
*
|
|
1951
|
+
* Seeded two ways, and both are floors rather than filters. From the sourced refs this config
|
|
1952
|
+
* spells, then closed over each target's own refs inside the source, because their entity may alias
|
|
1953
|
+
* another of theirs. And from what the pin declares it took ({@link adoptedFrom}) — a surface
|
|
1954
|
+
* saying "every token" is describing entities no ref has landed on yet, and a closure that carried
|
|
1955
|
+
* only the reached ones would leave those rows absent from the artifact. Reachability stays the
|
|
1956
|
+
* floor either way, so a ref always brings its target whether or not a declaration claims it.
|
|
1957
|
+
*
|
|
1958
|
+
* Shared rather than computed per caller: the CSS emitter needs it to know what to declare, and the
|
|
1959
|
+
* build's validation needs the same set to know what to check — two answers to that question would
|
|
1960
|
+
* drift, and the drift would look like a token that emits but isn't validated.
|
|
1961
|
+
*
|
|
1962
|
+
* `kind` narrows it for a caller that emits or validates one kind, which is the caller's business
|
|
1963
|
+
* to name; the walk itself follows refs wherever they go, so borrowing something other than a token
|
|
1964
|
+
* is reached rather than silently dropped.
|
|
1965
|
+
*
|
|
1966
|
+
* Sorted, so anything built from it is deterministic.
|
|
1967
|
+
*/
|
|
1968
|
+
sourceClosure(slug: string, kind?: string): string[];
|
|
1969
|
+
/**
|
|
1970
|
+
* The walk behind {@link sourceClosure}, run once per snapshot and source set.
|
|
1971
|
+
*
|
|
1972
|
+
* Memoized because it is asked repeatedly of one config and is O(every entity): the CSS emit asks
|
|
1973
|
+
* per kind, the artifact walk asks per slug, and the row derivation asks again — three full passes
|
|
1974
|
+
* over a config that is routinely megabytes, for one answer that cannot change while the snapshot
|
|
1975
|
+
* and its sources are fixed.
|
|
1976
|
+
*/
|
|
1977
|
+
private computeSourceClosure;
|
|
1978
|
+
/**
|
|
1979
|
+
* A refusal for a pin patch whose derive pass would retire a minted row that something authored
|
|
1980
|
+
* here still points at. A minted row has no body of its own, so once it goes the referrer dangles;
|
|
1981
|
+
* copying what is used, or breaking the link with `keep`, is how the value stays.
|
|
1982
|
+
*/
|
|
1983
|
+
private assertDerivedDropsUnreferenced;
|
|
1984
|
+
/**
|
|
1985
|
+
* This config with a row for every entity it inherits — the build's output. A pin patch runs this
|
|
1986
|
+
* inside {@link apply}; a source attaching runs it through the session.
|
|
1987
|
+
*
|
|
1988
|
+
* A link declares a source and what it took from it; everything that arrives is derived here, one
|
|
1989
|
+
* pointer per entity at the path the source spells it, marked ({@link minted}). So authored source
|
|
1990
|
+
* carries a declaration rather than a copy — linking one system into another would otherwise write
|
|
1991
|
+
* 2,674 rows by hand — while everything downstream still reads a stored row: `list`, the pickers, the
|
|
1992
|
+
* rename gate, `inlineAllSources` and the emitters are untouched, because a derived row is a stored
|
|
1993
|
+
* row by the time any of them run.
|
|
1994
|
+
*
|
|
1995
|
+
* The set is {@link adoptedFrom} — what the pin adopts. Not
|
|
1996
|
+
* {@link sourceClosure}, which is that plus whatever this config's refs happen to reach: a ref
|
|
1997
|
+
* already resolves qualified, so minting a bare row for it gives one entity two spellings, and which
|
|
1998
|
+
* one you get depends on what some unrelated file happened to reference. Adoption is a declaration,
|
|
1999
|
+
* and the rows follow it.
|
|
2000
|
+
*
|
|
2001
|
+
* The folders standing above a derived item come with it — a var name carries its group's
|
|
2002
|
+
* `cssPrefix`, so a leaf without its folder emits a custom property nothing declares.
|
|
2003
|
+
*
|
|
2004
|
+
* **A path this config already holds is left alone, silently.** Local shadows a source, whether the
|
|
2005
|
+
* local row is authored, borrowed by hand, or derived by an earlier pass — which is what makes this
|
|
2006
|
+
* idempotent and makes re-running it after a source grows additive.
|
|
2007
|
+
*
|
|
2008
|
+
* Only slugs this config pins derive rows. A slug reached through a borrowed entity names a third
|
|
2009
|
+
* system's namespace, which this config has no `@slug` for and no claim on.
|
|
2010
|
+
*/
|
|
2011
|
+
withDerivedRows(): this;
|
|
2012
|
+
/**
|
|
2013
|
+
* Whether the snapshot already stores exactly the row a derive pass would write at this path.
|
|
2014
|
+
*
|
|
2015
|
+
* A minted row is recomputed on every pass, so the plan names every inherited entity whether or not
|
|
2016
|
+
* anything about it moved. Writing an unchanged row would still mint a new config, and a session
|
|
2017
|
+
* derives on every publish — so on a linked branch every edit, and every holder keyed on the
|
|
2018
|
+
* config's identity, would see a fresh value for a snapshot that says the same thing.
|
|
2019
|
+
*/
|
|
2020
|
+
private holdsDerivedRow;
|
|
2021
|
+
/**
|
|
2022
|
+
* The paths two or more sources both offer and this config has not settled — `kind:path` with the
|
|
2023
|
+
* slugs that wanted it.
|
|
2024
|
+
*
|
|
2025
|
+
* Local shadows a source silently, and that is what makes re-deriving additive: a row you authored
|
|
2026
|
+
* wins and nothing is lost, because you said what you meant. Two *sources* offering one path is a
|
|
2027
|
+
* different question with no such answer — deciding it by pin order would make which vendor's
|
|
2028
|
+
* `actions/Button` you got depend on the order links happen to sit in, and nothing would look wrong.
|
|
2029
|
+
*
|
|
2030
|
+
* So neither derives, and this names them. The way out is an authored row at that path extending the
|
|
2031
|
+
* one you meant, which settles it as an ordinary local shadow and is reviewable in a diff.
|
|
2032
|
+
*/
|
|
2033
|
+
collidingSources(): {
|
|
2034
|
+
kind: string;
|
|
2035
|
+
path: string;
|
|
2036
|
+
slugs: string[];
|
|
2037
|
+
}[];
|
|
2038
|
+
/**
|
|
2039
|
+
* The derivation made for the snapshot this one came from, when this edit could not have changed
|
|
2040
|
+
* it: the same sources, no change to a pin or to which paths exist, and every row that did change
|
|
2041
|
+
* is authored on both sides with the same source-qualified refs and the same style-bag keys —
|
|
2042
|
+
* which is all {@link groupRefMembers} reads off an authored body. A value edit on an authored row
|
|
2043
|
+
* is the common case and passes.
|
|
2044
|
+
*
|
|
2045
|
+
* A session derives on every publish, so on a linked branch every keystroke redoes the walk
|
|
2046
|
+
* otherwise.
|
|
2047
|
+
*/
|
|
2048
|
+
private inheritedDerivation;
|
|
2049
|
+
/**
|
|
2050
|
+
* Same paths in the same order, and every row that changed is authored on both sides and reaches
|
|
2051
|
+
* the same sources the same way — see {@link inheritedDerivation}. Order counts because the walk
|
|
2052
|
+
* visits rows in stored order and mints in the order it reaches them, and key order is meaning
|
|
2053
|
+
* downstream.
|
|
2054
|
+
*/
|
|
2055
|
+
private sameRowsForDerivation;
|
|
2056
|
+
/**
|
|
2057
|
+
* What the row derivation reads off an authored body, as one comparable string: the refs that
|
|
2058
|
+
* name a source, and the style-bag keys (a bag's keys name style properties a source may declare).
|
|
2059
|
+
*/
|
|
2060
|
+
private derivationFacts;
|
|
2061
|
+
/**
|
|
2062
|
+
* The derivation, run once per snapshot and source set — {@link withDerivedRows} takes the changes
|
|
2063
|
+
* and {@link collidingSources} takes what it refused to decide.
|
|
2064
|
+
*
|
|
2065
|
+
* One walk rather than two, because the two answers come from the same pass and a second walk is a
|
|
2066
|
+
* second chance to disagree about which source claimed a path.
|
|
2067
|
+
*/
|
|
2068
|
+
private rowDerivation;
|
|
2069
|
+
/**
|
|
2070
|
+
* The targets inside `slug` this config already RE-aliases — `kind:path` in the source's namespace,
|
|
2071
|
+
* under a local name of its own rather than the source's.
|
|
2072
|
+
*
|
|
2073
|
+
* Entity-level pointers only, which is the borrow that moves an entity here. A value borrow leaves
|
|
2074
|
+
* the entity theirs and points a row of ours at what it is worth, so their path is still free and a
|
|
2075
|
+
* derived row there is the ordinary inheritance rather than a duplicate.
|
|
2076
|
+
*
|
|
2077
|
+
* A row this same declaration derived is never a re-alias — {@link derivedItemChange} always points
|
|
2078
|
+
* a target at its own path, so excluding it here is what lets {@link rowDerivation} re-derive one
|
|
2079
|
+
* rather than reading its presence as somebody's chosen alias.
|
|
2080
|
+
*
|
|
2081
|
+
* Also what the emitted types qualify: `Token.ref('x@ds')` is spelled in authored source to extend
|
|
2082
|
+
* an entity you hold, so the qualified name is worth typing for exactly these and not for the rest
|
|
2083
|
+
* of a source's catalogue.
|
|
2084
|
+
*/
|
|
2085
|
+
borrowedTargets(slug: string): Set<string>;
|
|
2086
|
+
/**
|
|
2087
|
+
* Everything `slug`'s pin declares this system took, as `kind:path` targets in the source's own
|
|
2088
|
+
* namespace — the second seed {@link sourceClosure} closes over.
|
|
2089
|
+
*
|
|
2090
|
+
* The declaration lives on the pin and only that kind knows how to read it, so this asks per
|
|
2091
|
+
* candidate ({@link EntityKind.bulkAdopts}) rather than interpreting the field here. Candidates are
|
|
2092
|
+
* what the source offers and this config could hold ({@link borrowableKinds}), which is what keeps a
|
|
2093
|
+
* whole-source take to design entities instead of reaching the source's own pins and sealed files.
|
|
2094
|
+
*
|
|
2095
|
+
* Empty for a pin that declares nothing, which is the ordinary case — so a config that borrowed by
|
|
2096
|
+
* hand enumerates no catalogue.
|
|
2097
|
+
*
|
|
2098
|
+
* Public because it is the half of {@link sourceClosure} that stands on its own: a row the pin
|
|
2099
|
+
* declares is there whatever this config's refs do, where a row seeded by reachability exists
|
|
2100
|
+
* because of the very ref asking about it. {@link redundantQualifiers} needs that distinction.
|
|
2101
|
+
*/
|
|
2102
|
+
/**
|
|
2103
|
+
* What the rows drawn through `slug` depend on: the members of a source GROUP something here names,
|
|
2104
|
+
* and every entity a LINKED row points at, walked to a fixpoint.
|
|
2105
|
+
*
|
|
2106
|
+
* A linked row's dependencies land as rows of their own, so everything a link needs has a path in
|
|
2107
|
+
* this system, the way a copy writes every one of them. A ref this config wrote by hand at the
|
|
2108
|
+
* source (`Token.ref('x@ds')`) brings a folder's members but not an item: the ref already reaches
|
|
2109
|
+
* the value, and a row minted for it would be that value under a second spelling.
|
|
2110
|
+
*
|
|
2111
|
+
* A ref may name a folder rather than an entity: `primitives/Text` binds its `variant` prop to
|
|
2112
|
+
* `composite:textVariant`, and a style property's `values: [Token.ref(ramp)]` does the same. A
|
|
2113
|
+
* folder has no stored row to borrow, so reachability — which brings what a ref NAMES — brings
|
|
2114
|
+
* nothing, and a config that borrowed one component ended up with a component whose prop domain is
|
|
2115
|
+
* empty and whose emitted module imports a lookup table that was never emitted.
|
|
2116
|
+
*
|
|
2117
|
+
* Taking every member is what makes the two spellings one set: the members land as ordinary rows at
|
|
2118
|
+
* their own paths, so this config emits their rules under ITS prefix and the borrowed module's
|
|
2119
|
+
* import resolves against its own table. A wholesale link already has them, so this adds nothing
|
|
2120
|
+
* there — it is the narrow borrow, which adopts no kinds at all, that has the hole.
|
|
2121
|
+
*
|
|
2122
|
+
* Walked to a fixpoint, because a member may name a folder of its own. That is also what keeps
|
|
2123
|
+
* {@link withDerivedRows} idempotent: a second pass finds every member already held and plans no
|
|
2124
|
+
* change.
|
|
2125
|
+
*
|
|
2126
|
+
* Every guard the adopted targets pass through applies to these too — a name this config already
|
|
2127
|
+
* holds wins, an entry it cannot express is left behind — because
|
|
2128
|
+
* they join the same loop rather than being written directly.
|
|
2129
|
+
*/
|
|
2130
|
+
private groupRefMembers;
|
|
2131
|
+
adoptedFrom(slug: string): string[];
|
|
2132
|
+
/**
|
|
2133
|
+
* One source item's identity, from the fields its own kind declares as `sourceIdentity` — what a walk
|
|
2134
|
+
* that must stay kind-blind keys a cache and a cycle check on.
|
|
2135
|
+
*
|
|
2136
|
+
* `undefined` when the kind declares none, or nothing is stored at that path. A declared field the
|
|
2137
|
+
* item does not set contributes an empty segment rather than being skipped: two items differing only
|
|
2138
|
+
* in which fields they set are different sources, and collapsing them would share one cache entry.
|
|
2139
|
+
*
|
|
2140
|
+
* Derived rather than spelled, which is what makes it right for kinds nobody has written — it covers
|
|
2141
|
+
* whatever fields a kind actually declares instead of the three `linked-system` happens to have. The
|
|
2142
|
+
* key it replaces was `${systemId}@${version}`, which a repo-local source cannot state at all;
|
|
2143
|
+
* that is the case the walk using it had to pre-skip.
|
|
2144
|
+
*/
|
|
2145
|
+
sourceIdentityKey(kind: string, path: string): string | undefined;
|
|
2146
|
+
private sourceKey;
|
|
2147
|
+
/**
|
|
2148
|
+
* Where what this config shows at `path` originates: the system it was first authored in, followed
|
|
2149
|
+
* through however many pins it took to get here. `undefined` for a row this config authored itself.
|
|
2150
|
+
*
|
|
2151
|
+
* A row borrowed or copied through a pin is asked of that pin's source in turn, so a system linked
|
|
2152
|
+
* to a system linked to a foundation answers with the foundation. A row a materialized artifact
|
|
2153
|
+
* flattened to a literal has no pointer left, so the pin whose `link` declaration covers the path
|
|
2154
|
+
* stands in, provided the row still reads as the source's ({@link matchesSource}); one the
|
|
2155
|
+
* publisher changed is its own. Two answers with one `key` are one entity reached by two routes,
|
|
2156
|
+
* whatever versions the pins name: the key is the pin kind's `sourceOrigin`, not its identity.
|
|
2157
|
+
*
|
|
2158
|
+
* `pin` is the pin entity at the hop where the answer was found, for a surface that names the
|
|
2159
|
+
* origin the way it names any source.
|
|
2160
|
+
*/
|
|
2161
|
+
originOf({
|
|
2162
|
+
kind,
|
|
2163
|
+
path
|
|
2164
|
+
}: {
|
|
2165
|
+
kind: string;
|
|
2166
|
+
path: string;
|
|
2167
|
+
}): {
|
|
2168
|
+
key: string;
|
|
2169
|
+
pin: Entity;
|
|
2170
|
+
} | undefined;
|
|
2171
|
+
/**
|
|
2172
|
+
* The pin whose `link` declaration covers `kind:path` while the row there still reads as that
|
|
2173
|
+
* source's: how a row a materialized artifact flattened to a literal is traced back to the pin it
|
|
2174
|
+
* came through. `undefined` when no declaration covers it, or the row differs from the source's.
|
|
2175
|
+
*/
|
|
2176
|
+
private declaredThrough;
|
|
2177
|
+
/**
|
|
2178
|
+
* The identity of the source behind a slug — {@link sourceIdentityKey} for a caller holding the
|
|
2179
|
+
* address every source lookup uses.
|
|
2180
|
+
*
|
|
2181
|
+
* A slug is unique across source kinds ({@link assertDistinctSourceSlugs}), so the first kind holding
|
|
2182
|
+
* it is the only one that can, and nothing here has to say which kind that was.
|
|
2183
|
+
*
|
|
2184
|
+
* A client that fetched a catalogue needs to know whether the pin it fetched under is still the same
|
|
2185
|
+
* pin. Comparing slugs alone can't tell a retarget from a no-op, and comparing `systemId`/`version`
|
|
2186
|
+
* names fields one kind happens to declare. `undefined` for a slug this config doesn't pin.
|
|
2187
|
+
*/
|
|
2188
|
+
sourceIdentityOf(slug: string): string | undefined;
|
|
2189
|
+
/**
|
|
2190
|
+
* Every slug this config draws a source under — the item paths of every kind declaring
|
|
2191
|
+
* {@link EntityKind.sourceIdentity}, whatever those kinds are called.
|
|
2192
|
+
*
|
|
2193
|
+
* One declaration site is the point: a source's value says both which source and where from, so
|
|
2194
|
+
* nothing has to reconcile two lists.
|
|
2195
|
+
*
|
|
2196
|
+
* Across all source kinds, and that is the right answer rather than a convenience: a ref spells `@ds`
|
|
2197
|
+
* with no room for a collection, so a slug is taken if any source kind has it. Two kinds claiming one
|
|
2198
|
+
* is refused outright ({@link assertDistinctSourceSlugs}), so this never reports a duplicate.
|
|
2199
|
+
*/
|
|
2200
|
+
sourceSlugs(): string[];
|
|
2201
|
+
/**
|
|
2202
|
+
* The kinds whose items name an external source — the ones declaring
|
|
2203
|
+
* {@link EntityKind.sourceIdentity}.
|
|
2204
|
+
*
|
|
2205
|
+
* The framework knows borrowing as a mechanism and asks which kind implements it rather than testing
|
|
2206
|
+
* for one by name. A config registering no such kind borrows nothing, and every reader below returns
|
|
2207
|
+
* empty without a special case.
|
|
2208
|
+
*/
|
|
2209
|
+
sourceKinds(): string[];
|
|
2210
|
+
/**
|
|
2211
|
+
* The source kind whose declared item is `slug` — asked rather than named, the same reason
|
|
2212
|
+
* {@link sourceKinds} is a list rather than a single kind the framework assumes.
|
|
2213
|
+
*
|
|
2214
|
+
* At most one: a config's slugs are flat and unique across every source kind
|
|
2215
|
+
* ({@link assertDistinctSourceSlugs}), because a ref spells `@ds` with no room for which
|
|
2216
|
+
* collection `ds` came from.
|
|
2217
|
+
*/
|
|
2218
|
+
private sourceKindFor;
|
|
2219
|
+
/**
|
|
2220
|
+
* Every source kind by the accessor name its config type declares it under — `linkedSystems` →
|
|
2221
|
+
* `linked-system`.
|
|
2222
|
+
*
|
|
2223
|
+
* The accessor name is what every source-shaped argument is keyed by ({@link AttachedSources},
|
|
2224
|
+
* `register`, `Config.load`'s resolvers), because it's the name an author writes, where the kind is
|
|
2225
|
+
* what the framework indexes items under. Something has to hold the pair, and this is the only place
|
|
2226
|
+
* it needs holding.
|
|
2227
|
+
*
|
|
2228
|
+
* Read structurally off the config type's own manifest, so it names no kind and needs nothing added
|
|
2229
|
+
* per kind. A `Config` built outside `defineConfig` has no manifest, so its source kinds answer under
|
|
2230
|
+
* their kind name, which is the only key such a config has.
|
|
2231
|
+
*/
|
|
2232
|
+
/**
|
|
2233
|
+
* The accessor a kind is registered under — `token` → `tokens` — or `undefined` for a config with no
|
|
2234
|
+
* manifest, and for a kind it does not declare.
|
|
2235
|
+
*
|
|
2236
|
+
* Config files are keyed by accessor (`register({ tokens, components })`) while ops and refs are
|
|
2237
|
+
* keyed by kind (`token:color/brand`). Anything stored that a person will read in a config file
|
|
2238
|
+
* therefore wants the accessor, and something has to be able to say which — this is that, read
|
|
2239
|
+
* structurally off the config type's own manifest exactly as {@link sourceAccessors} does, so it
|
|
2240
|
+
* names no kind and needs nothing added per kind.
|
|
2241
|
+
*/
|
|
2242
|
+
accessorOf(kind: string): string | undefined;
|
|
2243
|
+
sourceAccessors(): ReadonlyMap<string, string>;
|
|
2244
|
+
/**
|
|
2245
|
+
* Every source this config reaches into, captured as an ordinary artifact — what `toJSON` stores.
|
|
2246
|
+
*
|
|
2247
|
+
* Membership is reachability and nothing else: follow the refs and keep what they land on, whatever
|
|
2248
|
+
* kind it is. No kind opts in and no field is required, which is why a modifier reached through a
|
|
2249
|
+
* Token's condition and a font reached through a typography token both survive. A value-map shape
|
|
2250
|
+
* drops both, and the font is native borrowing's blocker.
|
|
2251
|
+
*
|
|
2252
|
+
* Stored as a config rather than a bag of values, so nothing about reading it is special. Bodies are
|
|
2253
|
+
* verbatim, so each validates against its own kind's schema on the way back in, and groups and
|
|
2254
|
+
* settings ride along so `cssVar` recomputes from the source's own `cssPrefix` instead of being
|
|
2255
|
+
* frozen per entity.
|
|
2256
|
+
*
|
|
2257
|
+
* Away from a build there is nothing attached, so this carries through what was hydrated rather than
|
|
2258
|
+
* blanking it.
|
|
2259
|
+
*
|
|
2260
|
+
* That is sound because a pin is exact: a published version is immutable, and a repo-local pin is
|
|
2261
|
+
* recomputed on every local build. A stale closure would need a source to change under a fixed pin,
|
|
2262
|
+
* which is what a pin prevents.
|
|
2263
|
+
*/
|
|
2264
|
+
/**
|
|
2265
|
+
* The closures this config would embed if it were serialized, without serializing it.
|
|
2266
|
+
*
|
|
2267
|
+
* {@link toJSON} already carries them, which is how an artifact keeps its borrowed values readable
|
|
2268
|
+
* when nothing is attached. A caller that wants only the closures — Create's load path, adding a
|
|
2269
|
+
* draft-aware closure to the base artifact a client hydrates — would otherwise pay a full walk over
|
|
2270
|
+
* every entity in a config that is routinely megabytes, to read a few kilobytes off the end.
|
|
2271
|
+
*
|
|
2272
|
+
* The same map `toJSON` embeds, from the same private walk, so the two can't describe a borrow
|
|
2273
|
+
* differently.
|
|
2274
|
+
*/
|
|
2275
|
+
recordedSources(): Record<string, RecordedSource>;
|
|
2276
|
+
private recordSources;
|
|
2277
|
+
/**
|
|
2278
|
+
* Every slug this config's refs name, declared here or not — what {@link recordSources} owes a
|
|
2279
|
+
* closure for.
|
|
2280
|
+
*
|
|
2281
|
+
* Declared slugs are only half of it. Materializing a published chain keeps a ref whose target has no
|
|
2282
|
+
* value to collapse into, so a component borrowed from `mid` arrives holding `style-property:bg@base`,
|
|
2283
|
+
* a slug only `mid` declares. Recording only what this config pins leaves that ref with nothing behind
|
|
2284
|
+
* it, and a consumer holding the artifact alone reads a borrowed component whose style property
|
|
2285
|
+
* resolves to nothing.
|
|
2286
|
+
*
|
|
2287
|
+
* Reachability is already what a closure means; this applies the same rule to the source address
|
|
2288
|
+
* rather than the target. {@link sourceThrough} is what answers for such a slug, and the pair is why
|
|
2289
|
+
* the recorded artifact is self-contained.
|
|
2290
|
+
*
|
|
2291
|
+
* Skipped entirely for a config that borrows nothing, so the common serialize walks nothing extra.
|
|
2292
|
+
*/
|
|
2293
|
+
private reachedSlugs;
|
|
2294
|
+
/**
|
|
2295
|
+
* Every item of a kind, in authored order — the flat list to map over in a UI. This config's own
|
|
2296
|
+
* kinds only: a source's items are read through a qualified ref, in the config that answers.
|
|
2297
|
+
*
|
|
2298
|
+
* **Memoized on the snapshot, like {@link Config.withComputed}, and for a sharper reason than
|
|
2299
|
+
* convenience.** A value domain is expanded per read (`stylePropEntries` → `members` → here), so
|
|
2300
|
+
* one CSS emit over this system's own artifact asked for the token list 23,895 times and paid 673
|
|
2301
|
+
* `withComputed` lookups each — 16M of them, 1.7s, on the browser's main thread. The list a
|
|
2302
|
+
* Snapshot has of a kind cannot change without the snapshot changing, so there is nothing to
|
|
2303
|
+
* expire.
|
|
2304
|
+
*
|
|
2305
|
+
* The array is shared with every other caller at this snapshot: consume it, don't mutate it. Sort
|
|
2306
|
+
* or splice a projection of it (`list(k).map(…).sort()`), never the list itself.
|
|
2307
|
+
*/
|
|
2308
|
+
/**
|
|
2309
|
+
* Every item of a kind. Total by default: the stylesheet, the type union, ref resolution and the rail
|
|
2310
|
+
* all read it, and each has to keep seeing a hidden or deprecated entity. `{ retired: false }` is the
|
|
2311
|
+
* offer surface's read — a picker, an autocomplete, a prompt catalogue — and leaves out every entity
|
|
2312
|
+
* {@link retired} answers true for. Both answers are cached per snapshot, so either is safe in a dep
|
|
2313
|
+
* array.
|
|
2314
|
+
*/
|
|
2315
|
+
list(kind: string, opts?: ListOptions): Entity[];
|
|
2316
|
+
/** The total list behind {@link list}. */
|
|
2317
|
+
private allOf;
|
|
2318
|
+
/** A kind's items as a nested folder/file tree, built from the qualified paths at any
|
|
2319
|
+
* depth (`color/spectrum/purple-60` → folder `color` → folder `color/spectrum` → item). A
|
|
2320
|
+
* folder carries its group metadata when a node exists at that prefix, else `undefined`
|
|
2321
|
+
* — folders come from the paths, so they render whether or not they were augmented. */
|
|
2322
|
+
tree(kind: string): TreeNode[];
|
|
2323
|
+
/** Every `kind:path` whose definition references the given entity — the reverse
|
|
2324
|
+
* dependency graph, harvested generically from refs in each item's data. */
|
|
2325
|
+
dependentsOf(kind: string, path: string, opts?: TraverseOptions): string[];
|
|
2326
|
+
/** The inverse: what `kind:path` needs in order to resolve. `{ transitive: true }` returns the
|
|
2327
|
+
* whole closure — what a sealed/exported copy would have to carry along with it. */
|
|
2328
|
+
dependenciesOf(kind: string, path: string, opts?: TraverseOptions): string[];
|
|
2329
|
+
/** Validate a create body against the kind's schema — derived from its fields. */
|
|
2330
|
+
validateCreate(kind: string, data: unknown): {
|
|
2331
|
+
ok: true;
|
|
2332
|
+
} | {
|
|
2333
|
+
ok: false;
|
|
2334
|
+
error: string;
|
|
2335
|
+
};
|
|
2336
|
+
/**
|
|
2337
|
+
* The wire form. Annotated with {@link SerializedConfig} rather than defining it by inference —
|
|
2338
|
+
* see that type for why the direction flipped, and why both drift directions still fail to compile.
|
|
2339
|
+
*
|
|
2340
|
+
* Every optional field stays a conditional spread rather than a later assignment: a
|
|
2341
|
+
* `{...(cond ? { k } : {})}` is checked against the annotation as one literal, where assigning to a
|
|
2342
|
+
* mutable object would defeat the excess-property check that replaces the old inference.
|
|
2343
|
+
*/
|
|
2344
|
+
toJSON(): SerializedConfig;
|
|
2345
|
+
/** `defineConfig` registers its class here so `Config.fromJSON(json)` can construct the right type
|
|
2346
|
+
* from `json.name`. */
|
|
2347
|
+
static registerConfigType(name: string, ctor: new () => Config): void;
|
|
2348
|
+
/** Rehydrate a config from its wire form. Untyped escape hatch for generic code that only holds a
|
|
2349
|
+
* plain `Config` (the ~100 studio patch appliers — all link-less system configs, so they call it
|
|
2350
|
+
* one-arg). Dispatches on `json.name` to the registered config type, then hydrates. Typed callers
|
|
2351
|
+
* use the config type's own `fromJSON` static (strict `LinkedSources`). `sources` values
|
|
2352
|
+
* self-describe their linked kind, so keys are irrelevant here. */
|
|
2353
|
+
static fromJSON(json: StoredConfig, options?: HydrationOptions): Config;
|
|
2354
|
+
/**
|
|
2355
|
+
* Hydrate an artifact with its sources resolved — the async counterpart to {@link fromJSON}.
|
|
2356
|
+
*
|
|
2357
|
+
* `fromJSON` is sync and every environment's fetch is not, which is the whole reason this exists.
|
|
2358
|
+
* Attaching at hydration rather than after is load-bearing: `registerSources` leaves a window in which
|
|
2359
|
+
* every sourced ref dangles, and emitters, integrity checks and memos all read exactly there.
|
|
2360
|
+
*
|
|
2361
|
+
* `sources` is keyed the way every other source-shaped argument is — by the source kind's accessor
|
|
2362
|
+
* name, `{ linkedSystems: resolver }` — so the resolver set, the declaration, and
|
|
2363
|
+
* {@link registerSources} all spell the same key. Each resolver is the kind's own
|
|
2364
|
+
* (`LinkedSystemResolver`), and its answer is validated against the schemas that kind declares.
|
|
2365
|
+
*
|
|
2366
|
+
* Reads the source entities off a throwaway bare parse, resolves them all, then hydrates once with
|
|
2367
|
+
* whatever came back attached. The bare parse is cheap and is what lets a resolver be handed the
|
|
2368
|
+
* Entity — `entity.path` is the slug and every other field is its kind's own, so a config declaring
|
|
2369
|
+
* some other source kind needs nothing added here.
|
|
2370
|
+
*
|
|
2371
|
+
* **Two levels of totality, failing differently.** The resolver map must cover every declared source
|
|
2372
|
+
* kind — a missing key is a programming error, and answering nothing for a kind reads identically to
|
|
2373
|
+
* "this config borrows nothing". Each resolver must then answer for every item of its kind;
|
|
2374
|
+
* `unavailable` is a legal answer, no answer is not.
|
|
2375
|
+
*
|
|
2376
|
+
* Refusing on `unavailable` is the caller's policy, not this method's: a build refuses, an editor
|
|
2377
|
+
* records it and paints it. So this attaches what it got, records every state, and returns.
|
|
2378
|
+
*/
|
|
2379
|
+
static load(json: SerializedConfig, options: {
|
|
2380
|
+
readonly sources: Record<string, SourceResolver<never>>;
|
|
2381
|
+
}): Promise<Config>;
|
|
2382
|
+
/**
|
|
2383
|
+
* This config with its declared sources resolved — the authoring-side counterpart to {@link load}.
|
|
2384
|
+
*
|
|
2385
|
+
* `load` is for an artifact, where attaching at hydration is load-bearing: between a bare `fromJSON`
|
|
2386
|
+
* and a later attach every sourced ref dangles, and the emitters and integrity checks read exactly
|
|
2387
|
+
* there. A config that was authored has no such window — it already exists, its sourced refs already
|
|
2388
|
+
* dangle, and the only question is what its pins resolve to. That is the build's shape, and forcing it
|
|
2389
|
+
* through `load` would mean serializing a config the caller is holding just to hydrate it again.
|
|
2390
|
+
*
|
|
2391
|
+
* Same resolvers, same validation, same recorded states, so the two paths cannot disagree about what a
|
|
2392
|
+
* source resolved to.
|
|
2393
|
+
*/
|
|
2394
|
+
resolveSources(options: {
|
|
2395
|
+
readonly sources: Record<string, SourceResolver<never>>;
|
|
2396
|
+
}): Promise<this>;
|
|
2397
|
+
/**
|
|
2398
|
+
* Attach one source and record what it resolved to — the incremental counterpart to
|
|
2399
|
+
* {@link resolveSources}.
|
|
2400
|
+
*
|
|
2401
|
+
* A walk answers for every declared pin at once, which is what a build and a page load want. A
|
|
2402
|
+
* surface that fetched a source because someone opened a panel holds one answer about one slug, and
|
|
2403
|
+
* putting that through the walk would mean inventing answers for the others: "as before" for the ones
|
|
2404
|
+
* already attached, and something for the ones nobody has looked at, which turns the third state
|
|
2405
|
+
* ({@link sources}: nobody resolved this) into a verdict. So the incremental case gets its own door.
|
|
2406
|
+
*/
|
|
2407
|
+
attachResolved({
|
|
2408
|
+
source,
|
|
2409
|
+
slug,
|
|
2410
|
+
answer
|
|
2411
|
+
}: {
|
|
2412
|
+
readonly source: string;
|
|
2413
|
+
readonly slug: string; /** What a resolver — or the server action standing in for one — answered about this slug. */
|
|
2414
|
+
readonly answer: SourceAnswer;
|
|
2415
|
+
}): this;
|
|
2416
|
+
/**
|
|
2417
|
+
* This config with another's live attachments and receipts carried across — minus any whose pin moved.
|
|
2418
|
+
*
|
|
2419
|
+
* **A rebuild from bytes loses every attachment.** A payload is bytes and an attached source isn't in
|
|
2420
|
+
* them, so `fromJSON` hands back a config with nothing attached, and every holder that rehydrates — a
|
|
2421
|
+
* realtime echo, a draft sync, a tab-switch true-up, a rebase — wipes a catalogue someone just
|
|
2422
|
+
* fetched. It looks like this: link a system, watch its catalogue fill in, then watch the panel revert
|
|
2423
|
+
* to "their values aren't loaded here yet" with no interaction. Nothing errors and the pin is still
|
|
2424
|
+
* there; only the attachment is gone, and with it every borrowed read.
|
|
2425
|
+
*
|
|
2426
|
+
* The receipts ride along for the same reason. They are what a rail reads to say why a pin is unmet,
|
|
2427
|
+
* and rebuilding without them turns "we looked and it failed" back into "nobody looked".
|
|
2428
|
+
*
|
|
2429
|
+
* Carried per slug, and only where the pin still names the same source, judged by the fields the kind
|
|
2430
|
+
* declares as its identity ({@link sourceIdentityOf}). A pin retargeted to another version is a
|
|
2431
|
+
* different source, and carrying its catalogue would have the picker offering the old version's
|
|
2432
|
+
* Entities under the new pin's name.
|
|
2433
|
+
*
|
|
2434
|
+
* It never overwrites — a caller that hydrated with sources of its own keeps them — and returns the
|
|
2435
|
+
* receiver unchanged when there is nothing to carry, so the ordinary path allocates nothing and
|
|
2436
|
+
* callers can compare by reference.
|
|
2437
|
+
*/
|
|
2438
|
+
withAttachmentsFrom(previous: Config): this;
|
|
2439
|
+
/**
|
|
2440
|
+
* Retire the attachment for every slug a walk answered `unavailable` for.
|
|
2441
|
+
*
|
|
2442
|
+
* A resolve walk is total over the declared items of a source kind — `unavailable` is a legal answer
|
|
2443
|
+
* and silence is not — so what it reports is the whole truth about those slugs rather than an
|
|
2444
|
+
* amendment to what was attached before. {@link registerSources} only ever sets, which is right for
|
|
2445
|
+
* the incremental case, attaching one source someone just fetched, and wrong for an answer that
|
|
2446
|
+
*/
|
|
2447
|
+
private detachUnresolved;
|
|
2448
|
+
/**
|
|
2449
|
+
* The resolve walk {@link load} and {@link resolveSources} share — declared sources in, the states
|
|
2450
|
+
* they answered with and the configs to attach out.
|
|
2451
|
+
*
|
|
2452
|
+
* Static and taking its config, because `load` runs it against a throwaway bare parse while
|
|
2453
|
+
* `resolveSources` runs it against the config the caller already holds.
|
|
2454
|
+
*/
|
|
2455
|
+
private static resolveDeclared;
|
|
2456
|
+
/**
|
|
2457
|
+
* One source kind's resolution schema, from what the kind declared — the framework's `status` tag over
|
|
2458
|
+
* its own `{ resolved, unavailable }`.
|
|
2459
|
+
*
|
|
2460
|
+
* A separate step so the refusal reads as a declaration problem rather than as a validation failure on
|
|
2461
|
+
* whatever the first resolver happened to answer. `defineConfig` already refuses a `sources` entry whose
|
|
2462
|
+
* kind declares none, so reaching this throw means a kind was resolved through some other route.
|
|
2463
|
+
*/
|
|
2464
|
+
private static resolutionSchemaOf;
|
|
2465
|
+
/** The shared hydration path both the untyped dispatcher and a config type's typed `fromJSON` funnel
|
|
2466
|
+
* through. The instance is freshly constructed by a config type, so its entity metadata is already
|
|
2467
|
+
* seeded (owned + derived); this loads the data (items, groups, sparse overrides) and wires
|
|
2468
|
+
* links from the provided sources. A declared `linkedKind` with no provided source throws. */
|
|
2469
|
+
protected hydrateFrom(stored: StoredConfig, options?: HydrationOptions): this;
|
|
2470
|
+
}
|
|
2471
|
+
//#endregion
|
|
2472
|
+
export { AttachedSources, Config, CssGrammar, Edition, HydrationOptions, ListOptions, LiveConfig, Patch, RecordedSource, SERIALIZED_CONFIG_VERSION, SerializedConfig, SkippedOp, SourcesBySlug, StoredConfig, SubPatch, TreeNode };
|