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

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
package/dist/index.cjs CHANGED
@@ -46,6 +46,315 @@ module.exports = __toCommonJS(index_exports);
46
46
 
47
47
  // src/canonicalize/chain-dsl-canonicalizer.ts
48
48
  var import_internals = require("@formspec/core/internals");
49
+
50
+ // src/metadata/policy.ts
51
+ var NOOP_INFLECT = () => "";
52
+ function normalizePluralization(input) {
53
+ if (input?.mode === "infer-if-missing") {
54
+ return {
55
+ mode: "infer-if-missing",
56
+ infer: () => "",
57
+ inflect: input.inflect
58
+ };
59
+ }
60
+ if (input?.mode === "require-explicit") {
61
+ return {
62
+ mode: "require-explicit",
63
+ infer: () => "",
64
+ inflect: NOOP_INFLECT
65
+ };
66
+ }
67
+ return {
68
+ mode: "disabled",
69
+ infer: () => "",
70
+ inflect: NOOP_INFLECT
71
+ };
72
+ }
73
+ function normalizeScalarPolicy(input) {
74
+ if (input?.mode === "infer-if-missing") {
75
+ return {
76
+ mode: "infer-if-missing",
77
+ infer: input.infer,
78
+ pluralization: normalizePluralization(input.pluralization)
79
+ };
80
+ }
81
+ if (input?.mode === "require-explicit") {
82
+ return {
83
+ mode: "require-explicit",
84
+ infer: () => "",
85
+ pluralization: normalizePluralization(input.pluralization)
86
+ };
87
+ }
88
+ return {
89
+ mode: "disabled",
90
+ infer: () => "",
91
+ pluralization: normalizePluralization(input?.pluralization)
92
+ };
93
+ }
94
+ function normalizeDeclarationPolicy(input) {
95
+ return {
96
+ apiName: normalizeScalarPolicy(input?.apiName),
97
+ displayName: normalizeScalarPolicy(input?.displayName)
98
+ };
99
+ }
100
+ function normalizeMetadataPolicy(input) {
101
+ return {
102
+ type: normalizeDeclarationPolicy(input?.type),
103
+ field: normalizeDeclarationPolicy(input?.field),
104
+ method: normalizeDeclarationPolicy(input?.method)
105
+ };
106
+ }
107
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
108
+ return policy[declarationKind];
109
+ }
110
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
111
+ return {
112
+ surface,
113
+ declarationKind,
114
+ logicalName,
115
+ ...buildContext !== void 0 && { buildContext }
116
+ };
117
+ }
118
+
119
+ // src/metadata/resolve.ts
120
+ function toExplicitScalar(value) {
121
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
122
+ }
123
+ function toExplicitResolvedMetadata(explicit) {
124
+ if (explicit === void 0) {
125
+ return void 0;
126
+ }
127
+ const apiName = toExplicitScalar(explicit.apiName);
128
+ const displayName = toExplicitScalar(explicit.displayName);
129
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
130
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
131
+ const metadata = {
132
+ ...apiName !== void 0 && { apiName },
133
+ ...displayName !== void 0 && { displayName },
134
+ ...apiNamePlural !== void 0 && { apiNamePlural },
135
+ ...displayNamePlural !== void 0 && { displayNamePlural }
136
+ };
137
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
138
+ }
139
+ function resolveScalar(current, policy, context, metadataLabel) {
140
+ if (current !== void 0) {
141
+ return current;
142
+ }
143
+ if (policy.mode === "require-explicit") {
144
+ throw new Error(
145
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
146
+ );
147
+ }
148
+ if (policy.mode !== "infer-if-missing") {
149
+ return void 0;
150
+ }
151
+ const inferredValue = policy.infer(context);
152
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
153
+ }
154
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
155
+ if (current !== void 0) {
156
+ return current;
157
+ }
158
+ if (policy.mode === "require-explicit") {
159
+ throw new Error(
160
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
161
+ );
162
+ }
163
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
164
+ return void 0;
165
+ }
166
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
167
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
168
+ }
169
+ function resolveResolvedMetadata(current, policy, context) {
170
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
171
+ const displayName = resolveScalar(
172
+ current?.displayName,
173
+ policy.displayName,
174
+ context,
175
+ "displayName"
176
+ );
177
+ const apiNamePlural = resolvePlural(
178
+ current?.apiNamePlural,
179
+ apiName,
180
+ policy.apiName.pluralization,
181
+ context,
182
+ "apiNamePlural"
183
+ );
184
+ const displayNamePlural = resolvePlural(
185
+ current?.displayNamePlural,
186
+ displayName,
187
+ policy.displayName.pluralization,
188
+ context,
189
+ "displayNamePlural"
190
+ );
191
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
192
+ return void 0;
193
+ }
194
+ return {
195
+ ...apiName !== void 0 && { apiName },
196
+ ...displayName !== void 0 && { displayName },
197
+ ...apiNamePlural !== void 0 && { apiNamePlural },
198
+ ...displayNamePlural !== void 0 && { displayNamePlural }
199
+ };
200
+ }
201
+ function pickResolvedMetadataValue(baseValue, overlayValue) {
202
+ if (overlayValue?.source === "explicit") {
203
+ return overlayValue;
204
+ }
205
+ if (baseValue?.source === "explicit") {
206
+ return baseValue;
207
+ }
208
+ return baseValue ?? overlayValue;
209
+ }
210
+ function resolveTypeNodeMetadata(type, options) {
211
+ switch (type.kind) {
212
+ case "array":
213
+ return {
214
+ ...type,
215
+ items: resolveTypeNodeMetadata(type.items, options)
216
+ };
217
+ case "object":
218
+ return {
219
+ ...type,
220
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
221
+ };
222
+ case "record":
223
+ return {
224
+ ...type,
225
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
226
+ };
227
+ case "union":
228
+ return {
229
+ ...type,
230
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
231
+ };
232
+ case "reference":
233
+ case "primitive":
234
+ case "enum":
235
+ case "dynamic":
236
+ case "custom":
237
+ return type;
238
+ default: {
239
+ const _exhaustive = type;
240
+ return _exhaustive;
241
+ }
242
+ }
243
+ }
244
+ function resolveObjectPropertyMetadata(property, options) {
245
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
246
+ surface: options.surface,
247
+ declarationKind: "field",
248
+ logicalName: property.name,
249
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
250
+ });
251
+ return {
252
+ ...property,
253
+ ...metadata !== void 0 && { metadata },
254
+ type: resolveTypeNodeMetadata(property.type, options)
255
+ };
256
+ }
257
+ function resolveFieldMetadataNode(field, options) {
258
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
259
+ surface: options.surface,
260
+ declarationKind: "field",
261
+ logicalName: field.name,
262
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
263
+ });
264
+ return {
265
+ ...field,
266
+ ...metadata !== void 0 && { metadata },
267
+ type: resolveTypeNodeMetadata(field.type, options)
268
+ };
269
+ }
270
+ function resolveFormElementMetadata(element, options) {
271
+ switch (element.kind) {
272
+ case "field":
273
+ return resolveFieldMetadataNode(element, options);
274
+ case "group":
275
+ return {
276
+ ...element,
277
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
278
+ };
279
+ case "conditional":
280
+ return {
281
+ ...element,
282
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
283
+ };
284
+ default: {
285
+ const _exhaustive = element;
286
+ return _exhaustive;
287
+ }
288
+ }
289
+ }
290
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
291
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
292
+ surface: options.surface,
293
+ declarationKind: "type",
294
+ logicalName: typeDefinition.name,
295
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
296
+ });
297
+ return {
298
+ ...typeDefinition,
299
+ ...metadata !== void 0 && { metadata },
300
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
301
+ };
302
+ }
303
+ function resolveMetadata(explicit, policy, context) {
304
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
305
+ }
306
+ function mergeResolvedMetadata(baseMetadata, overlayMetadata) {
307
+ const apiName = pickResolvedMetadataValue(baseMetadata?.apiName, overlayMetadata?.apiName);
308
+ const displayName = pickResolvedMetadataValue(
309
+ baseMetadata?.displayName,
310
+ overlayMetadata?.displayName
311
+ );
312
+ const apiNamePlural = pickResolvedMetadataValue(
313
+ baseMetadata?.apiNamePlural,
314
+ overlayMetadata?.apiNamePlural
315
+ );
316
+ const displayNamePlural = pickResolvedMetadataValue(
317
+ baseMetadata?.displayNamePlural,
318
+ overlayMetadata?.displayNamePlural
319
+ );
320
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
321
+ return void 0;
322
+ }
323
+ return {
324
+ ...apiName !== void 0 && { apiName },
325
+ ...displayName !== void 0 && { displayName },
326
+ ...apiNamePlural !== void 0 && { apiNamePlural },
327
+ ...displayNamePlural !== void 0 && { displayNamePlural }
328
+ };
329
+ }
330
+ function getSerializedName(logicalName, metadata) {
331
+ return metadata?.apiName?.value ?? logicalName;
332
+ }
333
+ function getDisplayName(metadata) {
334
+ return metadata?.displayName?.value;
335
+ }
336
+ function resolveFormIRMetadata(ir, options) {
337
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
338
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
339
+ surface: options.surface,
340
+ declarationKind: "type",
341
+ logicalName: rootLogicalName,
342
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
343
+ });
344
+ return {
345
+ ...ir,
346
+ ...metadata !== void 0 && { metadata },
347
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
348
+ typeRegistry: Object.fromEntries(
349
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
350
+ name,
351
+ resolveTypeDefinitionMetadata(definition, options)
352
+ ])
353
+ )
354
+ };
355
+ }
356
+
357
+ // src/canonicalize/chain-dsl-canonicalizer.ts
49
358
  var CHAIN_DSL_PROVENANCE = {
50
359
  surface: "chain-dsl",
51
360
  file: "",
@@ -61,57 +370,60 @@ function isConditional(el) {
61
370
  function isField(el) {
62
371
  return el._type === "field";
63
372
  }
64
- function canonicalizeChainDSL(form) {
373
+ function canonicalizeChainDSL(form, options) {
374
+ const metadataPolicy = normalizeMetadataPolicy(
375
+ options?.metadata ?? (0, import_internals._getFormSpecMetadataPolicy)(form)
376
+ );
65
377
  return {
66
378
  kind: "form-ir",
67
379
  irVersion: import_internals.IR_VERSION,
68
- elements: canonicalizeElements(form.elements),
380
+ elements: canonicalizeElements(form.elements, metadataPolicy),
69
381
  rootAnnotations: [],
70
382
  typeRegistry: {},
71
383
  provenance: CHAIN_DSL_PROVENANCE
72
384
  };
73
385
  }
74
- function canonicalizeElements(elements) {
75
- return elements.map(canonicalizeElement);
386
+ function canonicalizeElements(elements, metadataPolicy) {
387
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
76
388
  }
77
- function canonicalizeElement(element) {
389
+ function canonicalizeElement(element, metadataPolicy) {
78
390
  if (isField(element)) {
79
- return canonicalizeField(element);
391
+ return canonicalizeField(element, metadataPolicy);
80
392
  }
81
393
  if (isGroup(element)) {
82
- return canonicalizeGroup(element);
394
+ return canonicalizeGroup(element, metadataPolicy);
83
395
  }
84
396
  if (isConditional(element)) {
85
- return canonicalizeConditional(element);
397
+ return canonicalizeConditional(element, metadataPolicy);
86
398
  }
87
399
  const _exhaustive = element;
88
400
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
89
401
  }
90
- function canonicalizeField(field) {
402
+ function canonicalizeField(field, metadataPolicy) {
91
403
  switch (field._field) {
92
404
  case "text":
93
- return canonicalizeTextField(field);
405
+ return canonicalizeTextField(field, metadataPolicy);
94
406
  case "number":
95
- return canonicalizeNumberField(field);
407
+ return canonicalizeNumberField(field, metadataPolicy);
96
408
  case "boolean":
97
- return canonicalizeBooleanField(field);
409
+ return canonicalizeBooleanField(field, metadataPolicy);
98
410
  case "enum":
99
- return canonicalizeStaticEnumField(field);
411
+ return canonicalizeStaticEnumField(field, metadataPolicy);
100
412
  case "dynamic_enum":
101
- return canonicalizeDynamicEnumField(field);
413
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
102
414
  case "dynamic_schema":
103
- return canonicalizeDynamicSchemaField(field);
415
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
104
416
  case "array":
105
- return canonicalizeArrayField(field);
417
+ return canonicalizeArrayField(field, metadataPolicy);
106
418
  case "object":
107
- return canonicalizeObjectField(field);
419
+ return canonicalizeObjectField(field, metadataPolicy);
108
420
  default: {
109
421
  const _exhaustive = field;
110
422
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
111
423
  }
112
424
  }
113
425
  }
114
- function canonicalizeTextField(field) {
426
+ function canonicalizeTextField(field, metadataPolicy) {
115
427
  const type = { kind: "primitive", primitiveKind: "string" };
116
428
  const constraints = [];
117
429
  if (field.minLength !== void 0) {
@@ -143,13 +455,14 @@ function canonicalizeTextField(field) {
143
455
  }
144
456
  return buildFieldNode(
145
457
  field.name,
458
+ resolveFieldMetadata(field.name, field, metadataPolicy),
146
459
  type,
147
460
  field.required,
148
- buildAnnotations(field.label, field.placeholder),
461
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
149
462
  constraints
150
463
  );
151
464
  }
152
- function canonicalizeNumberField(field) {
465
+ function canonicalizeNumberField(field, metadataPolicy) {
153
466
  const type = { kind: "primitive", primitiveKind: "number" };
154
467
  const constraints = [];
155
468
  if (field.min !== void 0) {
@@ -181,17 +494,24 @@ function canonicalizeNumberField(field) {
181
494
  }
182
495
  return buildFieldNode(
183
496
  field.name,
497
+ resolveFieldMetadata(field.name, field, metadataPolicy),
184
498
  type,
185
499
  field.required,
186
- buildAnnotations(field.label),
500
+ buildAnnotations(getExplicitDisplayName(field)),
187
501
  constraints
188
502
  );
189
503
  }
190
- function canonicalizeBooleanField(field) {
504
+ function canonicalizeBooleanField(field, metadataPolicy) {
191
505
  const type = { kind: "primitive", primitiveKind: "boolean" };
192
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
506
+ return buildFieldNode(
507
+ field.name,
508
+ resolveFieldMetadata(field.name, field, metadataPolicy),
509
+ type,
510
+ field.required,
511
+ buildAnnotations(getExplicitDisplayName(field))
512
+ );
193
513
  }
194
- function canonicalizeStaticEnumField(field) {
514
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
195
515
  const members = field.options.map((opt) => {
196
516
  if (typeof opt === "string") {
197
517
  return { value: opt };
@@ -199,28 +519,46 @@ function canonicalizeStaticEnumField(field) {
199
519
  return { value: opt.id, displayName: opt.label };
200
520
  });
201
521
  const type = { kind: "enum", members };
202
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
522
+ return buildFieldNode(
523
+ field.name,
524
+ resolveFieldMetadata(field.name, field, metadataPolicy),
525
+ type,
526
+ field.required,
527
+ buildAnnotations(getExplicitDisplayName(field))
528
+ );
203
529
  }
204
- function canonicalizeDynamicEnumField(field) {
530
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
205
531
  const type = {
206
532
  kind: "dynamic",
207
533
  dynamicKind: "enum",
208
534
  sourceKey: field.source,
209
535
  parameterFields: field.params ? [...field.params] : []
210
536
  };
211
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
537
+ return buildFieldNode(
538
+ field.name,
539
+ resolveFieldMetadata(field.name, field, metadataPolicy),
540
+ type,
541
+ field.required,
542
+ buildAnnotations(getExplicitDisplayName(field))
543
+ );
212
544
  }
213
- function canonicalizeDynamicSchemaField(field) {
545
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
214
546
  const type = {
215
547
  kind: "dynamic",
216
548
  dynamicKind: "schema",
217
549
  sourceKey: field.schemaSource,
218
550
  parameterFields: []
219
551
  };
220
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
552
+ return buildFieldNode(
553
+ field.name,
554
+ resolveFieldMetadata(field.name, field, metadataPolicy),
555
+ type,
556
+ field.required,
557
+ buildAnnotations(getExplicitDisplayName(field))
558
+ );
221
559
  }
222
- function canonicalizeArrayField(field) {
223
- const itemProperties = buildObjectProperties(field.items);
560
+ function canonicalizeArrayField(field, metadataPolicy) {
561
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
224
562
  const itemsType = {
225
563
  kind: "object",
226
564
  properties: itemProperties,
@@ -248,37 +586,44 @@ function canonicalizeArrayField(field) {
248
586
  }
249
587
  return buildFieldNode(
250
588
  field.name,
589
+ resolveFieldMetadata(field.name, field, metadataPolicy),
251
590
  type,
252
591
  field.required,
253
- buildAnnotations(field.label),
592
+ buildAnnotations(getExplicitDisplayName(field)),
254
593
  constraints
255
594
  );
256
595
  }
257
- function canonicalizeObjectField(field) {
258
- const properties = buildObjectProperties(field.properties);
596
+ function canonicalizeObjectField(field, metadataPolicy) {
597
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
259
598
  const type = {
260
599
  kind: "object",
261
600
  properties,
262
601
  additionalProperties: true
263
602
  };
264
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
603
+ return buildFieldNode(
604
+ field.name,
605
+ resolveFieldMetadata(field.name, field, metadataPolicy),
606
+ type,
607
+ field.required,
608
+ buildAnnotations(getExplicitDisplayName(field))
609
+ );
265
610
  }
266
- function canonicalizeGroup(g) {
611
+ function canonicalizeGroup(g, metadataPolicy) {
267
612
  return {
268
613
  kind: "group",
269
614
  label: g.label,
270
- elements: canonicalizeElements(g.elements),
615
+ elements: canonicalizeElements(g.elements, metadataPolicy),
271
616
  provenance: CHAIN_DSL_PROVENANCE
272
617
  };
273
618
  }
274
- function canonicalizeConditional(c) {
619
+ function canonicalizeConditional(c, metadataPolicy) {
275
620
  return {
276
621
  kind: "conditional",
277
622
  fieldName: c.field,
278
623
  // Conditional values from the chain DSL are JSON-serializable primitives
279
624
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
280
625
  value: assertJsonValue(c.value),
281
- elements: canonicalizeElements(c.elements),
626
+ elements: canonicalizeElements(c.elements, metadataPolicy),
282
627
  provenance: CHAIN_DSL_PROVENANCE
283
628
  };
284
629
  }
@@ -298,10 +643,11 @@ function assertJsonValue(v) {
298
643
  }
299
644
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
300
645
  }
301
- function buildFieldNode(name, type, required, annotations, constraints = []) {
646
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
302
647
  return {
303
648
  kind: "field",
304
649
  name,
650
+ ...metadata !== void 0 && { metadata },
305
651
  type,
306
652
  required: required === true,
307
653
  constraints,
@@ -331,13 +677,14 @@ function buildAnnotations(label, placeholder) {
331
677
  }
332
678
  return annotations;
333
679
  }
334
- function buildObjectProperties(elements, insideConditional = false) {
680
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
335
681
  const properties = [];
336
682
  for (const el of elements) {
337
683
  if (isField(el)) {
338
- const fieldNode = canonicalizeField(el);
684
+ const fieldNode = canonicalizeField(el, metadataPolicy);
339
685
  properties.push({
340
686
  name: fieldNode.name,
687
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
341
688
  type: fieldNode.type,
342
689
  // Fields inside a conditional branch are always optional in the
343
690
  // data schema, regardless of their `required` flag — the condition
@@ -348,17 +695,34 @@ function buildObjectProperties(elements, insideConditional = false) {
348
695
  provenance: CHAIN_DSL_PROVENANCE
349
696
  });
350
697
  } else if (isGroup(el)) {
351
- properties.push(...buildObjectProperties(el.elements, insideConditional));
698
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
352
699
  } else if (isConditional(el)) {
353
- properties.push(...buildObjectProperties(el.elements, true));
700
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
354
701
  }
355
702
  }
356
703
  return properties;
357
704
  }
705
+ function getExplicitDisplayName(field) {
706
+ if (field.label !== void 0 && field.displayName !== void 0) {
707
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
708
+ }
709
+ return field.displayName ?? field.label;
710
+ }
711
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
712
+ const displayName = getExplicitDisplayName(field);
713
+ return resolveMetadata(
714
+ {
715
+ ...field.apiName !== void 0 && { apiName: field.apiName },
716
+ ...displayName !== void 0 && { displayName }
717
+ },
718
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
719
+ makeMetadataContext("chain-dsl", "field", logicalName)
720
+ );
721
+ }
358
722
 
359
723
  // src/canonicalize/tsdoc-canonicalizer.ts
360
724
  var import_internals2 = require("@formspec/core/internals");
361
- function canonicalizeTSDoc(analysis, source) {
725
+ function canonicalizeTSDoc(analysis, source, options) {
362
726
  const file = source?.file ?? "";
363
727
  const provenance = {
364
728
  surface: "tsdoc",
@@ -367,15 +731,21 @@ function canonicalizeTSDoc(analysis, source) {
367
731
  column: 0
368
732
  };
369
733
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
370
- return {
734
+ const ir = {
371
735
  kind: "form-ir",
736
+ name: analysis.name,
372
737
  irVersion: import_internals2.IR_VERSION,
373
738
  elements,
739
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
374
740
  typeRegistry: analysis.typeRegistry,
375
741
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
376
742
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
377
743
  provenance
378
744
  };
745
+ return resolveFormIRMetadata(ir, {
746
+ policy: normalizeMetadataPolicy(options?.metadata),
747
+ surface: "tsdoc"
748
+ });
379
749
  }
380
750
  function assembleElements(fields, layouts, provenance) {
381
751
  const elements = [];
@@ -432,6 +802,120 @@ function wrapInConditional(field, layout, provenance) {
432
802
  return conditional;
433
803
  }
434
804
 
805
+ // src/metadata/collision-guards.ts
806
+ function assertUniqueSerializedNames(entries, scope) {
807
+ const seen = /* @__PURE__ */ new Map();
808
+ for (const entry of entries) {
809
+ const previous = seen.get(entry.serializedName);
810
+ if (previous !== void 0) {
811
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
812
+ continue;
813
+ }
814
+ throw new Error(
815
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
816
+ );
817
+ }
818
+ seen.set(entry.serializedName, entry);
819
+ }
820
+ }
821
+ function collectFlattenedFields(elements) {
822
+ const fields = [];
823
+ for (const element of elements) {
824
+ switch (element.kind) {
825
+ case "field":
826
+ fields.push(element);
827
+ break;
828
+ case "group":
829
+ case "conditional":
830
+ fields.push(...collectFlattenedFields(element.elements));
831
+ break;
832
+ default: {
833
+ const exhaustive = element;
834
+ void exhaustive;
835
+ }
836
+ }
837
+ }
838
+ return fields;
839
+ }
840
+ function validateObjectProperties(properties, scope) {
841
+ assertUniqueSerializedNames(
842
+ properties.map((property) => ({
843
+ logicalName: property.name,
844
+ serializedName: getSerializedName(property.name, property.metadata),
845
+ category: "object property"
846
+ })),
847
+ scope
848
+ );
849
+ for (const property of properties) {
850
+ validateTypeNode(
851
+ property.type,
852
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
853
+ );
854
+ }
855
+ }
856
+ function validateTypeNode(type, scope) {
857
+ switch (type.kind) {
858
+ case "array":
859
+ validateTypeNode(type.items, `${scope}[]`);
860
+ break;
861
+ case "object":
862
+ validateObjectProperties(type.properties, scope);
863
+ break;
864
+ case "record":
865
+ validateTypeNode(type.valueType, `${scope}.*`);
866
+ break;
867
+ case "union":
868
+ type.members.forEach((member, index) => {
869
+ validateTypeNode(member, `${scope}|${String(index)}`);
870
+ });
871
+ break;
872
+ case "reference":
873
+ case "primitive":
874
+ case "enum":
875
+ case "dynamic":
876
+ case "custom":
877
+ break;
878
+ default: {
879
+ const exhaustive = type;
880
+ void exhaustive;
881
+ }
882
+ }
883
+ }
884
+ function validateTypeDefinitions(typeRegistry) {
885
+ const definitions = Object.values(typeRegistry);
886
+ assertUniqueSerializedNames(
887
+ definitions.map((definition) => ({
888
+ logicalName: definition.name,
889
+ serializedName: getSerializedName(definition.name, definition.metadata),
890
+ category: "type definition"
891
+ })),
892
+ "$defs"
893
+ );
894
+ for (const definition of definitions) {
895
+ validateTypeDefinition(definition);
896
+ }
897
+ }
898
+ function validateTypeDefinition(definition) {
899
+ validateTypeNode(
900
+ definition.type,
901
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
902
+ );
903
+ }
904
+ function assertNoSerializedNameCollisions(ir) {
905
+ assertUniqueSerializedNames(
906
+ collectFlattenedFields(ir.elements).map((field) => ({
907
+ logicalName: field.name,
908
+ serializedName: getSerializedName(field.name, field.metadata),
909
+ category: "field"
910
+ })),
911
+ "form root"
912
+ );
913
+ for (const field of collectFlattenedFields(ir.elements)) {
914
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
915
+ }
916
+ validateTypeDefinitions(ir.typeRegistry);
917
+ }
918
+
435
919
  // src/json-schema/ir-generator.ts
436
920
  function makeContext(options) {
437
921
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -442,19 +926,33 @@ function makeContext(options) {
442
926
  }
443
927
  return {
444
928
  defs: {},
929
+ typeNameMap: {},
930
+ typeRegistry: {},
445
931
  extensionRegistry: options?.extensionRegistry,
446
932
  vendorPrefix
447
933
  };
448
934
  }
449
935
  function generateJsonSchemaFromIR(ir, options) {
450
- const ctx = makeContext(options);
936
+ assertNoSerializedNameCollisions(ir);
937
+ const ctx = {
938
+ ...makeContext(options),
939
+ typeRegistry: ir.typeRegistry,
940
+ typeNameMap: Object.fromEntries(
941
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
942
+ name,
943
+ getSerializedName(name, typeDef.metadata)
944
+ ])
945
+ )
946
+ };
451
947
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
452
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
948
+ const schemaName = ctx.typeNameMap[name] ?? name;
949
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
950
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
453
951
  if (typeDef.constraints && typeDef.constraints.length > 0) {
454
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
952
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
455
953
  }
456
954
  if (typeDef.annotations && typeDef.annotations.length > 0) {
457
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
955
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
458
956
  }
459
957
  }
460
958
  const properties = {};
@@ -467,6 +965,7 @@ function generateJsonSchemaFromIR(ir, options) {
467
965
  properties,
468
966
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
469
967
  };
968
+ applyResolvedMetadata(result, ir.metadata);
470
969
  if (ir.annotations && ir.annotations.length > 0) {
471
970
  applyAnnotations(result, ir.annotations, ctx);
472
971
  }
@@ -479,9 +978,9 @@ function collectFields(elements, properties, required, ctx) {
479
978
  for (const element of elements) {
480
979
  switch (element.kind) {
481
980
  case "field":
482
- properties[element.name] = generateFieldSchema(element, ctx);
981
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
483
982
  if (element.required) {
484
- required.push(element.name);
983
+ required.push(getSerializedName(element.name, element.metadata));
485
984
  }
486
985
  break;
487
986
  case "group":
@@ -525,6 +1024,7 @@ function generateFieldSchema(field, ctx) {
525
1024
  rootAnnotations.push(annotation);
526
1025
  }
527
1026
  }
1027
+ applyResolvedMetadata(schema, field.metadata);
528
1028
  applyAnnotations(schema, rootAnnotations, ctx);
529
1029
  if (itemStringSchema !== void 0) {
530
1030
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -532,7 +1032,7 @@ function generateFieldSchema(field, ctx) {
532
1032
  if (pathConstraints.length === 0) {
533
1033
  return schema;
534
1034
  }
535
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
1035
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
536
1036
  }
537
1037
  function isStringItemConstraint(constraint) {
538
1038
  switch (constraint.constraintKind) {
@@ -544,9 +1044,11 @@ function isStringItemConstraint(constraint) {
544
1044
  return false;
545
1045
  }
546
1046
  }
547
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
1047
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
548
1048
  if (schema.type === "array" && schema.items) {
549
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
1049
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
1050
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
1051
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
550
1052
  return schema;
551
1053
  }
552
1054
  const byTarget = /* @__PURE__ */ new Map();
@@ -561,7 +1063,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
561
1063
  for (const [target, constraints] of byTarget) {
562
1064
  const subSchema = {};
563
1065
  applyConstraints(subSchema, constraints, ctx);
564
- propertyOverrides[target] = subSchema;
1066
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
565
1067
  }
566
1068
  if (schema.$ref) {
567
1069
  const { $ref, ...rest } = schema;
@@ -609,7 +1111,7 @@ function generateTypeNode(type, ctx) {
609
1111
  case "union":
610
1112
  return generateUnionType(type, ctx);
611
1113
  case "reference":
612
- return generateReferenceType(type);
1114
+ return generateReferenceType(type, ctx);
613
1115
  case "dynamic":
614
1116
  return generateDynamicType(type);
615
1117
  case "custom":
@@ -650,9 +1152,10 @@ function generateObjectType(type, ctx) {
650
1152
  const properties = {};
651
1153
  const required = [];
652
1154
  for (const prop of type.properties) {
653
- properties[prop.name] = generatePropertySchema(prop, ctx);
1155
+ const propertyName = getSerializedName(prop.name, prop.metadata);
1156
+ properties[propertyName] = generatePropertySchema(prop, ctx);
654
1157
  if (!prop.optional) {
655
- required.push(prop.name);
1158
+ required.push(propertyName);
656
1159
  }
657
1160
  }
658
1161
  const schema = { type: "object", properties };
@@ -673,6 +1176,7 @@ function generateRecordType(type, ctx) {
673
1176
  function generatePropertySchema(prop, ctx) {
674
1177
  const schema = generateTypeNode(prop.type, ctx);
675
1178
  applyConstraints(schema, prop.constraints, ctx);
1179
+ applyResolvedMetadata(schema, prop.metadata);
676
1180
  applyAnnotations(schema, prop.annotations, ctx);
677
1181
  return schema;
678
1182
  }
@@ -701,8 +1205,28 @@ function isNullableUnion(type) {
701
1205
  ).length;
702
1206
  return nullCount === 1;
703
1207
  }
704
- function generateReferenceType(type) {
705
- return { $ref: `#/$defs/${type.name}` };
1208
+ function generateReferenceType(type, ctx) {
1209
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
1210
+ }
1211
+ function applyResolvedMetadata(schema, metadata) {
1212
+ const displayName = getDisplayName(metadata);
1213
+ if (displayName !== void 0) {
1214
+ schema.title = displayName;
1215
+ }
1216
+ }
1217
+ function resolveReferencedType(type, ctx) {
1218
+ return ctx.typeRegistry[type.name]?.type;
1219
+ }
1220
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1221
+ if (typeNode?.kind === "object") {
1222
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1223
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1224
+ }
1225
+ if (typeNode?.kind === "reference") {
1226
+ const referencedType = resolveReferencedType(typeNode, ctx);
1227
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1228
+ }
1229
+ return logicalName;
706
1230
  }
707
1231
  function generateDynamicType(type) {
708
1232
  if (type.dynamicKind === "enum") {
@@ -782,7 +1306,7 @@ function applyAnnotations(schema, annotations, ctx) {
782
1306
  for (const annotation of annotations) {
783
1307
  switch (annotation.annotationKind) {
784
1308
  case "displayName":
785
- schema.title = annotation.value;
1309
+ schema.title ??= annotation.value;
786
1310
  break;
787
1311
  case "description":
788
1312
  schema.description = annotation.value;
@@ -869,7 +1393,10 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
869
1393
 
870
1394
  // src/json-schema/generator.ts
871
1395
  function generateJsonSchema(form, options) {
872
- const ir = canonicalizeChainDSL(form);
1396
+ const ir = canonicalizeChainDSL(
1397
+ form,
1398
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1399
+ );
873
1400
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
874
1401
  return generateJsonSchemaFromIR(ir, internalOptions);
875
1402
  }
@@ -1039,13 +1566,21 @@ function combineRules(parentRule, childRule) {
1039
1566
  }
1040
1567
  };
1041
1568
  }
1042
- function fieldNodeToControl(field, parentRule) {
1043
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1569
+ function getFieldDisplayName(field) {
1570
+ const resolvedDisplayName = getDisplayName(field.metadata);
1571
+ if (resolvedDisplayName !== void 0) {
1572
+ return resolvedDisplayName;
1573
+ }
1574
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1575
+ }
1576
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
1044
1577
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1578
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1579
+ const displayName = getFieldDisplayName(field);
1045
1580
  const control = {
1046
1581
  type: "Control",
1047
- scope: fieldToScope(field.name),
1048
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1582
+ scope: fieldToScope(serializedName),
1583
+ ...displayName !== void 0 && { label: displayName },
1049
1584
  ...placeholderAnnotation !== void 0 && {
1050
1585
  options: { placeholder: placeholderAnnotation.value }
1051
1586
  },
@@ -1053,30 +1588,30 @@ function fieldNodeToControl(field, parentRule) {
1053
1588
  };
1054
1589
  return control;
1055
1590
  }
1056
- function groupNodeToLayout(group, parentRule) {
1591
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
1057
1592
  return {
1058
1593
  type: "Group",
1059
1594
  label: group.label,
1060
- elements: irElementsToUiSchema(group.elements, parentRule),
1595
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
1061
1596
  ...parentRule !== void 0 && { rule: parentRule }
1062
1597
  };
1063
1598
  }
1064
- function irElementsToUiSchema(elements, parentRule) {
1599
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1065
1600
  const result = [];
1066
1601
  for (const element of elements) {
1067
1602
  switch (element.kind) {
1068
1603
  case "field": {
1069
- result.push(fieldNodeToControl(element, parentRule));
1604
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1070
1605
  break;
1071
1606
  }
1072
1607
  case "group": {
1073
- result.push(groupNodeToLayout(element, parentRule));
1608
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1074
1609
  break;
1075
1610
  }
1076
1611
  case "conditional": {
1077
- const newRule = createShowRule(element.fieldName, element.value);
1612
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1078
1613
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1079
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1614
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1080
1615
  result.push(...childElements);
1081
1616
  break;
1082
1617
  }
@@ -1090,16 +1625,42 @@ function irElementsToUiSchema(elements, parentRule) {
1090
1625
  return result;
1091
1626
  }
1092
1627
  function generateUiSchemaFromIR(ir) {
1628
+ assertNoSerializedNameCollisions(ir);
1629
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1093
1630
  const result = {
1094
1631
  type: "VerticalLayout",
1095
- elements: irElementsToUiSchema(ir.elements)
1632
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1096
1633
  };
1097
1634
  return parseOrThrow(uiSchema, result, "UI Schema");
1098
1635
  }
1636
+ function collectFieldNameMap(elements) {
1637
+ const map = /* @__PURE__ */ new Map();
1638
+ for (const element of elements) {
1639
+ switch (element.kind) {
1640
+ case "field":
1641
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1642
+ break;
1643
+ case "group":
1644
+ case "conditional":
1645
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1646
+ map.set(key, value);
1647
+ }
1648
+ break;
1649
+ default: {
1650
+ const _exhaustive = element;
1651
+ void _exhaustive;
1652
+ }
1653
+ }
1654
+ }
1655
+ return map;
1656
+ }
1099
1657
 
1100
1658
  // src/ui-schema/generator.ts
1101
- function generateUiSchema(form) {
1102
- const ir = canonicalizeChainDSL(form);
1659
+ function generateUiSchema(form, options) {
1660
+ const ir = canonicalizeChainDSL(
1661
+ form,
1662
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1663
+ );
1103
1664
  return generateUiSchemaFromIR(ir);
1104
1665
  }
1105
1666
 
@@ -2136,7 +2697,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
2136
2697
  ...hostType !== void 0 && { hostType }
2137
2698
  };
2138
2699
  }
2139
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2700
+ function makeExplicitScalarMetadata(value) {
2701
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
2702
+ }
2703
+ function extractExplicitMetadata(node) {
2704
+ let apiName;
2705
+ let displayName;
2706
+ let apiNamePlural;
2707
+ let displayNamePlural;
2708
+ for (const tag of getLeadingParsedTags(node)) {
2709
+ const value = tag.argumentText.trim();
2710
+ if (value === "") {
2711
+ continue;
2712
+ }
2713
+ if (tag.normalizedTagName === "apiName") {
2714
+ if (tag.target === null) {
2715
+ apiName ??= value;
2716
+ } else if (tag.target.kind === "variant") {
2717
+ if (tag.target.rawText === "singular") {
2718
+ apiName ??= value;
2719
+ } else if (tag.target.rawText === "plural") {
2720
+ apiNamePlural ??= value;
2721
+ }
2722
+ }
2723
+ continue;
2724
+ }
2725
+ if (tag.normalizedTagName === "displayName") {
2726
+ if (tag.target === null) {
2727
+ displayName ??= value;
2728
+ } else if (tag.target.kind === "variant") {
2729
+ if (tag.target.rawText === "singular") {
2730
+ displayName ??= value;
2731
+ } else if (tag.target.rawText === "plural") {
2732
+ displayNamePlural ??= value;
2733
+ }
2734
+ }
2735
+ }
2736
+ }
2737
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
2738
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
2739
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
2740
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
2741
+ const metadata = {
2742
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
2743
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
2744
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
2745
+ ...resolvedDisplayNamePlural !== void 0 && {
2746
+ displayNamePlural: resolvedDisplayNamePlural
2747
+ }
2748
+ };
2749
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
2750
+ }
2751
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
2752
+ const explicit = extractExplicitMetadata(node);
2753
+ return resolveMetadata(
2754
+ {
2755
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
2756
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
2757
+ ...explicit?.apiNamePlural !== void 0 && {
2758
+ apiNamePlural: explicit.apiNamePlural.value
2759
+ },
2760
+ ...explicit?.displayNamePlural !== void 0 && {
2761
+ displayNamePlural: explicit.displayNamePlural.value
2762
+ }
2763
+ },
2764
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
2765
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
2766
+ );
2767
+ }
2768
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2769
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2140
2770
  const name = classDecl.name?.text ?? "AnonymousClass";
2141
2771
  const fields = [];
2142
2772
  const fieldLayouts = [];
@@ -2163,6 +2793,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2163
2793
  visiting,
2164
2794
  diagnostics,
2165
2795
  classType,
2796
+ normalizedMetadataPolicy,
2166
2797
  extensionRegistry
2167
2798
  );
2168
2799
  if (fieldNode) {
@@ -2187,10 +2818,18 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2187
2818
  classType,
2188
2819
  checker,
2189
2820
  file,
2190
- diagnostics
2821
+ diagnostics,
2822
+ normalizedMetadataPolicy
2191
2823
  );
2824
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2825
+ checker,
2826
+ declaration: classDecl,
2827
+ subjectType: classType,
2828
+ hostType: classType
2829
+ });
2192
2830
  return {
2193
2831
  name,
2832
+ ...metadata !== void 0 && { metadata },
2194
2833
  fields: specializedFields,
2195
2834
  fieldLayouts,
2196
2835
  typeRegistry,
@@ -2200,7 +2839,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2200
2839
  staticMethods
2201
2840
  };
2202
2841
  }
2203
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2842
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2843
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2204
2844
  const name = interfaceDecl.name.text;
2205
2845
  const fields = [];
2206
2846
  const typeRegistry = {};
@@ -2224,6 +2864,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2224
2864
  visiting,
2225
2865
  diagnostics,
2226
2866
  interfaceType,
2867
+ normalizedMetadataPolicy,
2227
2868
  extensionRegistry
2228
2869
  );
2229
2870
  if (fieldNode) {
@@ -2237,11 +2878,19 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2237
2878
  interfaceType,
2238
2879
  checker,
2239
2880
  file,
2240
- diagnostics
2881
+ diagnostics,
2882
+ normalizedMetadataPolicy
2241
2883
  );
2242
2884
  const fieldLayouts = specializedFields.map(() => ({}));
2885
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
2886
+ checker,
2887
+ declaration: interfaceDecl,
2888
+ subjectType: interfaceType,
2889
+ hostType: interfaceType
2890
+ });
2243
2891
  return {
2244
2892
  name,
2893
+ ...metadata !== void 0 && { metadata },
2245
2894
  fields: specializedFields,
2246
2895
  fieldLayouts,
2247
2896
  typeRegistry,
@@ -2251,7 +2900,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2251
2900
  staticMethods: []
2252
2901
  };
2253
2902
  }
2254
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2903
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2255
2904
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2256
2905
  const sourceFile = typeAlias.getSourceFile();
2257
2906
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2261,6 +2910,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2261
2910
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2262
2911
  };
2263
2912
  }
2913
+ const typeLiteral = typeAlias.type;
2914
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2264
2915
  const name = typeAlias.name.text;
2265
2916
  const fields = [];
2266
2917
  const typeRegistry = {};
@@ -2274,7 +2925,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2274
2925
  const annotations = [...typeAliasDoc.annotations];
2275
2926
  diagnostics.push(...typeAliasDoc.diagnostics);
2276
2927
  const visiting = /* @__PURE__ */ new Set();
2277
- for (const member of typeAlias.type.members) {
2928
+ for (const member of typeLiteral.members) {
2278
2929
  if (ts3.isPropertySignature(member)) {
2279
2930
  const fieldNode = analyzeInterfacePropertyToIR(
2280
2931
  member,
@@ -2284,6 +2935,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2284
2935
  visiting,
2285
2936
  diagnostics,
2286
2937
  aliasType,
2938
+ normalizedMetadataPolicy,
2287
2939
  extensionRegistry
2288
2940
  );
2289
2941
  if (fieldNode) {
@@ -2297,12 +2949,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2297
2949
  aliasType,
2298
2950
  checker,
2299
2951
  file,
2300
- diagnostics
2952
+ diagnostics,
2953
+ normalizedMetadataPolicy
2301
2954
  );
2955
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
2956
+ checker,
2957
+ declaration: typeAlias,
2958
+ subjectType: aliasType,
2959
+ hostType: aliasType
2960
+ });
2302
2961
  return {
2303
2962
  ok: true,
2304
2963
  analysis: {
2305
2964
  name,
2965
+ ...metadata !== void 0 && { metadata },
2306
2966
  fields: specializedFields,
2307
2967
  fieldLayouts: specializedFields.map(() => ({})),
2308
2968
  typeRegistry,
@@ -2342,31 +3002,20 @@ function getLeadingParsedTags(node) {
2342
3002
  }
2343
3003
  return parsedTags;
2344
3004
  }
2345
- function findDiscriminatorProperty(node, fieldName) {
2346
- if (ts3.isClassDeclaration(node)) {
2347
- for (const member of node.members) {
2348
- if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2349
- return member;
2350
- }
2351
- }
2352
- return null;
2353
- }
2354
- if (ts3.isInterfaceDeclaration(node)) {
2355
- for (const member of node.members) {
2356
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2357
- return member;
2358
- }
2359
- }
3005
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
3006
+ const subjectType = checker.getTypeAtLocation(node);
3007
+ const propertySymbol = subjectType.getProperty(fieldName);
3008
+ if (propertySymbol === void 0) {
2360
3009
  return null;
2361
3010
  }
2362
- if (ts3.isTypeLiteralNode(node.type)) {
2363
- for (const member of node.type.members) {
2364
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2365
- return member;
2366
- }
2367
- }
2368
- }
2369
- return null;
3011
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
3012
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
3013
+ ) ?? propertySymbol.declarations?.[0];
3014
+ return {
3015
+ declaration,
3016
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
3017
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
3018
+ };
2370
3019
  }
2371
3020
  function isLocalTypeParameterName(node, typeParameterName) {
2372
3021
  return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
@@ -2459,8 +3108,8 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2459
3108
  );
2460
3109
  return null;
2461
3110
  }
2462
- const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2463
- if (propertyDecl === null) {
3111
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3112
+ if (property === null) {
2464
3113
  diagnostics.push(
2465
3114
  makeAnalysisDiagnostic(
2466
3115
  "UNKNOWN_PATH_TARGET",
@@ -2470,36 +3119,35 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2470
3119
  );
2471
3120
  return null;
2472
3121
  }
2473
- if (propertyDecl.questionToken !== void 0) {
3122
+ if (property.optional) {
2474
3123
  diagnostics.push(
2475
3124
  makeAnalysisDiagnostic(
2476
3125
  "TYPE_MISMATCH",
2477
3126
  `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2478
3127
  directive.provenance,
2479
- [provenanceForNode(propertyDecl, file)]
3128
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2480
3129
  )
2481
3130
  );
2482
3131
  return null;
2483
3132
  }
2484
- const propertyType = checker.getTypeAtLocation(propertyDecl);
2485
- if (isNullishSemanticType(propertyType)) {
3133
+ if (isNullishSemanticType(property.type)) {
2486
3134
  diagnostics.push(
2487
3135
  makeAnalysisDiagnostic(
2488
3136
  "TYPE_MISMATCH",
2489
3137
  `Discriminator field "${directive.fieldName}" must not be nullable.`,
2490
3138
  directive.provenance,
2491
- [provenanceForNode(propertyDecl, file)]
3139
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2492
3140
  )
2493
3141
  );
2494
3142
  return null;
2495
3143
  }
2496
- if (!isStringLikeSemanticType(propertyType)) {
3144
+ if (!isStringLikeSemanticType(property.type)) {
2497
3145
  diagnostics.push(
2498
3146
  makeAnalysisDiagnostic(
2499
3147
  "TYPE_MISMATCH",
2500
3148
  `Discriminator field "${directive.fieldName}" must be string-like.`,
2501
3149
  directive.provenance,
2502
- [provenanceForNode(propertyDecl, file)]
3150
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2503
3151
  )
2504
3152
  );
2505
3153
  return null;
@@ -2520,25 +3168,58 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
2520
3168
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2521
3169
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2522
3170
  }
2523
- function extractDeclarationApiName(node) {
2524
- for (const tag of getLeadingParsedTags(node)) {
2525
- if (tag.normalizedTagName !== "apiName") {
2526
- continue;
2527
- }
2528
- if (tag.target === null && tag.argumentText.trim() !== "") {
2529
- return tag.argumentText.trim();
2530
- }
2531
- if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2532
- const value = tag.argumentText.trim();
2533
- if (value !== "") {
2534
- return value;
2535
- }
3171
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
3172
+ const propertySymbol = boundType.getProperty(fieldName);
3173
+ if (propertySymbol === void 0) {
3174
+ return void 0;
3175
+ }
3176
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
3177
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
3178
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
3179
+ if (resolvedAnchorNode === null) {
3180
+ return void 0;
3181
+ }
3182
+ const propertyType = checker.getTypeOfSymbolAtLocation(
3183
+ propertySymbol,
3184
+ resolvedAnchorNode
3185
+ );
3186
+ if (propertyType.isStringLiteral()) {
3187
+ return propertyType.value;
3188
+ }
3189
+ if (propertyType.isUnion()) {
3190
+ const nonNullMembers = propertyType.types.filter(
3191
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3192
+ );
3193
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
3194
+ diagnostics.push(
3195
+ makeAnalysisDiagnostic(
3196
+ "INVALID_TAG_ARGUMENT",
3197
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
3198
+ provenance
3199
+ )
3200
+ );
3201
+ return null;
2536
3202
  }
2537
3203
  }
2538
- return null;
3204
+ return void 0;
2539
3205
  }
2540
- function inferJsonFacingName(name) {
2541
- 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();
3206
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
3207
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
3208
+ if (declaration === null) {
3209
+ return void 0;
3210
+ }
3211
+ const metadata = resolveNodeMetadata(
3212
+ metadataPolicy,
3213
+ "type",
3214
+ getDiscriminatorLogicalName(boundType, declaration, checker),
3215
+ declaration,
3216
+ {
3217
+ checker,
3218
+ declaration,
3219
+ subjectType: boundType
3220
+ }
3221
+ );
3222
+ return metadata?.apiName;
2542
3223
  }
2543
3224
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2544
3225
  if (seen.has(type)) {
@@ -2565,7 +3246,7 @@ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__
2565
3246
  }
2566
3247
  return null;
2567
3248
  }
2568
- function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
3249
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
2569
3250
  if (boundType === null) {
2570
3251
  diagnostics.push(
2571
3252
  makeAnalysisDiagnostic(
@@ -2594,9 +3275,22 @@ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics)
2594
3275
  return null;
2595
3276
  }
2596
3277
  }
2597
- const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2598
- if (declaration !== null) {
2599
- return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
3278
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
3279
+ boundType,
3280
+ fieldName,
3281
+ checker,
3282
+ provenance,
3283
+ diagnostics
3284
+ );
3285
+ if (literalIdentityValue !== void 0) {
3286
+ return literalIdentityValue;
3287
+ }
3288
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
3289
+ if (apiName?.source === "explicit") {
3290
+ return apiName.value;
3291
+ }
3292
+ if (apiName?.source === "inferred") {
3293
+ return apiName.value;
2600
3294
  }
2601
3295
  diagnostics.push(
2602
3296
  makeAnalysisDiagnostic(
@@ -2613,7 +3307,15 @@ function getDeclarationName(node) {
2613
3307
  }
2614
3308
  return "anonymous";
2615
3309
  }
2616
- function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
3310
+ function getResolvedTypeArguments(type) {
3311
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
3312
+ }
3313
+ function getDiscriminatorLogicalName(type, declaration, checker) {
3314
+ const baseName = getDeclarationName(declaration);
3315
+ const typeArguments = getResolvedTypeArguments(type);
3316
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
3317
+ }
3318
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
2617
3319
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2618
3320
  if (directive === null) {
2619
3321
  return [...fields];
@@ -2625,9 +3327,11 @@ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checke
2625
3327
  checker,
2626
3328
  directive.typeParameterName
2627
3329
  ),
3330
+ directive.fieldName,
2628
3331
  checker,
2629
3332
  directive.provenance,
2630
- diagnostics
3333
+ diagnostics,
3334
+ metadataPolicy
2631
3335
  );
2632
3336
  if (discriminatorValue === null) {
2633
3337
  return [...fields];
@@ -2648,7 +3352,7 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2648
3352
  ).filter((value) => value !== "");
2649
3353
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2650
3354
  }
2651
- function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3355
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
2652
3356
  const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2653
3357
  if (typeNode === void 0) {
2654
3358
  return [];
@@ -2668,13 +3372,14 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
2668
3372
  typeRegistry,
2669
3373
  visiting,
2670
3374
  argumentNode,
3375
+ metadataPolicy,
2671
3376
  extensionRegistry,
2672
3377
  diagnostics
2673
3378
  )
2674
3379
  };
2675
3380
  });
2676
3381
  }
2677
- function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
3382
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
2678
3383
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2679
3384
  if (directive === null) {
2680
3385
  return properties;
@@ -2686,9 +3391,11 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2686
3391
  checker,
2687
3392
  directive.typeParameterName
2688
3393
  ),
3394
+ directive.fieldName,
2689
3395
  checker,
2690
3396
  directive.provenance,
2691
- diagnostics
3397
+ diagnostics,
3398
+ metadataPolicy
2692
3399
  );
2693
3400
  if (discriminatorValue === null) {
2694
3401
  return properties;
@@ -2703,7 +3410,7 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2703
3410
  } : property
2704
3411
  );
2705
3412
  }
2706
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3413
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2707
3414
  if (!ts3.isIdentifier(prop.name)) {
2708
3415
  return null;
2709
3416
  }
@@ -2718,6 +3425,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2718
3425
  typeRegistry,
2719
3426
  visiting,
2720
3427
  prop,
3428
+ metadataPolicy,
2721
3429
  extensionRegistry,
2722
3430
  diagnostics
2723
3431
  );
@@ -2741,9 +3449,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2741
3449
  annotations.push(defaultAnnotation);
2742
3450
  }
2743
3451
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3452
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3453
+ checker,
3454
+ declaration: prop,
3455
+ subjectType: tsType,
3456
+ hostType
3457
+ });
2744
3458
  return {
2745
3459
  kind: "field",
2746
3460
  name,
3461
+ ...metadata !== void 0 && { metadata },
2747
3462
  type,
2748
3463
  required: !optional,
2749
3464
  constraints,
@@ -2751,7 +3466,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2751
3466
  provenance
2752
3467
  };
2753
3468
  }
2754
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3469
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2755
3470
  if (!ts3.isIdentifier(prop.name)) {
2756
3471
  return null;
2757
3472
  }
@@ -2766,6 +3481,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2766
3481
  typeRegistry,
2767
3482
  visiting,
2768
3483
  prop,
3484
+ metadataPolicy,
2769
3485
  extensionRegistry,
2770
3486
  diagnostics
2771
3487
  );
@@ -2785,9 +3501,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2785
3501
  let annotations = [];
2786
3502
  annotations.push(...docResult.annotations);
2787
3503
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3504
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3505
+ checker,
3506
+ declaration: prop,
3507
+ subjectType: tsType,
3508
+ hostType
3509
+ });
2788
3510
  return {
2789
3511
  kind: "field",
2790
3512
  name,
3513
+ ...metadata !== void 0 && { metadata },
2791
3514
  type,
2792
3515
  required: !optional,
2793
3516
  constraints,
@@ -2912,7 +3635,7 @@ function getTypeNodeRegistrationName(typeNode) {
2912
3635
  }
2913
3636
  return null;
2914
3637
  }
2915
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3638
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2916
3639
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2917
3640
  if (customType) {
2918
3641
  return customType;
@@ -2924,6 +3647,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2924
3647
  typeRegistry,
2925
3648
  visiting,
2926
3649
  sourceNode,
3650
+ metadataPolicy,
2927
3651
  extensionRegistry,
2928
3652
  diagnostics
2929
3653
  );
@@ -2968,6 +3692,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2968
3692
  typeRegistry,
2969
3693
  visiting,
2970
3694
  sourceNode,
3695
+ metadataPolicy,
2971
3696
  extensionRegistry,
2972
3697
  diagnostics
2973
3698
  );
@@ -2980,6 +3705,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2980
3705
  typeRegistry,
2981
3706
  visiting,
2982
3707
  sourceNode,
3708
+ metadataPolicy,
2983
3709
  extensionRegistry,
2984
3710
  diagnostics
2985
3711
  );
@@ -2992,13 +3718,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2992
3718
  typeRegistry,
2993
3719
  visiting,
2994
3720
  sourceNode,
3721
+ metadataPolicy,
2995
3722
  extensionRegistry,
2996
3723
  diagnostics
2997
3724
  );
2998
3725
  }
2999
3726
  return { kind: "primitive", primitiveKind: "string" };
3000
3727
  }
3001
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3728
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3002
3729
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
3003
3730
  return null;
3004
3731
  }
@@ -3018,14 +3745,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
3018
3745
  file,
3019
3746
  makeParseOptions(extensionRegistry)
3020
3747
  );
3748
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3749
+ checker,
3750
+ declaration: aliasDecl,
3751
+ subjectType: aliasType
3752
+ });
3021
3753
  typeRegistry[aliasName] = {
3022
3754
  name: aliasName,
3755
+ ...metadata !== void 0 && { metadata },
3023
3756
  type: resolveAliasedPrimitiveTarget(
3024
3757
  aliasType,
3025
3758
  checker,
3026
3759
  file,
3027
3760
  typeRegistry,
3028
3761
  visiting,
3762
+ metadataPolicy,
3029
3763
  extensionRegistry,
3030
3764
  diagnostics
3031
3765
  ),
@@ -3054,7 +3788,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
3054
3788
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
3055
3789
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
3056
3790
  }
3057
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3791
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3058
3792
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
3059
3793
  if (nestedAliasDecl !== void 0) {
3060
3794
  return resolveAliasedPrimitiveTarget(
@@ -3063,6 +3797,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3063
3797
  file,
3064
3798
  typeRegistry,
3065
3799
  visiting,
3800
+ metadataPolicy,
3066
3801
  extensionRegistry,
3067
3802
  diagnostics
3068
3803
  );
@@ -3074,11 +3809,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3074
3809
  typeRegistry,
3075
3810
  visiting,
3076
3811
  void 0,
3812
+ metadataPolicy,
3077
3813
  extensionRegistry,
3078
3814
  diagnostics
3079
3815
  );
3080
3816
  }
3081
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3817
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3082
3818
  const typeName = getNamedTypeName(type);
3083
3819
  const namedDecl = getNamedTypeDeclaration(type);
3084
3820
  if (typeName && typeName in typeRegistry) {
@@ -3113,8 +3849,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3113
3849
  return result;
3114
3850
  }
3115
3851
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3852
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
3853
+ checker,
3854
+ declaration: namedDecl,
3855
+ subjectType: type
3856
+ }) : void 0;
3116
3857
  typeRegistry[typeName] = {
3117
3858
  name: typeName,
3859
+ ...metadata !== void 0 && { metadata },
3118
3860
  type: result,
3119
3861
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3120
3862
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -3168,6 +3910,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3168
3910
  typeRegistry,
3169
3911
  visiting,
3170
3912
  nonNullMembers[0].sourceNode ?? sourceNode,
3913
+ metadataPolicy,
3171
3914
  extensionRegistry,
3172
3915
  diagnostics
3173
3916
  );
@@ -3185,6 +3928,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3185
3928
  typeRegistry,
3186
3929
  visiting,
3187
3930
  memberSourceNode ?? sourceNode,
3931
+ metadataPolicy,
3188
3932
  extensionRegistry,
3189
3933
  diagnostics
3190
3934
  )
@@ -3194,7 +3938,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3194
3938
  }
3195
3939
  return registerNamed({ kind: "union", members });
3196
3940
  }
3197
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3941
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3198
3942
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
3199
3943
  const elementType = typeArgs?.[0];
3200
3944
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -3205,12 +3949,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
3205
3949
  typeRegistry,
3206
3950
  visiting,
3207
3951
  elementSourceNode,
3952
+ metadataPolicy,
3208
3953
  extensionRegistry,
3209
3954
  diagnostics
3210
3955
  ) : { kind: "primitive", primitiveKind: "string" };
3211
3956
  return { kind: "array", items };
3212
3957
  }
3213
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3958
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3214
3959
  if (type.getProperties().length > 0) {
3215
3960
  return null;
3216
3961
  }
@@ -3225,6 +3970,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
3225
3970
  typeRegistry,
3226
3971
  visiting,
3227
3972
  void 0,
3973
+ metadataPolicy,
3228
3974
  extensionRegistry,
3229
3975
  diagnostics
3230
3976
  );
@@ -3255,7 +4001,22 @@ function typeNodeContainsReference(type, targetName) {
3255
4001
  }
3256
4002
  }
3257
4003
  }
3258
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
4004
+ function shouldEmitResolvedObjectProperty(property, declaration) {
4005
+ if (property.name.startsWith("__@")) {
4006
+ return false;
4007
+ }
4008
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
4009
+ const name = declaration.name;
4010
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
4011
+ return false;
4012
+ }
4013
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
4014
+ return false;
4015
+ }
4016
+ }
4017
+ return true;
4018
+ }
4019
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3259
4020
  const collectedDiagnostics = diagnostics ?? [];
3260
4021
  const typeName = getNamedTypeName(type);
3261
4022
  const namedTypeName = typeName ?? void 0;
@@ -3267,6 +4028,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3267
4028
  typeRegistry,
3268
4029
  visiting,
3269
4030
  sourceNode,
4031
+ metadataPolicy,
3270
4032
  extensionRegistry,
3271
4033
  collectedDiagnostics
3272
4034
  );
@@ -3317,6 +4079,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3317
4079
  file,
3318
4080
  typeRegistry,
3319
4081
  visiting,
4082
+ metadataPolicy,
3320
4083
  extensionRegistry,
3321
4084
  collectedDiagnostics
3322
4085
  );
@@ -3329,8 +4092,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3329
4092
  return recordNode;
3330
4093
  }
3331
4094
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4095
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4096
+ checker,
4097
+ declaration: namedDecl,
4098
+ subjectType: type
4099
+ }) : void 0;
3332
4100
  typeRegistry[registryTypeName] = {
3333
4101
  name: registryTypeName,
4102
+ ...metadata !== void 0 && { metadata },
3334
4103
  type: recordNode,
3335
4104
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3336
4105
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3350,12 +4119,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3350
4119
  file,
3351
4120
  typeRegistry,
3352
4121
  visiting,
4122
+ metadataPolicy,
3353
4123
  collectedDiagnostics,
3354
4124
  extensionRegistry
3355
4125
  );
3356
4126
  for (const prop of type.getProperties()) {
3357
4127
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
3358
4128
  if (!declaration) continue;
4129
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
3359
4130
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
3360
4131
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
3361
4132
  const propTypeNode = resolveTypeNode(
@@ -3365,12 +4136,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3365
4136
  typeRegistry,
3366
4137
  visiting,
3367
4138
  declaration,
4139
+ metadataPolicy,
3368
4140
  extensionRegistry,
3369
4141
  collectedDiagnostics
3370
4142
  );
3371
4143
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
3372
4144
  properties.push({
3373
4145
  name: prop.name,
4146
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
3374
4147
  type: propTypeNode,
3375
4148
  optional,
3376
4149
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -3387,14 +4160,21 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3387
4160
  type,
3388
4161
  checker,
3389
4162
  file,
3390
- collectedDiagnostics
4163
+ collectedDiagnostics,
4164
+ metadataPolicy
3391
4165
  ) : properties,
3392
4166
  additionalProperties: true
3393
4167
  };
3394
4168
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3395
4169
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4170
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4171
+ checker,
4172
+ declaration: namedDecl,
4173
+ subjectType: type
4174
+ }) : void 0;
3396
4175
  typeRegistry[registryTypeName] = {
3397
4176
  name: registryTypeName,
4177
+ ...metadata !== void 0 && { metadata },
3398
4178
  type: objectNode,
3399
4179
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3400
4180
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3407,7 +4187,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3407
4187
  }
3408
4188
  return objectNode;
3409
4189
  }
3410
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4190
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
3411
4191
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
3412
4192
  (s) => s?.declarations != null && s.declarations.length > 0
3413
4193
  );
@@ -3428,10 +4208,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3428
4208
  visiting,
3429
4209
  diagnostics,
3430
4210
  hostType,
4211
+ metadataPolicy,
3431
4212
  extensionRegistry
3432
4213
  );
3433
4214
  if (fieldNode) {
3434
4215
  map.set(fieldNode.name, {
4216
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3435
4217
  constraints: [...fieldNode.constraints],
3436
4218
  annotations: [...fieldNode.annotations],
3437
4219
  provenance: fieldNode.provenance
@@ -3449,6 +4231,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3449
4231
  file,
3450
4232
  typeRegistry,
3451
4233
  visiting,
4234
+ metadataPolicy,
3452
4235
  checker.getTypeAtLocation(interfaceDecl),
3453
4236
  diagnostics,
3454
4237
  extensionRegistry
@@ -3462,6 +4245,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3462
4245
  file,
3463
4246
  typeRegistry,
3464
4247
  visiting,
4248
+ metadataPolicy,
3465
4249
  checker.getTypeAtLocation(typeAliasDecl),
3466
4250
  diagnostics,
3467
4251
  extensionRegistry
@@ -3513,7 +4297,7 @@ function isNullishTypeNode(typeNode) {
3513
4297
  }
3514
4298
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3515
4299
  }
3516
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4300
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3517
4301
  const map = /* @__PURE__ */ new Map();
3518
4302
  for (const member of members) {
3519
4303
  if (ts3.isPropertySignature(member)) {
@@ -3525,10 +4309,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3525
4309
  visiting,
3526
4310
  diagnostics,
3527
4311
  hostType,
4312
+ metadataPolicy,
3528
4313
  extensionRegistry
3529
4314
  );
3530
4315
  if (fieldNode) {
3531
4316
  map.set(fieldNode.name, {
4317
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3532
4318
  constraints: [...fieldNode.constraints],
3533
4319
  annotations: [...fieldNode.annotations],
3534
4320
  provenance: fieldNode.provenance
@@ -3559,6 +4345,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3559
4345
  {},
3560
4346
  /* @__PURE__ */ new Set(),
3561
4347
  aliasDecl.type,
4348
+ void 0,
3562
4349
  extensionRegistry
3563
4350
  );
3564
4351
  const constraints = extractJSDocConstraintNodes(
@@ -3744,19 +4531,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3744
4531
  function findTypeAliasByName(sourceFile, aliasName) {
3745
4532
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3746
4533
  }
3747
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4534
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3748
4535
  const ctx = createProgramContext(filePath);
3749
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4536
+ return analyzeNamedTypeToIRFromProgramContext(
4537
+ ctx,
4538
+ filePath,
4539
+ typeName,
4540
+ extensionRegistry,
4541
+ metadataPolicy
4542
+ );
3750
4543
  }
3751
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4544
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3752
4545
  const analysisFilePath = path.resolve(filePath);
3753
4546
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3754
4547
  if (classDecl !== null) {
3755
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4548
+ return analyzeClassToIR(
4549
+ classDecl,
4550
+ ctx.checker,
4551
+ analysisFilePath,
4552
+ extensionRegistry,
4553
+ metadataPolicy
4554
+ );
3756
4555
  }
3757
4556
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3758
4557
  if (interfaceDecl !== null) {
3759
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4558
+ return analyzeInterfaceToIR(
4559
+ interfaceDecl,
4560
+ ctx.checker,
4561
+ analysisFilePath,
4562
+ extensionRegistry,
4563
+ metadataPolicy
4564
+ );
3760
4565
  }
3761
4566
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3762
4567
  if (typeAlias !== null) {
@@ -3764,7 +4569,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3764
4569
  typeAlias,
3765
4570
  ctx.checker,
3766
4571
  analysisFilePath,
3767
- extensionRegistry
4572
+ extensionRegistry,
4573
+ metadataPolicy
3768
4574
  );
3769
4575
  if (result.ok) {
3770
4576
  return result.analysis;
@@ -3857,7 +4663,11 @@ function generateClassSchemas(analysis, source, options) {
3857
4663
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3858
4664
  throw new Error(formatValidationError(errorDiagnostics));
3859
4665
  }
3860
- const ir = canonicalizeTSDoc(analysis, source);
4666
+ const ir = canonicalizeTSDoc(
4667
+ analysis,
4668
+ source,
4669
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4670
+ );
3861
4671
  const validationResult = validateIR(ir, {
3862
4672
  ...options?.extensionRegistry !== void 0 && {
3863
4673
  extensionRegistry: options.extensionRegistry
@@ -3894,13 +4704,15 @@ function generateSchemasFromClass(options) {
3894
4704
  classDecl,
3895
4705
  ctx.checker,
3896
4706
  options.filePath,
3897
- options.extensionRegistry
4707
+ options.extensionRegistry,
4708
+ options.metadata
3898
4709
  );
3899
4710
  return generateClassSchemas(
3900
4711
  analysis,
3901
4712
  { file: options.filePath },
3902
4713
  {
3903
4714
  extensionRegistry: options.extensionRegistry,
4715
+ metadata: options.metadata,
3904
4716
  vendorPrefix: options.vendorPrefix
3905
4717
  }
3906
4718
  );
@@ -3918,13 +4730,15 @@ function generateSchemasFromProgram(options) {
3918
4730
  ctx,
3919
4731
  options.filePath,
3920
4732
  options.typeName,
3921
- options.extensionRegistry
4733
+ options.extensionRegistry,
4734
+ options.metadata
3922
4735
  );
3923
4736
  return generateClassSchemas(
3924
4737
  analysis,
3925
4738
  { file: options.filePath },
3926
4739
  {
3927
4740
  extensionRegistry: options.extensionRegistry,
4741
+ metadata: options.metadata,
3928
4742
  vendorPrefix: options.vendorPrefix
3929
4743
  }
3930
4744
  );
@@ -3933,16 +4747,28 @@ function generateSchemasFromProgram(options) {
3933
4747
  // src/generators/mixed-authoring.ts
3934
4748
  function buildMixedAuthoringSchemas(options) {
3935
4749
  const { filePath, typeName, overlays, ...schemaOptions } = options;
3936
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
3937
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
3938
- const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
4750
+ const analysis = analyzeNamedTypeToIR(
4751
+ filePath,
4752
+ typeName,
4753
+ schemaOptions.extensionRegistry,
4754
+ schemaOptions.metadata
4755
+ );
4756
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays, schemaOptions.metadata);
4757
+ const ir = canonicalizeTSDoc(
4758
+ composedAnalysis,
4759
+ { file: filePath },
4760
+ schemaOptions.metadata !== void 0 ? { metadata: schemaOptions.metadata } : void 0
4761
+ );
3939
4762
  return {
3940
4763
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
3941
4764
  uiSchema: generateUiSchemaFromIR(ir)
3942
4765
  };
3943
4766
  }
3944
- function composeAnalysisWithOverlays(analysis, overlays) {
3945
- const overlayIR = canonicalizeChainDSL(overlays);
4767
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4768
+ const overlayIR = canonicalizeChainDSL(
4769
+ overlays,
4770
+ metadata !== void 0 ? { metadata } : void 0
4771
+ );
3946
4772
  const overlayFields = collectOverlayFields(overlayIR.elements);
3947
4773
  if (overlayFields.length === 0) {
3948
4774
  return analysis;
@@ -3998,8 +4824,10 @@ function collectOverlayFields(elements) {
3998
4824
  }
3999
4825
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
4000
4826
  assertSupportedOverlayField(baseField, overlayField);
4827
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
4001
4828
  return {
4002
4829
  ...baseField,
4830
+ ...metadata !== void 0 && { metadata },
4003
4831
  type: mergeFieldType(baseField, overlayField, typeRegistry),
4004
4832
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
4005
4833
  };
@@ -4110,12 +4938,12 @@ function annotationKey(annotation) {
4110
4938
  function buildFormSchemas(form, options) {
4111
4939
  return {
4112
4940
  jsonSchema: generateJsonSchema(form, options),
4113
- uiSchema: generateUiSchema(form)
4941
+ uiSchema: generateUiSchema(form, options)
4114
4942
  };
4115
4943
  }
4116
4944
  function writeSchemas(form, options) {
4117
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
4118
- const buildOptions = vendorPrefix === void 0 ? void 0 : { vendorPrefix };
4945
+ const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
4946
+ const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
4119
4947
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
4120
4948
  if (!fs.existsSync(outDir)) {
4121
4949
  fs.mkdirSync(outDir, { recursive: true });