@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
package/dist/index.js CHANGED
@@ -1,5 +1,314 @@
1
1
  // src/canonicalize/chain-dsl-canonicalizer.ts
2
- import { IR_VERSION } from "@formspec/core/internals";
2
+ import { IR_VERSION, _getFormSpecMetadataPolicy } from "@formspec/core/internals";
3
+
4
+ // src/metadata/policy.ts
5
+ var NOOP_INFLECT = () => "";
6
+ function normalizePluralization(input) {
7
+ if (input?.mode === "infer-if-missing") {
8
+ return {
9
+ mode: "infer-if-missing",
10
+ infer: () => "",
11
+ inflect: input.inflect
12
+ };
13
+ }
14
+ if (input?.mode === "require-explicit") {
15
+ return {
16
+ mode: "require-explicit",
17
+ infer: () => "",
18
+ inflect: NOOP_INFLECT
19
+ };
20
+ }
21
+ return {
22
+ mode: "disabled",
23
+ infer: () => "",
24
+ inflect: NOOP_INFLECT
25
+ };
26
+ }
27
+ function normalizeScalarPolicy(input) {
28
+ if (input?.mode === "infer-if-missing") {
29
+ return {
30
+ mode: "infer-if-missing",
31
+ infer: input.infer,
32
+ pluralization: normalizePluralization(input.pluralization)
33
+ };
34
+ }
35
+ if (input?.mode === "require-explicit") {
36
+ return {
37
+ mode: "require-explicit",
38
+ infer: () => "",
39
+ pluralization: normalizePluralization(input.pluralization)
40
+ };
41
+ }
42
+ return {
43
+ mode: "disabled",
44
+ infer: () => "",
45
+ pluralization: normalizePluralization(input?.pluralization)
46
+ };
47
+ }
48
+ function normalizeDeclarationPolicy(input) {
49
+ return {
50
+ apiName: normalizeScalarPolicy(input?.apiName),
51
+ displayName: normalizeScalarPolicy(input?.displayName)
52
+ };
53
+ }
54
+ function normalizeMetadataPolicy(input) {
55
+ return {
56
+ type: normalizeDeclarationPolicy(input?.type),
57
+ field: normalizeDeclarationPolicy(input?.field),
58
+ method: normalizeDeclarationPolicy(input?.method)
59
+ };
60
+ }
61
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
62
+ return policy[declarationKind];
63
+ }
64
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
65
+ return {
66
+ surface,
67
+ declarationKind,
68
+ logicalName,
69
+ ...buildContext !== void 0 && { buildContext }
70
+ };
71
+ }
72
+
73
+ // src/metadata/resolve.ts
74
+ function toExplicitScalar(value) {
75
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
76
+ }
77
+ function toExplicitResolvedMetadata(explicit) {
78
+ if (explicit === void 0) {
79
+ return void 0;
80
+ }
81
+ const apiName = toExplicitScalar(explicit.apiName);
82
+ const displayName = toExplicitScalar(explicit.displayName);
83
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
84
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
85
+ const metadata = {
86
+ ...apiName !== void 0 && { apiName },
87
+ ...displayName !== void 0 && { displayName },
88
+ ...apiNamePlural !== void 0 && { apiNamePlural },
89
+ ...displayNamePlural !== void 0 && { displayNamePlural }
90
+ };
91
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
92
+ }
93
+ function resolveScalar(current, policy, context, metadataLabel) {
94
+ if (current !== void 0) {
95
+ return current;
96
+ }
97
+ if (policy.mode === "require-explicit") {
98
+ throw new Error(
99
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
100
+ );
101
+ }
102
+ if (policy.mode !== "infer-if-missing") {
103
+ return void 0;
104
+ }
105
+ const inferredValue = policy.infer(context);
106
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
107
+ }
108
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
109
+ if (current !== void 0) {
110
+ return current;
111
+ }
112
+ if (policy.mode === "require-explicit") {
113
+ throw new Error(
114
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
115
+ );
116
+ }
117
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
118
+ return void 0;
119
+ }
120
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
121
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
122
+ }
123
+ function resolveResolvedMetadata(current, policy, context) {
124
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
125
+ const displayName = resolveScalar(
126
+ current?.displayName,
127
+ policy.displayName,
128
+ context,
129
+ "displayName"
130
+ );
131
+ const apiNamePlural = resolvePlural(
132
+ current?.apiNamePlural,
133
+ apiName,
134
+ policy.apiName.pluralization,
135
+ context,
136
+ "apiNamePlural"
137
+ );
138
+ const displayNamePlural = resolvePlural(
139
+ current?.displayNamePlural,
140
+ displayName,
141
+ policy.displayName.pluralization,
142
+ context,
143
+ "displayNamePlural"
144
+ );
145
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
146
+ return void 0;
147
+ }
148
+ return {
149
+ ...apiName !== void 0 && { apiName },
150
+ ...displayName !== void 0 && { displayName },
151
+ ...apiNamePlural !== void 0 && { apiNamePlural },
152
+ ...displayNamePlural !== void 0 && { displayNamePlural }
153
+ };
154
+ }
155
+ function pickResolvedMetadataValue(baseValue, overlayValue) {
156
+ if (overlayValue?.source === "explicit") {
157
+ return overlayValue;
158
+ }
159
+ if (baseValue?.source === "explicit") {
160
+ return baseValue;
161
+ }
162
+ return baseValue ?? overlayValue;
163
+ }
164
+ function resolveTypeNodeMetadata(type, options) {
165
+ switch (type.kind) {
166
+ case "array":
167
+ return {
168
+ ...type,
169
+ items: resolveTypeNodeMetadata(type.items, options)
170
+ };
171
+ case "object":
172
+ return {
173
+ ...type,
174
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
175
+ };
176
+ case "record":
177
+ return {
178
+ ...type,
179
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
180
+ };
181
+ case "union":
182
+ return {
183
+ ...type,
184
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
185
+ };
186
+ case "reference":
187
+ case "primitive":
188
+ case "enum":
189
+ case "dynamic":
190
+ case "custom":
191
+ return type;
192
+ default: {
193
+ const _exhaustive = type;
194
+ return _exhaustive;
195
+ }
196
+ }
197
+ }
198
+ function resolveObjectPropertyMetadata(property, options) {
199
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
200
+ surface: options.surface,
201
+ declarationKind: "field",
202
+ logicalName: property.name,
203
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
204
+ });
205
+ return {
206
+ ...property,
207
+ ...metadata !== void 0 && { metadata },
208
+ type: resolveTypeNodeMetadata(property.type, options)
209
+ };
210
+ }
211
+ function resolveFieldMetadataNode(field, options) {
212
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
213
+ surface: options.surface,
214
+ declarationKind: "field",
215
+ logicalName: field.name,
216
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
217
+ });
218
+ return {
219
+ ...field,
220
+ ...metadata !== void 0 && { metadata },
221
+ type: resolveTypeNodeMetadata(field.type, options)
222
+ };
223
+ }
224
+ function resolveFormElementMetadata(element, options) {
225
+ switch (element.kind) {
226
+ case "field":
227
+ return resolveFieldMetadataNode(element, options);
228
+ case "group":
229
+ return {
230
+ ...element,
231
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
232
+ };
233
+ case "conditional":
234
+ return {
235
+ ...element,
236
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
237
+ };
238
+ default: {
239
+ const _exhaustive = element;
240
+ return _exhaustive;
241
+ }
242
+ }
243
+ }
244
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
245
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
246
+ surface: options.surface,
247
+ declarationKind: "type",
248
+ logicalName: typeDefinition.name,
249
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
250
+ });
251
+ return {
252
+ ...typeDefinition,
253
+ ...metadata !== void 0 && { metadata },
254
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
255
+ };
256
+ }
257
+ function resolveMetadata(explicit, policy, context) {
258
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
259
+ }
260
+ function mergeResolvedMetadata(baseMetadata, overlayMetadata) {
261
+ const apiName = pickResolvedMetadataValue(baseMetadata?.apiName, overlayMetadata?.apiName);
262
+ const displayName = pickResolvedMetadataValue(
263
+ baseMetadata?.displayName,
264
+ overlayMetadata?.displayName
265
+ );
266
+ const apiNamePlural = pickResolvedMetadataValue(
267
+ baseMetadata?.apiNamePlural,
268
+ overlayMetadata?.apiNamePlural
269
+ );
270
+ const displayNamePlural = pickResolvedMetadataValue(
271
+ baseMetadata?.displayNamePlural,
272
+ overlayMetadata?.displayNamePlural
273
+ );
274
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
275
+ return void 0;
276
+ }
277
+ return {
278
+ ...apiName !== void 0 && { apiName },
279
+ ...displayName !== void 0 && { displayName },
280
+ ...apiNamePlural !== void 0 && { apiNamePlural },
281
+ ...displayNamePlural !== void 0 && { displayNamePlural }
282
+ };
283
+ }
284
+ function getSerializedName(logicalName, metadata) {
285
+ return metadata?.apiName?.value ?? logicalName;
286
+ }
287
+ function getDisplayName(metadata) {
288
+ return metadata?.displayName?.value;
289
+ }
290
+ function resolveFormIRMetadata(ir, options) {
291
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
292
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
293
+ surface: options.surface,
294
+ declarationKind: "type",
295
+ logicalName: rootLogicalName,
296
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
297
+ });
298
+ return {
299
+ ...ir,
300
+ ...metadata !== void 0 && { metadata },
301
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
302
+ typeRegistry: Object.fromEntries(
303
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
304
+ name,
305
+ resolveTypeDefinitionMetadata(definition, options)
306
+ ])
307
+ )
308
+ };
309
+ }
310
+
311
+ // src/canonicalize/chain-dsl-canonicalizer.ts
3
312
  var CHAIN_DSL_PROVENANCE = {
4
313
  surface: "chain-dsl",
5
314
  file: "",
@@ -15,57 +324,60 @@ function isConditional(el) {
15
324
  function isField(el) {
16
325
  return el._type === "field";
17
326
  }
18
- function canonicalizeChainDSL(form) {
327
+ function canonicalizeChainDSL(form, options) {
328
+ const metadataPolicy = normalizeMetadataPolicy(
329
+ options?.metadata ?? _getFormSpecMetadataPolicy(form)
330
+ );
19
331
  return {
20
332
  kind: "form-ir",
21
333
  irVersion: IR_VERSION,
22
- elements: canonicalizeElements(form.elements),
334
+ elements: canonicalizeElements(form.elements, metadataPolicy),
23
335
  rootAnnotations: [],
24
336
  typeRegistry: {},
25
337
  provenance: CHAIN_DSL_PROVENANCE
26
338
  };
27
339
  }
28
- function canonicalizeElements(elements) {
29
- return elements.map(canonicalizeElement);
340
+ function canonicalizeElements(elements, metadataPolicy) {
341
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
30
342
  }
31
- function canonicalizeElement(element) {
343
+ function canonicalizeElement(element, metadataPolicy) {
32
344
  if (isField(element)) {
33
- return canonicalizeField(element);
345
+ return canonicalizeField(element, metadataPolicy);
34
346
  }
35
347
  if (isGroup(element)) {
36
- return canonicalizeGroup(element);
348
+ return canonicalizeGroup(element, metadataPolicy);
37
349
  }
38
350
  if (isConditional(element)) {
39
- return canonicalizeConditional(element);
351
+ return canonicalizeConditional(element, metadataPolicy);
40
352
  }
41
353
  const _exhaustive = element;
42
354
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
43
355
  }
44
- function canonicalizeField(field) {
356
+ function canonicalizeField(field, metadataPolicy) {
45
357
  switch (field._field) {
46
358
  case "text":
47
- return canonicalizeTextField(field);
359
+ return canonicalizeTextField(field, metadataPolicy);
48
360
  case "number":
49
- return canonicalizeNumberField(field);
361
+ return canonicalizeNumberField(field, metadataPolicy);
50
362
  case "boolean":
51
- return canonicalizeBooleanField(field);
363
+ return canonicalizeBooleanField(field, metadataPolicy);
52
364
  case "enum":
53
- return canonicalizeStaticEnumField(field);
365
+ return canonicalizeStaticEnumField(field, metadataPolicy);
54
366
  case "dynamic_enum":
55
- return canonicalizeDynamicEnumField(field);
367
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
56
368
  case "dynamic_schema":
57
- return canonicalizeDynamicSchemaField(field);
369
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
58
370
  case "array":
59
- return canonicalizeArrayField(field);
371
+ return canonicalizeArrayField(field, metadataPolicy);
60
372
  case "object":
61
- return canonicalizeObjectField(field);
373
+ return canonicalizeObjectField(field, metadataPolicy);
62
374
  default: {
63
375
  const _exhaustive = field;
64
376
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
65
377
  }
66
378
  }
67
379
  }
68
- function canonicalizeTextField(field) {
380
+ function canonicalizeTextField(field, metadataPolicy) {
69
381
  const type = { kind: "primitive", primitiveKind: "string" };
70
382
  const constraints = [];
71
383
  if (field.minLength !== void 0) {
@@ -97,13 +409,14 @@ function canonicalizeTextField(field) {
97
409
  }
98
410
  return buildFieldNode(
99
411
  field.name,
412
+ resolveFieldMetadata(field.name, field, metadataPolicy),
100
413
  type,
101
414
  field.required,
102
- buildAnnotations(field.label, field.placeholder),
415
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
103
416
  constraints
104
417
  );
105
418
  }
106
- function canonicalizeNumberField(field) {
419
+ function canonicalizeNumberField(field, metadataPolicy) {
107
420
  const type = { kind: "primitive", primitiveKind: "number" };
108
421
  const constraints = [];
109
422
  if (field.min !== void 0) {
@@ -135,17 +448,24 @@ function canonicalizeNumberField(field) {
135
448
  }
136
449
  return buildFieldNode(
137
450
  field.name,
451
+ resolveFieldMetadata(field.name, field, metadataPolicy),
138
452
  type,
139
453
  field.required,
140
- buildAnnotations(field.label),
454
+ buildAnnotations(getExplicitDisplayName(field)),
141
455
  constraints
142
456
  );
143
457
  }
144
- function canonicalizeBooleanField(field) {
458
+ function canonicalizeBooleanField(field, metadataPolicy) {
145
459
  const type = { kind: "primitive", primitiveKind: "boolean" };
146
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
460
+ return buildFieldNode(
461
+ field.name,
462
+ resolveFieldMetadata(field.name, field, metadataPolicy),
463
+ type,
464
+ field.required,
465
+ buildAnnotations(getExplicitDisplayName(field))
466
+ );
147
467
  }
148
- function canonicalizeStaticEnumField(field) {
468
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
149
469
  const members = field.options.map((opt) => {
150
470
  if (typeof opt === "string") {
151
471
  return { value: opt };
@@ -153,28 +473,46 @@ function canonicalizeStaticEnumField(field) {
153
473
  return { value: opt.id, displayName: opt.label };
154
474
  });
155
475
  const type = { kind: "enum", members };
156
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
476
+ return buildFieldNode(
477
+ field.name,
478
+ resolveFieldMetadata(field.name, field, metadataPolicy),
479
+ type,
480
+ field.required,
481
+ buildAnnotations(getExplicitDisplayName(field))
482
+ );
157
483
  }
158
- function canonicalizeDynamicEnumField(field) {
484
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
159
485
  const type = {
160
486
  kind: "dynamic",
161
487
  dynamicKind: "enum",
162
488
  sourceKey: field.source,
163
489
  parameterFields: field.params ? [...field.params] : []
164
490
  };
165
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
491
+ return buildFieldNode(
492
+ field.name,
493
+ resolveFieldMetadata(field.name, field, metadataPolicy),
494
+ type,
495
+ field.required,
496
+ buildAnnotations(getExplicitDisplayName(field))
497
+ );
166
498
  }
167
- function canonicalizeDynamicSchemaField(field) {
499
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
168
500
  const type = {
169
501
  kind: "dynamic",
170
502
  dynamicKind: "schema",
171
503
  sourceKey: field.schemaSource,
172
504
  parameterFields: []
173
505
  };
174
- 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
+ );
175
513
  }
176
- function canonicalizeArrayField(field) {
177
- const itemProperties = buildObjectProperties(field.items);
514
+ function canonicalizeArrayField(field, metadataPolicy) {
515
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
178
516
  const itemsType = {
179
517
  kind: "object",
180
518
  properties: itemProperties,
@@ -202,37 +540,44 @@ function canonicalizeArrayField(field) {
202
540
  }
203
541
  return buildFieldNode(
204
542
  field.name,
543
+ resolveFieldMetadata(field.name, field, metadataPolicy),
205
544
  type,
206
545
  field.required,
207
- buildAnnotations(field.label),
546
+ buildAnnotations(getExplicitDisplayName(field)),
208
547
  constraints
209
548
  );
210
549
  }
211
- function canonicalizeObjectField(field) {
212
- const properties = buildObjectProperties(field.properties);
550
+ function canonicalizeObjectField(field, metadataPolicy) {
551
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
213
552
  const type = {
214
553
  kind: "object",
215
554
  properties,
216
555
  additionalProperties: true
217
556
  };
218
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
557
+ return buildFieldNode(
558
+ field.name,
559
+ resolveFieldMetadata(field.name, field, metadataPolicy),
560
+ type,
561
+ field.required,
562
+ buildAnnotations(getExplicitDisplayName(field))
563
+ );
219
564
  }
220
- function canonicalizeGroup(g) {
565
+ function canonicalizeGroup(g, metadataPolicy) {
221
566
  return {
222
567
  kind: "group",
223
568
  label: g.label,
224
- elements: canonicalizeElements(g.elements),
569
+ elements: canonicalizeElements(g.elements, metadataPolicy),
225
570
  provenance: CHAIN_DSL_PROVENANCE
226
571
  };
227
572
  }
228
- function canonicalizeConditional(c) {
573
+ function canonicalizeConditional(c, metadataPolicy) {
229
574
  return {
230
575
  kind: "conditional",
231
576
  fieldName: c.field,
232
577
  // Conditional values from the chain DSL are JSON-serializable primitives
233
578
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
234
579
  value: assertJsonValue(c.value),
235
- elements: canonicalizeElements(c.elements),
580
+ elements: canonicalizeElements(c.elements, metadataPolicy),
236
581
  provenance: CHAIN_DSL_PROVENANCE
237
582
  };
238
583
  }
@@ -252,10 +597,11 @@ function assertJsonValue(v) {
252
597
  }
253
598
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
254
599
  }
255
- function buildFieldNode(name, type, required, annotations, constraints = []) {
600
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
256
601
  return {
257
602
  kind: "field",
258
603
  name,
604
+ ...metadata !== void 0 && { metadata },
259
605
  type,
260
606
  required: required === true,
261
607
  constraints,
@@ -285,13 +631,14 @@ function buildAnnotations(label, placeholder) {
285
631
  }
286
632
  return annotations;
287
633
  }
288
- function buildObjectProperties(elements, insideConditional = false) {
634
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
289
635
  const properties = [];
290
636
  for (const el of elements) {
291
637
  if (isField(el)) {
292
- const fieldNode = canonicalizeField(el);
638
+ const fieldNode = canonicalizeField(el, metadataPolicy);
293
639
  properties.push({
294
640
  name: fieldNode.name,
641
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
295
642
  type: fieldNode.type,
296
643
  // Fields inside a conditional branch are always optional in the
297
644
  // data schema, regardless of their `required` flag — the condition
@@ -302,17 +649,34 @@ function buildObjectProperties(elements, insideConditional = false) {
302
649
  provenance: CHAIN_DSL_PROVENANCE
303
650
  });
304
651
  } else if (isGroup(el)) {
305
- properties.push(...buildObjectProperties(el.elements, insideConditional));
652
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
306
653
  } else if (isConditional(el)) {
307
- properties.push(...buildObjectProperties(el.elements, true));
654
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
308
655
  }
309
656
  }
310
657
  return properties;
311
658
  }
659
+ function getExplicitDisplayName(field) {
660
+ if (field.label !== void 0 && field.displayName !== void 0) {
661
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
662
+ }
663
+ return field.displayName ?? field.label;
664
+ }
665
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
666
+ const displayName = getExplicitDisplayName(field);
667
+ return resolveMetadata(
668
+ {
669
+ ...field.apiName !== void 0 && { apiName: field.apiName },
670
+ ...displayName !== void 0 && { displayName }
671
+ },
672
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
673
+ makeMetadataContext("chain-dsl", "field", logicalName)
674
+ );
675
+ }
312
676
 
313
677
  // src/canonicalize/tsdoc-canonicalizer.ts
314
678
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
315
- function canonicalizeTSDoc(analysis, source) {
679
+ function canonicalizeTSDoc(analysis, source, options) {
316
680
  const file = source?.file ?? "";
317
681
  const provenance = {
318
682
  surface: "tsdoc",
@@ -321,15 +685,21 @@ function canonicalizeTSDoc(analysis, source) {
321
685
  column: 0
322
686
  };
323
687
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
324
- return {
688
+ const ir = {
325
689
  kind: "form-ir",
690
+ name: analysis.name,
326
691
  irVersion: IR_VERSION2,
327
692
  elements,
693
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
328
694
  typeRegistry: analysis.typeRegistry,
329
695
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
330
696
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
331
697
  provenance
332
698
  };
699
+ return resolveFormIRMetadata(ir, {
700
+ policy: normalizeMetadataPolicy(options?.metadata),
701
+ surface: "tsdoc"
702
+ });
333
703
  }
334
704
  function assembleElements(fields, layouts, provenance) {
335
705
  const elements = [];
@@ -386,6 +756,120 @@ function wrapInConditional(field, layout, provenance) {
386
756
  return conditional;
387
757
  }
388
758
 
759
+ // src/metadata/collision-guards.ts
760
+ function assertUniqueSerializedNames(entries, scope) {
761
+ const seen = /* @__PURE__ */ new Map();
762
+ for (const entry of entries) {
763
+ const previous = seen.get(entry.serializedName);
764
+ if (previous !== void 0) {
765
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
766
+ continue;
767
+ }
768
+ throw new Error(
769
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
770
+ );
771
+ }
772
+ seen.set(entry.serializedName, entry);
773
+ }
774
+ }
775
+ function collectFlattenedFields(elements) {
776
+ const fields = [];
777
+ for (const element of elements) {
778
+ switch (element.kind) {
779
+ case "field":
780
+ fields.push(element);
781
+ break;
782
+ case "group":
783
+ case "conditional":
784
+ fields.push(...collectFlattenedFields(element.elements));
785
+ break;
786
+ default: {
787
+ const exhaustive = element;
788
+ void exhaustive;
789
+ }
790
+ }
791
+ }
792
+ return fields;
793
+ }
794
+ function validateObjectProperties(properties, scope) {
795
+ assertUniqueSerializedNames(
796
+ properties.map((property) => ({
797
+ logicalName: property.name,
798
+ serializedName: getSerializedName(property.name, property.metadata),
799
+ category: "object property"
800
+ })),
801
+ scope
802
+ );
803
+ for (const property of properties) {
804
+ validateTypeNode(
805
+ property.type,
806
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
807
+ );
808
+ }
809
+ }
810
+ function validateTypeNode(type, scope) {
811
+ switch (type.kind) {
812
+ case "array":
813
+ validateTypeNode(type.items, `${scope}[]`);
814
+ break;
815
+ case "object":
816
+ validateObjectProperties(type.properties, scope);
817
+ break;
818
+ case "record":
819
+ validateTypeNode(type.valueType, `${scope}.*`);
820
+ break;
821
+ case "union":
822
+ type.members.forEach((member, index) => {
823
+ validateTypeNode(member, `${scope}|${String(index)}`);
824
+ });
825
+ break;
826
+ case "reference":
827
+ case "primitive":
828
+ case "enum":
829
+ case "dynamic":
830
+ case "custom":
831
+ break;
832
+ default: {
833
+ const exhaustive = type;
834
+ void exhaustive;
835
+ }
836
+ }
837
+ }
838
+ function validateTypeDefinitions(typeRegistry) {
839
+ const definitions = Object.values(typeRegistry);
840
+ assertUniqueSerializedNames(
841
+ definitions.map((definition) => ({
842
+ logicalName: definition.name,
843
+ serializedName: getSerializedName(definition.name, definition.metadata),
844
+ category: "type definition"
845
+ })),
846
+ "$defs"
847
+ );
848
+ for (const definition of definitions) {
849
+ validateTypeDefinition(definition);
850
+ }
851
+ }
852
+ function validateTypeDefinition(definition) {
853
+ validateTypeNode(
854
+ definition.type,
855
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
856
+ );
857
+ }
858
+ function assertNoSerializedNameCollisions(ir) {
859
+ assertUniqueSerializedNames(
860
+ collectFlattenedFields(ir.elements).map((field) => ({
861
+ logicalName: field.name,
862
+ serializedName: getSerializedName(field.name, field.metadata),
863
+ category: "field"
864
+ })),
865
+ "form root"
866
+ );
867
+ for (const field of collectFlattenedFields(ir.elements)) {
868
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
869
+ }
870
+ validateTypeDefinitions(ir.typeRegistry);
871
+ }
872
+
389
873
  // src/json-schema/ir-generator.ts
390
874
  function makeContext(options) {
391
875
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -396,19 +880,33 @@ function makeContext(options) {
396
880
  }
397
881
  return {
398
882
  defs: {},
883
+ typeNameMap: {},
884
+ typeRegistry: {},
399
885
  extensionRegistry: options?.extensionRegistry,
400
886
  vendorPrefix
401
887
  };
402
888
  }
403
889
  function generateJsonSchemaFromIR(ir, options) {
404
- const ctx = makeContext(options);
890
+ assertNoSerializedNameCollisions(ir);
891
+ const ctx = {
892
+ ...makeContext(options),
893
+ typeRegistry: ir.typeRegistry,
894
+ typeNameMap: Object.fromEntries(
895
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
896
+ name,
897
+ getSerializedName(name, typeDef.metadata)
898
+ ])
899
+ )
900
+ };
405
901
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
406
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
902
+ const schemaName = ctx.typeNameMap[name] ?? name;
903
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
904
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
407
905
  if (typeDef.constraints && typeDef.constraints.length > 0) {
408
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
906
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
409
907
  }
410
908
  if (typeDef.annotations && typeDef.annotations.length > 0) {
411
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
909
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
412
910
  }
413
911
  }
414
912
  const properties = {};
@@ -421,6 +919,7 @@ function generateJsonSchemaFromIR(ir, options) {
421
919
  properties,
422
920
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
423
921
  };
922
+ applyResolvedMetadata(result, ir.metadata);
424
923
  if (ir.annotations && ir.annotations.length > 0) {
425
924
  applyAnnotations(result, ir.annotations, ctx);
426
925
  }
@@ -433,9 +932,9 @@ function collectFields(elements, properties, required, ctx) {
433
932
  for (const element of elements) {
434
933
  switch (element.kind) {
435
934
  case "field":
436
- properties[element.name] = generateFieldSchema(element, ctx);
935
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
437
936
  if (element.required) {
438
- required.push(element.name);
937
+ required.push(getSerializedName(element.name, element.metadata));
439
938
  }
440
939
  break;
441
940
  case "group":
@@ -479,6 +978,7 @@ function generateFieldSchema(field, ctx) {
479
978
  rootAnnotations.push(annotation);
480
979
  }
481
980
  }
981
+ applyResolvedMetadata(schema, field.metadata);
482
982
  applyAnnotations(schema, rootAnnotations, ctx);
483
983
  if (itemStringSchema !== void 0) {
484
984
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -486,7 +986,7 @@ function generateFieldSchema(field, ctx) {
486
986
  if (pathConstraints.length === 0) {
487
987
  return schema;
488
988
  }
489
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
989
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
490
990
  }
491
991
  function isStringItemConstraint(constraint) {
492
992
  switch (constraint.constraintKind) {
@@ -498,9 +998,11 @@ function isStringItemConstraint(constraint) {
498
998
  return false;
499
999
  }
500
1000
  }
501
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
1001
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
502
1002
  if (schema.type === "array" && schema.items) {
503
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
1003
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
1004
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
1005
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
504
1006
  return schema;
505
1007
  }
506
1008
  const byTarget = /* @__PURE__ */ new Map();
@@ -515,7 +1017,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
515
1017
  for (const [target, constraints] of byTarget) {
516
1018
  const subSchema = {};
517
1019
  applyConstraints(subSchema, constraints, ctx);
518
- propertyOverrides[target] = subSchema;
1020
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
519
1021
  }
520
1022
  if (schema.$ref) {
521
1023
  const { $ref, ...rest } = schema;
@@ -563,7 +1065,7 @@ function generateTypeNode(type, ctx) {
563
1065
  case "union":
564
1066
  return generateUnionType(type, ctx);
565
1067
  case "reference":
566
- return generateReferenceType(type);
1068
+ return generateReferenceType(type, ctx);
567
1069
  case "dynamic":
568
1070
  return generateDynamicType(type);
569
1071
  case "custom":
@@ -604,9 +1106,10 @@ function generateObjectType(type, ctx) {
604
1106
  const properties = {};
605
1107
  const required = [];
606
1108
  for (const prop of type.properties) {
607
- properties[prop.name] = generatePropertySchema(prop, ctx);
1109
+ const propertyName = getSerializedName(prop.name, prop.metadata);
1110
+ properties[propertyName] = generatePropertySchema(prop, ctx);
608
1111
  if (!prop.optional) {
609
- required.push(prop.name);
1112
+ required.push(propertyName);
610
1113
  }
611
1114
  }
612
1115
  const schema = { type: "object", properties };
@@ -627,6 +1130,7 @@ function generateRecordType(type, ctx) {
627
1130
  function generatePropertySchema(prop, ctx) {
628
1131
  const schema = generateTypeNode(prop.type, ctx);
629
1132
  applyConstraints(schema, prop.constraints, ctx);
1133
+ applyResolvedMetadata(schema, prop.metadata);
630
1134
  applyAnnotations(schema, prop.annotations, ctx);
631
1135
  return schema;
632
1136
  }
@@ -655,8 +1159,28 @@ function isNullableUnion(type) {
655
1159
  ).length;
656
1160
  return nullCount === 1;
657
1161
  }
658
- function generateReferenceType(type) {
659
- return { $ref: `#/$defs/${type.name}` };
1162
+ function generateReferenceType(type, ctx) {
1163
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
1164
+ }
1165
+ function applyResolvedMetadata(schema, metadata) {
1166
+ const displayName = getDisplayName(metadata);
1167
+ if (displayName !== void 0) {
1168
+ schema.title = displayName;
1169
+ }
1170
+ }
1171
+ function resolveReferencedType(type, ctx) {
1172
+ return ctx.typeRegistry[type.name]?.type;
1173
+ }
1174
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1175
+ if (typeNode?.kind === "object") {
1176
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1177
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1178
+ }
1179
+ if (typeNode?.kind === "reference") {
1180
+ const referencedType = resolveReferencedType(typeNode, ctx);
1181
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1182
+ }
1183
+ return logicalName;
660
1184
  }
661
1185
  function generateDynamicType(type) {
662
1186
  if (type.dynamicKind === "enum") {
@@ -736,7 +1260,7 @@ function applyAnnotations(schema, annotations, ctx) {
736
1260
  for (const annotation of annotations) {
737
1261
  switch (annotation.annotationKind) {
738
1262
  case "displayName":
739
- schema.title = annotation.value;
1263
+ schema.title ??= annotation.value;
740
1264
  break;
741
1265
  case "description":
742
1266
  schema.description = annotation.value;
@@ -823,7 +1347,10 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
823
1347
 
824
1348
  // src/json-schema/generator.ts
825
1349
  function generateJsonSchema(form, options) {
826
- const ir = canonicalizeChainDSL(form);
1350
+ const ir = canonicalizeChainDSL(
1351
+ form,
1352
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1353
+ );
827
1354
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
828
1355
  return generateJsonSchemaFromIR(ir, internalOptions);
829
1356
  }
@@ -993,13 +1520,21 @@ function combineRules(parentRule, childRule) {
993
1520
  }
994
1521
  };
995
1522
  }
996
- function fieldNodeToControl(field, parentRule) {
997
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1523
+ function getFieldDisplayName(field) {
1524
+ const resolvedDisplayName = getDisplayName(field.metadata);
1525
+ if (resolvedDisplayName !== void 0) {
1526
+ return resolvedDisplayName;
1527
+ }
1528
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1529
+ }
1530
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
998
1531
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1532
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1533
+ const displayName = getFieldDisplayName(field);
999
1534
  const control = {
1000
1535
  type: "Control",
1001
- scope: fieldToScope(field.name),
1002
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1536
+ scope: fieldToScope(serializedName),
1537
+ ...displayName !== void 0 && { label: displayName },
1003
1538
  ...placeholderAnnotation !== void 0 && {
1004
1539
  options: { placeholder: placeholderAnnotation.value }
1005
1540
  },
@@ -1007,30 +1542,30 @@ function fieldNodeToControl(field, parentRule) {
1007
1542
  };
1008
1543
  return control;
1009
1544
  }
1010
- function groupNodeToLayout(group, parentRule) {
1545
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
1011
1546
  return {
1012
1547
  type: "Group",
1013
1548
  label: group.label,
1014
- elements: irElementsToUiSchema(group.elements, parentRule),
1549
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
1015
1550
  ...parentRule !== void 0 && { rule: parentRule }
1016
1551
  };
1017
1552
  }
1018
- function irElementsToUiSchema(elements, parentRule) {
1553
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1019
1554
  const result = [];
1020
1555
  for (const element of elements) {
1021
1556
  switch (element.kind) {
1022
1557
  case "field": {
1023
- result.push(fieldNodeToControl(element, parentRule));
1558
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1024
1559
  break;
1025
1560
  }
1026
1561
  case "group": {
1027
- result.push(groupNodeToLayout(element, parentRule));
1562
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1028
1563
  break;
1029
1564
  }
1030
1565
  case "conditional": {
1031
- const newRule = createShowRule(element.fieldName, element.value);
1566
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1032
1567
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1033
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1568
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1034
1569
  result.push(...childElements);
1035
1570
  break;
1036
1571
  }
@@ -1044,16 +1579,42 @@ function irElementsToUiSchema(elements, parentRule) {
1044
1579
  return result;
1045
1580
  }
1046
1581
  function generateUiSchemaFromIR(ir) {
1582
+ assertNoSerializedNameCollisions(ir);
1583
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1047
1584
  const result = {
1048
1585
  type: "VerticalLayout",
1049
- elements: irElementsToUiSchema(ir.elements)
1586
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1050
1587
  };
1051
1588
  return parseOrThrow(uiSchema, result, "UI Schema");
1052
1589
  }
1590
+ function collectFieldNameMap(elements) {
1591
+ const map = /* @__PURE__ */ new Map();
1592
+ for (const element of elements) {
1593
+ switch (element.kind) {
1594
+ case "field":
1595
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1596
+ break;
1597
+ case "group":
1598
+ case "conditional":
1599
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1600
+ map.set(key, value);
1601
+ }
1602
+ break;
1603
+ default: {
1604
+ const _exhaustive = element;
1605
+ void _exhaustive;
1606
+ }
1607
+ }
1608
+ }
1609
+ return map;
1610
+ }
1053
1611
 
1054
1612
  // src/ui-schema/generator.ts
1055
- function generateUiSchema(form) {
1056
- const ir = canonicalizeChainDSL(form);
1613
+ function generateUiSchema(form, options) {
1614
+ const ir = canonicalizeChainDSL(
1615
+ form,
1616
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1617
+ );
1057
1618
  return generateUiSchemaFromIR(ir);
1058
1619
  }
1059
1620
 
@@ -2117,7 +2678,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
2117
2678
  ...hostType !== void 0 && { hostType }
2118
2679
  };
2119
2680
  }
2120
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2681
+ function makeExplicitScalarMetadata(value) {
2682
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
2683
+ }
2684
+ function extractExplicitMetadata(node) {
2685
+ let apiName;
2686
+ let displayName;
2687
+ let apiNamePlural;
2688
+ let displayNamePlural;
2689
+ for (const tag of getLeadingParsedTags(node)) {
2690
+ const value = tag.argumentText.trim();
2691
+ if (value === "") {
2692
+ continue;
2693
+ }
2694
+ if (tag.normalizedTagName === "apiName") {
2695
+ if (tag.target === null) {
2696
+ apiName ??= value;
2697
+ } else if (tag.target.kind === "variant") {
2698
+ if (tag.target.rawText === "singular") {
2699
+ apiName ??= value;
2700
+ } else if (tag.target.rawText === "plural") {
2701
+ apiNamePlural ??= value;
2702
+ }
2703
+ }
2704
+ continue;
2705
+ }
2706
+ if (tag.normalizedTagName === "displayName") {
2707
+ if (tag.target === null) {
2708
+ displayName ??= value;
2709
+ } else if (tag.target.kind === "variant") {
2710
+ if (tag.target.rawText === "singular") {
2711
+ displayName ??= value;
2712
+ } else if (tag.target.rawText === "plural") {
2713
+ displayNamePlural ??= value;
2714
+ }
2715
+ }
2716
+ }
2717
+ }
2718
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
2719
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
2720
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
2721
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
2722
+ const metadata = {
2723
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
2724
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
2725
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
2726
+ ...resolvedDisplayNamePlural !== void 0 && {
2727
+ displayNamePlural: resolvedDisplayNamePlural
2728
+ }
2729
+ };
2730
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
2731
+ }
2732
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
2733
+ const explicit = extractExplicitMetadata(node);
2734
+ return resolveMetadata(
2735
+ {
2736
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
2737
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
2738
+ ...explicit?.apiNamePlural !== void 0 && {
2739
+ apiNamePlural: explicit.apiNamePlural.value
2740
+ },
2741
+ ...explicit?.displayNamePlural !== void 0 && {
2742
+ displayNamePlural: explicit.displayNamePlural.value
2743
+ }
2744
+ },
2745
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
2746
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
2747
+ );
2748
+ }
2749
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2750
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2121
2751
  const name = classDecl.name?.text ?? "AnonymousClass";
2122
2752
  const fields = [];
2123
2753
  const fieldLayouts = [];
@@ -2144,6 +2774,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2144
2774
  visiting,
2145
2775
  diagnostics,
2146
2776
  classType,
2777
+ normalizedMetadataPolicy,
2147
2778
  extensionRegistry
2148
2779
  );
2149
2780
  if (fieldNode) {
@@ -2168,10 +2799,18 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2168
2799
  classType,
2169
2800
  checker,
2170
2801
  file,
2171
- diagnostics
2802
+ diagnostics,
2803
+ normalizedMetadataPolicy
2172
2804
  );
2805
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2806
+ checker,
2807
+ declaration: classDecl,
2808
+ subjectType: classType,
2809
+ hostType: classType
2810
+ });
2173
2811
  return {
2174
2812
  name,
2813
+ ...metadata !== void 0 && { metadata },
2175
2814
  fields: specializedFields,
2176
2815
  fieldLayouts,
2177
2816
  typeRegistry,
@@ -2181,7 +2820,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2181
2820
  staticMethods
2182
2821
  };
2183
2822
  }
2184
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2823
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2824
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2185
2825
  const name = interfaceDecl.name.text;
2186
2826
  const fields = [];
2187
2827
  const typeRegistry = {};
@@ -2205,6 +2845,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2205
2845
  visiting,
2206
2846
  diagnostics,
2207
2847
  interfaceType,
2848
+ normalizedMetadataPolicy,
2208
2849
  extensionRegistry
2209
2850
  );
2210
2851
  if (fieldNode) {
@@ -2218,11 +2859,19 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2218
2859
  interfaceType,
2219
2860
  checker,
2220
2861
  file,
2221
- diagnostics
2862
+ diagnostics,
2863
+ normalizedMetadataPolicy
2222
2864
  );
2223
2865
  const fieldLayouts = specializedFields.map(() => ({}));
2866
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
2867
+ checker,
2868
+ declaration: interfaceDecl,
2869
+ subjectType: interfaceType,
2870
+ hostType: interfaceType
2871
+ });
2224
2872
  return {
2225
2873
  name,
2874
+ ...metadata !== void 0 && { metadata },
2226
2875
  fields: specializedFields,
2227
2876
  fieldLayouts,
2228
2877
  typeRegistry,
@@ -2232,7 +2881,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2232
2881
  staticMethods: []
2233
2882
  };
2234
2883
  }
2235
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2884
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2236
2885
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2237
2886
  const sourceFile = typeAlias.getSourceFile();
2238
2887
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2242,6 +2891,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2242
2891
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2243
2892
  };
2244
2893
  }
2894
+ const typeLiteral = typeAlias.type;
2895
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2245
2896
  const name = typeAlias.name.text;
2246
2897
  const fields = [];
2247
2898
  const typeRegistry = {};
@@ -2255,7 +2906,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2255
2906
  const annotations = [...typeAliasDoc.annotations];
2256
2907
  diagnostics.push(...typeAliasDoc.diagnostics);
2257
2908
  const visiting = /* @__PURE__ */ new Set();
2258
- for (const member of typeAlias.type.members) {
2909
+ for (const member of typeLiteral.members) {
2259
2910
  if (ts3.isPropertySignature(member)) {
2260
2911
  const fieldNode = analyzeInterfacePropertyToIR(
2261
2912
  member,
@@ -2265,6 +2916,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2265
2916
  visiting,
2266
2917
  diagnostics,
2267
2918
  aliasType,
2919
+ normalizedMetadataPolicy,
2268
2920
  extensionRegistry
2269
2921
  );
2270
2922
  if (fieldNode) {
@@ -2278,12 +2930,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2278
2930
  aliasType,
2279
2931
  checker,
2280
2932
  file,
2281
- diagnostics
2933
+ diagnostics,
2934
+ normalizedMetadataPolicy
2282
2935
  );
2936
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
2937
+ checker,
2938
+ declaration: typeAlias,
2939
+ subjectType: aliasType,
2940
+ hostType: aliasType
2941
+ });
2283
2942
  return {
2284
2943
  ok: true,
2285
2944
  analysis: {
2286
2945
  name,
2946
+ ...metadata !== void 0 && { metadata },
2287
2947
  fields: specializedFields,
2288
2948
  fieldLayouts: specializedFields.map(() => ({})),
2289
2949
  typeRegistry,
@@ -2323,31 +2983,20 @@ function getLeadingParsedTags(node) {
2323
2983
  }
2324
2984
  return parsedTags;
2325
2985
  }
2326
- function findDiscriminatorProperty(node, fieldName) {
2327
- if (ts3.isClassDeclaration(node)) {
2328
- for (const member of node.members) {
2329
- if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2330
- return member;
2331
- }
2332
- }
2333
- return null;
2334
- }
2335
- if (ts3.isInterfaceDeclaration(node)) {
2336
- for (const member of node.members) {
2337
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2338
- return member;
2339
- }
2340
- }
2986
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
2987
+ const subjectType = checker.getTypeAtLocation(node);
2988
+ const propertySymbol = subjectType.getProperty(fieldName);
2989
+ if (propertySymbol === void 0) {
2341
2990
  return null;
2342
2991
  }
2343
- if (ts3.isTypeLiteralNode(node.type)) {
2344
- for (const member of node.type.members) {
2345
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2346
- return member;
2347
- }
2348
- }
2349
- }
2350
- return null;
2992
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
2993
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
2994
+ ) ?? propertySymbol.declarations?.[0];
2995
+ return {
2996
+ declaration,
2997
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
2998
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
2999
+ };
2351
3000
  }
2352
3001
  function isLocalTypeParameterName(node, typeParameterName) {
2353
3002
  return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
@@ -2440,8 +3089,8 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2440
3089
  );
2441
3090
  return null;
2442
3091
  }
2443
- const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2444
- if (propertyDecl === null) {
3092
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3093
+ if (property === null) {
2445
3094
  diagnostics.push(
2446
3095
  makeAnalysisDiagnostic(
2447
3096
  "UNKNOWN_PATH_TARGET",
@@ -2451,36 +3100,35 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2451
3100
  );
2452
3101
  return null;
2453
3102
  }
2454
- if (propertyDecl.questionToken !== void 0) {
3103
+ if (property.optional) {
2455
3104
  diagnostics.push(
2456
3105
  makeAnalysisDiagnostic(
2457
3106
  "TYPE_MISMATCH",
2458
3107
  `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2459
3108
  directive.provenance,
2460
- [provenanceForNode(propertyDecl, file)]
3109
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2461
3110
  )
2462
3111
  );
2463
3112
  return null;
2464
3113
  }
2465
- const propertyType = checker.getTypeAtLocation(propertyDecl);
2466
- if (isNullishSemanticType(propertyType)) {
3114
+ if (isNullishSemanticType(property.type)) {
2467
3115
  diagnostics.push(
2468
3116
  makeAnalysisDiagnostic(
2469
3117
  "TYPE_MISMATCH",
2470
3118
  `Discriminator field "${directive.fieldName}" must not be nullable.`,
2471
3119
  directive.provenance,
2472
- [provenanceForNode(propertyDecl, file)]
3120
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2473
3121
  )
2474
3122
  );
2475
3123
  return null;
2476
3124
  }
2477
- if (!isStringLikeSemanticType(propertyType)) {
3125
+ if (!isStringLikeSemanticType(property.type)) {
2478
3126
  diagnostics.push(
2479
3127
  makeAnalysisDiagnostic(
2480
3128
  "TYPE_MISMATCH",
2481
3129
  `Discriminator field "${directive.fieldName}" must be string-like.`,
2482
3130
  directive.provenance,
2483
- [provenanceForNode(propertyDecl, file)]
3131
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2484
3132
  )
2485
3133
  );
2486
3134
  return null;
@@ -2501,25 +3149,58 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
2501
3149
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2502
3150
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2503
3151
  }
2504
- function extractDeclarationApiName(node) {
2505
- for (const tag of getLeadingParsedTags(node)) {
2506
- if (tag.normalizedTagName !== "apiName") {
2507
- continue;
2508
- }
2509
- if (tag.target === null && tag.argumentText.trim() !== "") {
2510
- return tag.argumentText.trim();
2511
- }
2512
- if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2513
- const value = tag.argumentText.trim();
2514
- if (value !== "") {
2515
- return value;
2516
- }
3152
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
3153
+ const propertySymbol = boundType.getProperty(fieldName);
3154
+ if (propertySymbol === void 0) {
3155
+ return void 0;
3156
+ }
3157
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
3158
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
3159
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
3160
+ if (resolvedAnchorNode === null) {
3161
+ return void 0;
3162
+ }
3163
+ const propertyType = checker.getTypeOfSymbolAtLocation(
3164
+ propertySymbol,
3165
+ resolvedAnchorNode
3166
+ );
3167
+ if (propertyType.isStringLiteral()) {
3168
+ return propertyType.value;
3169
+ }
3170
+ if (propertyType.isUnion()) {
3171
+ const nonNullMembers = propertyType.types.filter(
3172
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3173
+ );
3174
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
3175
+ diagnostics.push(
3176
+ makeAnalysisDiagnostic(
3177
+ "INVALID_TAG_ARGUMENT",
3178
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
3179
+ provenance
3180
+ )
3181
+ );
3182
+ return null;
2517
3183
  }
2518
3184
  }
2519
- return null;
3185
+ return void 0;
2520
3186
  }
2521
- function inferJsonFacingName(name) {
2522
- 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();
3187
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
3188
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
3189
+ if (declaration === null) {
3190
+ return void 0;
3191
+ }
3192
+ const metadata = resolveNodeMetadata(
3193
+ metadataPolicy,
3194
+ "type",
3195
+ getDiscriminatorLogicalName(boundType, declaration, checker),
3196
+ declaration,
3197
+ {
3198
+ checker,
3199
+ declaration,
3200
+ subjectType: boundType
3201
+ }
3202
+ );
3203
+ return metadata?.apiName;
2523
3204
  }
2524
3205
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2525
3206
  if (seen.has(type)) {
@@ -2546,7 +3227,7 @@ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__
2546
3227
  }
2547
3228
  return null;
2548
3229
  }
2549
- function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
3230
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
2550
3231
  if (boundType === null) {
2551
3232
  diagnostics.push(
2552
3233
  makeAnalysisDiagnostic(
@@ -2575,9 +3256,22 @@ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics)
2575
3256
  return null;
2576
3257
  }
2577
3258
  }
2578
- const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2579
- if (declaration !== null) {
2580
- return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
3259
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
3260
+ boundType,
3261
+ fieldName,
3262
+ checker,
3263
+ provenance,
3264
+ diagnostics
3265
+ );
3266
+ if (literalIdentityValue !== void 0) {
3267
+ return literalIdentityValue;
3268
+ }
3269
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
3270
+ if (apiName?.source === "explicit") {
3271
+ return apiName.value;
3272
+ }
3273
+ if (apiName?.source === "inferred") {
3274
+ return apiName.value;
2581
3275
  }
2582
3276
  diagnostics.push(
2583
3277
  makeAnalysisDiagnostic(
@@ -2594,7 +3288,15 @@ function getDeclarationName(node) {
2594
3288
  }
2595
3289
  return "anonymous";
2596
3290
  }
2597
- function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
3291
+ function getResolvedTypeArguments(type) {
3292
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
3293
+ }
3294
+ function getDiscriminatorLogicalName(type, declaration, checker) {
3295
+ const baseName = getDeclarationName(declaration);
3296
+ const typeArguments = getResolvedTypeArguments(type);
3297
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
3298
+ }
3299
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
2598
3300
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2599
3301
  if (directive === null) {
2600
3302
  return [...fields];
@@ -2606,9 +3308,11 @@ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checke
2606
3308
  checker,
2607
3309
  directive.typeParameterName
2608
3310
  ),
3311
+ directive.fieldName,
2609
3312
  checker,
2610
3313
  directive.provenance,
2611
- diagnostics
3314
+ diagnostics,
3315
+ metadataPolicy
2612
3316
  );
2613
3317
  if (discriminatorValue === null) {
2614
3318
  return [...fields];
@@ -2629,7 +3333,7 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2629
3333
  ).filter((value) => value !== "");
2630
3334
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2631
3335
  }
2632
- function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3336
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
2633
3337
  const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2634
3338
  if (typeNode === void 0) {
2635
3339
  return [];
@@ -2649,13 +3353,14 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
2649
3353
  typeRegistry,
2650
3354
  visiting,
2651
3355
  argumentNode,
3356
+ metadataPolicy,
2652
3357
  extensionRegistry,
2653
3358
  diagnostics
2654
3359
  )
2655
3360
  };
2656
3361
  });
2657
3362
  }
2658
- function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
3363
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
2659
3364
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2660
3365
  if (directive === null) {
2661
3366
  return properties;
@@ -2667,9 +3372,11 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2667
3372
  checker,
2668
3373
  directive.typeParameterName
2669
3374
  ),
3375
+ directive.fieldName,
2670
3376
  checker,
2671
3377
  directive.provenance,
2672
- diagnostics
3378
+ diagnostics,
3379
+ metadataPolicy
2673
3380
  );
2674
3381
  if (discriminatorValue === null) {
2675
3382
  return properties;
@@ -2684,7 +3391,7 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2684
3391
  } : property
2685
3392
  );
2686
3393
  }
2687
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3394
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2688
3395
  if (!ts3.isIdentifier(prop.name)) {
2689
3396
  return null;
2690
3397
  }
@@ -2699,6 +3406,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2699
3406
  typeRegistry,
2700
3407
  visiting,
2701
3408
  prop,
3409
+ metadataPolicy,
2702
3410
  extensionRegistry,
2703
3411
  diagnostics
2704
3412
  );
@@ -2722,9 +3430,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2722
3430
  annotations.push(defaultAnnotation);
2723
3431
  }
2724
3432
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3433
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3434
+ checker,
3435
+ declaration: prop,
3436
+ subjectType: tsType,
3437
+ hostType
3438
+ });
2725
3439
  return {
2726
3440
  kind: "field",
2727
3441
  name,
3442
+ ...metadata !== void 0 && { metadata },
2728
3443
  type,
2729
3444
  required: !optional,
2730
3445
  constraints,
@@ -2732,7 +3447,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2732
3447
  provenance
2733
3448
  };
2734
3449
  }
2735
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3450
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2736
3451
  if (!ts3.isIdentifier(prop.name)) {
2737
3452
  return null;
2738
3453
  }
@@ -2747,6 +3462,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2747
3462
  typeRegistry,
2748
3463
  visiting,
2749
3464
  prop,
3465
+ metadataPolicy,
2750
3466
  extensionRegistry,
2751
3467
  diagnostics
2752
3468
  );
@@ -2766,9 +3482,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2766
3482
  let annotations = [];
2767
3483
  annotations.push(...docResult.annotations);
2768
3484
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3485
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3486
+ checker,
3487
+ declaration: prop,
3488
+ subjectType: tsType,
3489
+ hostType
3490
+ });
2769
3491
  return {
2770
3492
  kind: "field",
2771
3493
  name,
3494
+ ...metadata !== void 0 && { metadata },
2772
3495
  type,
2773
3496
  required: !optional,
2774
3497
  constraints,
@@ -2893,7 +3616,7 @@ function getTypeNodeRegistrationName(typeNode) {
2893
3616
  }
2894
3617
  return null;
2895
3618
  }
2896
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3619
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2897
3620
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2898
3621
  if (customType) {
2899
3622
  return customType;
@@ -2905,6 +3628,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2905
3628
  typeRegistry,
2906
3629
  visiting,
2907
3630
  sourceNode,
3631
+ metadataPolicy,
2908
3632
  extensionRegistry,
2909
3633
  diagnostics
2910
3634
  );
@@ -2949,6 +3673,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2949
3673
  typeRegistry,
2950
3674
  visiting,
2951
3675
  sourceNode,
3676
+ metadataPolicy,
2952
3677
  extensionRegistry,
2953
3678
  diagnostics
2954
3679
  );
@@ -2961,6 +3686,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2961
3686
  typeRegistry,
2962
3687
  visiting,
2963
3688
  sourceNode,
3689
+ metadataPolicy,
2964
3690
  extensionRegistry,
2965
3691
  diagnostics
2966
3692
  );
@@ -2973,13 +3699,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2973
3699
  typeRegistry,
2974
3700
  visiting,
2975
3701
  sourceNode,
3702
+ metadataPolicy,
2976
3703
  extensionRegistry,
2977
3704
  diagnostics
2978
3705
  );
2979
3706
  }
2980
3707
  return { kind: "primitive", primitiveKind: "string" };
2981
3708
  }
2982
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3709
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2983
3710
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2984
3711
  return null;
2985
3712
  }
@@ -2999,14 +3726,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2999
3726
  file,
3000
3727
  makeParseOptions(extensionRegistry)
3001
3728
  );
3729
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3730
+ checker,
3731
+ declaration: aliasDecl,
3732
+ subjectType: aliasType
3733
+ });
3002
3734
  typeRegistry[aliasName] = {
3003
3735
  name: aliasName,
3736
+ ...metadata !== void 0 && { metadata },
3004
3737
  type: resolveAliasedPrimitiveTarget(
3005
3738
  aliasType,
3006
3739
  checker,
3007
3740
  file,
3008
3741
  typeRegistry,
3009
3742
  visiting,
3743
+ metadataPolicy,
3010
3744
  extensionRegistry,
3011
3745
  diagnostics
3012
3746
  ),
@@ -3035,7 +3769,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
3035
3769
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
3036
3770
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
3037
3771
  }
3038
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3772
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3039
3773
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
3040
3774
  if (nestedAliasDecl !== void 0) {
3041
3775
  return resolveAliasedPrimitiveTarget(
@@ -3044,6 +3778,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3044
3778
  file,
3045
3779
  typeRegistry,
3046
3780
  visiting,
3781
+ metadataPolicy,
3047
3782
  extensionRegistry,
3048
3783
  diagnostics
3049
3784
  );
@@ -3055,11 +3790,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3055
3790
  typeRegistry,
3056
3791
  visiting,
3057
3792
  void 0,
3793
+ metadataPolicy,
3058
3794
  extensionRegistry,
3059
3795
  diagnostics
3060
3796
  );
3061
3797
  }
3062
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3798
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3063
3799
  const typeName = getNamedTypeName(type);
3064
3800
  const namedDecl = getNamedTypeDeclaration(type);
3065
3801
  if (typeName && typeName in typeRegistry) {
@@ -3094,8 +3830,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3094
3830
  return result;
3095
3831
  }
3096
3832
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3833
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
3834
+ checker,
3835
+ declaration: namedDecl,
3836
+ subjectType: type
3837
+ }) : void 0;
3097
3838
  typeRegistry[typeName] = {
3098
3839
  name: typeName,
3840
+ ...metadata !== void 0 && { metadata },
3099
3841
  type: result,
3100
3842
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3101
3843
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -3149,6 +3891,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3149
3891
  typeRegistry,
3150
3892
  visiting,
3151
3893
  nonNullMembers[0].sourceNode ?? sourceNode,
3894
+ metadataPolicy,
3152
3895
  extensionRegistry,
3153
3896
  diagnostics
3154
3897
  );
@@ -3166,6 +3909,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3166
3909
  typeRegistry,
3167
3910
  visiting,
3168
3911
  memberSourceNode ?? sourceNode,
3912
+ metadataPolicy,
3169
3913
  extensionRegistry,
3170
3914
  diagnostics
3171
3915
  )
@@ -3175,7 +3919,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3175
3919
  }
3176
3920
  return registerNamed({ kind: "union", members });
3177
3921
  }
3178
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3922
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3179
3923
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
3180
3924
  const elementType = typeArgs?.[0];
3181
3925
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -3186,12 +3930,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
3186
3930
  typeRegistry,
3187
3931
  visiting,
3188
3932
  elementSourceNode,
3933
+ metadataPolicy,
3189
3934
  extensionRegistry,
3190
3935
  diagnostics
3191
3936
  ) : { kind: "primitive", primitiveKind: "string" };
3192
3937
  return { kind: "array", items };
3193
3938
  }
3194
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3939
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3195
3940
  if (type.getProperties().length > 0) {
3196
3941
  return null;
3197
3942
  }
@@ -3206,6 +3951,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
3206
3951
  typeRegistry,
3207
3952
  visiting,
3208
3953
  void 0,
3954
+ metadataPolicy,
3209
3955
  extensionRegistry,
3210
3956
  diagnostics
3211
3957
  );
@@ -3236,7 +3982,22 @@ function typeNodeContainsReference(type, targetName) {
3236
3982
  }
3237
3983
  }
3238
3984
  }
3239
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3985
+ function shouldEmitResolvedObjectProperty(property, declaration) {
3986
+ if (property.name.startsWith("__@")) {
3987
+ return false;
3988
+ }
3989
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
3990
+ const name = declaration.name;
3991
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
3992
+ return false;
3993
+ }
3994
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
3995
+ return false;
3996
+ }
3997
+ }
3998
+ return true;
3999
+ }
4000
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3240
4001
  const collectedDiagnostics = diagnostics ?? [];
3241
4002
  const typeName = getNamedTypeName(type);
3242
4003
  const namedTypeName = typeName ?? void 0;
@@ -3248,6 +4009,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3248
4009
  typeRegistry,
3249
4010
  visiting,
3250
4011
  sourceNode,
4012
+ metadataPolicy,
3251
4013
  extensionRegistry,
3252
4014
  collectedDiagnostics
3253
4015
  );
@@ -3298,6 +4060,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3298
4060
  file,
3299
4061
  typeRegistry,
3300
4062
  visiting,
4063
+ metadataPolicy,
3301
4064
  extensionRegistry,
3302
4065
  collectedDiagnostics
3303
4066
  );
@@ -3310,8 +4073,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3310
4073
  return recordNode;
3311
4074
  }
3312
4075
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4076
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4077
+ checker,
4078
+ declaration: namedDecl,
4079
+ subjectType: type
4080
+ }) : void 0;
3313
4081
  typeRegistry[registryTypeName] = {
3314
4082
  name: registryTypeName,
4083
+ ...metadata !== void 0 && { metadata },
3315
4084
  type: recordNode,
3316
4085
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3317
4086
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3331,12 +4100,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3331
4100
  file,
3332
4101
  typeRegistry,
3333
4102
  visiting,
4103
+ metadataPolicy,
3334
4104
  collectedDiagnostics,
3335
4105
  extensionRegistry
3336
4106
  );
3337
4107
  for (const prop of type.getProperties()) {
3338
4108
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
3339
4109
  if (!declaration) continue;
4110
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
3340
4111
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
3341
4112
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
3342
4113
  const propTypeNode = resolveTypeNode(
@@ -3346,12 +4117,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3346
4117
  typeRegistry,
3347
4118
  visiting,
3348
4119
  declaration,
4120
+ metadataPolicy,
3349
4121
  extensionRegistry,
3350
4122
  collectedDiagnostics
3351
4123
  );
3352
4124
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
3353
4125
  properties.push({
3354
4126
  name: prop.name,
4127
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
3355
4128
  type: propTypeNode,
3356
4129
  optional,
3357
4130
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -3368,14 +4141,21 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3368
4141
  type,
3369
4142
  checker,
3370
4143
  file,
3371
- collectedDiagnostics
4144
+ collectedDiagnostics,
4145
+ metadataPolicy
3372
4146
  ) : properties,
3373
4147
  additionalProperties: true
3374
4148
  };
3375
4149
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3376
4150
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4151
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4152
+ checker,
4153
+ declaration: namedDecl,
4154
+ subjectType: type
4155
+ }) : void 0;
3377
4156
  typeRegistry[registryTypeName] = {
3378
4157
  name: registryTypeName,
4158
+ ...metadata !== void 0 && { metadata },
3379
4159
  type: objectNode,
3380
4160
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3381
4161
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3388,7 +4168,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3388
4168
  }
3389
4169
  return objectNode;
3390
4170
  }
3391
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4171
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
3392
4172
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
3393
4173
  (s) => s?.declarations != null && s.declarations.length > 0
3394
4174
  );
@@ -3409,10 +4189,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3409
4189
  visiting,
3410
4190
  diagnostics,
3411
4191
  hostType,
4192
+ metadataPolicy,
3412
4193
  extensionRegistry
3413
4194
  );
3414
4195
  if (fieldNode) {
3415
4196
  map.set(fieldNode.name, {
4197
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3416
4198
  constraints: [...fieldNode.constraints],
3417
4199
  annotations: [...fieldNode.annotations],
3418
4200
  provenance: fieldNode.provenance
@@ -3430,6 +4212,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3430
4212
  file,
3431
4213
  typeRegistry,
3432
4214
  visiting,
4215
+ metadataPolicy,
3433
4216
  checker.getTypeAtLocation(interfaceDecl),
3434
4217
  diagnostics,
3435
4218
  extensionRegistry
@@ -3443,6 +4226,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3443
4226
  file,
3444
4227
  typeRegistry,
3445
4228
  visiting,
4229
+ metadataPolicy,
3446
4230
  checker.getTypeAtLocation(typeAliasDecl),
3447
4231
  diagnostics,
3448
4232
  extensionRegistry
@@ -3494,7 +4278,7 @@ function isNullishTypeNode(typeNode) {
3494
4278
  }
3495
4279
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3496
4280
  }
3497
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4281
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3498
4282
  const map = /* @__PURE__ */ new Map();
3499
4283
  for (const member of members) {
3500
4284
  if (ts3.isPropertySignature(member)) {
@@ -3506,10 +4290,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3506
4290
  visiting,
3507
4291
  diagnostics,
3508
4292
  hostType,
4293
+ metadataPolicy,
3509
4294
  extensionRegistry
3510
4295
  );
3511
4296
  if (fieldNode) {
3512
4297
  map.set(fieldNode.name, {
4298
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3513
4299
  constraints: [...fieldNode.constraints],
3514
4300
  annotations: [...fieldNode.annotations],
3515
4301
  provenance: fieldNode.provenance
@@ -3540,6 +4326,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3540
4326
  {},
3541
4327
  /* @__PURE__ */ new Set(),
3542
4328
  aliasDecl.type,
4329
+ void 0,
3543
4330
  extensionRegistry
3544
4331
  );
3545
4332
  const constraints = extractJSDocConstraintNodes(
@@ -3725,19 +4512,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3725
4512
  function findTypeAliasByName(sourceFile, aliasName) {
3726
4513
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3727
4514
  }
3728
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4515
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3729
4516
  const ctx = createProgramContext(filePath);
3730
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4517
+ return analyzeNamedTypeToIRFromProgramContext(
4518
+ ctx,
4519
+ filePath,
4520
+ typeName,
4521
+ extensionRegistry,
4522
+ metadataPolicy
4523
+ );
3731
4524
  }
3732
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4525
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3733
4526
  const analysisFilePath = path.resolve(filePath);
3734
4527
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3735
4528
  if (classDecl !== null) {
3736
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4529
+ return analyzeClassToIR(
4530
+ classDecl,
4531
+ ctx.checker,
4532
+ analysisFilePath,
4533
+ extensionRegistry,
4534
+ metadataPolicy
4535
+ );
3737
4536
  }
3738
4537
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3739
4538
  if (interfaceDecl !== null) {
3740
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4539
+ return analyzeInterfaceToIR(
4540
+ interfaceDecl,
4541
+ ctx.checker,
4542
+ analysisFilePath,
4543
+ extensionRegistry,
4544
+ metadataPolicy
4545
+ );
3741
4546
  }
3742
4547
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3743
4548
  if (typeAlias !== null) {
@@ -3745,7 +4550,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3745
4550
  typeAlias,
3746
4551
  ctx.checker,
3747
4552
  analysisFilePath,
3748
- extensionRegistry
4553
+ extensionRegistry,
4554
+ metadataPolicy
3749
4555
  );
3750
4556
  if (result.ok) {
3751
4557
  return result.analysis;
@@ -3840,7 +4646,11 @@ function generateClassSchemas(analysis, source, options) {
3840
4646
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3841
4647
  throw new Error(formatValidationError(errorDiagnostics));
3842
4648
  }
3843
- const ir = canonicalizeTSDoc(analysis, source);
4649
+ const ir = canonicalizeTSDoc(
4650
+ analysis,
4651
+ source,
4652
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4653
+ );
3844
4654
  const validationResult = validateIR(ir, {
3845
4655
  ...options?.extensionRegistry !== void 0 && {
3846
4656
  extensionRegistry: options.extensionRegistry
@@ -3877,13 +4687,15 @@ function generateSchemasFromClass(options) {
3877
4687
  classDecl,
3878
4688
  ctx.checker,
3879
4689
  options.filePath,
3880
- options.extensionRegistry
4690
+ options.extensionRegistry,
4691
+ options.metadata
3881
4692
  );
3882
4693
  return generateClassSchemas(
3883
4694
  analysis,
3884
4695
  { file: options.filePath },
3885
4696
  {
3886
4697
  extensionRegistry: options.extensionRegistry,
4698
+ metadata: options.metadata,
3887
4699
  vendorPrefix: options.vendorPrefix
3888
4700
  }
3889
4701
  );
@@ -3901,13 +4713,15 @@ function generateSchemasFromProgram(options) {
3901
4713
  ctx,
3902
4714
  options.filePath,
3903
4715
  options.typeName,
3904
- options.extensionRegistry
4716
+ options.extensionRegistry,
4717
+ options.metadata
3905
4718
  );
3906
4719
  return generateClassSchemas(
3907
4720
  analysis,
3908
4721
  { file: options.filePath },
3909
4722
  {
3910
4723
  extensionRegistry: options.extensionRegistry,
4724
+ metadata: options.metadata,
3911
4725
  vendorPrefix: options.vendorPrefix
3912
4726
  }
3913
4727
  );
@@ -3916,16 +4730,28 @@ function generateSchemasFromProgram(options) {
3916
4730
  // src/generators/mixed-authoring.ts
3917
4731
  function buildMixedAuthoringSchemas(options) {
3918
4732
  const { filePath, typeName, overlays, ...schemaOptions } = options;
3919
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
3920
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
3921
- const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
4733
+ const analysis = analyzeNamedTypeToIR(
4734
+ filePath,
4735
+ typeName,
4736
+ schemaOptions.extensionRegistry,
4737
+ schemaOptions.metadata
4738
+ );
4739
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays, schemaOptions.metadata);
4740
+ const ir = canonicalizeTSDoc(
4741
+ composedAnalysis,
4742
+ { file: filePath },
4743
+ schemaOptions.metadata !== void 0 ? { metadata: schemaOptions.metadata } : void 0
4744
+ );
3922
4745
  return {
3923
4746
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
3924
4747
  uiSchema: generateUiSchemaFromIR(ir)
3925
4748
  };
3926
4749
  }
3927
- function composeAnalysisWithOverlays(analysis, overlays) {
3928
- const overlayIR = canonicalizeChainDSL(overlays);
4750
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4751
+ const overlayIR = canonicalizeChainDSL(
4752
+ overlays,
4753
+ metadata !== void 0 ? { metadata } : void 0
4754
+ );
3929
4755
  const overlayFields = collectOverlayFields(overlayIR.elements);
3930
4756
  if (overlayFields.length === 0) {
3931
4757
  return analysis;
@@ -3981,8 +4807,10 @@ function collectOverlayFields(elements) {
3981
4807
  }
3982
4808
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3983
4809
  assertSupportedOverlayField(baseField, overlayField);
4810
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
3984
4811
  return {
3985
4812
  ...baseField,
4813
+ ...metadata !== void 0 && { metadata },
3986
4814
  type: mergeFieldType(baseField, overlayField, typeRegistry),
3987
4815
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3988
4816
  };
@@ -4093,12 +4921,12 @@ function annotationKey(annotation) {
4093
4921
  function buildFormSchemas(form, options) {
4094
4922
  return {
4095
4923
  jsonSchema: generateJsonSchema(form, options),
4096
- uiSchema: generateUiSchema(form)
4924
+ uiSchema: generateUiSchema(form, options)
4097
4925
  };
4098
4926
  }
4099
4927
  function writeSchemas(form, options) {
4100
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
4101
- const buildOptions = vendorPrefix === void 0 ? void 0 : { vendorPrefix };
4928
+ const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
4929
+ const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
4102
4930
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
4103
4931
  if (!fs.existsSync(outDir)) {
4104
4932
  fs.mkdirSync(outDir, { recursive: true });