@yahoo/uds-create-config 5.73.1 → 5.74.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.
@@ -312,7 +312,7 @@ declare const CanvasConfigDefinition: ConfigClass<{
312
312
  tools: ToolClass;
313
313
  }, {
314
314
  systems: () => typeof System | typeof ReactNativeSystem;
315
- }>;
315
+ }, Record<never, never>>;
316
316
  interface CanvasConfigClass extends Named<typeof CanvasConfigDefinition> {}
317
317
  declare const CanvasConfig: CanvasConfigClass;
318
318
  /**
@@ -333,6 +333,6 @@ declare function createCanvasConfig(system: AnySystemConfig): ConfigInstance<{
333
333
  }, {
334
334
  operations: OperationClass;
335
335
  tools: ToolClass;
336
- }>;
336
+ }, Record<never, never>>;
337
337
  //#endregion
338
338
  export { AiChat, AiChatClass, AiFlow, AiFlowClass, AiFlowOrigin, AiGeneration, AiGenerationClass, AiMessage, AiMessageClass, AiMultiAgentMode, Annotation, AnnotationClass, AnnotationFields, AnnotationSegment, Canvas, CanvasClass, CanvasConfig, CanvasConfigClass, Node, NodeClass, Page, PageClass, SystemSource, SystemSourceClass, createCanvasConfig };
@@ -9,7 +9,7 @@ declare const DocumentationLibraryDefinition: ConfigClass<{
9
9
  }, {
10
10
  operations: OperationClass;
11
11
  tools: ToolClass;
12
- }, Record<never, never>>;
12
+ }, Record<never, never>, Record<never, never>>;
13
13
  interface DocumentationLibraryClass extends Named<typeof DocumentationLibraryDefinition> {}
14
14
  declare const DocumentationLibrary: DocumentationLibraryClass;
15
15
  //#endregion
@@ -12,7 +12,7 @@ import { CompositeClass } from "../entities/system/Composite.js";
12
12
  import { OverrideCondition } from "../entities/system/Token.js";
13
13
  import { StylePropValue } from "../entities/system/StyleProperty.js";
14
14
  import { ComponentClass } from "../entities/system/Component.js";
15
- import { LinkedSystemClass } from "../entities/system/LinkedSystem.js";
15
+ import { SourceClass } from "../entities/system/LinkedSystem.js";
16
16
  import { IconClass } from "../entities/system/Icon.js";
17
17
  import { NativeModifierClass } from "../entities/native/NativeModifier.js";
18
18
  import { CanvasRoleClass } from "../entities/system/CanvasRole.js";
@@ -408,12 +408,14 @@ declare const ReactNativeSystemDefinition: ConfigClass<{
408
408
  guidance: GuidanceClass;
409
409
  canvasRoles: CanvasRoleClass;
410
410
  devices: DeviceClass;
411
- linkedSystems: LinkedSystemClass;
411
+ sources: SourceClass;
412
412
  }, {
413
413
  operations: OperationClass;
414
414
  tools: ToolClass;
415
415
  }, {
416
- linkedSystems: () => ReactNativeSystemClass;
416
+ sources: () => ReactNativeSystemClass;
417
+ }, {
418
+ /** @deprecated Use `sources`. */readonly linkedSystems: "sources";
417
419
  }>;
418
420
  interface ReactNativeSystemClass extends Named<typeof ReactNativeSystemDefinition> {}
419
421
  declare const ReactNativeSystem: ReactNativeSystemClass;
@@ -18,7 +18,7 @@ import { motionJsonSchemaMigration } from "../migrations/20261015221052_motion_j
18
18
  import { motionTypeOnlyMigration } from "../migrations/20261016135620_motion_type_only.js";
19
19
  import { CanvasRole } from "../entities/system/CanvasRole.js";
20
20
  import { Guidance } from "../entities/system/Guidance.js";
21
- import { LinkedSystem } from "../entities/system/LinkedSystem.js";
21
+ import { Source } from "../entities/system/LinkedSystem.js";
22
22
  import { settingsAsKindMigration } from "../migrations/20260908171448_settings_as_kind.js";
23
23
  import { visibleStatePointerMigration } from "../migrations/20260908194245_visible_state_pointer.js";
24
24
  import { iconKnockoutVariantsMigration } from "../migrations/20260909183001_icon_knockout_variants.js";
