@formspec/build 0.1.0-alpha.28 → 0.1.0-alpha.29

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.
Files changed (50) hide show
  1. package/dist/analyzer/class-analyzer.d.ts +11 -5
  2. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  3. package/dist/analyzer/program.d.ts +3 -2
  4. package/dist/analyzer/program.d.ts.map +1 -1
  5. package/dist/browser.cjs +485 -76
  6. package/dist/browser.cjs.map +1 -1
  7. package/dist/browser.js +486 -77
  8. package/dist/browser.js.map +1 -1
  9. package/dist/build-alpha.d.ts +18 -2
  10. package/dist/build-beta.d.ts +18 -2
  11. package/dist/build-internal.d.ts +18 -2
  12. package/dist/build.d.ts +18 -2
  13. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  14. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  15. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  16. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  17. package/dist/cli.cjs +1031 -170
  18. package/dist/cli.cjs.map +1 -1
  19. package/dist/cli.js +1032 -171
  20. package/dist/cli.js.map +1 -1
  21. package/dist/generators/class-schema.d.ts +6 -1
  22. package/dist/generators/class-schema.d.ts.map +1 -1
  23. package/dist/generators/method-schema.d.ts.map +1 -1
  24. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  25. package/dist/index.cjs +998 -170
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +4 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +999 -171
  30. package/dist/index.js.map +1 -1
  31. package/dist/internals.cjs +921 -155
  32. package/dist/internals.cjs.map +1 -1
  33. package/dist/internals.js +922 -156
  34. package/dist/internals.js.map +1 -1
  35. package/dist/json-schema/generator.d.ts +3 -1
  36. package/dist/json-schema/generator.d.ts.map +1 -1
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/metadata/collision-guards.d.ts +3 -0
  39. package/dist/metadata/collision-guards.d.ts.map +1 -0
  40. package/dist/metadata/index.d.ts +7 -0
  41. package/dist/metadata/index.d.ts.map +1 -0
  42. package/dist/metadata/policy.d.ts +11 -0
  43. package/dist/metadata/policy.d.ts.map +1 -0
  44. package/dist/metadata/resolve.d.ts +20 -0
  45. package/dist/metadata/resolve.d.ts.map +1 -0
  46. package/dist/ui-schema/generator.d.ts +11 -2
  47. package/dist/ui-schema/generator.d.ts.map +1 -1
  48. package/dist/ui-schema/ir-generator.d.ts +2 -1
  49. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  50. package/package.json +4 -4
@@ -53,6 +53,282 @@ module.exports = __toCommonJS(internals_exports);
53
53
 
54
54
  // src/canonicalize/chain-dsl-canonicalizer.ts
55
55
  var import_internals = require("@formspec/core/internals");
