@yahoo/uds-create-config 2.44.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,183 @@
|
|
|
1
|
+
import { CONFIG_OPERATION_KIND } from "./config-operation-kind.js";
|
|
2
|
+
import { isRef, kindOf, pathOf, ref, refSchema, sourceOf } from "./utils/refs.js";
|
|
3
|
+
import { resolveFields } from "./schemas.js";
|
|
4
|
+
import { buildKind } from "./registry.js";
|
|
5
|
+
import { brandGroup } from "./utils/group.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/framework/defineEntity.ts
|
|
8
|
+
/**
|
|
9
|
+
* `defineEntity({ kind, fields, groupFields })` — the whole authoring toolkit for one
|
|
10
|
+
* kind, derived from its `z.object` field schema. Declare the fields; get the typed
|
|
11
|
+
* `item`/`group` constructors, the kind-bound `ref`, and a typed `get`. Pure: it builds the
|
|
12
|
+
* kind's metadata (`kindDef`) and attaches it to the returned class — no process-global write.
|
|
13
|
+
*
|
|
14
|
+
* A config type (`defineConfig`) names this kind in its `entities` map; the resulting config's typed
|
|
15
|
+
* `register({ <name>: items })` reads each class's `kindDef` into that config's own `#entities` and
|
|
16
|
+
* loads its items. So a config's behavior depends on what was registered into that instance — not
|
|
17
|
+
* import order.
|
|
18
|
+
*/
|
|
19
|
+
/** The options `extend` understands. Checked at runtime because `extend` shadows zod's: an
|
|
20
|
+
* unrecognized key is a typo, or raw zod fields from a caller expecting `ZodObject.extend`, and
|
|
21
|
+
* both should fail loudly instead of quietly building a kind that ignored them. `kind` is
|
|
22
|
+
* deliberately not here — a variant keeps its base's kind. */
|
|
23
|
+
const EXTENDABLE_KEYS = new Set([
|
|
24
|
+
"fields",
|
|
25
|
+
"groupFields",
|
|
26
|
+
"subEntities",
|
|
27
|
+
"schemas",
|
|
28
|
+
"label",
|
|
29
|
+
"labelPlural",
|
|
30
|
+
"entityName"
|
|
31
|
+
]);
|
|
32
|
+
/** Merge an override into the base's shape, key by key — using zod's real `extend`/`omit` on the
|
|
33
|
+
* plain object, where they are the right tools. A named key replaces or adds a field; `null`
|
|
34
|
+
* removes one; everything unnamed is inherited. There is no implicit wipe: only declared fields
|
|
35
|
+
* survive save and load, so removal has to be said out loud, and removing a key the base never
|
|
36
|
+
* declared is a typo worth naming. */
|
|
37
|
+
function extendShape(kind, area, base, override) {
|
|
38
|
+
if (override === void 0) return base;
|
|
39
|
+
if (!(base instanceof z.ZodObject)) throw new Error(`${kind}.extend: \`${area}\` merges into a plain z.object — a kind whose ${area} are a discriminated union cannot be extended here.`);
|
|
40
|
+
const entries = override instanceof z.ZodObject ? Object.entries(override.shape) : Object.entries(override);
|
|
41
|
+
const removed = entries.filter(([, value]) => value === null).map(([key]) => key);
|
|
42
|
+
const added = Object.fromEntries(entries.filter(([, value]) => value !== null));
|
|
43
|
+
for (const key of removed) if (!(key in base.shape)) throw new Error(`${kind}.extend: \`${area}.${key}: null\` removes nothing — the base declares no "${key}".`);
|
|
44
|
+
const kept = removed.length > 0 ? base.omit(Object.fromEntries(removed.map((key) => [key, true]))) : base;
|
|
45
|
+
return Object.keys(added).length > 0 ? kept.extend(added) : kept;
|
|
46
|
+
}
|
|
47
|
+
/** Merge a sub-entities override into the base's, key by key — same rule as the shapes: name one
|
|
48
|
+
* to swap it, `null` to drop it, the rest are inherited by reference (the framework locates a
|
|
49
|
+
* collection by object identity, so inheriting must pass the same instances through). */
|
|
50
|
+
function extendSubEntities(base, override) {
|
|
51
|
+
if (override === void 0) return base;
|
|
52
|
+
const merged = { ...base };
|
|
53
|
+
for (const [key, value] of Object.entries(override)) if (value === null) delete merged[key];
|
|
54
|
+
else if (value !== void 0) merged[key] = value;
|
|
55
|
+
return merged;
|
|
56
|
+
}
|
|
57
|
+
function defineEntity(spec) {
|
|
58
|
+
if (spec.kind === "config") throw new Error(`Entity kind "${CONFIG_OPERATION_KIND}" is reserved: it is the address segment of the config's own operations (\`config/info\`). Name the kind for what it holds.`);
|
|
59
|
+
const declaredGroupFields = spec.groupFields ?? z.object({});
|
|
60
|
+
const groupFields = declaredGroupFields instanceof z.ZodObject ? declaredGroupFields.extend({ ref: refSchema.optional() }) : declaredGroupFields;
|
|
61
|
+
const kindDef = buildKind({
|
|
62
|
+
name: spec.kind,
|
|
63
|
+
fields: spec.fields,
|
|
64
|
+
groupFields,
|
|
65
|
+
subEntities: spec.subEntities,
|
|
66
|
+
label: spec.label,
|
|
67
|
+
labelPlural: spec.labelPlural,
|
|
68
|
+
entityName: spec.entityName,
|
|
69
|
+
schemas: spec.schemas,
|
|
70
|
+
computed: spec.computed,
|
|
71
|
+
sourceIdentity: spec.sourceIdentity,
|
|
72
|
+
sourceOrigin: spec.sourceOrigin,
|
|
73
|
+
authoredRefs: spec.authoredRefs,
|
|
74
|
+
linkable: spec.linkable,
|
|
75
|
+
localGroupFields: spec.localGroupFields,
|
|
76
|
+
emergentGroups: spec.emergentGroups,
|
|
77
|
+
singleton: spec.singleton,
|
|
78
|
+
bareMember: spec.bareMember,
|
|
79
|
+
sourceResolution: spec.sourceResolution,
|
|
80
|
+
bulkAdopts: spec.bulkAdopts,
|
|
81
|
+
bulkOverlay: spec.bulkOverlay,
|
|
82
|
+
bulkDirectives: spec.bulkDirectives,
|
|
83
|
+
bulkOverlayWrite: spec.bulkOverlayWrite,
|
|
84
|
+
nameEdges: spec.nameEdges,
|
|
85
|
+
styleBags: spec.styleBags,
|
|
86
|
+
rewriteName: spec.rewriteName,
|
|
87
|
+
uniqueLeaves: spec.uniqueLeaves,
|
|
88
|
+
invariants: spec.invariants
|
|
89
|
+
});
|
|
90
|
+
const schema = z.object({ __ref: z.string().refine((raw) => kindOf(raw) === spec.kind) }).meta({
|
|
91
|
+
ref: true,
|
|
92
|
+
refKind: spec.kind
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* The path a caller named — from a path, a ref object, or a ref string (`component:Box`, which is
|
|
96
|
+
* what a spec element's `type` is, so a caller reading one needn't take it apart first).
|
|
97
|
+
*
|
|
98
|
+
* `undefined` when the name belongs to a different kind: `icon:phosphor/StarIcon` asked of
|
|
99
|
+
* `Component` resolves to nothing, whichever form it arrives in. Kept separate from `get` so the
|
|
100
|
+
* parsing is stated once, where a reader looking for "what forms does a name come in" will find it.
|
|
101
|
+
*/
|
|
102
|
+
const pathNamed = (pathOrRef) => {
|
|
103
|
+
if (isRef(pathOrRef)) return kindOf(pathOrRef.__ref) === spec.kind ? pathOf(pathOrRef.__ref) : void 0;
|
|
104
|
+
if (!pathOrRef.includes(":")) return pathOrRef;
|
|
105
|
+
return kindOf(pathOrRef) === spec.kind ? pathOf(pathOrRef) : void 0;
|
|
106
|
+
};
|
|
107
|
+
return Object.assign(schema, {
|
|
108
|
+
kind: spec.kind,
|
|
109
|
+
schemas: spec.schemas ?? {},
|
|
110
|
+
kindDef,
|
|
111
|
+
ref: (path) => {
|
|
112
|
+
if (path === void 0 && !spec.singleton) throw new Error(`${spec.kind}.ref: a path is required — only a singleton's one item can be addressed without one`);
|
|
113
|
+
return ref(spec.kind, path ?? spec.kind);
|
|
114
|
+
},
|
|
115
|
+
item: (def) => def,
|
|
116
|
+
group: (meta) => brandGroup(meta),
|
|
117
|
+
singleton: spec.singleton ?? false,
|
|
118
|
+
get: (config, pathOrRef) => {
|
|
119
|
+
if (pathOrRef === void 0) {
|
|
120
|
+
if (!spec.singleton) throw new Error(`${spec.kind}.get: a path is required — only a singleton's one item can be read without one`);
|
|
121
|
+
return config.resolve(spec.kind, spec.kind);
|
|
122
|
+
}
|
|
123
|
+
const path = pathNamed(pathOrRef);
|
|
124
|
+
if (path === void 0) return void 0;
|
|
125
|
+
if ((isRef(pathOrRef) || pathOrRef.includes(":") ? sourceOf(pathOrRef) : void 0) !== void 0) return config.resolveRef(pathOrRef);
|
|
126
|
+
return config.resolve(spec.kind, path);
|
|
127
|
+
},
|
|
128
|
+
list: (config, opts) => config.list(spec.kind, opts),
|
|
129
|
+
tree: (config) => config.tree(spec.kind),
|
|
130
|
+
groups: (config, opts) => config.groups(spec.kind, opts),
|
|
131
|
+
dependents: (config, path, opts) => config.dependentsOf(spec.kind, path, opts),
|
|
132
|
+
create: (config, path, data) => config.apply({
|
|
133
|
+
kind: spec.kind,
|
|
134
|
+
operation: "create",
|
|
135
|
+
path,
|
|
136
|
+
data
|
|
137
|
+
}),
|
|
138
|
+
update: (config, path, data) => config.apply({
|
|
139
|
+
kind: spec.kind,
|
|
140
|
+
operation: "update",
|
|
141
|
+
path,
|
|
142
|
+
data
|
|
143
|
+
}),
|
|
144
|
+
delete: (config, path) => config.apply({
|
|
145
|
+
kind: spec.kind,
|
|
146
|
+
operation: "delete",
|
|
147
|
+
path
|
|
148
|
+
}),
|
|
149
|
+
rename: (config, path, to) => config.apply({
|
|
150
|
+
kind: spec.kind,
|
|
151
|
+
operation: "rename",
|
|
152
|
+
path,
|
|
153
|
+
to
|
|
154
|
+
}),
|
|
155
|
+
extend: (overrides) => {
|
|
156
|
+
const unknown = Object.keys(overrides).filter((key) => !EXTENDABLE_KEYS.has(key));
|
|
157
|
+
if (unknown.length > 0) throw new Error(`${spec.kind}.extend: unknown option ${unknown.map((key) => `"${key}"`).join(", ")} — expected one of ${[...EXTENDABLE_KEYS].join(", ")}. This is an entity extension, not \`ZodObject.extend\`.`);
|
|
158
|
+
const declared = spec.fields;
|
|
159
|
+
const fields = typeof declared === "function" ? (ctx) => extendShape(spec.kind, "fields", declared(ctx), overrides.fields) : extendShape(spec.kind, "fields", declared, overrides.fields);
|
|
160
|
+
const staticFields = resolveFields(fields);
|
|
161
|
+
const groupFields = extendShape(spec.kind, "groupFields", spec.groupFields ?? z.object({}), overrides.groupFields);
|
|
162
|
+
const subEntities = extendSubEntities(spec.subEntities, overrides.subEntities);
|
|
163
|
+
if (staticFields instanceof z.ZodObject) {
|
|
164
|
+
for (const key of Object.keys(subEntities ?? {})) if (!(key in staticFields.shape)) throw new Error(`${spec.kind}.extend: sub-entity "${key}" has no field — remove it too (\`subEntities: { ${key}: null }\`) or keep its field.`);
|
|
165
|
+
}
|
|
166
|
+
const schemas = spec.schemas || overrides.schemas ? {
|
|
167
|
+
...spec.schemas,
|
|
168
|
+
...overrides.schemas
|
|
169
|
+
} : void 0;
|
|
170
|
+
return defineEntity({
|
|
171
|
+
...spec,
|
|
172
|
+
...overrides,
|
|
173
|
+
kind: spec.kind,
|
|
174
|
+
fields,
|
|
175
|
+
groupFields,
|
|
176
|
+
subEntities,
|
|
177
|
+
schemas
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { defineEntity };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Ref } from "./utils/refs.js";
|
|
2
|
+
import { FieldsCtx, UserSchemas } from "./schemas.js";
|
|
3
|
+
import { Config } from "./Config.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
//#region src/framework/defineSubEntity.d.ts
|
|
7
|
+
/** A member's schema, or a FACTORY that builds one from its context — the config registry and the
|
|
8
|
+
* owner being edited. A factory is what lets a member's schema depend on its owner (a slot's target
|
|
9
|
+
* must be one of THAT component's layers), so the check is the schema rather than a refinement. */
|
|
10
|
+
type MemberFields<F extends z.ZodTypeAny = z.ZodTypeAny> = F | ((ctx: FieldsCtx) => F);
|
|
11
|
+
/** The zod schema a sub-entity IS — the COLLECTION (`{ [key]: member }`), so it can be used
|
|
12
|
+
* directly as its owner's field: `fields: z.object({ layers: Layer })`. Generic over the member
|
|
13
|
+
* so the owner's inferred body keeps the member's real type (`layers.root` is its element, a
|
|
14
|
+
* prop narrows on `type`) instead of collapsing to an opaque record. */
|
|
15
|
+
type CollectionSchema<F extends z.ZodTypeAny = z.ZodTypeAny> = z.ZodRecord<z.ZodString, F>;
|
|
16
|
+
/** One keyed collection's definition — what a member is, what it's called, and any op overrides.
|
|
17
|
+
* It IS a zod schema (the collection), mirroring how an entity class IS its ref schema, so an
|
|
18
|
+
* owner declares the field by naming the sub-entity rather than restating its shape.
|
|
19
|
+
*
|
|
20
|
+
* A singleton sub-entity (`singleton: true`) is the member itself rather than a collection of them:
|
|
21
|
+
* the owner's field holds one body, at the field's own name. Its runtime schema is the member's, so
|
|
22
|
+
* an owner declares the field as `fields: z.object({ css: cssFields.optional() })` and lists the
|
|
23
|
+
* sub-entity under `subEntities`, where the framework finds it by identity. */
|
|
24
|
+
type SubEntityClass<F extends z.ZodTypeAny = z.ZodTypeAny> = CollectionSchema<F> & {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** Whether the owner's field holds ONE member rather than a keyed collection — a settings
|
|
27
|
+
* section. A singleton offers `sub-get` and `sub-update` only: there is nothing to add, remove,
|
|
28
|
+
* rename or order, and its address is the field with no key. */
|
|
29
|
+
readonly singleton?: true; /** Human label for one member (a row header, a tool label). Defaults to a title-cased `name`. */
|
|
30
|
+
readonly label: string; /** Human label for the collection. Defaults to `label + 's'`. */
|
|
31
|
+
readonly labelPlural: string; /** The MEMBER's schema — what one entry in the collection is. Declared, not inferred. */
|
|
32
|
+
readonly fields: MemberFields<F>; /** The member shape every owner shares, for a FACTORY `fields` — see the spec field below. */
|
|
33
|
+
readonly base?: z.ZodTypeAny;
|
|
34
|
+
/** Derive a member's key FROM ITS VALUE, when the member has a natural identity (a style rule IS
|
|
35
|
+
* its condition). The framework then computes the key on write rather than taking one from the
|
|
36
|
+
* caller, so key and value can't drift, two members with the same identity can't both exist, and
|
|
37
|
+
* an edit that changes the identity moves the entry. Omit when the key is arbitrary. */
|
|
38
|
+
readonly key?: (member: unknown) => string;
|
|
39
|
+
/** Whether a member DECLARES nothing — a style rule whose every layer bag is empty. Asked only
|
|
40
|
+
* when a \`sub-update\` would bring the member into being: a delta made entirely of removals has
|
|
41
|
+
* nothing to remove from a member that is not there, so creating one files an entry that emits
|
|
42
|
+
* nothing and that nobody asked for. An EXISTING member emptied by a delta is left alone; a rule
|
|
43
|
+
* with an empty bag is still a rule, and \`sub-delete\` is how one goes away.
|
|
44
|
+
*
|
|
45
|
+
* Omit when every member declares something by existing (a component layer is its element). */
|
|
46
|
+
readonly vacant?: (member: unknown) => boolean; /** Op-schema overrides/additions for this collection's sub-ops, same shape entities take. */
|
|
47
|
+
readonly schemas?: UserSchemas;
|
|
48
|
+
/** The ref KIND whose paths this collection's members namespace — `'value'` for a component's
|
|
49
|
+
* props, whose variant values are `value:<prop>/<name>`. Renaming a member then moves every
|
|
50
|
+
* ref under it, which a member rename alone can't reach (a `/` boundary inside the path, not
|
|
51
|
+
* the `#` boundary {@link rewriteRefs} matches). Omit when nothing hangs off a member's name. */
|
|
52
|
+
readonly namespaces?: string;
|
|
53
|
+
/**
|
|
54
|
+
* Fields of THIS member that are a record keyed by another collection's member NAMES — a style
|
|
55
|
+
* rule's `when` is keyed by the component's own props.
|
|
56
|
+
*
|
|
57
|
+
* A key is not a ref, so the rename cascade cannot see it. Without this, renaming a prop leaves
|
|
58
|
+
* every rule conditioned on it filed under a prop that no longer exists: the rule stops
|
|
59
|
+
* applying, and because its own key is DERIVED from that condition, the key is stale too.
|
|
60
|
+
* Declared per field rather than inferred, since only the entity knows what its records mean.
|
|
61
|
+
*/
|
|
62
|
+
readonly namesMembersOf?: Readonly<Record<string, string>>;
|
|
63
|
+
/**
|
|
64
|
+
* Collections nested INSIDE one member — a variant prop's `value`, which is a keyed set of the
|
|
65
|
+
* values that prop offers.
|
|
66
|
+
*
|
|
67
|
+
* One level, and only one: a nested collection is addressed by its owner's key (`props/value` +
|
|
68
|
+
* `owner: 'variant'`), and a second level would need a path rather than a key. Nothing has asked
|
|
69
|
+
* for one, and the addressing is the part that would stop being simple.
|
|
70
|
+
*/
|
|
71
|
+
readonly subEntities?: Readonly<Record<string, SubEntityClass>>;
|
|
72
|
+
/**
|
|
73
|
+
* Fields of the OWNING ENTITY that hold this member's NAME as a VALUE, keyed by the member's
|
|
74
|
+
* owner — a component's `defaultProps` and `previewProps` are both `{ [prop]: value }`.
|
|
75
|
+
*
|
|
76
|
+
* The mirror of {@link namesMembersOf}, which covers a name held as a record KEY. Neither is
|
|
77
|
+
* reachable by a ref rewrite, and both leave the same kind of wreckage: a default naming a
|
|
78
|
+
* variant value that was renamed out from under it reads as "this component has no default",
|
|
79
|
+
* silently, on a surface nobody was looking at when the rename happened.
|
|
80
|
+
*/
|
|
81
|
+
readonly namedValuesIn?: readonly string[];
|
|
82
|
+
/**
|
|
83
|
+
* Fields of the OWNING ENTITY that are KEYED by this member's name — a component's
|
|
84
|
+
* `defaultProps` and `previewProps` are both `{ [prop]: value }`, so the prop's name is the key
|
|
85
|
+
* rather than the value.
|
|
86
|
+
*
|
|
87
|
+
* The other half of {@link namedValuesIn}, which covers the same two fields from the value side
|
|
88
|
+
* (a variant VALUE's name). Both are needed because both positions hold a name no ref rewrite
|
|
89
|
+
* reaches: renaming a prop and leaving the key behind orphans its default under the old name, so
|
|
90
|
+
* the renamed prop reports no default and the component reports one for a prop it hasn't got.
|
|
91
|
+
*/
|
|
92
|
+
readonly keyedByNameIn?: readonly string[];
|
|
93
|
+
/**
|
|
94
|
+
* Sibling collections whose members exist only to say something ABOUT one of these — a style
|
|
95
|
+
* rule IS its condition, so a rule conditioned on a variant value that has been removed has
|
|
96
|
+
* nothing left to test.
|
|
97
|
+
*
|
|
98
|
+
* Deleting them WITH the member rather than leaving them: a rule that can never match is inert,
|
|
99
|
+
* but it still lists itself in every surface that shows a component's styles, and its condition
|
|
100
|
+
* still names something gone. Declared per collection because "the dependents go too" is only
|
|
101
|
+
* true where the dependent is meaningless without its subject — a layer's deletion does NOT
|
|
102
|
+
* imply that of the rules targeting it, which have their own other layers to style.
|
|
103
|
+
*/
|
|
104
|
+
readonly orphans?: readonly string[]; /** A ref to one member: `Prop.ref('component', 'Pressable', 'gap')`. */
|
|
105
|
+
ref(kind: string, path: string, member: string): Ref;
|
|
106
|
+
/** One member of this collection on `owner` (a path or a ref) — the sub-entity twin of
|
|
107
|
+
* `Component.get`. A member ref resolves without naming the member twice. `ownerMember` names
|
|
108
|
+
* the enclosing member for a NESTED collection (which prop's values are being read). */
|
|
109
|
+
get(config: Config, owner: string | Ref, member?: string, ownerMember?: string): unknown;
|
|
110
|
+
/** Every member of this collection on `owner`, as `{ key, ref, value }` — the sub-entity twin
|
|
111
|
+
* of `Component.list`. */
|
|
112
|
+
list(config: Config, owner: string | Ref, ownerMember?: string): {
|
|
113
|
+
key: string;
|
|
114
|
+
ref: string;
|
|
115
|
+
value: unknown;
|
|
116
|
+
}[];
|
|
117
|
+
};
|
|
118
|
+
declare function defineSubEntity<F extends z.ZodTypeAny>(spec: {
|
|
119
|
+
name: string;
|
|
120
|
+
label?: string;
|
|
121
|
+
labelPlural?: string;
|
|
122
|
+
/** See {@link SubEntityClass.singleton}. A singleton's `fields` is the one member's schema and
|
|
123
|
+
* may not be a factory: there is no owner-dependent shape to build. */
|
|
124
|
+
singleton?: true;
|
|
125
|
+
fields: MemberFields<F>;
|
|
126
|
+
/**
|
|
127
|
+
* The member shape every owner shares, when `fields` is a factory.
|
|
128
|
+
*
|
|
129
|
+
* This is what the COLLECTION schema validates, and so the only check a member gets at
|
|
130
|
+
* `register()`: a factory needs an owner to build its exact shape, and a body being registered has
|
|
131
|
+
* no owner yet. Without a `base` the member is `z.unknown()` there, which means an authoring
|
|
132
|
+
* mistake in a registered body is ACCEPTED and only surfaces as whatever the field's reader makes
|
|
133
|
+
* of it — for `forwards`, a list of prop names where member refs belong resolves to nothing at all,
|
|
134
|
+
* silently. Declare it as the same schema the factory returns for the no-owner case.
|
|
135
|
+
*/
|
|
136
|
+
base?: z.ZodTypeAny;
|
|
137
|
+
key?: (member: unknown) => string;
|
|
138
|
+
vacant?: (member: unknown) => boolean;
|
|
139
|
+
schemas?: UserSchemas;
|
|
140
|
+
namespaces?: string;
|
|
141
|
+
namesMembersOf?: Readonly<Record<string, string>>;
|
|
142
|
+
subEntities?: Readonly<Record<string, SubEntityClass>>;
|
|
143
|
+
namedValuesIn?: readonly string[];
|
|
144
|
+
keyedByNameIn?: readonly string[];
|
|
145
|
+
orphans?: readonly string[];
|
|
146
|
+
}): SubEntityClass<F>;
|
|
147
|
+
//#endregion
|
|
148
|
+
export { SubEntityClass, defineSubEntity };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { kindOf, memberOf, memberRef, pathOf } from "./utils/refs.js";
|
|
2
|
+
import { resolveSchema } from "./schemas.js";
|
|
3
|
+
import { titleCase } from "./registry.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/framework/defineSubEntity.ts
|
|
6
|
+
/**
|
|
7
|
+
* `defineSubEntity({ name, fields })` — the toolkit for one KEYED COLLECTION inside an entity (a
|
|
8
|
+
* component's `layers` / `props`, an AI flow's `sections`). Previously these were bare names
|
|
9
|
+
* (`subEntities: ['layers']`) whose member shape had to be INFERRED from the parent's field type,
|
|
10
|
+
* which meant a member could carry no labels, no op overrides, and no schema of its own.
|
|
11
|
+
*
|
|
12
|
+
* A sub-entity now declares the same things an entity does — its member `fields`, display labels,
|
|
13
|
+
* and `schemas` overrides for its own sub-ops — so `subEntities: { layers: Layer }` reads like
|
|
14
|
+
* `entities: { components: Component }` one level down. PURE: it returns metadata; the parent's
|
|
15
|
+
* `buildKind` reads it when deriving that collection's ops.
|
|
16
|
+
*/
|
|
17
|
+
function defineSubEntity(spec) {
|
|
18
|
+
const label = spec.label ?? titleCase(spec.name);
|
|
19
|
+
const member = typeof spec.fields === "function" ? spec.base ?? resolveSchema(spec.fields, {}) : spec.fields;
|
|
20
|
+
if (spec.singleton && typeof spec.fields === "function") throw new Error(`defineSubEntity: singleton "${spec.name}" takes a member schema, not a factory — its one member has no owner-dependent shape.`);
|
|
21
|
+
const schema = spec.singleton ? member : z.record(z.string(), member);
|
|
22
|
+
/** Where this collection sits on `kind` — its own field, plus the field of the collection it is
|
|
23
|
+
* nested INSIDE when it is one. Found by identity, so the same sub-entity can be reused and a
|
|
24
|
+
* collection is never addressed by a hardcoded field name. */
|
|
25
|
+
const fieldOn = (config, kind) => {
|
|
26
|
+
const subs = config.entityOf(kind)?.subEntities ?? {};
|
|
27
|
+
for (const [field, def] of Object.entries(subs)) {
|
|
28
|
+
if (def === api) return { field };
|
|
29
|
+
const nested = Object.entries(def.subEntities ?? {}).find(([, inner]) => inner === api);
|
|
30
|
+
if (nested) return {
|
|
31
|
+
field: nested[0],
|
|
32
|
+
ownerField: field
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** The owner's kind + path. A ref carries both; a bare path can't say its kind, so the caller
|
|
37
|
+
* must pass a ref to reach a collection. */
|
|
38
|
+
const ownerParts = (owner) => {
|
|
39
|
+
const target = typeof owner === "string" ? owner : owner.__ref;
|
|
40
|
+
return {
|
|
41
|
+
path: pathOf(target),
|
|
42
|
+
kind: typeof owner === "string" ? void 0 : kindOf(target)
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const collectionOn = (config, owner, ownerMember) => {
|
|
46
|
+
const { path, kind } = ownerParts(owner);
|
|
47
|
+
if (!kind) return void 0;
|
|
48
|
+
const found = fieldOn(config, kind);
|
|
49
|
+
if (!found) return void 0;
|
|
50
|
+
const body = config.resolve(kind, path)?.toJSON();
|
|
51
|
+
let holder = body;
|
|
52
|
+
if (found.ownerField) {
|
|
53
|
+
if (ownerMember === void 0) return void 0;
|
|
54
|
+
holder = (body?.[found.ownerField])?.[ownerMember];
|
|
55
|
+
}
|
|
56
|
+
const collection = holder?.[found.field];
|
|
57
|
+
const items = collection !== null && typeof collection === "object" && !Array.isArray(collection) ? collection : {};
|
|
58
|
+
return {
|
|
59
|
+
kind,
|
|
60
|
+
path,
|
|
61
|
+
field: found.field,
|
|
62
|
+
items
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
/** A singleton's one member on `owner` — the owner's field, read whole. */
|
|
66
|
+
const memberOn = (config, owner) => {
|
|
67
|
+
const { path, kind } = ownerParts(owner);
|
|
68
|
+
if (!kind) return void 0;
|
|
69
|
+
const found = fieldOn(config, kind);
|
|
70
|
+
if (!found || found.ownerField) return void 0;
|
|
71
|
+
return (config.resolve(kind, path)?.toJSON())?.[found.field];
|
|
72
|
+
};
|
|
73
|
+
const api = Object.assign(schema, {
|
|
74
|
+
name: spec.name,
|
|
75
|
+
...spec.singleton ? { singleton: true } : {},
|
|
76
|
+
label,
|
|
77
|
+
labelPlural: spec.labelPlural ?? (spec.singleton ? label : `${label}s`),
|
|
78
|
+
fields: spec.fields,
|
|
79
|
+
base: spec.base,
|
|
80
|
+
key: spec.key,
|
|
81
|
+
vacant: spec.vacant,
|
|
82
|
+
schemas: spec.schemas,
|
|
83
|
+
namespaces: spec.namespaces,
|
|
84
|
+
namesMembersOf: spec.namesMembersOf,
|
|
85
|
+
subEntities: spec.subEntities,
|
|
86
|
+
namedValuesIn: spec.namedValuesIn,
|
|
87
|
+
keyedByNameIn: spec.keyedByNameIn,
|
|
88
|
+
orphans: spec.orphans,
|
|
89
|
+
ref: (kind, path, member) => memberRef(kind, path, spec.name, member),
|
|
90
|
+
get: (config, owner, member, ownerMember) => {
|
|
91
|
+
if (spec.singleton) return memberOn(config, owner);
|
|
92
|
+
const raw = typeof owner === "string" ? void 0 : memberOf(owner.__ref);
|
|
93
|
+
const fromRef = raw?.includes("/") ? raw.split("/")[1] : raw;
|
|
94
|
+
const key = member ?? fromRef;
|
|
95
|
+
if (key === void 0) return void 0;
|
|
96
|
+
return collectionOn(config, owner, ownerMember)?.items[key];
|
|
97
|
+
},
|
|
98
|
+
list: (config, owner, ownerMember) => {
|
|
99
|
+
if (spec.singleton) {
|
|
100
|
+
const value = memberOn(config, owner);
|
|
101
|
+
if (value === void 0) return [];
|
|
102
|
+
const { path, kind } = ownerParts(owner);
|
|
103
|
+
return [{
|
|
104
|
+
key: spec.name,
|
|
105
|
+
ref: `${kind}:${path}#${spec.name}`,
|
|
106
|
+
value
|
|
107
|
+
}];
|
|
108
|
+
}
|
|
109
|
+
const found = collectionOn(config, owner, ownerMember);
|
|
110
|
+
if (!found) return [];
|
|
111
|
+
return Object.entries(found.items).map(([key, value]) => ({
|
|
112
|
+
key,
|
|
113
|
+
ref: memberRef(found.kind, found.path, spec.name, ownerMember === void 0 ? key : `${ownerMember}/${key}`).__ref,
|
|
114
|
+
value
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return api;
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
export { defineSubEntity };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/framework/derived-mutations.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* `DERIVED_MUTATIONS` — every mutation verb the framework derives for a kind, in BASE form
|
|
4
|
+
* (`sub-create`, not `sub-create/layers`).
|
|
5
|
+
*
|
|
6
|
+
* ASKED of `defaultSchemas` rather than listed, because the list has a consumer that has to keep up
|
|
7
|
+
* with it: the write engine (`@yahoo/uds-create-codegen`) promises to author every op a config
|
|
8
|
+
* accepts, and its coverage test reads this set. A hand-kept copy would have let a new derived op
|
|
9
|
+
* ship writable-in-name-only — the config takes the patch, the file it came from never learns of it.
|
|
10
|
+
*
|
|
11
|
+
* Derived from a PROBE kind, not from a real config, because this vocabulary is a property of the
|
|
12
|
+
* FRAMEWORK. An entity's own custom ops (`linked-system`'s `link`, a canvas `element-insert`) mean
|
|
13
|
+
* whatever that entity says and carry a handler to match, so they are absent by construction rather
|
|
14
|
+
* than filtered out — nothing else is obliged to implement them.
|
|
15
|
+
*
|
|
16
|
+
* Reads are excluded: nothing is written, so there is nothing to write back.
|
|
17
|
+
*/
|
|
18
|
+
declare const DERIVED_MUTATIONS: ReadonlySet<string>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { DERIVED_MUTATIONS };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defaultSchemas } from "./schemas.js";
|
|
2
|
+
import { defineSubEntity } from "./defineSubEntity.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/framework/derived-mutations.ts
|
|
5
|
+
/**
|
|
6
|
+
* `DERIVED_MUTATIONS` — every mutation verb the framework derives for a kind, in BASE form
|
|
7
|
+
* (`sub-create`, not `sub-create/layers`).
|
|
8
|
+
*
|
|
9
|
+
* ASKED of `defaultSchemas` rather than listed, because the list has a consumer that has to keep up
|
|
10
|
+
* with it: the write engine (`@yahoo/uds-create-codegen`) promises to author every op a config
|
|
11
|
+
* accepts, and its coverage test reads this set. A hand-kept copy would have let a new derived op
|
|
12
|
+
* ship writable-in-name-only — the config takes the patch, the file it came from never learns of it.
|
|
13
|
+
*
|
|
14
|
+
* Derived from a PROBE kind, not from a real config, because this vocabulary is a property of the
|
|
15
|
+
* FRAMEWORK. An entity's own custom ops (`linked-system`'s `link`, a canvas `element-insert`) mean
|
|
16
|
+
* whatever that entity says and carry a handler to match, so they are absent by construction rather
|
|
17
|
+
* than filtered out — nothing else is obliged to implement them.
|
|
18
|
+
*
|
|
19
|
+
* Reads are excluded: nothing is written, so there is nothing to write back.
|
|
20
|
+
*/
|
|
21
|
+
/** One collection, so the `sub-*` half of the surface is derived too. Its member shape is irrelevant
|
|
22
|
+
* — the verbs a collection declares don't depend on what a member is. */
|
|
23
|
+
const PROBE_COLLECTION = defineSubEntity({
|
|
24
|
+
name: "member",
|
|
25
|
+
fields: z.unknown()
|
|
26
|
+
});
|
|
27
|
+
function derivedMutations() {
|
|
28
|
+
const schemas = defaultSchemas("probe", z.object({}), z.object({}), { members: PROBE_COLLECTION });
|
|
29
|
+
const verbs = /* @__PURE__ */ new Set();
|
|
30
|
+
for (const [verb, entry] of Object.entries(schemas)) {
|
|
31
|
+
if (entry.readOnly) continue;
|
|
32
|
+
verbs.add(verb.split("/")[0]);
|
|
33
|
+
}
|
|
34
|
+
return verbs;
|
|
35
|
+
}
|
|
36
|
+
const DERIVED_MUTATIONS = derivedMutations();
|
|
37
|
+
//#endregion
|
|
38
|
+
export { DERIVED_MUTATIONS };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Config } from "./Config.js";
|
|
2
|
+
|
|
3
|
+
//#region src/framework/entity-search.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Collapse a typed name into a comparable key: lowercased, with every run of separators
|
|
6
|
+
* (`/ . - _` and whitespace) folded to a single `-`. This is what makes `purple/9`, `purple.9`,
|
|
7
|
+
* `Purple 9` and `purple-9` name the same thing, and it applies to `Card/Header` just as well.
|
|
8
|
+
*/
|
|
9
|
+
declare function normalizeName(input: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Paths of the given kind that this name names EXACTLY, separator- and case-insensitively.
|
|
12
|
+
*
|
|
13
|
+
* Empty means nothing matched. More than one means the name is genuinely ambiguous — the same leaf in
|
|
14
|
+
* several groups — and the caller has to disambiguate rather than take the first.
|
|
15
|
+
*/
|
|
16
|
+
declare function matchEntities(config: Config, kind: string, query: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* Paths of the given kind that a partial query might mean, best first.
|
|
19
|
+
*
|
|
20
|
+
* `filter` is how a caller narrows on something only it knows — a value type, a group, whether the
|
|
21
|
+
* entity is already used. It runs against the resolved entity, after ranking and before the limit, so
|
|
22
|
+
* a limit counts what survives rather than what was considered.
|
|
23
|
+
*/
|
|
24
|
+
declare function searchEntities(config: Config, kind: string, query: string, opts?: {
|
|
25
|
+
limit?: number;
|
|
26
|
+
filter?: (path: string) => boolean;
|
|
27
|
+
}): string[];
|
|
28
|
+
//#endregion
|
|
29
|
+
export { matchEntities, normalizeName, searchEntities };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createSliceMemo } from "./memo.js";
|
|
2
|
+
//#region src/framework/entity-search.ts
|
|
3
|
+
/**
|
|
4
|
+
* Collapse a typed name into a comparable key: lowercased, with every run of separators
|
|
5
|
+
* (`/ . - _` and whitespace) folded to a single `-`. This is what makes `purple/9`, `purple.9`,
|
|
6
|
+
* `Purple 9` and `purple-9` name the same thing, and it applies to `Card/Header` just as well.
|
|
7
|
+
*/
|
|
8
|
+
function normalizeName(input) {
|
|
9
|
+
return input.trim().toLowerCase().replace(/[\s/._-]+/g, "-").replace(/^-|-$/g, "");
|
|
10
|
+
}
|
|
11
|
+
function push(index, key, path) {
|
|
12
|
+
const existing = index.get(key);
|
|
13
|
+
if (existing) existing.push(path);
|
|
14
|
+
else index.set(key, [path]);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One memo per kind, built on first use.
|
|
18
|
+
*
|
|
19
|
+
* A per-render scan is the whole reason an index earns its keep: separator-insensitive matching can't
|
|
20
|
+
* be a path comparison, and an icon library is thousands of entries.
|
|
21
|
+
*
|
|
22
|
+
* A linked kind is handled without declaring anything: `createSliceMemo` keys a kind on the CHAIN of
|
|
23
|
+
* slices down its links, so an index over a kind this config borrows tracks the SOURCE's slice too.
|
|
24
|
+
* That matters here because `Config.list` reads through to a linked kind's source, and a consumer that
|
|
25
|
+
* owns nothing of that kind has a slice which never changes — so keying on it alone would cache the
|
|
26
|
+
* source's first answer forever.
|
|
27
|
+
*/
|
|
28
|
+
const indexes = /* @__PURE__ */ new Map();
|
|
29
|
+
function indexFor(config, kind) {
|
|
30
|
+
let memo = indexes.get(kind);
|
|
31
|
+
if (!memo) {
|
|
32
|
+
memo = createSliceMemo({
|
|
33
|
+
kinds: [kind],
|
|
34
|
+
compute: (c) => {
|
|
35
|
+
const byQualified = /* @__PURE__ */ new Map();
|
|
36
|
+
const byLeaf = /* @__PURE__ */ new Map();
|
|
37
|
+
const entries = [];
|
|
38
|
+
for (const entity of c.list(kind)) {
|
|
39
|
+
const { path } = entity;
|
|
40
|
+
const qualified = normalizeName(path);
|
|
41
|
+
push(byQualified, qualified, path);
|
|
42
|
+
const slash = path.lastIndexOf("/");
|
|
43
|
+
const leaf = slash > 0 ? normalizeName(path.slice(slash + 1)) : qualified;
|
|
44
|
+
if (slash > 0) push(byLeaf, leaf, path);
|
|
45
|
+
entries.push({
|
|
46
|
+
path,
|
|
47
|
+
qualified,
|
|
48
|
+
leaf
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
byQualified,
|
|
53
|
+
byLeaf,
|
|
54
|
+
entries
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
indexes.set(kind, memo);
|
|
59
|
+
}
|
|
60
|
+
return memo(config);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Paths of the given kind that this name names EXACTLY, separator- and case-insensitively.
|
|
64
|
+
*
|
|
65
|
+
* Empty means nothing matched. More than one means the name is genuinely ambiguous — the same leaf in
|
|
66
|
+
* several groups — and the caller has to disambiguate rather than take the first.
|
|
67
|
+
*/
|
|
68
|
+
function matchEntities(config, kind, query) {
|
|
69
|
+
const key = normalizeName(query);
|
|
70
|
+
if (!key) return [];
|
|
71
|
+
const { byQualified, byLeaf } = indexFor(config, kind);
|
|
72
|
+
const paths = [];
|
|
73
|
+
for (const path of [...byQualified.get(key) ?? [], ...byLeaf.get(key) ?? []]) if (!paths.includes(path)) paths.push(path);
|
|
74
|
+
return paths;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* How well an entry answers a partial query. Lower first; `undefined` is no match.
|
|
78
|
+
*
|
|
79
|
+
* A prefix beats a substring because a list that ranks them together buries the obvious answer —
|
|
80
|
+
* typing `purple` should offer `purple-9` above `deep-purple`. The leaf beats the qualified name for
|
|
81
|
+
* the same reason: people type the name, not the group.
|
|
82
|
+
*/
|
|
83
|
+
function rankOf(entry, query) {
|
|
84
|
+
if (entry.leaf.startsWith(query)) return 0;
|
|
85
|
+
if (entry.qualified.startsWith(query)) return 1;
|
|
86
|
+
if (entry.leaf.includes(query)) return 2;
|
|
87
|
+
if (entry.qualified.includes(query)) return 3;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Paths of the given kind that a partial query might mean, best first.
|
|
91
|
+
*
|
|
92
|
+
* `filter` is how a caller narrows on something only it knows — a value type, a group, whether the
|
|
93
|
+
* entity is already used. It runs against the resolved entity, after ranking and before the limit, so
|
|
94
|
+
* a limit counts what survives rather than what was considered.
|
|
95
|
+
*/
|
|
96
|
+
function searchEntities(config, kind, query, opts) {
|
|
97
|
+
const key = normalizeName(query);
|
|
98
|
+
if (!key) return [];
|
|
99
|
+
const ranked = [];
|
|
100
|
+
for (const entry of indexFor(config, kind).entries) {
|
|
101
|
+
const rank = rankOf(entry, key);
|
|
102
|
+
if (rank !== void 0) ranked.push({
|
|
103
|
+
rank,
|
|
104
|
+
path: entry.path
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
ranked.sort((a, b) => a.rank - b.rank);
|
|
108
|
+
const paths = [];
|
|
109
|
+
for (const { path } of ranked) {
|
|
110
|
+
if (opts?.filter && !opts.filter(path)) continue;
|
|
111
|
+
paths.push(path);
|
|
112
|
+
if (opts?.limit !== void 0 && paths.length >= opts.limit) break;
|
|
113
|
+
}
|
|
114
|
+
return paths;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { matchEntities, normalizeName, searchEntities };
|