@@ -50,9 +50,12 @@ const ReactNativeSystem = defineConfig({
50
50
  guidance: Guidance,
51
51
  canvasRoles: CanvasRole,
52
52
  devices: Device,
53
- linkedSystems: LinkedSystem
53
+ sources: Source
54
54
  },
55
- sources: { linkedSystems: () => ReactNativeSystem },
55
+ sources: { sources: () => ReactNativeSystem },
56
+ deprecatedAccessors: {
57
+ /** @deprecated Use `sources`. */
58
+ linkedSystems: "sources" },
56
59
  derivedEntities: {
57
60
  operations: Operation,
58
61
  tools: Tool
@@ -6,7 +6,7 @@ import { ModifierClass } from "../entities/system/Modifier.js";
6
6
  import { TokenClass } from "../entities/system/Token.js";
7
7
  import { StylePropertyClass } from "../entities/system/StyleProperty.js";
8
8
  import { ComponentClass } from "../entities/system/Component.js";
9
- import { LinkedSystemClass } from "../entities/system/LinkedSystem.js";
9
+ import { SourceClass } from "../entities/system/LinkedSystem.js";
10
10
  import { IconClass } from "../entities/system/Icon.js";
11
11
  import { CanvasRoleClass } from "../entities/system/CanvasRole.js";
12
12
  import { DeviceClass } from "../entities/system/Device.js";
@@ -37,12 +37,14 @@ declare const SystemDefinition: ConfigClass<{
37
37
  canvasRoles: CanvasRoleClass;
38
38
  globalStyles: GlobalStyleClass;
39
39
  devices: DeviceClass;
40
- linkedSystems: LinkedSystemClass;
40
+ sources: SourceClass;
41
41
  }, {
42
42
  operations: OperationClass;
43
43
  tools: ToolClass;
44
44
  }, {
45
- linkedSystems: () => SystemClass;
45
+ sources: () => SystemClass;
46
+ }, {
47
+ /** @deprecated Use `sources`. */readonly linkedSystems: "sources";
46
48
  }>;
47
49
  interface SystemClass extends Named<typeof SystemDefinition> {}
48
50
  declare const System: SystemClass;
@@ -38,11 +38,12 @@ import { layerPrefixMigration } from "../migrations/20261030163028_layer_prefix.
38
38
  import { defaultColorsMigration } from "../migrations/20261102143821_default_colors.js";
39
39
  import { linkReplaceMigration } from "../migrations/20261102185610_link_replace.js";
40
40
  import { mapPropsFromMigration } from "../migrations/20261102193912_map_props_from.js";
41
+ import { sourcesAuthoringMigration } from "../migrations/20261103115059_sources_authoring.js";
41
42
  import { CanvasRole } from "../entities/system/CanvasRole.js";
42
43
  import { GlobalStyle } from "../entities/system/GlobalStyle.js";
43
44
  import { Guidance } from "../entities/system/Guidance.js";
44
45
  import { GuidanceStyle } from "../entities/system/GuidanceStyle.js";
45
- import { LinkedSystem } from "../entities/system/LinkedSystem.js";
46
+ import { Source } from "../entities/system/LinkedSystem.js";
46
47
  import { loadV1Artifact } from "../migrations/2.0.0/v1-artifact.js";
47
48
  import { settingsAsKindMigration } from "../migrations/20260908171448_settings_as_kind.js";
48
49
  import { visibleStatePointerMigration } from "../migrations/20260908194245_visible_state_pointer.js";
@@ -118,9 +119,12 @@ const System = defineConfig({
118
119
  canvasRoles: CanvasRole,
119
120
  globalStyles: GlobalStyle,
120
121
  devices: Device,
121
- linkedSystems: LinkedSystem
122
+ sources: Source
122
123
  },
123
- sources: { linkedSystems: () => System },
124
+ sources: { sources: () => System },
125
+ deprecatedAccessors: {
126
+ /** @deprecated Use `sources`. */
127
+ linkedSystems: "sources" },
124
128
  derivedEntities: {
125
129
  operations: Operation,
126
130
  tools: Tool
@@ -209,5 +213,6 @@ if (!registeredSchemaMigrations().has(20261030163028)) registerSchemaMigrations(
209
213
  if (!registeredSchemaMigrations().has(20261102143821)) registerSchemaMigrations(defaultColorsMigration);
210
214
  if (!registeredSchemaMigrations().has(20261102185610)) registerSchemaMigrations(linkReplaceMigration);
211
215
  if (!registeredSchemaMigrations().has(20261102193912)) registerSchemaMigrations(mapPropsFromMigration);
216
+ if (!registeredSchemaMigrations().has(20261103115059)) registerSchemaMigrations(sourcesAuthoringMigration);
212
217
  //#endregion
213
218
  export { System };
@@ -212,19 +212,46 @@ declare const LinkedSystemDefinition: EntityClass<z.ZodObject<{
212
212
  path
213
213
  }: ComputedInput) => string;
214
214
  }, false, Record<never, never>, Record<never, never>>;
215
- /** A pin body as `register` takes one. */
216
- type LinkedSystemCreateInput = Exclude<ReturnType<typeof LinkedSystemDefinition.item>, string | number | Ref>;
217
- /** `LinkedSystem.item`'s body: the stored fields, with `options` as {@link PinOptionsInput} takes it. */
218
- type LinkedSystemItemInput<K extends string = string> = Omit<LinkedSystemCreateInput, 'options'> & {
219
- readonly options?: PinOptionsInput<K>;
215
+ /** A source body as `register` takes one. */
216
+ type SourceCreateInput = Exclude<ReturnType<typeof LinkedSystemDefinition.item>, string | number | Ref>;
217
+ /** @deprecated Use {@link SourceCreateInput}. */
218
+ type LinkedSystemCreateInput = SourceCreateInput;
219
+ /**
220
+ * `Source.item`'s body: the stored fields, with `options` as {@link PinOptionsInput} takes it, plus
221
+ * `options.include`, which is what the source brings in and is stored as `link`.
222
+ */
223
+ type SourceItemInput<K extends string = string> = Omit<SourceCreateInput, 'options' | 'link'> & {
224
+ /** @deprecated Use `options.include`. */readonly link?: LinkDeclaration;
225
+ readonly options?: SourceOptionsInput<K>;
220
226
  };
221
- interface LinkedSystemClass extends Omit<Named<typeof LinkedSystemDefinition>, 'item'> {
222
- /** One pin. A component entry's `override` and `extend` take their `styles` as the stored record or
223
- * the rule list `defineComponent.styles` takes, and a list is stored as the record. */
224
- item<const K extends string = string>(def: LinkedSystemItemInput<K>): LinkedSystemCreateInput;
225
- item(def: LinkedSystemCreateInput | string | number | Ref): LinkedSystemCreateInput | string | number | Ref;
227
+ /**
228
+ * What a source includes, beside {@link PinOptionsInput}'s directives. Where no accessors are
229
+ * registered the directives are an open record, as {@link PinOptionsInput} leaves them.
230
+ */
231
+ type SourceOptionsInput<K extends string> = ([keyof RegisteredEntities] extends [never] ? Record<string, unknown> : PinOptionsInput<K>) & {
232
+ /**
233
+ * What this system draws from the source, standing: `'*'` for everything, or a record by accessor
234
+ * (`'*'` keys every accessor it doesn't name). A list's `'!path'` entry leaves that path out.
235
+ */
236
+ readonly include?: LinkDeclaration;
237
+ };
238
+ /** @deprecated Use {@link SourceItemInput}. */
239
+ type LinkedSystemItemInput<K extends string = string> = SourceItemInput<K>;
240
+ interface SourceClass extends Omit<Named<typeof LinkedSystemDefinition>, 'item'> {
241
+ /** One source. A component entry's `override` and `extend` take their `styles` as the stored record
242
+ * or the rule list `defineComponent.styles` takes, and a list is stored as the record. */
243
+ item<const K extends string = string>(def: SourceItemInput<K>): SourceCreateInput;
244
+ item(def: SourceCreateInput | string | number | Ref): SourceCreateInput | string | number | Ref;
226
245
  }
227
- declare const LinkedSystem: LinkedSystemClass;
246
+ /** @deprecated Use {@link SourceClass}. */
247
+ type LinkedSystemClass = SourceClass;
248
+ /**
249
+ * `Source` (`yahoo-os`): another team's design system, drawn from at an exact version. Registered
250
+ * under `sources`.
251
+ */
252
+ declare const Source: SourceClass;
253
+ /** @deprecated Use {@link Source}, registered under `sources`. */
254
+ declare const LinkedSystem: SourceClass;
228
255
  /**
229
256
  * What a pin's `options` may say, checked against the config type it is declared in — one record per
230
257
  * accessor, keyed by the path the source spells, valued by what this config does to it.
@@ -332,7 +359,7 @@ interface PinDirective {
332
359
  readonly hidden?: boolean;
333
360
  }
334
361
  /** One declared source, typed off this kind's own schema — what the readers below hand back. */
335
- type Source = ItemOf<typeof LinkedSystem>;
362
+ type Source = ItemOf<typeof Source>;
336
363
  /**
337
364
  * A linked system's state as `Config.sourceStates()` reports it, narrowed to this kind's vocabulary.
338
365
  *
@@ -478,4 +505,4 @@ declare function linkRedirects(config: Config): LinkRedirect[];
478
505
  /** One sentence for a re-pointed path that can't be read. */
479
506
  declare function redirectSentence(redirect: LinkRedirect): string;
480
507
  //#endregion
481
- export { LINK_SLUG, LINK_SLUG_MESSAGE, LinkDeclaration, LinkRedirect, LinkedSystem, LinkedSystemClass, LinkedSystemCreateInput, LinkedSystemItemInput, LinkedSystemResolver, LinkedSystemState, PinDirective, PinOptions, PinOptionsField, PinOptionsInput, ResolvedSource, Source, SourceUnavailable, borrowedGroup, borrowedItem, isLinkSlug, linkRedirects, redirectSentence, resolvedSource, sourceOfGroup, sourceOfItem, sourceSlugFor, sourceUnavailable, suggestLinkSlug };
508
+ export { LINK_SLUG, LINK_SLUG_MESSAGE, LinkDeclaration, LinkRedirect, LinkedSystem, LinkedSystemClass, LinkedSystemCreateInput, LinkedSystemItemInput, LinkedSystemResolver, LinkedSystemState, PinDirective, PinOptions, PinOptionsField, PinOptionsInput, ResolvedSource, Source, SourceClass, SourceCreateInput, SourceItemInput, SourceUnavailable, borrowedGroup, borrowedItem, isLinkSlug, linkRedirects, redirectSentence, resolvedSource, sourceOfGroup, sourceOfItem, sourceSlugFor, sourceUnavailable, suggestLinkSlug };
@@ -5,6 +5,7 @@ import { pathInput } from "../../framework/schemas.js";
5
5
  import { kindBorrowable } from "../../framework/registry.js";
6
6
  import { defineEntity } from "../../framework/defineEntity.js";
7
7
  import { storedStyleRules } from "./Component.js";
8
+ import { storedFromAuthored } from "../../framework/authored-at.js";
8
9
  import { sourceUnreachable } from "../../framework/sources.js";
9
10
  import { covers, excludes } from "./link-declaration.js";
10
11
  import { planCopy } from "./copy-plan.js";
@@ -290,10 +291,19 @@ const pinOptions = z.record(z.string(), z.record(z.string(), z.strictObject({
290
291
  extend: z.record(z.string(), z.unknown()).optional(),
291
292
  hidden: z.boolean().optional()
292
293
  }).refine((entry) => entry.override !== void 0 || entry.extend !== void 0 || entry.hidden !== void 0, { message: "An `options` entry needs `override`, `extend` or `hidden`." }))).optional().describe("Per-entity adjustments, keyed by accessor, then path, then verb.");
294
+ /**
295
+ * Where a source's stored fields are authored. What a source includes is authored as
296
+ * `options.include`, beside the directives, and stored as `link`; `link` is still taken in a body.
297
+ * `include` sits among `options`' accessor keys, and no config type registers an accessor called
298
+ * `include`.
299
+ */
300
+ const SOURCE_AUTHORED_AT = { link: "options.include" };
293
301
  const LinkedSystemDefinition = defineEntity({
294
302
  kind: "linked-system",
295
303
  label: "Linked system",
296
304
  labelPlural: "Linked systems",
305
+ entityName: "Source",
306
+ authoredAt: SOURCE_AUTHORED_AT,
297
307
  invariants: (config) => [
298
308
  ...config.list("linked-system").filter(({ path }) => !isLinkSlug(path)).map(({ path }) => `Link "${path}": ${LINK_SLUG_MESSAGE}`),
299
309
  ...linkRedirects(config).filter((redirect) => redirect.problem !== void 0 && redirect.problem !== "missing").map(redirectSentence),
@@ -897,15 +907,25 @@ function storedPinOptions(options) {
897
907
  }
898
908
  return stored;
899
909
  }
900
- function linkedSystemItem(def) {
910
+ function sourceItem(def) {
901
911
  if (typeof def !== "object" || def === null || isRef(def)) return def;
902
- if (!isPlainObject(def.options)) return def;
912
+ const body = storedFromAuthored({
913
+ body: def,
914
+ authoredAt: SOURCE_AUTHORED_AT
915
+ });
916
+ if (!isPlainObject(body.options)) return body;
903
917
  return {
904
- ...def,
905
- options: storedPinOptions(def.options)
918
+ ...body,
919
+ options: storedPinOptions(body.options)
906
920
  };
907
921
  }
908
- const LinkedSystem = Object.assign(LinkedSystemDefinition, { item: linkedSystemItem });
922
+ /**
923
+ * `Source` (`yahoo-os`): another team's design system, drawn from at an exact version. Registered
924
+ * under `sources`.
925
+ */
926
+ const Source = Object.assign(LinkedSystemDefinition, { item: sourceItem });
927
+ /** @deprecated Use {@link Source}, registered under `sources`. */
928
+ const LinkedSystem = Source;
909
929
  /** Each `options` entry at a path its pin's `link` excludes, which derives no row for it to adjust. */
910
930
  function excludedDirectives(config) {
911
931
  return config.list("linked-system").flatMap(({ path: slug }) => {
@@ -1080,4 +1100,4 @@ function redirectSentence(redirect) {
1080
1100
  return `${at}, which names nothing this config can read.`;
1081
1101
  }
1082
1102
  //#endregion
1083
- export { LINK_SLUG, LINK_SLUG_MESSAGE, LinkedSystem, borrowedGroup, borrowedItem, isLinkSlug, linkRedirects, redirectSentence, resolvedSource, sourceOfGroup, sourceOfItem, sourceSlugFor, sourceUnavailable, suggestLinkSlug };
1103
+ export { LINK_SLUG, LINK_SLUG_MESSAGE, LinkedSystem, Source, borrowedGroup, borrowedItem, isLinkSlug, linkRedirects, redirectSentence, resolvedSource, sourceOfGroup, sourceOfItem, sourceSlugFor, sourceUnavailable, suggestLinkSlug };
@@ -74,7 +74,7 @@ interface RecordedSource {
74
74
  * The dense-counter `2` a short-lived scheme stamped before the chain existed reads as the mint
75
75
  * that replaced it (see `detectedWireVersion`).
76
76
  */
77
- declare const SERIALIZED_CONFIG_VERSION = 20261102193912;
77
+ declare const SERIALIZED_CONFIG_VERSION = 20261103115059;
78
78
  interface SerializedConfig {
79
79
  /** See {@link SERIALIZED_CONFIG_VERSION}. Always written; a stored artifact may lack it. */
80
80
  readonly version: number;
@@ -2236,6 +2236,14 @@ declare class Config {
2236
2236
  accessors(): Record<string, {
2237
2237
  kind: string;
2238
2238
  }>;
2239
+ /**
2240
+ * The accessor spellings this config type has retired, each mapped to the accessor it is now
2241
+ * (`{ linkedSystems: 'sources' }`). A writer locating an entity in authored source reads it, since a
2242
+ * file written before the rename still spells the old one.
2243
+ */
2244
+ deprecatedAccessors(): Readonly<Record<string, string>>;
2245
+ /** `name`, or the accessor it now is when it's a retired spelling. */
2246
+ currentAccessor(name: string): string;
2239
2247
  /** The kinds registered into this config — what it owns, whether or not they hold items yet.
2240
2248
  * Tool derivation (reads + mutations) scopes to these so a config only exposes its own kinds
2241
2249
  * (a source's kinds are read through their own config, so they derive none). */
@@ -13,6 +13,7 @@ import { authoredBag, cssVar, declaresCssVariable, pinBodyStyleNumbers, stylePro
13
13
  import { addressOf, editOp, inputOf, opVerb } from "./config-op.js";
14
14
  import { isAuthoringPhantom, phantomsIn } from "./utils/authoring-phantom.js";
15
15
  import { configSchemas } from "./config-ops.js";
16
+ import { storedFromAuthored } from "./authored-at.js";
16
17
  import { isAuthored } from "./authoring.js";
17
18
  import { Entity } from "./Entity.js";
18
19
  import { inlineAllSources } from "./inline-sources.js";
@@ -4540,6 +4541,18 @@ var Config = class Config {
4540
4541
  accessors() {
4541
4542
  return this.constructor.entities ?? {};
4542
4543
  }
4544
+ /**
4545
+ * The accessor spellings this config type has retired, each mapped to the accessor it is now
4546
+ * (`{ linkedSystems: 'sources' }`). A writer locating an entity in authored source reads it, since a
4547
+ * file written before the rename still spells the old one.
4548
+ */
4549
+ deprecatedAccessors() {
4550
+ return this.constructor.deprecatedAccessors ?? {};
4551
+ }
4552
+ /** `name`, or the accessor it now is when it's a retired spelling. */
4553
+ currentAccessor(name) {
4554
+ return this.deprecatedAccessors()[name] ?? name;
4555
+ }
4543
4556
  /** The kinds registered into this config — what it owns, whether or not they hold items yet.
4544
4557
  * Tool derivation (reads + mutations) scopes to these so a config only exposes its own kinds
4545
4558
  * (a source's kinds are read through their own config, so they derive none). */
@@ -6255,12 +6268,13 @@ var Config = class Config {
6255
6268
  * ({@link sources}: nobody resolved this) into a verdict. So the incremental case gets its own door.
6256
6269
  */
6257
6270
  attachResolved({ source, slug, answer }) {
6258
- const kind = this.sourceAccessors().get(source);
6259
- if (!kind) throw new Error(`attachResolved: "${source}" is not a source this config declares. Its source accessors are ${[...this.sourceAccessors().keys()].map((name) => `"${name}"`).join(", ") || "(none)"}.`);
6271
+ const accessor = this.currentAccessor(source);
6272
+ const kind = this.sourceAccessors().get(accessor);
6273
+ if (!kind) throw new Error(`attachResolved: "${accessor}" is not a source this config declares. Its source accessors are ${[...this.sourceAccessors().keys()].map((name) => `"${name}"`).join(", ") || "(none)"}.`);
6260
6274
  const { config: resolved, ...state } = parseResolution({
6261
- source,
6275
+ source: accessor,
6262
6276
  slug,
6263
- schema: Config.resolutionSchemaOf(this, kind, source),
6277
+ schema: Config.resolutionSchemaOf(this, kind, accessor),
6264
6278
  answer
6265
6279
  });
6266
6280
  const sources = new Map(this.state.sources);
@@ -6344,12 +6358,13 @@ var Config = class Config {
6344
6358
  */
6345
6359
  static async resolveDeclared(config, resolvers) {
6346
6360
  const kindOfAccessor = config.sourceAccessors();
6347
- const missing = [...kindOfAccessor.keys()].filter((name) => !(name in resolvers));
6361
+ const byAccessor = Object.fromEntries(Object.entries(resolvers).map(([name, resolve]) => [config.currentAccessor(name), resolve]));
6362
+ const missing = [...kindOfAccessor.keys()].filter((name) => !(name in byAccessor));
6348
6363
  if (missing.length > 0) throw new Error(`Config.load: no resolver for source ${missing.map((name) => `"${name}"`).join(", ")}. A source kind with no resolver answers nothing, which reads exactly like a config that borrows nothing — so it is refused here instead.`);
6349
6364
  const states = /* @__PURE__ */ new Map();
6350
6365
  const attached = {};
6351
6366
  for (const [name, kind] of kindOfAccessor) {
6352
- const resolve = resolvers[name];
6367
+ const resolve = byAccessor[name];
6353
6368
  const bySlug = {};
6354
6369
  const declared = Config.resolutionSchemaOf(config, kind, name);
6355
6370
  const answers = await Promise.all(config.list(kind).map(async (entity) => {
@@ -6391,7 +6406,7 @@ var Config = class Config {
6391
6406
  * links from the provided sources. A declared `linkedKind` with no provided source throws. */
6392
6407
  hydrateFrom(stored, options) {
6393
6408
  const claimed = schemaVersionOf(stored);
6394
- if (claimed > 20261102193912) throw new SchemaVersionTooNew(claimed);
6409
+ if (claimed > 20261103115059) throw new SchemaVersionTooNew(claimed);
6395
6410
  const wire = upgradeSerializedConfig(stored);
6396
6411
  if (!Array.isArray(wire.ownedKinds) || typeof wire.items !== "object" || wire.items === null) throw new ConfigFormatError(`Config "${stored.name ?? "system-config"}" cannot be hydrated: this is not a serialized config-v2 config (no "ownedKinds"/"items"). A pre-cutover build artifact converts through the registered cutover migration — import the config type's module (e.g. \`configs/system\`) before hydrating.`);
6397
6412
  const leftover = wire.options;
@@ -6741,7 +6756,11 @@ function extensionChange(entry, kind, path, raw, validate = false) {
6741
6756
  */
6742
6757
  function itemChange(entry, kind, path, raw, validate = false) {
6743
6758
  const body = isRef(raw) || isComposedValue(raw) || typeof raw !== "object" || raw === null ? { [entry.primaryField]: raw } : raw;
6744
- const def = validate === "author" && entry.authoredBody ? entry.authoredBody(body) : body;
6759
+ let def = validate === "author" && entry.authoredBody ? entry.authoredBody(body) : body;
6760
+ if (validate === "author" && entry.authoredAt) def = storedFromAuthored({
6761
+ body: def,
6762
+ authoredAt: entry.authoredAt
6763
+ });
6745
6764
  if (validate) {
6746
6765
  const parsed = wholeBodySchema(entry)?.safeParse(def);
6747
6766
  if (parsed && !parsed.success) {
@@ -0,0 +1,31 @@
1
+ //#region src/framework/authored-at.d.ts
2
+ /**
3
+ * A stored field an author writes somewhere else in the body: `{ link: 'options.include' }` stores
4
+ * `options.include` as `link`. Declared by the kind (`EntityKind.authoredAt`), and read in three
5
+ * places that have to agree: `register` folds the authored spelling into the stored field, the config
6
+ * emitter writes the stored field back where an author writes it, and a patch written into a
7
+ * checkout lands at whichever of the two the file already spells.
8
+ *
9
+ * The stored field stays a spelling an author may use, so a file written before the move keeps
10
+ * evaluating. A body that spells both is refused, since the two are one value.
11
+ */
12
+ /** Stored field → the dotted path it is authored at. */
13
+ type AuthoredAt = Readonly<Record<string, string>>;
14
+ /** A body as authored, with each field `authoredAt` moves folded into the field it's stored as. */
15
+ declare function storedFromAuthored({
16
+ body,
17
+ authoredAt
18
+ }: {
19
+ readonly body: Record<string, unknown>;
20
+ readonly authoredAt: AuthoredAt;
21
+ }): Record<string, unknown>;
22
+ /** A stored body as an author writes it: each field `authoredAt` moves, at the path it's authored at. */
23
+ declare function authoredFromStored({
24
+ body,
25
+ authoredAt
26
+ }: {
27
+ readonly body: Record<string, unknown>;
28
+ readonly authoredAt: AuthoredAt;
29
+ }): Record<string, unknown>;
30
+ //#endregion
31
+ export { AuthoredAt, authoredFromStored, storedFromAuthored };
@@ -0,0 +1,73 @@
1
+ import { isPlainObject, setAtPath } from "./utils/field-path.js";
2
+ import { rejectData } from "./rejection.js";
3
+ //#region src/framework/authored-at.ts
4
+ /**
5
+ * A stored field an author writes somewhere else in the body: `{ link: 'options.include' }` stores
6
+ * `options.include` as `link`. Declared by the kind (`EntityKind.authoredAt`), and read in three
7
+ * places that have to agree: `register` folds the authored spelling into the stored field, the config
8
+ * emitter writes the stored field back where an author writes it, and a patch written into a
9
+ * checkout lands at whichever of the two the file already spells.
10
+ *
11
+ * The stored field stays a spelling an author may use, so a file written before the move keeps
12
+ * evaluating. A body that spells both is refused, since the two are one value.
13
+ */
14
+ /** The value at a dotted path, or `undefined` when nothing is there. */
15
+ function valueAt(body, segments) {
16
+ let at = body;
17
+ for (const segment of segments) {
18
+ if (!isPlainObject(at) || !Object.hasOwn(at, segment)) return void 0;
19
+ at = at[segment];
20
+ }
21
+ return at;
22
+ }
23
+ /** `body` without the value at `segments`, pruning a parent the removal empties. */
24
+ function without(body, segments) {
25
+ const [head, ...rest] = segments;
26
+ if (head === void 0 || !Object.hasOwn(body, head)) return body;
27
+ const { [head]: child, ...others } = body;
28
+ if (rest.length === 0) return others;
29
+ if (!isPlainObject(child)) return body;
30
+ const remaining = without(child, rest);
31
+ return Object.keys(remaining).length === 0 ? others : {
32
+ ...others,
33
+ [head]: remaining
34
+ };
35
+ }
36
+ /** `body` with `value` first among the keys of the container at `segments`. */
37
+ function withFirst(body, segments, value) {
38
+ const [head, ...rest] = segments;
39
+ if (head === void 0) return body;
40
+ if (rest.length === 0) return {
41
+ [head]: value,
42
+ ...body
43
+ };
44
+ const child = isPlainObject(body[head]) ? body[head] : {};
45
+ return {
46
+ ...body,
47
+ [head]: withFirst(child, rest, value)
48
+ };
49
+ }
50
+ /** A body as authored, with each field `authoredAt` moves folded into the field it's stored as. */
51
+ function storedFromAuthored({ body, authoredAt }) {
52
+ let stored = body;
53
+ for (const [field, at] of Object.entries(authoredAt)) {
54
+ const segments = at.split(".");
55
+ const value = valueAt(stored, segments);
56
+ if (value === void 0) continue;
57
+ if (stored[field] !== void 0) rejectData(`\`${at}\` and \`${field}\` are one value, so a body sets only one of them. \`${field}\` is the deprecated spelling.`, segments);
58
+ stored = setAtPath(without(stored, segments), [field], value);
59
+ }
60
+ return stored;
61
+ }
62
+ /** A stored body as an author writes it: each field `authoredAt` moves, at the path it's authored at. */
63
+ function authoredFromStored({ body, authoredAt }) {
64
+ let authored = body;
65
+ for (const [field, at] of Object.entries(authoredAt)) {
66
+ if (authored[field] === void 0) continue;
67
+ const { [field]: value, ...rest } = authored;
68
+ authored = withFirst(rest, at.split("."), value);
69
+ }
70
+ return authored;
71
+ }
72
+ //#endregion
73
+ export { authoredFromStored, storedFromAuthored };
@@ -113,8 +113,19 @@ type SourceMap<Owned extends EntityMap> = { readonly [K in keyof Owned]?: () =>
113
113
  type SourcesOf<C> = C extends {
114
114
  readonly sources: infer D;
115
115
  } ? D : Record<never, never>;
116
- /** `register` arg — partial, keyed by owned accessor name; a key not in `entities` is a type error. */
117
- type OwnedRecords<Owned extends EntityMap> = { [K in keyof Owned]?: RecordFor<Owned[K]> };
116
+ /**
117
+ * Accessor spellings a config type has retired, each naming the accessor it is now:
118
+ * `{ linkedSystems: 'sources' }`.
119
+ *
120
+ * A retired spelling keeps working wherever an accessor is written (`register`, a `Collection`
121
+ * accessor, the resolver map `Config.load` takes) and is read as the accessor it names, so nothing
122
+ * stored or reported ever carries it. A JSDoc `@deprecated` on the entry reaches every one of those
123
+ * spellings, since the types below map over this record's own keys.
124
+ */
125
+ type AccessorAliases<Owned extends EntityMap> = Readonly<Record<string, keyof Owned & string>>;
126
+ /** `register` arg — partial, keyed by owned accessor name or a retired spelling of one; a key not in
127
+ * `entities` is a type error. */
128
+ type OwnedRecords<Owned extends EntityMap, Aliases extends AccessorAliases<Owned> = Record<never, never>> = { [K in keyof Owned]?: RecordFor<Owned[K]> } & { [A in keyof Aliases]?: RecordFor<Owned[Aliases[A]]> };
118
129
  /** A read-capable window onto a kind's items in a config — the value an accessor (`system.components`)
119
130
  * returns, branded by the entity CLASS type so a wrong-kind one fails to type.
120
131
  *
@@ -142,9 +153,9 @@ interface Collection<E extends AnyEntityClass> {
142
153
  * the nominal brand its private members carry, so a `ConfigInstance` would stop being assignable to
143
154
  * `Config` — and nearly every reader in the codebase takes a `Config`.
144
155
  */
145
- type ConfigInstance<Owned extends EntityMap, Derived extends EntityMap> = {
146
- register(records: OwnedRecords<Owned>): ConfigInstance<Owned, Derived>;
147
- } & Config & { [K in keyof Owned]: Collection<Owned[K]> } & { [K in keyof Derived]: Collection<Derived[K]> };
156
+ type ConfigInstance<Owned extends EntityMap, Derived extends EntityMap, Aliases extends AccessorAliases<Owned> = Record<never, never>> = {
157
+ register(records: OwnedRecords<Owned, Aliases>): ConfigInstance<Owned, Derived, Aliases>;
158
+ } & Config & { [K in keyof Owned]: Collection<Owned[K]> } & { [K in keyof Derived]: Collection<Derived[K]> } & { [A in keyof Aliases]: Collection<Owned[Aliases[A]]> };
148
159
  /**
149
160
  * What `defineConfig` returns — a constructor plus a typed static `fromJSON`.
150
161
  *
@@ -152,14 +163,16 @@ type ConfigInstance<Owned extends EntityMap, Derived extends EntityMap> = {
152
163
  * read — its borrowed values simply do not resolve — which is what `Config.load` exists to make
153
164
  * deliberate rather than accidental.
154
165
  */
155
- interface ConfigClass<Owned extends EntityMap, Derived extends EntityMap, Sources extends SourceMap<Owned> = Record<never, never>> {
156
- new (): ConfigInstance<Owned, Derived>;
157
- fromJSON(json: StoredConfig, options?: HydrationOptions): ConfigInstance<Owned, Derived>;
166
+ interface ConfigClass<Owned extends EntityMap, Derived extends EntityMap, Sources extends SourceMap<Owned> = Record<never, never>, Aliases extends AccessorAliases<Owned> = Record<never, never>> {
167
+ new (): ConfigInstance<Owned, Derived, Aliases>;
168
+ fromJSON(json: StoredConfig, options?: HydrationOptions): ConfigInstance<Owned, Derived, Aliases>;
158
169
  readonly configName: string;
159
170
  readonly entities: Owned;
160
171
  readonly derivedEntities: Derived;
161
172
  /** What this type DRAWS ON — see {@link SourceMap}. Empty for a config that draws on nothing. */
162
173
  readonly sources: Sources;
174
+ /** Retired accessor spellings — see {@link AccessorAliases}. */
175
+ readonly deprecatedAccessors: Aliases;
163
176
  /**
164
177
  * Extend this config type — the same config with some kinds swapped for their extensions
165
178
  * (`System.extend({ tools: CliTool })`). What differs between two consumers is which entity answers
@@ -172,9 +185,9 @@ interface ConfigClass<Owned extends EntityMap, Derived extends EntityMap, Source
172
185
  * `fromJSON`. (Nothing is lost — a swapped DERIVED kind never serializes, so the wire form is
173
186
  * identical.)
174
187
  */
175
- extend(swaps: Partial<Record<keyof Owned | keyof Derived, unknown>>): ConfigClass<Owned, Derived, Sources>;
188
+ extend(swaps: Partial<Record<keyof Owned | keyof Derived, unknown>>): ConfigClass<Owned, Derived, Sources, Aliases>;
176
189
  }
177
- declare function defineConfig<Owned extends EntityMap, Derived extends EntityMap = Record<never, never>, Sources extends SourceMap<Owned> = Record<never, never>>(spec: {
190
+ declare function defineConfig<Owned extends EntityMap, Derived extends EntityMap = Record<never, never>, Sources extends SourceMap<Owned> = Record<never, never>, const Aliases extends AccessorAliases<Owned> = Record<never, never>>(spec: {
178
191
  name: string;
179
192
  /**
180
193
  * What this config type holds and how one is authored, for a reader arriving cold — an agent
@@ -193,10 +206,11 @@ declare function defineConfig<Owned extends EntityMap, Derived extends EntityMap
193
206
  * would declare a source on a collection no ref can qualify with, and the failure would surface as
194
207
  * "this config borrows nothing" much later.
195
208
  */
196
- sources?: Sources;
209
+ sources?: Sources; /** Retired accessor spellings, each naming the accessor it is now — see {@link AccessorAliases}. */
210
+ deprecatedAccessors?: Aliases;
197
211
  /** Internal: built by `.extend` — it shares its base's name, so it must not take over the base's
198
212
  * slot in the untyped `Config.fromJSON` dispatch registry. */
199
213
  extendsBase?: boolean;
200
- }): ConfigClass<Owned, Derived, Sources>;
214
+ }): ConfigClass<Owned, Derived, Sources, Aliases>;
201
215
  //#endregion
202
- export { Collection, ConfigClass, ConfigInstance, ConfigKind, EntityMap, OwnedRecords, PartialBodyOf, PartialGroupBodyOf, SourceMap, SourcesOf, defineConfig, kindsOf };
216
+ export { AccessorAliases, Collection, ConfigClass, ConfigInstance, ConfigKind, EntityMap, OwnedRecords, PartialBodyOf, PartialGroupBodyOf, SourceMap, SourcesOf, defineConfig, kindsOf };
@@ -21,6 +21,8 @@ function kindsOf(...configTypes) {
21
21
  function defineConfig(spec) {
22
22
  const owned = spec.entities;
23
23
  const sources = spec.sources ?? {};
24
+ const aliases = spec.deprecatedAccessors ?? {};
25
+ for (const [retired, current] of Object.entries(aliases)) if (retired in owned || !(current in owned)) throw new Error(`${spec.name}: retired accessor "${retired}" has to name an owned accessor and not be one itself`);
24
26
  for (const name of Object.keys(sources)) {
25
27
  const cls = owned[name];
26
28
  if (!cls) throw new Error(`${spec.name}: source "${name}" is not an owned entity of this config`);
@@ -50,7 +52,9 @@ function defineConfig(spec) {
50
52
  * which is why the chained authoring form (`new System().register(…)`) is the idiom. */
51
53
  register(records) {
52
54
  let next = this;
53
- for (const [name, record] of Object.entries(records)) {
55
+ for (const [written, record] of Object.entries(records)) {
56
+ const name = aliases[written] ?? written;
57
+ if (name !== written && name in records) throw new Error(`"${written}" and "${name}" are one collection in config "${spec.name}" ("${written}" is its retired spelling), so register takes only one of them`);
54
58
  const cls = owned[name];
55
59
  if (!cls) throw new Error(`"${name}" is not an owned entity of config "${spec.name}"`);
56
60
  next = next.registerEntity(cls, record);
@@ -64,6 +68,7 @@ function defineConfig(spec) {
64
68
  static entities = owned;
65
69
  static derivedEntities = derived;
66
70
  static sources = sources;
71
+ static deprecatedAccessors = aliases;
67
72
  /** Swap kinds by their accessor name, across owned / derived. */
68
73
  static extend(swaps) {
69
74
  const pick = (base) => {
@@ -79,11 +84,19 @@ function defineConfig(spec) {
79
84
  entities: pick(owned),
80
85
  derivedEntities: pick(derived),
81
86
  sources,
87
+ deprecatedAccessors: aliases,
82
88
  extendsBase: true
83
89
  });
84
90
  }
85
91
  }
86
- for (const [name, cls] of [...Object.entries(owned), ...Object.entries(derived)]) {
92
+ for (const [name, cls] of [
93
+ ...Object.entries(owned),
94
+ ...Object.entries(derived),
95
+ ...Object.entries(aliases).flatMap(([retired, current]) => {
96
+ const entity = owned[current];
97
+ return entity ? [[retired, entity]] : [];
98
+ })
99
+ ]) {
87
100
  if (name in TypedConfig.prototype || name === "state") throw new Error(`${spec.name}: entity accessor "${name}" collides with a Config member — rename the accessor (the KIND itself is unaffected)`);
88
101
  Object.defineProperty(TypedConfig.prototype, name, {
89
102
  get() {