56
+
57
+ // src/metadata/policy.ts
58
+ var NOOP_INFLECT = () => "";
59
+ function normalizePluralization(input) {
60
+ if (input?.mode === "infer-if-missing") {
61
+ return {
62
+ mode: "infer-if-missing",
63
+ infer: () => "",
64
+ inflect: input.inflect
65
+ };
66
+ }
67
+ if (input?.mode === "require-explicit") {
68
+ return {
69
+ mode: "require-explicit",
70
+ infer: () => "",
71
+ inflect: NOOP_INFLECT
72
+ };
73
+ }
74
+ return {
75
+ mode: "disabled",
76
+ infer: () => "",
77
+ inflect: NOOP_INFLECT
78
+ };
79
+ }
80
+ function normalizeScalarPolicy(input) {
81
+ if (input?.mode === "infer-if-missing") {
82
+ return {
83
+ mode: "infer-if-missing",
84
+ infer: input.infer,
85
+ pluralization: normalizePluralization(input.pluralization)
86
+ };
87
+ }
88
+ if (input?.mode === "require-explicit") {
89
+ return {
90
+ mode: "require-explicit",
91
+ infer: () => "",
92
+ pluralization: normalizePluralization(input.pluralization)
93
+ };
94
+ }
95
+ return {
96
+ mode: "disabled",
97
+ infer: () => "",
98
+ pluralization: normalizePluralization(input?.pluralization)
99
+ };
100
+ }
101
+ function normalizeDeclarationPolicy(input) {
102
+ return {
103
+ apiName: normalizeScalarPolicy(input?.apiName),
104
+ displayName: normalizeScalarPolicy(input?.displayName)
105
+ };
106
+ }
107
+ function normalizeMetadataPolicy(input) {
108
+ return {
109
+ type: normalizeDeclarationPolicy(input?.type),
110
+ field: normalizeDeclarationPolicy(input?.field),
111
+ method: normalizeDeclarationPolicy(input?.method)
112
+ };
113
+ }
114
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
115
+ return policy[declarationKind];
116
+ }
117
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
118
+ return {
119
+ surface,
120
+ declarationKind,
121
+ logicalName,
122
+ ...buildContext !== void 0 && { buildContext }
123
+ };
124
+ }
125
+
126
+ // src/metadata/resolve.ts
127
+ function toExplicitScalar(value) {
128
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
129
+ }
130
+ function toExplicitResolvedMetadata(explicit) {
131
+ if (explicit === void 0) {
132
+ return void 0;
133
+ }
134
+ const apiName = toExplicitScalar(explicit.apiName);
135
+ const displayName = toExplicitScalar(explicit.displayName);
136
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
137
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
138
+ const metadata = {
139
+ ...apiName !== void 0 && { apiName },
140
+ ...displayName !== void 0 && { displayName },
141
+ ...apiNamePlural !== void 0 && { apiNamePlural },
142
+ ...displayNamePlural !== void 0 && { displayNamePlural }
143
+ };
144
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
145
+ }
146
+ function resolveScalar(current, policy, context, metadataLabel) {
147
+ if (current !== void 0) {
148
+ return current;
149
+ }
150
+ if (policy.mode === "require-explicit") {
151
+ throw new Error(
152
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
153
+ );
154
+ }
155
+ if (policy.mode !== "infer-if-missing") {
156
+ return void 0;
157
+ }
158
+ const inferredValue = policy.infer(context);
159
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
160
+ }
161
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
162
+ if (current !== void 0) {
163
+ return current;
164
+ }
165
+ if (policy.mode === "require-explicit") {
166
+ throw new Error(
167
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
168
+ );
169
+ }
170
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
171
+ return void 0;
172
+ }
173
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
174
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
175
+ }
176
+ function resolveResolvedMetadata(current, policy, context) {
177
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
178
+ const displayName = resolveScalar(
179
+ current?.displayName,
180
+ policy.displayName,
181
+ context,
182
+ "displayName"
183
+ );
184
+ const apiNamePlural = resolvePlural(
185
+ current?.apiNamePlural,
186
+ apiName,
187
+ policy.apiName.pluralization,
188
+ context,
189
+ "apiNamePlural"
190
+ );
191
+ const displayNamePlural = resolvePlural(
192
+ current?.displayNamePlural,
193
+ displayName,
194
+ policy.displayName.pluralization,
195
+ context,
196
+ "displayNamePlural"
197
+ );
198
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
199
+ return void 0;
200
+ }
201
+ return {
202
+ ...apiName !== void 0 && { apiName },
203
+ ...displayName !== void 0 && { displayName },
204
+ ...apiNamePlural !== void 0 && { apiNamePlural },
205
+ ...displayNamePlural !== void 0 && { displayNamePlural }
206
+ };
207
+ }
208
+ function resolveTypeNodeMetadata(type, options) {
209
+ switch (type.kind) {
210
+ case "array":
211
+ return {
212
+ ...type,
213
+ items: resolveTypeNodeMetadata(type.items, options)
214
+ };
215
+ case "object":
216
+ return {
217
+ ...type,
218
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
219
+ };
220
+ case "record":
221
+ return {
222
+ ...type,
223
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
224
+ };
225
+ case "union":
226
+ return {
227
+ ...type,
228
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
229
+ };
230
+ case "reference":
231
+ case "primitive":
232
+ case "enum":
233
+ case "dynamic":
234
+ case "custom":
235
+ return type;
236
+ default: {
237
+ const _exhaustive = type;
238
+ return _exhaustive;
239
+ }
240
+ }
241
+ }
242
+ function resolveObjectPropertyMetadata(property, options) {
243
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
244
+ surface: options.surface,
245
+ declarationKind: "field",
246
+ logicalName: property.name,
247
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
248
+ });
249
+ return {
250
+ ...property,
251
+ ...metadata !== void 0 && { metadata },
252
+ type: resolveTypeNodeMetadata(property.type, options)
253
+ };
254
+ }
255
+ function resolveFieldMetadataNode(field, options) {
256
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
257
+ surface: options.surface,
258
+ declarationKind: "field",
259
+ logicalName: field.name,
260
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
261
+ });
262
+ return {
263
+ ...field,
264
+ ...metadata !== void 0 && { metadata },
265
+ type: resolveTypeNodeMetadata(field.type, options)
266
+ };
267
+ }
268
+ function resolveFormElementMetadata(element, options) {
269
+ switch (element.kind) {
270
+ case "field":
271
+ return resolveFieldMetadataNode(element, options);
272
+ case "group":
273
+ return {
274
+ ...element,
275
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
276
+ };
277
+ case "conditional":
278
+ return {
279
+ ...element,
280
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
281
+ };
282
+ default: {
283
+ const _exhaustive = element;
284
+ return _exhaustive;
285
+ }
286
+ }
287
+ }
288
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
289
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
290
+ surface: options.surface,
291
+ declarationKind: "type",
292
+ logicalName: typeDefinition.name,
293
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
294
+ });
295
+ return {
296
+ ...typeDefinition,
297
+ ...metadata !== void 0 && { metadata },
298
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
299
+ };
300
+ }
301
+ function resolveMetadata(explicit, policy, context) {
302
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
303
+ }
304
+ function getSerializedName(logicalName, metadata) {
305
+ return metadata?.apiName?.value ?? logicalName;
306
+ }
307
+ function getDisplayName(metadata) {
308
+ return metadata?.displayName?.value;
309
+ }
310
+ function resolveFormIRMetadata(ir, options) {
311
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
312
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
313
+ surface: options.surface,
314
+ declarationKind: "type",
315
+ logicalName: rootLogicalName,
316
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
317
+ });
318
+ return {
319
+ ...ir,
320
+ ...metadata !== void 0 && { metadata },
321
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
322
+ typeRegistry: Object.fromEntries(
323
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
324
+ name,
325
+ resolveTypeDefinitionMetadata(definition, options)
326
+ ])
327
+ )
328
+ };
329
+ }
330
+
331
+ // src/canonicalize/chain-dsl-canonicalizer.ts
56
332
  var CHAIN_DSL_PROVENANCE = {
57
333
  surface: "chain-dsl",
58
334
  file: "",
@@ -68,57 +344,60 @@ function isConditional(el) {
68
344
  function isField(el) {
69
345
  return el._type === "field";
70
346
  }
71
- function canonicalizeChainDSL(form) {
347
+ function canonicalizeChainDSL(form, options) {
348
+ const metadataPolicy = normalizeMetadataPolicy(
349
+ options?.metadata ?? (0, import_internals._getFormSpecMetadataPolicy)(form)
350
+ );
72
351
  return {
73
352
  kind: "form-ir",
74
353
  irVersion: import_internals.IR_VERSION,
75
- elements: canonicalizeElements(form.elements),
354
+ elements: canonicalizeElements(form.elements, metadataPolicy),
76
355
  rootAnnotations: [],
77
356
  typeRegistry: {},
78
357
  provenance: CHAIN_DSL_PROVENANCE
79
358
  };
80
359
  }
81
- function canonicalizeElements(elements) {
82
- return elements.map(canonicalizeElement);
360
+ function canonicalizeElements(elements, metadataPolicy) {
361
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
83
362
  }
84
- function canonicalizeElement(element) {
363
+ function canonicalizeElement(element, metadataPolicy) {
85
364
  if (isField(element)) {
86
- return canonicalizeField(element);
365
+ return canonicalizeField(element, metadataPolicy);
87
366
  }
88
367
  if (isGroup(element)) {
89
- return canonicalizeGroup(element);
368
+ return canonicalizeGroup(element, metadataPolicy);
90
369
  }
91
370
  if (isConditional(element)) {
92
- return canonicalizeConditional(element);
371
+ return canonicalizeConditional(element, metadataPolicy);
93
372
  }
94
373
  const _exhaustive = element;
95
374
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
96
375
  }
97
- function canonicalizeField(field) {
376
+ function canonicalizeField(field, metadataPolicy) {
98
377
  switch (field._field) {
99
378
  case "text":
100
- return canonicalizeTextField(field);
379
+ return canonicalizeTextField(field, metadataPolicy);
101
380
  case "number":
102
- return canonicalizeNumberField(field);
381
+ return canonicalizeNumberField(field, metadataPolicy);
103
382
  case "boolean":
104
- return canonicalizeBooleanField(field);
383
+ return canonicalizeBooleanField(field, metadataPolicy);
105
384
  case "enum":
106
- return canonicalizeStaticEnumField(field);
385
+ return canonicalizeStaticEnumField(field, metadataPolicy);
107
386
  case "dynamic_enum":
108
- return canonicalizeDynamicEnumField(field);
387
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
109
388
  case "dynamic_schema":
110
- return canonicalizeDynamicSchemaField(field);
389
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
111
390
  case "array":
112
- return canonicalizeArrayField(field);
391
+ return canonicalizeArrayField(field, metadataPolicy);
113
392
  case "object":
114
- return canonicalizeObjectField(field);
393
+ return canonicalizeObjectField(field, metadataPolicy);
115
394
  default: {
116
395
  const _exhaustive = field;
117
396
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
118
397
  }
119
398
  }
120
399
  }
121
- function canonicalizeTextField(field) {
400
+ function canonicalizeTextField(field, metadataPolicy) {
122
401
  const type = { kind: "primitive", primitiveKind: "string" };
123
402
  const constraints = [];
124
403
  if (field.minLength !== void 0) {
@@ -150,13 +429,14 @@ function canonicalizeTextField(field) {
150
429
  }
151
430
  return buildFieldNode(
152
431
  field.name,
432
+ resolveFieldMetadata(field.name, field, metadataPolicy),
153
433
  type,
154
434
  field.required,
155
- buildAnnotations(field.label, field.placeholder),
435
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
156
436
  constraints
157
437
  );
158
438
  }
159
- function canonicalizeNumberField(field) {
439
+ function canonicalizeNumberField(field, metadataPolicy) {
160
440
  const type = { kind: "primitive", primitiveKind: "number" };
161
441
  const constraints = [];
162
442
  if (field.min !== void 0) {
@@ -188,17 +468,24 @@ function canonicalizeNumberField(field) {
188
468
  }
189
469
  return buildFieldNode(
190
470
  field.name,
471
+ resolveFieldMetadata(field.name, field, metadataPolicy),
191
472
  type,
192
473
  field.required,
193
- buildAnnotations(field.label),
474
+ buildAnnotations(getExplicitDisplayName(field)),
194
475
  constraints
195
476
  );
196
477
  }
197
- function canonicalizeBooleanField(field) {
478
+ function canonicalizeBooleanField(field, metadataPolicy) {
198
479
  const type = { kind: "primitive", primitiveKind: "boolean" };
199
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
480
+ return buildFieldNode(
481
+ field.name,
482
+ resolveFieldMetadata(field.name, field, metadataPolicy),
483
+ type,
484
+ field.required,
485
+ buildAnnotations(getExplicitDisplayName(field))
486
+ );
200
487
  }
201
- function canonicalizeStaticEnumField(field) {
488
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
202
489
  const members = field.options.map((opt) => {
203
490
  if (typeof opt === "string") {
204
491
  return { value: opt };
@@ -206,28 +493,46 @@ function canonicalizeStaticEnumField(field) {
206
493
  return { value: opt.id, displayName: opt.label };
207
494
  });
208
495
  const type = { kind: "enum", members };
209
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
496
+ return buildFieldNode(
497
+ field.name,
498
+ resolveFieldMetadata(field.name, field, metadataPolicy),
499
+ type,
500
+ field.required,
501
+ buildAnnotations(getExplicitDisplayName(field))
502
+ );
210
503
  }
211
- function canonicalizeDynamicEnumField(field) {
504
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
212
505
  const type = {
213
506
  kind: "dynamic",
214
507
  dynamicKind: "enum",
215
508
  sourceKey: field.source,
216
509
  parameterFields: field.params ? [...field.params] : []
217
510
  };
218
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
511
+ return buildFieldNode(
512
+ field.name,
513
+ resolveFieldMetadata(field.name, field, metadataPolicy),
514
+ type,
515
+ field.required,
516
+ buildAnnotations(getExplicitDisplayName(field))
517
+ );
219
518
  }
220
- function canonicalizeDynamicSchemaField(field) {
519
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
221
520
  const type = {
222
521
  kind: "dynamic",
223
522
  dynamicKind: "schema",
224
523
  sourceKey: field.schemaSource,
225
524
  parameterFields: []
226
525
  };
227
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
526
+ return buildFieldNode(
527
+ field.name,
528
+ resolveFieldMetadata(field.name, field, metadataPolicy),
529
+ type,
530
+ field.required,
531
+ buildAnnotations(getExplicitDisplayName(field))
532
+ );
228
533
  }
229
- function canonicalizeArrayField(field) {
230
- const itemProperties = buildObjectProperties(field.items);
534
+ function canonicalizeArrayField(field, metadataPolicy) {
535
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
231
536
  const itemsType = {
232
537
  kind: "object",
233
538
  properties: itemProperties,
@@ -255,37 +560,44 @@ function canonicalizeArrayField(field) {
255
560
  }
256
561
  return buildFieldNode(
257
562
  field.name,
563
+ resolveFieldMetadata(field.name, field, metadataPolicy),
258
564
  type,
259
565
  field.required,
260
- buildAnnotations(field.label),
566
+ buildAnnotations(getExplicitDisplayName(field)),
261
567
  constraints
262
568
  );
263
569
  }
264
- function canonicalizeObjectField(field) {
265
- const properties = buildObjectProperties(field.properties);
570
+ function canonicalizeObjectField(field, metadataPolicy) {
571
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
266
572
  const type = {
267
573
  kind: "object",
268
574
  properties,
269
575
  additionalProperties: true
270
576
  };
271
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
577
+ return buildFieldNode(
578
+ field.name,
579
+ resolveFieldMetadata(field.name, field, metadataPolicy),
580
+ type,
581
+ field.required,
582
+ buildAnnotations(getExplicitDisplayName(field))
583
+ );
272
584
  }
273
- function canonicalizeGroup(g) {
585
+ function canonicalizeGroup(g, metadataPolicy) {
274
586
  return {
275
587
  kind: "group",
276
588
  label: g.label,
277
- elements: canonicalizeElements(g.elements),
589
+ elements: canonicalizeElements(g.elements, metadataPolicy),
278
590
  provenance: CHAIN_DSL_PROVENANCE
279
591
  };
280
592
  }
281
- function canonicalizeConditional(c) {
593
+ function canonicalizeConditional(c, metadataPolicy) {
282
594
  return {
283
595
  kind: "conditional",
284
596
  fieldName: c.field,
285
597
  // Conditional values from the chain DSL are JSON-serializable primitives
286
598
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
287
599
  value: assertJsonValue(c.value),
288
- elements: canonicalizeElements(c.elements),
600
+ elements: canonicalizeElements(c.elements, metadataPolicy),
289
601
  provenance: CHAIN_DSL_PROVENANCE
290
602
  };
291
603
  }
@@ -305,10 +617,11 @@ function assertJsonValue(v) {
305
617
  }
306
618
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
307
619
  }
308
- function buildFieldNode(name, type, required, annotations, constraints = []) {
620
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
309
621
  return {
310
622
  kind: "field",
311
623
  name,
624
+ ...metadata !== void 0 && { metadata },
312
625
  type,
313
626
  required: required === true,
314
627
  constraints,
@@ -338,13 +651,14 @@ function buildAnnotations(label, placeholder) {
338
651
  }
339
652
  return annotations;
340
653
  }
341
- function buildObjectProperties(elements, insideConditional = false) {
654
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
342
655
  const properties = [];
343
656
  for (const el of elements) {
344
657
  if (isField(el)) {
345
- const fieldNode = canonicalizeField(el);
658
+ const fieldNode = canonicalizeField(el, metadataPolicy);
346
659
  properties.push({
347
660
  name: fieldNode.name,
661
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
348
662
  type: fieldNode.type,
349
663
  // Fields inside a conditional branch are always optional in the
350
664
  // data schema, regardless of their `required` flag — the condition
@@ -355,17 +669,34 @@ function buildObjectProperties(elements, insideConditional = false) {
355
669
  provenance: CHAIN_DSL_PROVENANCE
356
670
  });
357
671
  } else if (isGroup(el)) {
358
- properties.push(...buildObjectProperties(el.elements, insideConditional));
672
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
359
673
  } else if (isConditional(el)) {
360
- properties.push(...buildObjectProperties(el.elements, true));
674
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
361
675
  }
362
676
  }
363
677
  return properties;
364
678
  }
679
+ function getExplicitDisplayName(field) {
680
+ if (field.label !== void 0 && field.displayName !== void 0) {
681
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
682
+ }
683
+ return field.displayName ?? field.label;
684
+ }
685
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
686
+ const displayName = getExplicitDisplayName(field);
687
+ return resolveMetadata(
688
+ {
689
+ ...field.apiName !== void 0 && { apiName: field.apiName },
690
+ ...displayName !== void 0 && { displayName }
691
+ },
692
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
693
+ makeMetadataContext("chain-dsl", "field", logicalName)
694
+ );
695
+ }
365
696
 
366
697
  // src/canonicalize/tsdoc-canonicalizer.ts
367
698
  var import_internals2 = require("@formspec/core/internals");
368
- function canonicalizeTSDoc(analysis, source) {
699
+ function canonicalizeTSDoc(analysis, source, options) {
369
700
  const file = source?.file ?? "";
370
701
  const provenance = {
371
702
  surface: "tsdoc",
@@ -374,15 +705,21 @@ function canonicalizeTSDoc(analysis, source) {
374
705
  column: 0
375
706
  };
376
707
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
377
- return {
708
+ const ir = {
378
709
  kind: "form-ir",
710
+ name: analysis.name,
379
711
  irVersion: import_internals2.IR_VERSION,
380
712
  elements,
713
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
381
714
  typeRegistry: analysis.typeRegistry,
382
715
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
383
716
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
384
717
  provenance
385
718
  };
719
+ return resolveFormIRMetadata(ir, {
720
+ policy: normalizeMetadataPolicy(options?.metadata),
721
+ surface: "tsdoc"
722
+ });
386
723
  }
387
724
  function assembleElements(fields, layouts, provenance) {
388
725
  const elements = [];
@@ -1323,7 +1660,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
1323
1660
  ...hostType !== void 0 && { hostType }
1324
1661
  };
1325
1662
  }
1326
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1663
+ function makeExplicitScalarMetadata(value) {
1664
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
1665
+ }
1666
+ function extractExplicitMetadata(node) {
1667
+ let apiName;
1668
+ let displayName;
1669
+ let apiNamePlural;
1670
+ let displayNamePlural;
1671
+ for (const tag of getLeadingParsedTags(node)) {
1672
+ const value = tag.argumentText.trim();
1673
+ if (value === "") {
1674
+ continue;
1675
+ }
1676
+ if (tag.normalizedTagName === "apiName") {
1677
+ if (tag.target === null) {
1678
+ apiName ??= value;
1679
+ } else if (tag.target.kind === "variant") {
1680
+ if (tag.target.rawText === "singular") {
1681
+ apiName ??= value;
1682
+ } else if (tag.target.rawText === "plural") {
1683
+ apiNamePlural ??= value;
1684
+ }
1685
+ }
1686
+ continue;
1687
+ }
1688
+ if (tag.normalizedTagName === "displayName") {
1689
+ if (tag.target === null) {
1690
+ displayName ??= value;
1691
+ } else if (tag.target.kind === "variant") {
1692
+ if (tag.target.rawText === "singular") {
1693
+ displayName ??= value;
1694
+ } else if (tag.target.rawText === "plural") {
1695
+ displayNamePlural ??= value;
1696
+ }
1697
+ }
1698
+ }
1699
+ }
1700
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
1701
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
1702
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
1703
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
1704
+ const metadata = {
1705
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
1706
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
1707
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
1708
+ ...resolvedDisplayNamePlural !== void 0 && {
1709
+ displayNamePlural: resolvedDisplayNamePlural
1710
+ }
1711
+ };
1712
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
1713
+ }
1714
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
1715
+ const explicit = extractExplicitMetadata(node);
1716
+ return resolveMetadata(
1717
+ {
1718
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
1719
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
1720
+ ...explicit?.apiNamePlural !== void 0 && {
1721
+ apiNamePlural: explicit.apiNamePlural.value
1722
+ },
1723
+ ...explicit?.displayNamePlural !== void 0 && {
1724
+ displayNamePlural: explicit.displayNamePlural.value
1725
+ }
1726
+ },
1727
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
1728
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
1729
+ );
1730
+ }
1731
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1732
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1327
1733
  const name = classDecl.name?.text ?? "AnonymousClass";
1328
1734
  const fields = [];
1329
1735
  const fieldLayouts = [];
@@ -1350,6 +1756,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1350
1756
  visiting,
1351
1757
  diagnostics,
1352
1758
  classType,
1759
+ normalizedMetadataPolicy,
1353
1760
  extensionRegistry
1354
1761
  );
1355
1762
  if (fieldNode) {
@@ -1374,10 +1781,18 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1374
1781
  classType,
1375
1782
  checker,
1376
1783
  file,
1377
- diagnostics
1784
+ diagnostics,
1785
+ normalizedMetadataPolicy
1378
1786
  );
1787
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
1788
+ checker,
1789
+ declaration: classDecl,
1790
+ subjectType: classType,
1791
+ hostType: classType
1792
+ });
1379
1793
  return {
1380
1794
  name,
1795
+ ...metadata !== void 0 && { metadata },
1381
1796
  fields: specializedFields,
1382
1797
  fieldLayouts,
1383
1798
  typeRegistry,
@@ -1387,7 +1802,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1387
1802
  staticMethods
1388
1803
  };
1389
1804
  }
1390
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1805
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1806
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1391
1807
  const name = interfaceDecl.name.text;
1392
1808
  const fields = [];
1393
1809
  const typeRegistry = {};
@@ -1411,6 +1827,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1411
1827
  visiting,
1412
1828
  diagnostics,
1413
1829
  interfaceType,
1830
+ normalizedMetadataPolicy,
1414
1831
  extensionRegistry
1415
1832
  );
1416
1833
  if (fieldNode) {
@@ -1424,11 +1841,19 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1424
1841
  interfaceType,
1425
1842
  checker,
1426
1843
  file,
1427
- diagnostics
1844
+ diagnostics,
1845
+ normalizedMetadataPolicy
1428
1846
  );
1429
1847
  const fieldLayouts = specializedFields.map(() => ({}));
1848
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
1849
+ checker,
1850
+ declaration: interfaceDecl,
1851
+ subjectType: interfaceType,
1852
+ hostType: interfaceType
1853
+ });
1430
1854
  return {
1431
1855
  name,
1856
+ ...metadata !== void 0 && { metadata },
1432
1857
  fields: specializedFields,
1433
1858
  fieldLayouts,
1434
1859
  typeRegistry,
@@ -1438,7 +1863,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1438
1863
  staticMethods: []
1439
1864
  };
1440
1865
  }
1441
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1866
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
1442
1867
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1443
1868
  const sourceFile = typeAlias.getSourceFile();
1444
1869
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -1448,6 +1873,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1448
1873
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
1449
1874
  };
1450
1875
  }
1876
+ const typeLiteral = typeAlias.type;
1877
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1451
1878
  const name = typeAlias.name.text;
1452
1879
  const fields = [];
1453
1880
  const typeRegistry = {};
@@ -1461,7 +1888,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1461
1888
  const annotations = [...typeAliasDoc.annotations];
1462
1889
  diagnostics.push(...typeAliasDoc.diagnostics);
1463
1890
  const visiting = /* @__PURE__ */ new Set();
1464
- for (const member of typeAlias.type.members) {
1891
+ for (const member of typeLiteral.members) {
1465
1892
  if (ts3.isPropertySignature(member)) {
1466
1893
  const fieldNode = analyzeInterfacePropertyToIR(
1467
1894
  member,
@@ -1471,6 +1898,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1471
1898
  visiting,
1472
1899
  diagnostics,
1473
1900
  aliasType,
1901
+ normalizedMetadataPolicy,
1474
1902
  extensionRegistry
1475
1903
  );
1476
1904
  if (fieldNode) {
@@ -1484,12 +1912,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1484
1912
  aliasType,
1485
1913
  checker,
1486
1914
  file,
1487
- diagnostics
1915
+ diagnostics,
1916
+ normalizedMetadataPolicy
1488
1917
  );
1918
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
1919
+ checker,
1920
+ declaration: typeAlias,
1921
+ subjectType: aliasType,
1922
+ hostType: aliasType
1923
+ });
1489
1924
  return {
1490
1925
  ok: true,
1491
1926
  analysis: {
1492
1927
  name,
1928
+ ...metadata !== void 0 && { metadata },
1493
1929
  fields: specializedFields,
1494
1930
  fieldLayouts: specializedFields.map(() => ({})),
1495
1931
  typeRegistry,
@@ -1529,31 +1965,20 @@ function getLeadingParsedTags(node) {
1529
1965
  }
1530
1966
  return parsedTags;
1531
1967
  }
1532
- function findDiscriminatorProperty(node, fieldName) {
1533
- if (ts3.isClassDeclaration(node)) {
1534
- for (const member of node.members) {
1535
- if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1536
- return member;
1537
- }
1538
- }
1539
- return null;
1540
- }
1541
- if (ts3.isInterfaceDeclaration(node)) {
1542
- for (const member of node.members) {
1543
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1544
- return member;
1545
- }
1546
- }
1968
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
1969
+ const subjectType = checker.getTypeAtLocation(node);
1970
+ const propertySymbol = subjectType.getProperty(fieldName);
1971
+ if (propertySymbol === void 0) {
1547
1972
  return null;
1548
1973
  }
1549
- if (ts3.isTypeLiteralNode(node.type)) {
1550
- for (const member of node.type.members) {
1551
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1552
- return member;
1553
- }
1554
- }
1555
- }
1556
- return null;
1974
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
1975
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
1976
+ ) ?? propertySymbol.declarations?.[0];
1977
+ return {
1978
+ declaration,
1979
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
1980
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
1981
+ };
1557
1982
  }
1558
1983
  function isLocalTypeParameterName(node, typeParameterName) {
1559
1984
  return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
@@ -1646,8 +2071,8 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
1646
2071
  );
1647
2072
  return null;
1648
2073
  }
1649
- const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
1650
- if (propertyDecl === null) {
2074
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
2075
+ if (property === null) {
1651
2076
  diagnostics.push(
1652
2077
  makeAnalysisDiagnostic(
1653
2078
  "UNKNOWN_PATH_TARGET",
@@ -1657,36 +2082,35 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
1657
2082
  );
1658
2083
  return null;
1659
2084
  }
1660
- if (propertyDecl.questionToken !== void 0) {
2085
+ if (property.optional) {
1661
2086
  diagnostics.push(
1662
2087
  makeAnalysisDiagnostic(
1663
2088
  "TYPE_MISMATCH",
1664
2089
  `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
1665
2090
  directive.provenance,
1666
- [provenanceForNode(propertyDecl, file)]
2091
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1667
2092
  )
1668
2093
  );
1669
2094
  return null;
1670
2095
  }
1671
- const propertyType = checker.getTypeAtLocation(propertyDecl);
1672
- if (isNullishSemanticType(propertyType)) {
2096
+ if (isNullishSemanticType(property.type)) {
1673
2097
  diagnostics.push(
1674
2098
  makeAnalysisDiagnostic(
1675
2099
  "TYPE_MISMATCH",
1676
2100
  `Discriminator field "${directive.fieldName}" must not be nullable.`,
1677
2101
  directive.provenance,
1678
- [provenanceForNode(propertyDecl, file)]
2102
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1679
2103
  )
1680
2104
  );
1681
2105
  return null;
1682
2106
  }
1683
- if (!isStringLikeSemanticType(propertyType)) {
2107
+ if (!isStringLikeSemanticType(property.type)) {
1684
2108
  diagnostics.push(
1685
2109
  makeAnalysisDiagnostic(
1686
2110
  "TYPE_MISMATCH",
1687
2111
  `Discriminator field "${directive.fieldName}" must be string-like.`,
1688
2112
  directive.provenance,
1689
- [provenanceForNode(propertyDecl, file)]
2113
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1690
2114
  )
1691
2115
  );
1692
2116
  return null;
@@ -1707,25 +2131,58 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
1707
2131
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
1708
2132
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
1709
2133
  }
1710
- function extractDeclarationApiName(node) {
1711
- for (const tag of getLeadingParsedTags(node)) {
1712
- if (tag.normalizedTagName !== "apiName") {
1713
- continue;
1714
- }
1715
- if (tag.target === null && tag.argumentText.trim() !== "") {
1716
- return tag.argumentText.trim();
1717
- }
1718
- if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
1719
- const value = tag.argumentText.trim();
1720
- if (value !== "") {
1721
- return value;
1722
- }
2134
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
2135
+ const propertySymbol = boundType.getProperty(fieldName);
2136
+ if (propertySymbol === void 0) {
2137
+ return void 0;
2138
+ }
2139
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
2140
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
2141
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
2142
+ if (resolvedAnchorNode === null) {
2143
+ return void 0;
2144
+ }
2145
+ const propertyType = checker.getTypeOfSymbolAtLocation(
2146
+ propertySymbol,
2147
+ resolvedAnchorNode
2148
+ );
2149
+ if (propertyType.isStringLiteral()) {
2150
+ return propertyType.value;
2151
+ }
2152
+ if (propertyType.isUnion()) {
2153
+ const nonNullMembers = propertyType.types.filter(
2154
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2155
+ );
2156
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
2157
+ diagnostics.push(
2158
+ makeAnalysisDiagnostic(
2159
+ "INVALID_TAG_ARGUMENT",
2160
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
2161
+ provenance
2162
+ )
2163
+ );
2164
+ return null;
1723
2165
  }
1724
2166
  }
1725
- return null;
2167
+ return void 0;
1726
2168
  }
1727
- function inferJsonFacingName(name) {
1728
- return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
2169
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
2170
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2171
+ if (declaration === null) {
2172
+ return void 0;
2173
+ }
2174
+ const metadata = resolveNodeMetadata(
2175
+ metadataPolicy,
2176
+ "type",
2177
+ getDiscriminatorLogicalName(boundType, declaration, checker),
2178
+ declaration,
2179
+ {
2180
+ checker,
2181
+ declaration,
2182
+ subjectType: boundType
2183
+ }
2184
+ );
2185
+ return metadata?.apiName;
1729
2186
  }
1730
2187
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
1731
2188
  if (seen.has(type)) {
@@ -1752,7 +2209,7 @@ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__
1752
2209
  }
1753
2210
  return null;
1754
2211
  }
1755
- function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
2212
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
1756
2213
  if (boundType === null) {
1757
2214
  diagnostics.push(
1758
2215
  makeAnalysisDiagnostic(
@@ -1781,9 +2238,22 @@ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics)
1781
2238
  return null;
1782
2239
  }
1783
2240
  }
1784
- const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
1785
- if (declaration !== null) {
1786
- return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
2241
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
2242
+ boundType,
2243
+ fieldName,
2244
+ checker,
2245
+ provenance,
2246
+ diagnostics
2247
+ );
2248
+ if (literalIdentityValue !== void 0) {
2249
+ return literalIdentityValue;
2250
+ }
2251
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
2252
+ if (apiName?.source === "explicit") {
2253
+ return apiName.value;
2254
+ }
2255
+ if (apiName?.source === "inferred") {
2256
+ return apiName.value;
1787
2257
  }
1788
2258
  diagnostics.push(
1789
2259
  makeAnalysisDiagnostic(
@@ -1800,7 +2270,15 @@ function getDeclarationName(node) {
1800
2270
  }
1801
2271
  return "anonymous";
1802
2272
  }
1803
- function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
2273
+ function getResolvedTypeArguments(type) {
2274
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
2275
+ }
2276
+ function getDiscriminatorLogicalName(type, declaration, checker) {
2277
+ const baseName = getDeclarationName(declaration);
2278
+ const typeArguments = getResolvedTypeArguments(type);
2279
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
2280
+ }
2281
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
1804
2282
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
1805
2283
  if (directive === null) {
1806
2284
  return [...fields];
@@ -1812,9 +2290,11 @@ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checke
1812
2290
  checker,
1813
2291
  directive.typeParameterName
1814
2292
  ),
2293
+ directive.fieldName,
1815
2294
  checker,
1816
2295
  directive.provenance,
1817
- diagnostics
2296
+ diagnostics,
2297
+ metadataPolicy
1818
2298
  );
1819
2299
  if (discriminatorValue === null) {
1820
2300
  return [...fields];
@@ -1835,7 +2315,7 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
1835
2315
  ).filter((value) => value !== "");
1836
2316
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
1837
2317
  }
1838
- function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2318
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
1839
2319
  const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1840
2320
  if (typeNode === void 0) {
1841
2321
  return [];
@@ -1855,13 +2335,14 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
1855
2335
  typeRegistry,
1856
2336
  visiting,
1857
2337
  argumentNode,
2338
+ metadataPolicy,
1858
2339
  extensionRegistry,
1859
2340
  diagnostics
1860
2341
  )
1861
2342
  };
1862
2343
  });
1863
2344
  }
1864
- function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
2345
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
1865
2346
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
1866
2347
  if (directive === null) {
1867
2348
  return properties;
@@ -1873,9 +2354,11 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
1873
2354
  checker,
1874
2355
  directive.typeParameterName
1875
2356
  ),
2357
+ directive.fieldName,
1876
2358
  checker,
1877
2359
  directive.provenance,
1878
- diagnostics
2360
+ diagnostics,
2361
+ metadataPolicy
1879
2362
  );
1880
2363
  if (discriminatorValue === null) {
1881
2364
  return properties;
@@ -1890,7 +2373,7 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
1890
2373
  } : property
1891
2374
  );
1892
2375
  }
1893
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2376
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
1894
2377
  if (!ts3.isIdentifier(prop.name)) {
1895
2378
  return null;
1896
2379
  }
@@ -1905,6 +2388,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1905
2388
  typeRegistry,
1906
2389
  visiting,
1907
2390
  prop,
2391
+ metadataPolicy,
1908
2392
  extensionRegistry,
1909
2393
  diagnostics
1910
2394
  );
@@ -1928,9 +2412,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1928
2412
  annotations.push(defaultAnnotation);
1929
2413
  }
1930
2414
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2415
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2416
+ checker,
2417
+ declaration: prop,
2418
+ subjectType: tsType,
2419
+ hostType
2420
+ });
1931
2421
  return {
1932
2422
  kind: "field",
1933
2423
  name,
2424
+ ...metadata !== void 0 && { metadata },
1934
2425
  type,
1935
2426
  required: !optional,
1936
2427
  constraints,
@@ -1938,7 +2429,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1938
2429
  provenance
1939
2430
  };
1940
2431
  }
1941
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2432
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
1942
2433
  if (!ts3.isIdentifier(prop.name)) {
1943
2434
  return null;
1944
2435
  }
@@ -1953,6 +2444,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1953
2444
  typeRegistry,
1954
2445
  visiting,
1955
2446
  prop,
2447
+ metadataPolicy,
1956
2448
  extensionRegistry,
1957
2449
  diagnostics
1958
2450
  );
@@ -1972,9 +2464,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1972
2464
  let annotations = [];
1973
2465
  annotations.push(...docResult.annotations);
1974
2466
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2467
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2468
+ checker,
2469
+ declaration: prop,
2470
+ subjectType: tsType,
2471
+ hostType
2472
+ });
1975
2473
  return {
1976
2474
  kind: "field",
1977
2475
  name,
2476
+ ...metadata !== void 0 && { metadata },
1978
2477
  type,
1979
2478
  required: !optional,
1980
2479
  constraints,
@@ -2099,7 +2598,7 @@ function getTypeNodeRegistrationName(typeNode) {
2099
2598
  }
2100
2599
  return null;
2101
2600
  }
2102
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2601
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2103
2602
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2104
2603
  if (customType) {
2105
2604
  return customType;
@@ -2111,6 +2610,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2111
2610
  typeRegistry,
2112
2611
  visiting,
2113
2612
  sourceNode,
2613
+ metadataPolicy,
2114
2614
  extensionRegistry,
2115
2615
  diagnostics
2116
2616
  );
@@ -2155,6 +2655,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2155
2655
  typeRegistry,
2156
2656
  visiting,
2157
2657
  sourceNode,
2658
+ metadataPolicy,
2158
2659
  extensionRegistry,
2159
2660
  diagnostics
2160
2661
  );
@@ -2167,6 +2668,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2167
2668
  typeRegistry,
2168
2669
  visiting,
2169
2670
  sourceNode,
2671
+ metadataPolicy,
2170
2672
  extensionRegistry,
2171
2673
  diagnostics
2172
2674
  );
@@ -2179,13 +2681,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2179
2681
  typeRegistry,
2180
2682
  visiting,
2181
2683
  sourceNode,
2684
+ metadataPolicy,
2182
2685
  extensionRegistry,
2183
2686
  diagnostics
2184
2687
  );
2185
2688
  }
2186
2689
  return { kind: "primitive", primitiveKind: "string" };
2187
2690
  }
2188
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2691
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2189
2692
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2190
2693
  return null;
2191
2694
  }
@@ -2205,14 +2708,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2205
2708
  file,
2206
2709
  makeParseOptions(extensionRegistry)
2207
2710
  );
2711
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
2712
+ checker,
2713
+ declaration: aliasDecl,
2714
+ subjectType: aliasType
2715
+ });
2208
2716
  typeRegistry[aliasName] = {
2209
2717
  name: aliasName,
2718
+ ...metadata !== void 0 && { metadata },
2210
2719
  type: resolveAliasedPrimitiveTarget(
2211
2720
  aliasType,
2212
2721
  checker,
2213
2722
  file,
2214
2723
  typeRegistry,
2215
2724
  visiting,
2725
+ metadataPolicy,
2216
2726
  extensionRegistry,
2217
2727
  diagnostics
2218
2728
  ),
@@ -2241,7 +2751,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2241
2751
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2242
2752
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2243
2753
  }
2244
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2754
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2245
2755
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2246
2756
  if (nestedAliasDecl !== void 0) {
2247
2757
  return resolveAliasedPrimitiveTarget(
@@ -2250,6 +2760,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2250
2760
  file,
2251
2761
  typeRegistry,
2252
2762
  visiting,
2763
+ metadataPolicy,
2253
2764
  extensionRegistry,
2254
2765
  diagnostics
2255
2766
  );
@@ -2261,11 +2772,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2261
2772
  typeRegistry,
2262
2773
  visiting,
2263
2774
  void 0,
2775
+ metadataPolicy,
2264
2776
  extensionRegistry,
2265
2777
  diagnostics
2266
2778
  );
2267
2779
  }
2268
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2780
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2269
2781
  const typeName = getNamedTypeName(type);
2270
2782
  const namedDecl = getNamedTypeDeclaration(type);
2271
2783
  if (typeName && typeName in typeRegistry) {
@@ -2300,8 +2812,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2300
2812
  return result;
2301
2813
  }
2302
2814
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2815
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
2816
+ checker,
2817
+ declaration: namedDecl,
2818
+ subjectType: type
2819
+ }) : void 0;
2303
2820
  typeRegistry[typeName] = {
2304
2821
  name: typeName,
2822
+ ...metadata !== void 0 && { metadata },
2305
2823
  type: result,
2306
2824
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2307
2825
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -2355,6 +2873,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2355
2873
  typeRegistry,
2356
2874
  visiting,
2357
2875
  nonNullMembers[0].sourceNode ?? sourceNode,
2876
+ metadataPolicy,
2358
2877
  extensionRegistry,
2359
2878
  diagnostics
2360
2879
  );
@@ -2372,6 +2891,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2372
2891
  typeRegistry,
2373
2892
  visiting,
2374
2893
  memberSourceNode ?? sourceNode,
2894
+ metadataPolicy,
2375
2895
  extensionRegistry,
2376
2896
  diagnostics
2377
2897
  )
@@ -2381,7 +2901,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2381
2901
  }
2382
2902
  return registerNamed({ kind: "union", members });
2383
2903
  }
2384
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2904
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2385
2905
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2386
2906
  const elementType = typeArgs?.[0];
2387
2907
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2392,12 +2912,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2392
2912
  typeRegistry,
2393
2913
  visiting,
2394
2914
  elementSourceNode,
2915
+ metadataPolicy,
2395
2916
  extensionRegistry,
2396
2917
  diagnostics
2397
2918
  ) : { kind: "primitive", primitiveKind: "string" };
2398
2919
  return { kind: "array", items };
2399
2920
  }
2400
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2921
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2401
2922
  if (type.getProperties().length > 0) {
2402
2923
  return null;
2403
2924
  }
@@ -2412,6 +2933,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2412
2933
  typeRegistry,
2413
2934
  visiting,
2414
2935
  void 0,
2936
+ metadataPolicy,
2415
2937
  extensionRegistry,
2416
2938
  diagnostics
2417
2939
  );
@@ -2442,7 +2964,22 @@ function typeNodeContainsReference(type, targetName) {
2442
2964
  }
2443
2965
  }
2444
2966
  }
2445
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2967
+ function shouldEmitResolvedObjectProperty(property, declaration) {
2968
+ if (property.name.startsWith("__@")) {
2969
+ return false;
2970
+ }
2971
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
2972
+ const name = declaration.name;
2973
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
2974
+ return false;
2975
+ }
2976
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
2977
+ return false;
2978
+ }
2979
+ }
2980
+ return true;
2981
+ }
2982
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2446
2983
  const collectedDiagnostics = diagnostics ?? [];
2447
2984
  const typeName = getNamedTypeName(type);
2448
2985
  const namedTypeName = typeName ?? void 0;
@@ -2454,6 +2991,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2454
2991
  typeRegistry,
2455
2992
  visiting,
2456
2993
  sourceNode,
2994
+ metadataPolicy,
2457
2995
  extensionRegistry,
2458
2996
  collectedDiagnostics
2459
2997
  );
@@ -2504,6 +3042,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2504
3042
  file,
2505
3043
  typeRegistry,
2506
3044
  visiting,
3045
+ metadataPolicy,
2507
3046
  extensionRegistry,
2508
3047
  collectedDiagnostics
2509
3048
  );
@@ -2516,8 +3055,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2516
3055
  return recordNode;
2517
3056
  }
2518
3057
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3058
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3059
+ checker,
3060
+ declaration: namedDecl,
3061
+ subjectType: type
3062
+ }) : void 0;
2519
3063
  typeRegistry[registryTypeName] = {
2520
3064
  name: registryTypeName,
3065
+ ...metadata !== void 0 && { metadata },
2521
3066
  type: recordNode,
2522
3067
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2523
3068
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -2537,12 +3082,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2537
3082
  file,
2538
3083
  typeRegistry,
2539
3084
  visiting,
3085
+ metadataPolicy,
2540
3086
  collectedDiagnostics,
2541
3087
  extensionRegistry
2542
3088
  );
2543
3089
  for (const prop of type.getProperties()) {
2544
3090
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2545
3091
  if (!declaration) continue;
3092
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
2546
3093
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2547
3094
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2548
3095
  const propTypeNode = resolveTypeNode(
@@ -2552,12 +3099,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2552
3099
  typeRegistry,
2553
3100
  visiting,
2554
3101
  declaration,
3102
+ metadataPolicy,
2555
3103
  extensionRegistry,
2556
3104
  collectedDiagnostics
2557
3105
  );
2558
3106
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2559
3107
  properties.push({
2560
3108
  name: prop.name,
3109
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
2561
3110
  type: propTypeNode,
2562
3111
  optional,
2563
3112
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -2574,14 +3123,21 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2574
3123
  type,
2575
3124
  checker,
2576
3125
  file,
2577
- collectedDiagnostics
3126
+ collectedDiagnostics,
3127
+ metadataPolicy
2578
3128
  ) : properties,
2579
3129
  additionalProperties: true
2580
3130
  };
2581
3131
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2582
3132
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3133
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3134
+ checker,
3135
+ declaration: namedDecl,
3136
+ subjectType: type
3137
+ }) : void 0;
2583
3138
  typeRegistry[registryTypeName] = {
2584
3139
  name: registryTypeName,
3140
+ ...metadata !== void 0 && { metadata },
2585
3141
  type: objectNode,
2586
3142
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2587
3143
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -2594,7 +3150,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2594
3150
  }
2595
3151
  return objectNode;
2596
3152
  }
2597
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
3153
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
2598
3154
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2599
3155
  (s) => s?.declarations != null && s.declarations.length > 0
2600
3156
  );
@@ -2615,10 +3171,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2615
3171
  visiting,
2616
3172
  diagnostics,
2617
3173
  hostType,
3174
+ metadataPolicy,
2618
3175
  extensionRegistry
2619
3176
  );
2620
3177
  if (fieldNode) {
2621
3178
  map.set(fieldNode.name, {
3179
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
2622
3180
  constraints: [...fieldNode.constraints],
2623
3181
  annotations: [...fieldNode.annotations],
2624
3182
  provenance: fieldNode.provenance
@@ -2636,6 +3194,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2636
3194
  file,
2637
3195
  typeRegistry,
2638
3196
  visiting,
3197
+ metadataPolicy,
2639
3198
  checker.getTypeAtLocation(interfaceDecl),
2640
3199
  diagnostics,
2641
3200
  extensionRegistry
@@ -2649,6 +3208,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2649
3208
  file,
2650
3209
  typeRegistry,
2651
3210
  visiting,
3211
+ metadataPolicy,
2652
3212
  checker.getTypeAtLocation(typeAliasDecl),
2653
3213
  diagnostics,
2654
3214
  extensionRegistry
@@ -2700,7 +3260,7 @@ function isNullishTypeNode(typeNode) {
2700
3260
  }
2701
3261
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2702
3262
  }
2703
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
3263
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
2704
3264
  const map = /* @__PURE__ */ new Map();
2705
3265
  for (const member of members) {
2706
3266
  if (ts3.isPropertySignature(member)) {
@@ -2712,10 +3272,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
2712
3272
  visiting,
2713
3273
  diagnostics,
2714
3274
  hostType,
3275
+ metadataPolicy,
2715
3276
  extensionRegistry
2716
3277
  );
2717
3278
  if (fieldNode) {
2718
3279
  map.set(fieldNode.name, {
3280
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
2719
3281
  constraints: [...fieldNode.constraints],
2720
3282
  annotations: [...fieldNode.annotations],
2721
3283
  provenance: fieldNode.provenance
@@ -2746,6 +3308,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
2746
3308
  {},
2747
3309
  /* @__PURE__ */ new Set(),
2748
3310
  aliasDecl.type,
3311
+ void 0,
2749
3312
  extensionRegistry
2750
3313
  );
2751
3314
  const constraints = extractJSDocConstraintNodes(
@@ -2931,15 +3494,27 @@ function findInterfaceByName(sourceFile, interfaceName) {
2931
3494
  function findTypeAliasByName(sourceFile, aliasName) {
2932
3495
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2933
3496
  }
2934
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
3497
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
2935
3498
  const analysisFilePath = path.resolve(filePath);
2936
3499
  const classDecl = findClassByName(ctx.sourceFile, typeName);
2937
3500
  if (classDecl !== null) {
2938
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
3501
+ return analyzeClassToIR(
3502
+ classDecl,
3503
+ ctx.checker,
3504
+ analysisFilePath,
3505
+ extensionRegistry,
3506
+ metadataPolicy
3507
+ );
2939
3508
  }
2940
3509
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2941
3510
  if (interfaceDecl !== null) {
2942
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
3511
+ return analyzeInterfaceToIR(
3512
+ interfaceDecl,
3513
+ ctx.checker,
3514
+ analysisFilePath,
3515
+ extensionRegistry,
3516
+ metadataPolicy
3517
+ );
2943
3518
  }
2944
3519
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2945
3520
  if (typeAlias !== null) {
@@ -2947,7 +3522,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
2947
3522
  typeAlias,
2948
3523
  ctx.checker,
2949
3524
  analysisFilePath,
2950
- extensionRegistry
3525
+ extensionRegistry,
3526
+ metadataPolicy
2951
3527
  );
2952
3528
  if (result.ok) {
2953
3529
  return result.analysis;
@@ -2962,6 +3538,120 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
2962
3538
  // src/generators/class-schema.ts
2963
3539
  var ts5 = require("typescript");
2964
3540
 
3541
+ // src/metadata/collision-guards.ts
3542
+ function assertUniqueSerializedNames(entries, scope) {
3543
+ const seen = /* @__PURE__ */ new Map();
3544
+ for (const entry of entries) {
3545
+ const previous = seen.get(entry.serializedName);
3546
+ if (previous !== void 0) {
3547
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
3548
+ continue;
3549
+ }
3550
+ throw new Error(
3551
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
3552
+ );
3553
+ }
3554
+ seen.set(entry.serializedName, entry);
3555
+ }
3556
+ }
3557
+ function collectFlattenedFields(elements) {
3558
+ const fields = [];
3559
+ for (const element of elements) {
3560
+ switch (element.kind) {
3561
+ case "field":
3562
+ fields.push(element);
3563
+ break;
3564
+ case "group":
3565
+ case "conditional":
3566
+ fields.push(...collectFlattenedFields(element.elements));
3567
+ break;
3568
+ default: {
3569
+ const exhaustive = element;
3570
+ void exhaustive;
3571
+ }
3572
+ }
3573
+ }
3574
+ return fields;
3575
+ }
3576
+ function validateObjectProperties(properties, scope) {
3577
+ assertUniqueSerializedNames(
3578
+ properties.map((property) => ({
3579
+ logicalName: property.name,
3580
+ serializedName: getSerializedName(property.name, property.metadata),
3581
+ category: "object property"
3582
+ })),
3583
+ scope
3584
+ );
3585
+ for (const property of properties) {
3586
+ validateTypeNode(
3587
+ property.type,
3588
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
3589
+ );
3590
+ }
3591
+ }
3592
+ function validateTypeNode(type, scope) {
3593
+ switch (type.kind) {
3594
+ case "array":
3595
+ validateTypeNode(type.items, `${scope}[]`);
3596
+ break;
3597
+ case "object":
3598
+ validateObjectProperties(type.properties, scope);
3599
+ break;
3600
+ case "record":
3601
+ validateTypeNode(type.valueType, `${scope}.*`);
3602
+ break;
3603
+ case "union":
3604
+ type.members.forEach((member, index) => {
3605
+ validateTypeNode(member, `${scope}|${String(index)}`);
3606
+ });
3607
+ break;
3608
+ case "reference":
3609
+ case "primitive":
3610
+ case "enum":
3611
+ case "dynamic":
3612
+ case "custom":
3613
+ break;
3614
+ default: {
3615
+ const exhaustive = type;
3616
+ void exhaustive;
3617
+ }
3618
+ }
3619
+ }
3620
+ function validateTypeDefinitions(typeRegistry) {
3621
+ const definitions = Object.values(typeRegistry);
3622
+ assertUniqueSerializedNames(
3623
+ definitions.map((definition) => ({
3624
+ logicalName: definition.name,
3625
+ serializedName: getSerializedName(definition.name, definition.metadata),
3626
+ category: "type definition"
3627
+ })),
3628
+ "$defs"
3629
+ );
3630
+ for (const definition of definitions) {
3631
+ validateTypeDefinition(definition);
3632
+ }
3633
+ }
3634
+ function validateTypeDefinition(definition) {
3635
+ validateTypeNode(
3636
+ definition.type,
3637
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
3638
+ );
3639
+ }
3640
+ function assertNoSerializedNameCollisions(ir) {
3641
+ assertUniqueSerializedNames(
3642
+ collectFlattenedFields(ir.elements).map((field) => ({
3643
+ logicalName: field.name,
3644
+ serializedName: getSerializedName(field.name, field.metadata),
3645
+ category: "field"
3646
+ })),
3647
+ "form root"
3648
+ );
3649
+ for (const field of collectFlattenedFields(ir.elements)) {
3650
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
3651
+ }
3652
+ validateTypeDefinitions(ir.typeRegistry);
3653
+ }
3654
+
2965
3655
  // src/json-schema/ir-generator.ts
2966
3656
  function makeContext(options) {
2967
3657
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -2972,19 +3662,33 @@ function makeContext(options) {
2972
3662
  }
2973
3663
  return {
2974
3664
  defs: {},
3665
+ typeNameMap: {},
3666
+ typeRegistry: {},
2975
3667
  extensionRegistry: options?.extensionRegistry,
2976
3668
  vendorPrefix
2977
3669
  };
2978
3670
  }
2979
3671
  function generateJsonSchemaFromIR(ir, options) {
2980
- const ctx = makeContext(options);
3672
+ assertNoSerializedNameCollisions(ir);
3673
+ const ctx = {
3674
+ ...makeContext(options),
3675
+ typeRegistry: ir.typeRegistry,
3676
+ typeNameMap: Object.fromEntries(
3677
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
3678
+ name,
3679
+ getSerializedName(name, typeDef.metadata)
3680
+ ])
3681
+ )
3682
+ };
2981
3683
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
2982
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
3684
+ const schemaName = ctx.typeNameMap[name] ?? name;
3685
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
3686
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
2983
3687
  if (typeDef.constraints && typeDef.constraints.length > 0) {
2984
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
3688
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
2985
3689
  }
2986
3690
  if (typeDef.annotations && typeDef.annotations.length > 0) {
2987
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
3691
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
2988
3692
  }
2989
3693
  }
2990
3694
  const properties = {};
@@ -2997,6 +3701,7 @@ function generateJsonSchemaFromIR(ir, options) {
2997
3701
  properties,
2998
3702
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
2999
3703
  };
3704
+ applyResolvedMetadata(result, ir.metadata);
3000
3705
  if (ir.annotations && ir.annotations.length > 0) {
3001
3706
  applyAnnotations(result, ir.annotations, ctx);
3002
3707
  }
@@ -3009,9 +3714,9 @@ function collectFields(elements, properties, required, ctx) {
3009
3714
  for (const element of elements) {
3010
3715
  switch (element.kind) {
3011
3716
  case "field":
3012
- properties[element.name] = generateFieldSchema(element, ctx);
3717
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
3013
3718
  if (element.required) {
3014
- required.push(element.name);
3719
+ required.push(getSerializedName(element.name, element.metadata));
3015
3720
  }
3016
3721
  break;
3017
3722
  case "group":
@@ -3055,6 +3760,7 @@ function generateFieldSchema(field, ctx) {
3055
3760
  rootAnnotations.push(annotation);
3056
3761
  }
3057
3762
  }
3763
+ applyResolvedMetadata(schema, field.metadata);
3058
3764
  applyAnnotations(schema, rootAnnotations, ctx);
3059
3765
  if (itemStringSchema !== void 0) {
3060
3766
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -3062,7 +3768,7 @@ function generateFieldSchema(field, ctx) {
3062
3768
  if (pathConstraints.length === 0) {
3063
3769
  return schema;
3064
3770
  }
3065
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
3771
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
3066
3772
  }
3067
3773
  function isStringItemConstraint(constraint) {
3068
3774
  switch (constraint.constraintKind) {
@@ -3074,9 +3780,11 @@ function isStringItemConstraint(constraint) {
3074
3780
  return false;
3075
3781
  }
3076
3782
  }
3077
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
3783
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
3078
3784
  if (schema.type === "array" && schema.items) {
3079
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
3785
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
3786
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
3787
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
3080
3788
  return schema;
3081
3789
  }
3082
3790
  const byTarget = /* @__PURE__ */ new Map();
@@ -3091,7 +3799,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
3091
3799
  for (const [target, constraints] of byTarget) {
3092
3800
  const subSchema = {};
3093
3801
  applyConstraints(subSchema, constraints, ctx);
3094
- propertyOverrides[target] = subSchema;
3802
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
3095
3803
  }
3096
3804
  if (schema.$ref) {
3097
3805
  const { $ref, ...rest } = schema;
@@ -3139,7 +3847,7 @@ function generateTypeNode(type, ctx) {
3139
3847
  case "union":
3140
3848
  return generateUnionType(type, ctx);
3141
3849
  case "reference":
3142
- return generateReferenceType(type);
3850
+ return generateReferenceType(type, ctx);
3143
3851
  case "dynamic":
3144
3852
  return generateDynamicType(type);
3145
3853
  case "custom":
@@ -3180,9 +3888,10 @@ function generateObjectType(type, ctx) {
3180
3888
  const properties = {};
3181
3889
  const required = [];
3182
3890
  for (const prop of type.properties) {
3183
- properties[prop.name] = generatePropertySchema(prop, ctx);
3891
+ const propertyName = getSerializedName(prop.name, prop.metadata);
3892
+ properties[propertyName] = generatePropertySchema(prop, ctx);
3184
3893
  if (!prop.optional) {
3185
- required.push(prop.name);
3894
+ required.push(propertyName);
3186
3895
  }
3187
3896
  }
3188
3897
  const schema = { type: "object", properties };
@@ -3203,6 +3912,7 @@ function generateRecordType(type, ctx) {
3203
3912
  function generatePropertySchema(prop, ctx) {
3204
3913
  const schema = generateTypeNode(prop.type, ctx);
3205
3914
  applyConstraints(schema, prop.constraints, ctx);
3915
+ applyResolvedMetadata(schema, prop.metadata);
3206
3916
  applyAnnotations(schema, prop.annotations, ctx);
3207
3917
  return schema;
3208
3918
  }
@@ -3231,8 +3941,28 @@ function isNullableUnion(type) {
3231
3941
  ).length;
3232
3942
  return nullCount === 1;
3233
3943
  }
3234
- function generateReferenceType(type) {
3235
- return { $ref: `#/$defs/${type.name}` };
3944
+ function generateReferenceType(type, ctx) {
3945
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
3946
+ }
3947
+ function applyResolvedMetadata(schema, metadata) {
3948
+ const displayName = getDisplayName(metadata);
3949
+ if (displayName !== void 0) {
3950
+ schema.title = displayName;
3951
+ }
3952
+ }
3953
+ function resolveReferencedType(type, ctx) {
3954
+ return ctx.typeRegistry[type.name]?.type;
3955
+ }
3956
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
3957
+ if (typeNode?.kind === "object") {
3958
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
3959
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
3960
+ }
3961
+ if (typeNode?.kind === "reference") {
3962
+ const referencedType = resolveReferencedType(typeNode, ctx);
3963
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
3964
+ }
3965
+ return logicalName;
3236
3966
  }
3237
3967
  function generateDynamicType(type) {
3238
3968
  if (type.dynamicKind === "enum") {
@@ -3312,7 +4042,7 @@ function applyAnnotations(schema, annotations, ctx) {
3312
4042
  for (const annotation of annotations) {
3313
4043
  switch (annotation.annotationKind) {
3314
4044
  case "displayName":
3315
- schema.title = annotation.value;
4045
+ schema.title ??= annotation.value;
3316
4046
  break;
3317
4047
  case "description":
3318
4048
  schema.description = annotation.value;
@@ -3562,13 +4292,21 @@ function combineRules(parentRule, childRule) {
3562
4292
  }
3563
4293
  };
3564
4294
  }
3565
- function fieldNodeToControl(field, parentRule) {
3566
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
4295
+ function getFieldDisplayName(field) {
4296
+ const resolvedDisplayName = getDisplayName(field.metadata);
4297
+ if (resolvedDisplayName !== void 0) {
4298
+ return resolvedDisplayName;
4299
+ }
4300
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
4301
+ }
4302
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
3567
4303
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
4304
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
4305
+ const displayName = getFieldDisplayName(field);
3568
4306
  const control = {
3569
4307
  type: "Control",
3570
- scope: fieldToScope(field.name),
3571
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
4308
+ scope: fieldToScope(serializedName),
4309
+ ...displayName !== void 0 && { label: displayName },
3572
4310
  ...placeholderAnnotation !== void 0 && {
3573
4311
  options: { placeholder: placeholderAnnotation.value }
3574
4312
  },
@@ -3576,30 +4314,30 @@ function fieldNodeToControl(field, parentRule) {
3576
4314
  };
3577
4315
  return control;
3578
4316
  }
3579
- function groupNodeToLayout(group, parentRule) {
4317
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
3580
4318
  return {
3581
4319
  type: "Group",
3582
4320
  label: group.label,
3583
- elements: irElementsToUiSchema(group.elements, parentRule),
4321
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
3584
4322
  ...parentRule !== void 0 && { rule: parentRule }
3585
4323
  };
3586
4324
  }
3587
- function irElementsToUiSchema(elements, parentRule) {
4325
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
3588
4326
  const result = [];
3589
4327
  for (const element of elements) {
3590
4328
  switch (element.kind) {
3591
4329
  case "field": {
3592
- result.push(fieldNodeToControl(element, parentRule));
4330
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
3593
4331
  break;
3594
4332
  }
3595
4333
  case "group": {
3596
- result.push(groupNodeToLayout(element, parentRule));
4334
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
3597
4335
  break;
3598
4336
  }
3599
4337
  case "conditional": {
3600
- const newRule = createShowRule(element.fieldName, element.value);
4338
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
3601
4339
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
3602
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
4340
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
3603
4341
  result.push(...childElements);
3604
4342
  break;
3605
4343
  }
@@ -3613,12 +4351,35 @@ function irElementsToUiSchema(elements, parentRule) {
3613
4351
  return result;
3614
4352
  }
3615
4353
  function generateUiSchemaFromIR(ir) {
4354
+ assertNoSerializedNameCollisions(ir);
4355
+ const fieldNameMap = collectFieldNameMap(ir.elements);
3616
4356
  const result = {
3617
4357
  type: "VerticalLayout",
3618
- elements: irElementsToUiSchema(ir.elements)
4358
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
3619
4359
  };
3620
4360
  return parseOrThrow(uiSchema, result, "UI Schema");
3621
4361
  }
4362
+ function collectFieldNameMap(elements) {
4363
+ const map = /* @__PURE__ */ new Map();
4364
+ for (const element of elements) {
4365
+ switch (element.kind) {
4366
+ case "field":
4367
+ map.set(element.name, getSerializedName(element.name, element.metadata));
4368
+ break;
4369
+ case "group":
4370
+ case "conditional":
4371
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
4372
+ map.set(key, value);
4373
+ }
4374
+ break;
4375
+ default: {
4376
+ const _exhaustive = element;
4377
+ void _exhaustive;
4378
+ }
4379
+ }
4380
+ }
4381
+ return map;
4382
+ }
3622
4383
 
3623
4384
  // src/validate/constraint-validator.ts
3624
4385
  var import_internal3 = require("@formspec/analysis/internal");
@@ -3701,7 +4462,11 @@ function generateClassSchemas(analysis, source, options) {
3701
4462
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3702
4463
  throw new Error(formatValidationError(errorDiagnostics));
3703
4464
  }
3704
- const ir = canonicalizeTSDoc(analysis, source);
4465
+ const ir = canonicalizeTSDoc(
4466
+ analysis,
4467
+ source,
4468
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4469
+ );
3705
4470
  const validationResult = validateIR(ir, {
3706
4471
  ...options?.extensionRegistry !== void 0 && {
3707
4472
  extensionRegistry: options.extensionRegistry
@@ -3823,6 +4588,7 @@ function typeToJsonSchema(type, checker) {
3823
4588
  visiting,
3824
4589
  void 0,
3825
4590
  void 0,
4591
+ void 0,
3826
4592
  diagnostics
3827
4593
  );
3828
4594
  if (diagnostics.length > 0) {