@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/internals.js CHANGED
@@ -1,5 +1,281 @@
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 resolveTypeNodeMetadata(type, options) {
156
+ switch (type.kind) {
157
+ case "array":
158
+ return {
159
+ ...type,
160
+ items: resolveTypeNodeMetadata(type.items, options)
161
+ };
162
+ case "object":
163
+ return {
164
+ ...type,
165
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
166
+ };
167
+ case "record":
168
+ return {
169
+ ...type,
170
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
171
+ };
172
+ case "union":
173
+ return {
174
+ ...type,
175
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
176
+ };
177
+ case "reference":
178
+ case "primitive":
179
+ case "enum":
180
+ case "dynamic":
181
+ case "custom":
182
+ return type;
183
+ default: {
184
+ const _exhaustive = type;
185
+ return _exhaustive;
186
+ }
187
+ }
188
+ }
189
+ function resolveObjectPropertyMetadata(property, options) {
190
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
191
+ surface: options.surface,
192
+ declarationKind: "field",
193
+ logicalName: property.name,
194
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
195
+ });
196
+ return {
197
+ ...property,
198
+ ...metadata !== void 0 && { metadata },
199
+ type: resolveTypeNodeMetadata(property.type, options)
200
+ };
201
+ }
202
+ function resolveFieldMetadataNode(field, options) {
203
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
204
+ surface: options.surface,
205
+ declarationKind: "field",
206
+ logicalName: field.name,
207
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
208
+ });
209
+ return {
210
+ ...field,
211
+ ...metadata !== void 0 && { metadata },
212
+ type: resolveTypeNodeMetadata(field.type, options)
213
+ };
214
+ }
215
+ function resolveFormElementMetadata(element, options) {
216
+ switch (element.kind) {
217
+ case "field":
218
+ return resolveFieldMetadataNode(element, options);
219
+ case "group":
220
+ return {
221
+ ...element,
222
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
223
+ };
224
+ case "conditional":
225
+ return {
226
+ ...element,
227
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
228
+ };
229
+ default: {
230
+ const _exhaustive = element;
231
+ return _exhaustive;
232
+ }
233
+ }
234
+ }
235
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
236
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
237
+ surface: options.surface,
238
+ declarationKind: "type",
239
+ logicalName: typeDefinition.name,
240
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
241
+ });
242
+ return {
243
+ ...typeDefinition,
244
+ ...metadata !== void 0 && { metadata },
245
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
246
+ };
247
+ }
248
+ function resolveMetadata(explicit, policy, context) {
249
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
250
+ }
251
+ function getSerializedName(logicalName, metadata) {
252
+ return metadata?.apiName?.value ?? logicalName;
253
+ }
254
+ function getDisplayName(metadata) {
255
+ return metadata?.displayName?.value;
256
+ }
257
+ function resolveFormIRMetadata(ir, options) {
258
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
259
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
260
+ surface: options.surface,
261
+ declarationKind: "type",
262
+ logicalName: rootLogicalName,
263
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
264
+ });
265
+ return {
266
+ ...ir,
267
+ ...metadata !== void 0 && { metadata },
268
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
269
+ typeRegistry: Object.fromEntries(
270
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
271
+ name,
272
+ resolveTypeDefinitionMetadata(definition, options)
273
+ ])
274
+ )
275
+ };
276
+ }
277
+
278
+ // src/canonicalize/chain-dsl-canonicalizer.ts
3
279
  var CHAIN_DSL_PROVENANCE = {
4
280
  surface: "chain-dsl",
5
281
  file: "",
@@ -15,57 +291,60 @@ function isConditional(el) {
15
291
  function isField(el) {
16
292
  return el._type === "field";
17
293
  }
18
- function canonicalizeChainDSL(form) {
294
+ function canonicalizeChainDSL(form, options) {
295
+ const metadataPolicy = normalizeMetadataPolicy(
296
+ options?.metadata ?? _getFormSpecMetadataPolicy(form)
297
+ );
19
298
  return {
20
299
  kind: "form-ir",
21
300
  irVersion: IR_VERSION,
22
- elements: canonicalizeElements(form.elements),
301
+ elements: canonicalizeElements(form.elements, metadataPolicy),
23
302
  rootAnnotations: [],
24
303
  typeRegistry: {},
25
304
  provenance: CHAIN_DSL_PROVENANCE
26
305
  };
27
306
  }
28
- function canonicalizeElements(elements) {
29
- return elements.map(canonicalizeElement);
307
+ function canonicalizeElements(elements, metadataPolicy) {
308
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
30
309
  }
31
- function canonicalizeElement(element) {
310
+ function canonicalizeElement(element, metadataPolicy) {
32
311
  if (isField(element)) {
33
- return canonicalizeField(element);
312
+ return canonicalizeField(element, metadataPolicy);
34
313
  }
35
314
  if (isGroup(element)) {
36
- return canonicalizeGroup(element);
315
+ return canonicalizeGroup(element, metadataPolicy);
37
316
  }
38
317
  if (isConditional(element)) {
39
- return canonicalizeConditional(element);
318
+ return canonicalizeConditional(element, metadataPolicy);
40
319
  }
41
320
  const _exhaustive = element;
42
321
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
43
322
  }
44
- function canonicalizeField(field) {
323
+ function canonicalizeField(field, metadataPolicy) {
45
324
  switch (field._field) {
46
325
  case "text":
47
- return canonicalizeTextField(field);
326
+ return canonicalizeTextField(field, metadataPolicy);
48
327
  case "number":
49
- return canonicalizeNumberField(field);
328
+ return canonicalizeNumberField(field, metadataPolicy);
50
329
  case "boolean":
51
- return canonicalizeBooleanField(field);
330
+ return canonicalizeBooleanField(field, metadataPolicy);
52
331
  case "enum":
53
- return canonicalizeStaticEnumField(field);
332
+ return canonicalizeStaticEnumField(field, metadataPolicy);
54
333
  case "dynamic_enum":
55
- return canonicalizeDynamicEnumField(field);
334
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
56
335
  case "dynamic_schema":
57
- return canonicalizeDynamicSchemaField(field);
336
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
58
337
  case "array":
59
- return canonicalizeArrayField(field);
338
+ return canonicalizeArrayField(field, metadataPolicy);
60
339
  case "object":
61
- return canonicalizeObjectField(field);
340
+ return canonicalizeObjectField(field, metadataPolicy);
62
341
  default: {
63
342
  const _exhaustive = field;
64
343
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
65
344
  }
66
345
  }
67
346
  }
68
- function canonicalizeTextField(field) {
347
+ function canonicalizeTextField(field, metadataPolicy) {
69
348
  const type = { kind: "primitive", primitiveKind: "string" };
70
349
  const constraints = [];
71
350
  if (field.minLength !== void 0) {
@@ -97,13 +376,14 @@ function canonicalizeTextField(field) {
97
376
  }
98
377
  return buildFieldNode(
99
378
  field.name,
379
+ resolveFieldMetadata(field.name, field, metadataPolicy),
100
380
  type,
101
381
  field.required,
102
- buildAnnotations(field.label, field.placeholder),
382
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
103
383
  constraints
104
384
  );
105
385
  }
106
- function canonicalizeNumberField(field) {
386
+ function canonicalizeNumberField(field, metadataPolicy) {
107
387
  const type = { kind: "primitive", primitiveKind: "number" };
108
388
  const constraints = [];
109
389
  if (field.min !== void 0) {
@@ -135,17 +415,24 @@ function canonicalizeNumberField(field) {
135
415
  }
136
416
  return buildFieldNode(
137
417
  field.name,
418
+ resolveFieldMetadata(field.name, field, metadataPolicy),
138
419
  type,
139
420
  field.required,
140
- buildAnnotations(field.label),
421
+ buildAnnotations(getExplicitDisplayName(field)),
141
422
  constraints
142
423
  );
143
424
  }
144
- function canonicalizeBooleanField(field) {
425
+ function canonicalizeBooleanField(field, metadataPolicy) {
145
426
  const type = { kind: "primitive", primitiveKind: "boolean" };
146
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
427
+ return buildFieldNode(
428
+ field.name,
429
+ resolveFieldMetadata(field.name, field, metadataPolicy),
430
+ type,
431
+ field.required,
432
+ buildAnnotations(getExplicitDisplayName(field))
433
+ );
147
434
  }
148
- function canonicalizeStaticEnumField(field) {
435
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
149
436
  const members = field.options.map((opt) => {
150
437
  if (typeof opt === "string") {
151
438
  return { value: opt };
@@ -153,28 +440,46 @@ function canonicalizeStaticEnumField(field) {
153
440
  return { value: opt.id, displayName: opt.label };
154
441
  });
155
442
  const type = { kind: "enum", members };
156
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
443
+ return buildFieldNode(
444
+ field.name,
445
+ resolveFieldMetadata(field.name, field, metadataPolicy),
446
+ type,
447
+ field.required,
448
+ buildAnnotations(getExplicitDisplayName(field))
449
+ );
157
450
  }
158
- function canonicalizeDynamicEnumField(field) {
451
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
159
452
  const type = {
160
453
  kind: "dynamic",
161
454
  dynamicKind: "enum",
162
455
  sourceKey: field.source,
163
456
  parameterFields: field.params ? [...field.params] : []
164
457
  };
165
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
458
+ return buildFieldNode(
459
+ field.name,
460
+ resolveFieldMetadata(field.name, field, metadataPolicy),
461
+ type,
462
+ field.required,
463
+ buildAnnotations(getExplicitDisplayName(field))
464
+ );
166
465
  }
167
- function canonicalizeDynamicSchemaField(field) {
466
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
168
467
  const type = {
169
468
  kind: "dynamic",
170
469
  dynamicKind: "schema",
171
470
  sourceKey: field.schemaSource,
172
471
  parameterFields: []
173
472
  };
174
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
473
+ return buildFieldNode(
474
+ field.name,
475
+ resolveFieldMetadata(field.name, field, metadataPolicy),
476
+ type,
477
+ field.required,
478
+ buildAnnotations(getExplicitDisplayName(field))
479
+ );
175
480
  }
176
- function canonicalizeArrayField(field) {
177
- const itemProperties = buildObjectProperties(field.items);
481
+ function canonicalizeArrayField(field, metadataPolicy) {
482
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
178
483
  const itemsType = {
179
484
  kind: "object",
180
485
  properties: itemProperties,
@@ -202,37 +507,44 @@ function canonicalizeArrayField(field) {
202
507
  }
203
508
  return buildFieldNode(
204
509
  field.name,
510
+ resolveFieldMetadata(field.name, field, metadataPolicy),
205
511
  type,
206
512
  field.required,
207
- buildAnnotations(field.label),
513
+ buildAnnotations(getExplicitDisplayName(field)),
208
514
  constraints
209
515
  );
210
516
  }
211
- function canonicalizeObjectField(field) {
212
- const properties = buildObjectProperties(field.properties);
517
+ function canonicalizeObjectField(field, metadataPolicy) {
518
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
213
519
  const type = {
214
520
  kind: "object",
215
521
  properties,
216
522
  additionalProperties: true
217
523
  };
218
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
524
+ return buildFieldNode(
525
+ field.name,
526
+ resolveFieldMetadata(field.name, field, metadataPolicy),
527
+ type,
528
+ field.required,
529
+ buildAnnotations(getExplicitDisplayName(field))
530
+ );
219
531
  }
220
- function canonicalizeGroup(g) {
532
+ function canonicalizeGroup(g, metadataPolicy) {
221
533
  return {
222
534
  kind: "group",
223
535
  label: g.label,
224
- elements: canonicalizeElements(g.elements),
536
+ elements: canonicalizeElements(g.elements, metadataPolicy),
225
537
  provenance: CHAIN_DSL_PROVENANCE
226
538
  };
227
539
  }
228
- function canonicalizeConditional(c) {
540
+ function canonicalizeConditional(c, metadataPolicy) {
229
541
  return {
230
542
  kind: "conditional",
231
543
  fieldName: c.field,
232
544
  // Conditional values from the chain DSL are JSON-serializable primitives
233
545
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
234
546
  value: assertJsonValue(c.value),
235
- elements: canonicalizeElements(c.elements),
547
+ elements: canonicalizeElements(c.elements, metadataPolicy),
236
548
  provenance: CHAIN_DSL_PROVENANCE
237
549
  };
238
550
  }
@@ -252,10 +564,11 @@ function assertJsonValue(v) {
252
564
  }
253
565
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
254
566
  }
255
- function buildFieldNode(name, type, required, annotations, constraints = []) {
567
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
256
568
  return {
257
569
  kind: "field",
258
570
  name,
571
+ ...metadata !== void 0 && { metadata },
259
572
  type,
260
573
  required: required === true,
261
574
  constraints,
@@ -285,13 +598,14 @@ function buildAnnotations(label, placeholder) {
285
598
  }
286
599
  return annotations;
287
600
  }
288
- function buildObjectProperties(elements, insideConditional = false) {
601
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
289
602
  const properties = [];
290
603
  for (const el of elements) {
291
604
  if (isField(el)) {
292
- const fieldNode = canonicalizeField(el);
605
+ const fieldNode = canonicalizeField(el, metadataPolicy);
293
606
  properties.push({
294
607
  name: fieldNode.name,
608
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
295
609
  type: fieldNode.type,
296
610
  // Fields inside a conditional branch are always optional in the
297
611
  // data schema, regardless of their `required` flag — the condition
@@ -302,17 +616,34 @@ function buildObjectProperties(elements, insideConditional = false) {
302
616
  provenance: CHAIN_DSL_PROVENANCE
303
617
  });
304
618
  } else if (isGroup(el)) {
305
- properties.push(...buildObjectProperties(el.elements, insideConditional));
619
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
306
620
  } else if (isConditional(el)) {
307
- properties.push(...buildObjectProperties(el.elements, true));
621
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
308
622
  }
309
623
  }
310
624
  return properties;
311
625
  }
626
+ function getExplicitDisplayName(field) {
627
+ if (field.label !== void 0 && field.displayName !== void 0) {
628
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
629
+ }
630
+ return field.displayName ?? field.label;
631
+ }
632
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
633
+ const displayName = getExplicitDisplayName(field);
634
+ return resolveMetadata(
635
+ {
636
+ ...field.apiName !== void 0 && { apiName: field.apiName },
637
+ ...displayName !== void 0 && { displayName }
638
+ },
639
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
640
+ makeMetadataContext("chain-dsl", "field", logicalName)
641
+ );
642
+ }
312
643
 
313
644
  // src/canonicalize/tsdoc-canonicalizer.ts
314
645
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
315
- function canonicalizeTSDoc(analysis, source) {
646
+ function canonicalizeTSDoc(analysis, source, options) {
316
647
  const file = source?.file ?? "";
317
648
  const provenance = {
318
649
  surface: "tsdoc",
@@ -321,15 +652,21 @@ function canonicalizeTSDoc(analysis, source) {
321
652
  column: 0
322
653
  };
323
654
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
324
- return {
655
+ const ir = {
325
656
  kind: "form-ir",
657
+ name: analysis.name,
326
658
  irVersion: IR_VERSION2,
327
659
  elements,
660
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
328
661
  typeRegistry: analysis.typeRegistry,
329
662
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
330
663
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
331
664
  provenance
332
665
  };
666
+ return resolveFormIRMetadata(ir, {
667
+ policy: normalizeMetadataPolicy(options?.metadata),
668
+ surface: "tsdoc"
669
+ });
333
670
  }
334
671
  function assembleElements(fields, layouts, provenance) {
335
672
  const elements = [];
@@ -1297,7 +1634,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
1297
1634
  ...hostType !== void 0 && { hostType }
1298
1635
  };
1299
1636
  }
1300
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1637
+ function makeExplicitScalarMetadata(value) {
1638
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
1639
+ }
1640
+ function extractExplicitMetadata(node) {
1641
+ let apiName;
1642
+ let displayName;
1643
+ let apiNamePlural;
1644
+ let displayNamePlural;
1645
+ for (const tag of getLeadingParsedTags(node)) {
1646
+ const value = tag.argumentText.trim();
1647
+ if (value === "") {
1648
+ continue;
1649
+ }
1650
+ if (tag.normalizedTagName === "apiName") {
1651
+ if (tag.target === null) {
1652
+ apiName ??= value;
1653
+ } else if (tag.target.kind === "variant") {
1654
+ if (tag.target.rawText === "singular") {
1655
+ apiName ??= value;
1656
+ } else if (tag.target.rawText === "plural") {
1657
+ apiNamePlural ??= value;
1658
+ }
1659
+ }
1660
+ continue;
1661
+ }
1662
+ if (tag.normalizedTagName === "displayName") {
1663
+ if (tag.target === null) {
1664
+ displayName ??= value;
1665
+ } else if (tag.target.kind === "variant") {
1666
+ if (tag.target.rawText === "singular") {
1667
+ displayName ??= value;
1668
+ } else if (tag.target.rawText === "plural") {
1669
+ displayNamePlural ??= value;
1670
+ }
1671
+ }
1672
+ }
1673
+ }
1674
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
1675
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
1676
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
1677
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
1678
+ const metadata = {
1679
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
1680
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
1681
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
1682
+ ...resolvedDisplayNamePlural !== void 0 && {
1683
+ displayNamePlural: resolvedDisplayNamePlural
1684
+ }
1685
+ };
1686
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
1687
+ }
1688
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
1689
+ const explicit = extractExplicitMetadata(node);
1690
+ return resolveMetadata(
1691
+ {
1692
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
1693
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
1694
+ ...explicit?.apiNamePlural !== void 0 && {
1695
+ apiNamePlural: explicit.apiNamePlural.value
1696
+ },
1697
+ ...explicit?.displayNamePlural !== void 0 && {
1698
+ displayNamePlural: explicit.displayNamePlural.value
1699
+ }
1700
+ },
1701
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
1702
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
1703
+ );
1704
+ }
1705
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1706
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1301
1707
  const name = classDecl.name?.text ?? "AnonymousClass";
1302
1708
  const fields = [];
1303
1709
  const fieldLayouts = [];
@@ -1324,6 +1730,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1324
1730
  visiting,
1325
1731
  diagnostics,
1326
1732
  classType,
1733
+ normalizedMetadataPolicy,
1327
1734
  extensionRegistry
1328
1735
  );
1329
1736
  if (fieldNode) {
@@ -1348,10 +1755,18 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1348
1755
  classType,
1349
1756
  checker,
1350
1757
  file,
1351
- diagnostics
1758
+ diagnostics,
1759
+ normalizedMetadataPolicy
1352
1760
  );
1761
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
1762
+ checker,
1763
+ declaration: classDecl,
1764
+ subjectType: classType,
1765
+ hostType: classType
1766
+ });
1353
1767
  return {
1354
1768
  name,
1769
+ ...metadata !== void 0 && { metadata },
1355
1770
  fields: specializedFields,
1356
1771
  fieldLayouts,
1357
1772
  typeRegistry,
@@ -1361,7 +1776,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
1361
1776
  staticMethods
1362
1777
  };
1363
1778
  }
1364
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
1779
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
1780
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1365
1781
  const name = interfaceDecl.name.text;
1366
1782
  const fields = [];
1367
1783
  const typeRegistry = {};
@@ -1385,6 +1801,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1385
1801
  visiting,
1386
1802
  diagnostics,
1387
1803
  interfaceType,
1804
+ normalizedMetadataPolicy,
1388
1805
  extensionRegistry
1389
1806
  );
1390
1807
  if (fieldNode) {
@@ -1398,11 +1815,19 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1398
1815
  interfaceType,
1399
1816
  checker,
1400
1817
  file,
1401
- diagnostics
1818
+ diagnostics,
1819
+ normalizedMetadataPolicy
1402
1820
  );
1403
1821
  const fieldLayouts = specializedFields.map(() => ({}));
1822
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
1823
+ checker,
1824
+ declaration: interfaceDecl,
1825
+ subjectType: interfaceType,
1826
+ hostType: interfaceType
1827
+ });
1404
1828
  return {
1405
1829
  name,
1830
+ ...metadata !== void 0 && { metadata },
1406
1831
  fields: specializedFields,
1407
1832
  fieldLayouts,
1408
1833
  typeRegistry,
@@ -1412,7 +1837,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
1412
1837
  staticMethods: []
1413
1838
  };
1414
1839
  }
1415
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
1840
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
1416
1841
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
1417
1842
  const sourceFile = typeAlias.getSourceFile();
1418
1843
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -1422,6 +1847,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1422
1847
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
1423
1848
  };
1424
1849
  }
1850
+ const typeLiteral = typeAlias.type;
1851
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
1425
1852
  const name = typeAlias.name.text;
1426
1853
  const fields = [];
1427
1854
  const typeRegistry = {};
@@ -1435,7 +1862,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1435
1862
  const annotations = [...typeAliasDoc.annotations];
1436
1863
  diagnostics.push(...typeAliasDoc.diagnostics);
1437
1864
  const visiting = /* @__PURE__ */ new Set();
1438
- for (const member of typeAlias.type.members) {
1865
+ for (const member of typeLiteral.members) {
1439
1866
  if (ts3.isPropertySignature(member)) {
1440
1867
  const fieldNode = analyzeInterfacePropertyToIR(
1441
1868
  member,
@@ -1445,6 +1872,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1445
1872
  visiting,
1446
1873
  diagnostics,
1447
1874
  aliasType,
1875
+ normalizedMetadataPolicy,
1448
1876
  extensionRegistry
1449
1877
  );
1450
1878
  if (fieldNode) {
@@ -1458,12 +1886,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
1458
1886
  aliasType,
1459
1887
  checker,
1460
1888
  file,
1461
- diagnostics
1889
+ diagnostics,
1890
+ normalizedMetadataPolicy
1462
1891
  );
1892
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
1893
+ checker,
1894
+ declaration: typeAlias,
1895
+ subjectType: aliasType,
1896
+ hostType: aliasType
1897
+ });
1463
1898
  return {
1464
1899
  ok: true,
1465
1900
  analysis: {
1466
1901
  name,
1902
+ ...metadata !== void 0 && { metadata },
1467
1903
  fields: specializedFields,
1468
1904
  fieldLayouts: specializedFields.map(() => ({})),
1469
1905
  typeRegistry,
@@ -1503,31 +1939,20 @@ function getLeadingParsedTags(node) {
1503
1939
  }
1504
1940
  return parsedTags;
1505
1941
  }
1506
- function findDiscriminatorProperty(node, fieldName) {
1507
- if (ts3.isClassDeclaration(node)) {
1508
- for (const member of node.members) {
1509
- if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1510
- return member;
1511
- }
1512
- }
1513
- return null;
1514
- }
1515
- if (ts3.isInterfaceDeclaration(node)) {
1516
- for (const member of node.members) {
1517
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1518
- return member;
1519
- }
1520
- }
1942
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
1943
+ const subjectType = checker.getTypeAtLocation(node);
1944
+ const propertySymbol = subjectType.getProperty(fieldName);
1945
+ if (propertySymbol === void 0) {
1521
1946
  return null;
1522
1947
  }
1523
- if (ts3.isTypeLiteralNode(node.type)) {
1524
- for (const member of node.type.members) {
1525
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
1526
- return member;
1527
- }
1528
- }
1529
- }
1530
- return null;
1948
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
1949
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
1950
+ ) ?? propertySymbol.declarations?.[0];
1951
+ return {
1952
+ declaration,
1953
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
1954
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
1955
+ };
1531
1956
  }
1532
1957
  function isLocalTypeParameterName(node, typeParameterName) {
1533
1958
  return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
@@ -1620,8 +2045,8 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
1620
2045
  );
1621
2046
  return null;
1622
2047
  }
1623
- const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
1624
- if (propertyDecl === null) {
2048
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
2049
+ if (property === null) {
1625
2050
  diagnostics.push(
1626
2051
  makeAnalysisDiagnostic(
1627
2052
  "UNKNOWN_PATH_TARGET",
@@ -1631,36 +2056,35 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
1631
2056
  );
1632
2057
  return null;
1633
2058
  }
1634
- if (propertyDecl.questionToken !== void 0) {
2059
+ if (property.optional) {
1635
2060
  diagnostics.push(
1636
2061
  makeAnalysisDiagnostic(
1637
2062
  "TYPE_MISMATCH",
1638
2063
  `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
1639
2064
  directive.provenance,
1640
- [provenanceForNode(propertyDecl, file)]
2065
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1641
2066
  )
1642
2067
  );
1643
2068
  return null;
1644
2069
  }
1645
- const propertyType = checker.getTypeAtLocation(propertyDecl);
1646
- if (isNullishSemanticType(propertyType)) {
2070
+ if (isNullishSemanticType(property.type)) {
1647
2071
  diagnostics.push(
1648
2072
  makeAnalysisDiagnostic(
1649
2073
  "TYPE_MISMATCH",
1650
2074
  `Discriminator field "${directive.fieldName}" must not be nullable.`,
1651
2075
  directive.provenance,
1652
- [provenanceForNode(propertyDecl, file)]
2076
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1653
2077
  )
1654
2078
  );
1655
2079
  return null;
1656
2080
  }
1657
- if (!isStringLikeSemanticType(propertyType)) {
2081
+ if (!isStringLikeSemanticType(property.type)) {
1658
2082
  diagnostics.push(
1659
2083
  makeAnalysisDiagnostic(
1660
2084
  "TYPE_MISMATCH",
1661
2085
  `Discriminator field "${directive.fieldName}" must be string-like.`,
1662
2086
  directive.provenance,
1663
- [provenanceForNode(propertyDecl, file)]
2087
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
1664
2088
  )
1665
2089
  );
1666
2090
  return null;
@@ -1681,25 +2105,58 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
1681
2105
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
1682
2106
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
1683
2107
  }
1684
- function extractDeclarationApiName(node) {
1685
- for (const tag of getLeadingParsedTags(node)) {
1686
- if (tag.normalizedTagName !== "apiName") {
1687
- continue;
1688
- }
1689
- if (tag.target === null && tag.argumentText.trim() !== "") {
1690
- return tag.argumentText.trim();
1691
- }
1692
- if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
1693
- const value = tag.argumentText.trim();
1694
- if (value !== "") {
1695
- return value;
1696
- }
2108
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
2109
+ const propertySymbol = boundType.getProperty(fieldName);
2110
+ if (propertySymbol === void 0) {
2111
+ return void 0;
2112
+ }
2113
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
2114
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
2115
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
2116
+ if (resolvedAnchorNode === null) {
2117
+ return void 0;
2118
+ }
2119
+ const propertyType = checker.getTypeOfSymbolAtLocation(
2120
+ propertySymbol,
2121
+ resolvedAnchorNode
2122
+ );
2123
+ if (propertyType.isStringLiteral()) {
2124
+ return propertyType.value;
2125
+ }
2126
+ if (propertyType.isUnion()) {
2127
+ const nonNullMembers = propertyType.types.filter(
2128
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
2129
+ );
2130
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
2131
+ diagnostics.push(
2132
+ makeAnalysisDiagnostic(
2133
+ "INVALID_TAG_ARGUMENT",
2134
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
2135
+ provenance
2136
+ )
2137
+ );
2138
+ return null;
1697
2139
  }
1698
2140
  }
1699
- return null;
2141
+ return void 0;
1700
2142
  }
1701
- function inferJsonFacingName(name) {
1702
- 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();
2143
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
2144
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2145
+ if (declaration === null) {
2146
+ return void 0;
2147
+ }
2148
+ const metadata = resolveNodeMetadata(
2149
+ metadataPolicy,
2150
+ "type",
2151
+ getDiscriminatorLogicalName(boundType, declaration, checker),
2152
+ declaration,
2153
+ {
2154
+ checker,
2155
+ declaration,
2156
+ subjectType: boundType
2157
+ }
2158
+ );
2159
+ return metadata?.apiName;
1703
2160
  }
1704
2161
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
1705
2162
  if (seen.has(type)) {
@@ -1726,7 +2183,7 @@ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__
1726
2183
  }
1727
2184
  return null;
1728
2185
  }
1729
- function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
2186
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
1730
2187
  if (boundType === null) {
1731
2188
  diagnostics.push(
1732
2189
  makeAnalysisDiagnostic(
@@ -1755,9 +2212,22 @@ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics)
1755
2212
  return null;
1756
2213
  }
1757
2214
  }
1758
- const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
1759
- if (declaration !== null) {
1760
- return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
2215
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
2216
+ boundType,
2217
+ fieldName,
2218
+ checker,
2219
+ provenance,
2220
+ diagnostics
2221
+ );
2222
+ if (literalIdentityValue !== void 0) {
2223
+ return literalIdentityValue;
2224
+ }
2225
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
2226
+ if (apiName?.source === "explicit") {
2227
+ return apiName.value;
2228
+ }
2229
+ if (apiName?.source === "inferred") {
2230
+ return apiName.value;
1761
2231
  }
1762
2232
  diagnostics.push(
1763
2233
  makeAnalysisDiagnostic(
@@ -1774,7 +2244,15 @@ function getDeclarationName(node) {
1774
2244
  }
1775
2245
  return "anonymous";
1776
2246
  }
1777
- function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
2247
+ function getResolvedTypeArguments(type) {
2248
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
2249
+ }
2250
+ function getDiscriminatorLogicalName(type, declaration, checker) {
2251
+ const baseName = getDeclarationName(declaration);
2252
+ const typeArguments = getResolvedTypeArguments(type);
2253
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
2254
+ }
2255
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
1778
2256
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
1779
2257
  if (directive === null) {
1780
2258
  return [...fields];
@@ -1786,9 +2264,11 @@ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checke
1786
2264
  checker,
1787
2265
  directive.typeParameterName
1788
2266
  ),
2267
+ directive.fieldName,
1789
2268
  checker,
1790
2269
  directive.provenance,
1791
- diagnostics
2270
+ diagnostics,
2271
+ metadataPolicy
1792
2272
  );
1793
2273
  if (discriminatorValue === null) {
1794
2274
  return [...fields];
@@ -1809,7 +2289,7 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
1809
2289
  ).filter((value) => value !== "");
1810
2290
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
1811
2291
  }
1812
- function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2292
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
1813
2293
  const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
1814
2294
  if (typeNode === void 0) {
1815
2295
  return [];
@@ -1829,13 +2309,14 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
1829
2309
  typeRegistry,
1830
2310
  visiting,
1831
2311
  argumentNode,
2312
+ metadataPolicy,
1832
2313
  extensionRegistry,
1833
2314
  diagnostics
1834
2315
  )
1835
2316
  };
1836
2317
  });
1837
2318
  }
1838
- function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
2319
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
1839
2320
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
1840
2321
  if (directive === null) {
1841
2322
  return properties;
@@ -1847,9 +2328,11 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
1847
2328
  checker,
1848
2329
  directive.typeParameterName
1849
2330
  ),
2331
+ directive.fieldName,
1850
2332
  checker,
1851
2333
  directive.provenance,
1852
- diagnostics
2334
+ diagnostics,
2335
+ metadataPolicy
1853
2336
  );
1854
2337
  if (discriminatorValue === null) {
1855
2338
  return properties;
@@ -1864,7 +2347,7 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
1864
2347
  } : property
1865
2348
  );
1866
2349
  }
1867
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2350
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
1868
2351
  if (!ts3.isIdentifier(prop.name)) {
1869
2352
  return null;
1870
2353
  }
@@ -1879,6 +2362,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1879
2362
  typeRegistry,
1880
2363
  visiting,
1881
2364
  prop,
2365
+ metadataPolicy,
1882
2366
  extensionRegistry,
1883
2367
  diagnostics
1884
2368
  );
@@ -1902,9 +2386,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1902
2386
  annotations.push(defaultAnnotation);
1903
2387
  }
1904
2388
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2389
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2390
+ checker,
2391
+ declaration: prop,
2392
+ subjectType: tsType,
2393
+ hostType
2394
+ });
1905
2395
  return {
1906
2396
  kind: "field",
1907
2397
  name,
2398
+ ...metadata !== void 0 && { metadata },
1908
2399
  type,
1909
2400
  required: !optional,
1910
2401
  constraints,
@@ -1912,7 +2403,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
1912
2403
  provenance
1913
2404
  };
1914
2405
  }
1915
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
2406
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
1916
2407
  if (!ts3.isIdentifier(prop.name)) {
1917
2408
  return null;
1918
2409
  }
@@ -1927,6 +2418,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1927
2418
  typeRegistry,
1928
2419
  visiting,
1929
2420
  prop,
2421
+ metadataPolicy,
1930
2422
  extensionRegistry,
1931
2423
  diagnostics
1932
2424
  );
@@ -1946,9 +2438,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1946
2438
  let annotations = [];
1947
2439
  annotations.push(...docResult.annotations);
1948
2440
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
2441
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
2442
+ checker,
2443
+ declaration: prop,
2444
+ subjectType: tsType,
2445
+ hostType
2446
+ });
1949
2447
  return {
1950
2448
  kind: "field",
1951
2449
  name,
2450
+ ...metadata !== void 0 && { metadata },
1952
2451
  type,
1953
2452
  required: !optional,
1954
2453
  constraints,
@@ -2073,7 +2572,7 @@ function getTypeNodeRegistrationName(typeNode) {
2073
2572
  }
2074
2573
  return null;
2075
2574
  }
2076
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2575
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2077
2576
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2078
2577
  if (customType) {
2079
2578
  return customType;
@@ -2085,6 +2584,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2085
2584
  typeRegistry,
2086
2585
  visiting,
2087
2586
  sourceNode,
2587
+ metadataPolicy,
2088
2588
  extensionRegistry,
2089
2589
  diagnostics
2090
2590
  );
@@ -2129,6 +2629,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2129
2629
  typeRegistry,
2130
2630
  visiting,
2131
2631
  sourceNode,
2632
+ metadataPolicy,
2132
2633
  extensionRegistry,
2133
2634
  diagnostics
2134
2635
  );
@@ -2141,6 +2642,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2141
2642
  typeRegistry,
2142
2643
  visiting,
2143
2644
  sourceNode,
2645
+ metadataPolicy,
2144
2646
  extensionRegistry,
2145
2647
  diagnostics
2146
2648
  );
@@ -2153,13 +2655,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2153
2655
  typeRegistry,
2154
2656
  visiting,
2155
2657
  sourceNode,
2658
+ metadataPolicy,
2156
2659
  extensionRegistry,
2157
2660
  diagnostics
2158
2661
  );
2159
2662
  }
2160
2663
  return { kind: "primitive", primitiveKind: "string" };
2161
2664
  }
2162
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2665
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2163
2666
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2164
2667
  return null;
2165
2668
  }
@@ -2179,14 +2682,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2179
2682
  file,
2180
2683
  makeParseOptions(extensionRegistry)
2181
2684
  );
2685
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
2686
+ checker,
2687
+ declaration: aliasDecl,
2688
+ subjectType: aliasType
2689
+ });
2182
2690
  typeRegistry[aliasName] = {
2183
2691
  name: aliasName,
2692
+ ...metadata !== void 0 && { metadata },
2184
2693
  type: resolveAliasedPrimitiveTarget(
2185
2694
  aliasType,
2186
2695
  checker,
2187
2696
  file,
2188
2697
  typeRegistry,
2189
2698
  visiting,
2699
+ metadataPolicy,
2190
2700
  extensionRegistry,
2191
2701
  diagnostics
2192
2702
  ),
@@ -2215,7 +2725,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2215
2725
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2216
2726
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2217
2727
  }
2218
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2728
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2219
2729
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2220
2730
  if (nestedAliasDecl !== void 0) {
2221
2731
  return resolveAliasedPrimitiveTarget(
@@ -2224,6 +2734,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2224
2734
  file,
2225
2735
  typeRegistry,
2226
2736
  visiting,
2737
+ metadataPolicy,
2227
2738
  extensionRegistry,
2228
2739
  diagnostics
2229
2740
  );
@@ -2235,11 +2746,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2235
2746
  typeRegistry,
2236
2747
  visiting,
2237
2748
  void 0,
2749
+ metadataPolicy,
2238
2750
  extensionRegistry,
2239
2751
  diagnostics
2240
2752
  );
2241
2753
  }
2242
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2754
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2243
2755
  const typeName = getNamedTypeName(type);
2244
2756
  const namedDecl = getNamedTypeDeclaration(type);
2245
2757
  if (typeName && typeName in typeRegistry) {
@@ -2274,8 +2786,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2274
2786
  return result;
2275
2787
  }
2276
2788
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2789
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
2790
+ checker,
2791
+ declaration: namedDecl,
2792
+ subjectType: type
2793
+ }) : void 0;
2277
2794
  typeRegistry[typeName] = {
2278
2795
  name: typeName,
2796
+ ...metadata !== void 0 && { metadata },
2279
2797
  type: result,
2280
2798
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2281
2799
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -2329,6 +2847,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2329
2847
  typeRegistry,
2330
2848
  visiting,
2331
2849
  nonNullMembers[0].sourceNode ?? sourceNode,
2850
+ metadataPolicy,
2332
2851
  extensionRegistry,
2333
2852
  diagnostics
2334
2853
  );
@@ -2346,6 +2865,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2346
2865
  typeRegistry,
2347
2866
  visiting,
2348
2867
  memberSourceNode ?? sourceNode,
2868
+ metadataPolicy,
2349
2869
  extensionRegistry,
2350
2870
  diagnostics
2351
2871
  )
@@ -2355,7 +2875,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2355
2875
  }
2356
2876
  return registerNamed({ kind: "union", members });
2357
2877
  }
2358
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2878
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2359
2879
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2360
2880
  const elementType = typeArgs?.[0];
2361
2881
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2366,12 +2886,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2366
2886
  typeRegistry,
2367
2887
  visiting,
2368
2888
  elementSourceNode,
2889
+ metadataPolicy,
2369
2890
  extensionRegistry,
2370
2891
  diagnostics
2371
2892
  ) : { kind: "primitive", primitiveKind: "string" };
2372
2893
  return { kind: "array", items };
2373
2894
  }
2374
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
2895
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2375
2896
  if (type.getProperties().length > 0) {
2376
2897
  return null;
2377
2898
  }
@@ -2386,6 +2907,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2386
2907
  typeRegistry,
2387
2908
  visiting,
2388
2909
  void 0,
2910
+ metadataPolicy,
2389
2911
  extensionRegistry,
2390
2912
  diagnostics
2391
2913
  );
@@ -2416,7 +2938,22 @@ function typeNodeContainsReference(type, targetName) {
2416
2938
  }
2417
2939
  }
2418
2940
  }
2419
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
2941
+ function shouldEmitResolvedObjectProperty(property, declaration) {
2942
+ if (property.name.startsWith("__@")) {
2943
+ return false;
2944
+ }
2945
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
2946
+ const name = declaration.name;
2947
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
2948
+ return false;
2949
+ }
2950
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
2951
+ return false;
2952
+ }
2953
+ }
2954
+ return true;
2955
+ }
2956
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2420
2957
  const collectedDiagnostics = diagnostics ?? [];
2421
2958
  const typeName = getNamedTypeName(type);
2422
2959
  const namedTypeName = typeName ?? void 0;
@@ -2428,6 +2965,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2428
2965
  typeRegistry,
2429
2966
  visiting,
2430
2967
  sourceNode,
2968
+ metadataPolicy,
2431
2969
  extensionRegistry,
2432
2970
  collectedDiagnostics
2433
2971
  );
@@ -2478,6 +3016,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2478
3016
  file,
2479
3017
  typeRegistry,
2480
3018
  visiting,
3019
+ metadataPolicy,
2481
3020
  extensionRegistry,
2482
3021
  collectedDiagnostics
2483
3022
  );
@@ -2490,8 +3029,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2490
3029
  return recordNode;
2491
3030
  }
2492
3031
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3032
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3033
+ checker,
3034
+ declaration: namedDecl,
3035
+ subjectType: type
3036
+ }) : void 0;
2493
3037
  typeRegistry[registryTypeName] = {
2494
3038
  name: registryTypeName,
3039
+ ...metadata !== void 0 && { metadata },
2495
3040
  type: recordNode,
2496
3041
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2497
3042
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -2511,12 +3056,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2511
3056
  file,
2512
3057
  typeRegistry,
2513
3058
  visiting,
3059
+ metadataPolicy,
2514
3060
  collectedDiagnostics,
2515
3061
  extensionRegistry
2516
3062
  );
2517
3063
  for (const prop of type.getProperties()) {
2518
3064
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2519
3065
  if (!declaration) continue;
3066
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
2520
3067
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2521
3068
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2522
3069
  const propTypeNode = resolveTypeNode(
@@ -2526,12 +3073,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2526
3073
  typeRegistry,
2527
3074
  visiting,
2528
3075
  declaration,
3076
+ metadataPolicy,
2529
3077
  extensionRegistry,
2530
3078
  collectedDiagnostics
2531
3079
  );
2532
3080
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2533
3081
  properties.push({
2534
3082
  name: prop.name,
3083
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
2535
3084
  type: propTypeNode,
2536
3085
  optional,
2537
3086
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -2548,14 +3097,21 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2548
3097
  type,
2549
3098
  checker,
2550
3099
  file,
2551
- collectedDiagnostics
3100
+ collectedDiagnostics,
3101
+ metadataPolicy
2552
3102
  ) : properties,
2553
3103
  additionalProperties: true
2554
3104
  };
2555
3105
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2556
3106
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3107
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
3108
+ checker,
3109
+ declaration: namedDecl,
3110
+ subjectType: type
3111
+ }) : void 0;
2557
3112
  typeRegistry[registryTypeName] = {
2558
3113
  name: registryTypeName,
3114
+ ...metadata !== void 0 && { metadata },
2559
3115
  type: objectNode,
2560
3116
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2561
3117
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -2568,7 +3124,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
2568
3124
  }
2569
3125
  return objectNode;
2570
3126
  }
2571
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
3127
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
2572
3128
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
2573
3129
  (s) => s?.declarations != null && s.declarations.length > 0
2574
3130
  );
@@ -2589,10 +3145,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2589
3145
  visiting,
2590
3146
  diagnostics,
2591
3147
  hostType,
3148
+ metadataPolicy,
2592
3149
  extensionRegistry
2593
3150
  );
2594
3151
  if (fieldNode) {
2595
3152
  map.set(fieldNode.name, {
3153
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
2596
3154
  constraints: [...fieldNode.constraints],
2597
3155
  annotations: [...fieldNode.annotations],
2598
3156
  provenance: fieldNode.provenance
@@ -2610,6 +3168,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2610
3168
  file,
2611
3169
  typeRegistry,
2612
3170
  visiting,
3171
+ metadataPolicy,
2613
3172
  checker.getTypeAtLocation(interfaceDecl),
2614
3173
  diagnostics,
2615
3174
  extensionRegistry
@@ -2623,6 +3182,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
2623
3182
  file,
2624
3183
  typeRegistry,
2625
3184
  visiting,
3185
+ metadataPolicy,
2626
3186
  checker.getTypeAtLocation(typeAliasDecl),
2627
3187
  diagnostics,
2628
3188
  extensionRegistry
@@ -2674,7 +3234,7 @@ function isNullishTypeNode(typeNode) {
2674
3234
  }
2675
3235
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
2676
3236
  }
2677
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
3237
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
2678
3238
  const map = /* @__PURE__ */ new Map();
2679
3239
  for (const member of members) {
2680
3240
  if (ts3.isPropertySignature(member)) {
@@ -2686,10 +3246,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
2686
3246
  visiting,
2687
3247
  diagnostics,
2688
3248
  hostType,
3249
+ metadataPolicy,
2689
3250
  extensionRegistry
2690
3251
  );
2691
3252
  if (fieldNode) {
2692
3253
  map.set(fieldNode.name, {
3254
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
2693
3255
  constraints: [...fieldNode.constraints],
2694
3256
  annotations: [...fieldNode.annotations],
2695
3257
  provenance: fieldNode.provenance
@@ -2720,6 +3282,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
2720
3282
  {},
2721
3283
  /* @__PURE__ */ new Set(),
2722
3284
  aliasDecl.type,
3285
+ void 0,
2723
3286
  extensionRegistry
2724
3287
  );
2725
3288
  const constraints = extractJSDocConstraintNodes(
@@ -2905,15 +3468,27 @@ function findInterfaceByName(sourceFile, interfaceName) {
2905
3468
  function findTypeAliasByName(sourceFile, aliasName) {
2906
3469
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
2907
3470
  }
2908
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
3471
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
2909
3472
  const analysisFilePath = path.resolve(filePath);
2910
3473
  const classDecl = findClassByName(ctx.sourceFile, typeName);
2911
3474
  if (classDecl !== null) {
2912
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
3475
+ return analyzeClassToIR(
3476
+ classDecl,
3477
+ ctx.checker,
3478
+ analysisFilePath,
3479
+ extensionRegistry,
3480
+ metadataPolicy
3481
+ );
2913
3482
  }
2914
3483
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2915
3484
  if (interfaceDecl !== null) {
2916
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
3485
+ return analyzeInterfaceToIR(
3486
+ interfaceDecl,
3487
+ ctx.checker,
3488
+ analysisFilePath,
3489
+ extensionRegistry,
3490
+ metadataPolicy
3491
+ );
2917
3492
  }
2918
3493
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2919
3494
  if (typeAlias !== null) {
@@ -2921,7 +3496,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
2921
3496
  typeAlias,
2922
3497
  ctx.checker,
2923
3498
  analysisFilePath,
2924
- extensionRegistry
3499
+ extensionRegistry,
3500
+ metadataPolicy
2925
3501
  );
2926
3502
  if (result.ok) {
2927
3503
  return result.analysis;
@@ -2936,6 +3512,120 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
2936
3512
  // src/generators/class-schema.ts
2937
3513
  import "typescript";
2938
3514
 
3515
+ // src/metadata/collision-guards.ts
3516
+ function assertUniqueSerializedNames(entries, scope) {
3517
+ const seen = /* @__PURE__ */ new Map();
3518
+ for (const entry of entries) {
3519
+ const previous = seen.get(entry.serializedName);
3520
+ if (previous !== void 0) {
3521
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
3522
+ continue;
3523
+ }
3524
+ throw new Error(
3525
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
3526
+ );
3527
+ }
3528
+ seen.set(entry.serializedName, entry);
3529
+ }
3530
+ }
3531
+ function collectFlattenedFields(elements) {
3532
+ const fields = [];
3533
+ for (const element of elements) {
3534
+ switch (element.kind) {
3535
+ case "field":
3536
+ fields.push(element);
3537
+ break;
3538
+ case "group":
3539
+ case "conditional":
3540
+ fields.push(...collectFlattenedFields(element.elements));
3541
+ break;
3542
+ default: {
3543
+ const exhaustive = element;
3544
+ void exhaustive;
3545
+ }
3546
+ }
3547
+ }
3548
+ return fields;
3549
+ }
3550
+ function validateObjectProperties(properties, scope) {
3551
+ assertUniqueSerializedNames(
3552
+ properties.map((property) => ({
3553
+ logicalName: property.name,
3554
+ serializedName: getSerializedName(property.name, property.metadata),
3555
+ category: "object property"
3556
+ })),
3557
+ scope
3558
+ );
3559
+ for (const property of properties) {
3560
+ validateTypeNode(
3561
+ property.type,
3562
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
3563
+ );
3564
+ }
3565
+ }
3566
+ function validateTypeNode(type, scope) {
3567
+ switch (type.kind) {
3568
+ case "array":
3569
+ validateTypeNode(type.items, `${scope}[]`);
3570
+ break;
3571
+ case "object":
3572
+ validateObjectProperties(type.properties, scope);
3573
+ break;
3574
+ case "record":
3575
+ validateTypeNode(type.valueType, `${scope}.*`);
3576
+ break;
3577
+ case "union":
3578
+ type.members.forEach((member, index) => {
3579
+ validateTypeNode(member, `${scope}|${String(index)}`);
3580
+ });
3581
+ break;
3582
+ case "reference":
3583
+ case "primitive":
3584
+ case "enum":
3585
+ case "dynamic":
3586
+ case "custom":
3587
+ break;
3588
+ default: {
3589
+ const exhaustive = type;
3590
+ void exhaustive;
3591
+ }
3592
+ }
3593
+ }
3594
+ function validateTypeDefinitions(typeRegistry) {
3595
+ const definitions = Object.values(typeRegistry);
3596
+ assertUniqueSerializedNames(
3597
+ definitions.map((definition) => ({
3598
+ logicalName: definition.name,
3599
+ serializedName: getSerializedName(definition.name, definition.metadata),
3600
+ category: "type definition"
3601
+ })),
3602
+ "$defs"
3603
+ );
3604
+ for (const definition of definitions) {
3605
+ validateTypeDefinition(definition);
3606
+ }
3607
+ }
3608
+ function validateTypeDefinition(definition) {
3609
+ validateTypeNode(
3610
+ definition.type,
3611
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
3612
+ );
3613
+ }
3614
+ function assertNoSerializedNameCollisions(ir) {
3615
+ assertUniqueSerializedNames(
3616
+ collectFlattenedFields(ir.elements).map((field) => ({
3617
+ logicalName: field.name,
3618
+ serializedName: getSerializedName(field.name, field.metadata),
3619
+ category: "field"
3620
+ })),
3621
+ "form root"
3622
+ );
3623
+ for (const field of collectFlattenedFields(ir.elements)) {
3624
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
3625
+ }
3626
+ validateTypeDefinitions(ir.typeRegistry);
3627
+ }
3628
+
2939
3629
  // src/json-schema/ir-generator.ts
2940
3630
  function makeContext(options) {
2941
3631
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -2946,19 +3636,33 @@ function makeContext(options) {
2946
3636
  }
2947
3637
  return {
2948
3638
  defs: {},
3639
+ typeNameMap: {},
3640
+ typeRegistry: {},
2949
3641
  extensionRegistry: options?.extensionRegistry,
2950
3642
  vendorPrefix
2951
3643
  };
2952
3644
  }
2953
3645
  function generateJsonSchemaFromIR(ir, options) {
2954
- const ctx = makeContext(options);
3646
+ assertNoSerializedNameCollisions(ir);
3647
+ const ctx = {
3648
+ ...makeContext(options),
3649
+ typeRegistry: ir.typeRegistry,
3650
+ typeNameMap: Object.fromEntries(
3651
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
3652
+ name,
3653
+ getSerializedName(name, typeDef.metadata)
3654
+ ])
3655
+ )
3656
+ };
2955
3657
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
2956
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
3658
+ const schemaName = ctx.typeNameMap[name] ?? name;
3659
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
3660
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
2957
3661
  if (typeDef.constraints && typeDef.constraints.length > 0) {
2958
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
3662
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
2959
3663
  }
2960
3664
  if (typeDef.annotations && typeDef.annotations.length > 0) {
2961
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
3665
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
2962
3666
  }
2963
3667
  }
2964
3668
  const properties = {};
@@ -2971,6 +3675,7 @@ function generateJsonSchemaFromIR(ir, options) {
2971
3675
  properties,
2972
3676
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
2973
3677
  };
3678
+ applyResolvedMetadata(result, ir.metadata);
2974
3679
  if (ir.annotations && ir.annotations.length > 0) {
2975
3680
  applyAnnotations(result, ir.annotations, ctx);
2976
3681
  }
@@ -2983,9 +3688,9 @@ function collectFields(elements, properties, required, ctx) {
2983
3688
  for (const element of elements) {
2984
3689
  switch (element.kind) {
2985
3690
  case "field":
2986
- properties[element.name] = generateFieldSchema(element, ctx);
3691
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
2987
3692
  if (element.required) {
2988
- required.push(element.name);
3693
+ required.push(getSerializedName(element.name, element.metadata));
2989
3694
  }
2990
3695
  break;
2991
3696
  case "group":
@@ -3029,6 +3734,7 @@ function generateFieldSchema(field, ctx) {
3029
3734
  rootAnnotations.push(annotation);
3030
3735
  }
3031
3736
  }
3737
+ applyResolvedMetadata(schema, field.metadata);
3032
3738
  applyAnnotations(schema, rootAnnotations, ctx);
3033
3739
  if (itemStringSchema !== void 0) {
3034
3740
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -3036,7 +3742,7 @@ function generateFieldSchema(field, ctx) {
3036
3742
  if (pathConstraints.length === 0) {
3037
3743
  return schema;
3038
3744
  }
3039
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
3745
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
3040
3746
  }
3041
3747
  function isStringItemConstraint(constraint) {
3042
3748
  switch (constraint.constraintKind) {
@@ -3048,9 +3754,11 @@ function isStringItemConstraint(constraint) {
3048
3754
  return false;
3049
3755
  }
3050
3756
  }
3051
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
3757
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
3052
3758
  if (schema.type === "array" && schema.items) {
3053
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
3759
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
3760
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
3761
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
3054
3762
  return schema;
3055
3763
  }
3056
3764
  const byTarget = /* @__PURE__ */ new Map();
@@ -3065,7 +3773,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
3065
3773
  for (const [target, constraints] of byTarget) {
3066
3774
  const subSchema = {};
3067
3775
  applyConstraints(subSchema, constraints, ctx);
3068
- propertyOverrides[target] = subSchema;
3776
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
3069
3777
  }
3070
3778
  if (schema.$ref) {
3071
3779
  const { $ref, ...rest } = schema;
@@ -3113,7 +3821,7 @@ function generateTypeNode(type, ctx) {
3113
3821
  case "union":
3114
3822
  return generateUnionType(type, ctx);
3115
3823
  case "reference":
3116
- return generateReferenceType(type);
3824
+ return generateReferenceType(type, ctx);
3117
3825
  case "dynamic":
3118
3826
  return generateDynamicType(type);
3119
3827
  case "custom":
@@ -3154,9 +3862,10 @@ function generateObjectType(type, ctx) {
3154
3862
  const properties = {};
3155
3863
  const required = [];
3156
3864
  for (const prop of type.properties) {
3157
- properties[prop.name] = generatePropertySchema(prop, ctx);
3865
+ const propertyName = getSerializedName(prop.name, prop.metadata);
3866
+ properties[propertyName] = generatePropertySchema(prop, ctx);
3158
3867
  if (!prop.optional) {
3159
- required.push(prop.name);
3868
+ required.push(propertyName);
3160
3869
  }
3161
3870
  }
3162
3871
  const schema = { type: "object", properties };
@@ -3177,6 +3886,7 @@ function generateRecordType(type, ctx) {
3177
3886
  function generatePropertySchema(prop, ctx) {
3178
3887
  const schema = generateTypeNode(prop.type, ctx);
3179
3888
  applyConstraints(schema, prop.constraints, ctx);
3889
+ applyResolvedMetadata(schema, prop.metadata);
3180
3890
  applyAnnotations(schema, prop.annotations, ctx);
3181
3891
  return schema;
3182
3892
  }
@@ -3205,8 +3915,28 @@ function isNullableUnion(type) {
3205
3915
  ).length;
3206
3916
  return nullCount === 1;
3207
3917
  }
3208
- function generateReferenceType(type) {
3209
- return { $ref: `#/$defs/${type.name}` };
3918
+ function generateReferenceType(type, ctx) {
3919
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
3920
+ }
3921
+ function applyResolvedMetadata(schema, metadata) {
3922
+ const displayName = getDisplayName(metadata);
3923
+ if (displayName !== void 0) {
3924
+ schema.title = displayName;
3925
+ }
3926
+ }
3927
+ function resolveReferencedType(type, ctx) {
3928
+ return ctx.typeRegistry[type.name]?.type;
3929
+ }
3930
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
3931
+ if (typeNode?.kind === "object") {
3932
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
3933
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
3934
+ }
3935
+ if (typeNode?.kind === "reference") {
3936
+ const referencedType = resolveReferencedType(typeNode, ctx);
3937
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
3938
+ }
3939
+ return logicalName;
3210
3940
  }
3211
3941
  function generateDynamicType(type) {
3212
3942
  if (type.dynamicKind === "enum") {
@@ -3286,7 +4016,7 @@ function applyAnnotations(schema, annotations, ctx) {
3286
4016
  for (const annotation of annotations) {
3287
4017
  switch (annotation.annotationKind) {
3288
4018
  case "displayName":
3289
- schema.title = annotation.value;
4019
+ schema.title ??= annotation.value;
3290
4020
  break;
3291
4021
  case "description":
3292
4022
  schema.description = annotation.value;
@@ -3536,13 +4266,21 @@ function combineRules(parentRule, childRule) {
3536
4266
  }
3537
4267
  };
3538
4268
  }
3539
- function fieldNodeToControl(field, parentRule) {
3540
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
4269
+ function getFieldDisplayName(field) {
4270
+ const resolvedDisplayName = getDisplayName(field.metadata);
4271
+ if (resolvedDisplayName !== void 0) {
4272
+ return resolvedDisplayName;
4273
+ }
4274
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
4275
+ }
4276
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
3541
4277
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
4278
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
4279
+ const displayName = getFieldDisplayName(field);
3542
4280
  const control = {
3543
4281
  type: "Control",
3544
- scope: fieldToScope(field.name),
3545
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
4282
+ scope: fieldToScope(serializedName),
4283
+ ...displayName !== void 0 && { label: displayName },
3546
4284
  ...placeholderAnnotation !== void 0 && {
3547
4285
  options: { placeholder: placeholderAnnotation.value }
3548
4286
  },
@@ -3550,30 +4288,30 @@ function fieldNodeToControl(field, parentRule) {
3550
4288
  };
3551
4289
  return control;
3552
4290
  }
3553
- function groupNodeToLayout(group, parentRule) {
4291
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
3554
4292
  return {
3555
4293
  type: "Group",
3556
4294
  label: group.label,
3557
- elements: irElementsToUiSchema(group.elements, parentRule),
4295
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
3558
4296
  ...parentRule !== void 0 && { rule: parentRule }
3559
4297
  };
3560
4298
  }
3561
- function irElementsToUiSchema(elements, parentRule) {
4299
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
3562
4300
  const result = [];
3563
4301
  for (const element of elements) {
3564
4302
  switch (element.kind) {
3565
4303
  case "field": {
3566
- result.push(fieldNodeToControl(element, parentRule));
4304
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
3567
4305
  break;
3568
4306
  }
3569
4307
  case "group": {
3570
- result.push(groupNodeToLayout(element, parentRule));
4308
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
3571
4309
  break;
3572
4310
  }
3573
4311
  case "conditional": {
3574
- const newRule = createShowRule(element.fieldName, element.value);
4312
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
3575
4313
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
3576
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
4314
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
3577
4315
  result.push(...childElements);
3578
4316
  break;
3579
4317
  }
@@ -3587,12 +4325,35 @@ function irElementsToUiSchema(elements, parentRule) {
3587
4325
  return result;
3588
4326
  }
3589
4327
  function generateUiSchemaFromIR(ir) {
4328
+ assertNoSerializedNameCollisions(ir);
4329
+ const fieldNameMap = collectFieldNameMap(ir.elements);
3590
4330
  const result = {
3591
4331
  type: "VerticalLayout",
3592
- elements: irElementsToUiSchema(ir.elements)
4332
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
3593
4333
  };
3594
4334
  return parseOrThrow(uiSchema, result, "UI Schema");
3595
4335
  }
4336
+ function collectFieldNameMap(elements) {
4337
+ const map = /* @__PURE__ */ new Map();
4338
+ for (const element of elements) {
4339
+ switch (element.kind) {
4340
+ case "field":
4341
+ map.set(element.name, getSerializedName(element.name, element.metadata));
4342
+ break;
4343
+ case "group":
4344
+ case "conditional":
4345
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
4346
+ map.set(key, value);
4347
+ }
4348
+ break;
4349
+ default: {
4350
+ const _exhaustive = element;
4351
+ void _exhaustive;
4352
+ }
4353
+ }
4354
+ }
4355
+ return map;
4356
+ }
3596
4357
 
3597
4358
  // src/validate/constraint-validator.ts
3598
4359
  import {
@@ -3677,7 +4438,11 @@ function generateClassSchemas(analysis, source, options) {
3677
4438
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3678
4439
  throw new Error(formatValidationError(errorDiagnostics));
3679
4440
  }
3680
- const ir = canonicalizeTSDoc(analysis, source);
4441
+ const ir = canonicalizeTSDoc(
4442
+ analysis,
4443
+ source,
4444
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4445
+ );
3681
4446
  const validationResult = validateIR(ir, {
3682
4447
  ...options?.extensionRegistry !== void 0 && {
3683
4448
  extensionRegistry: options.extensionRegistry
@@ -3799,6 +4564,7 @@ function typeToJsonSchema(type, checker) {
3799
4564
  visiting,
3800
4565
  void 0,
3801
4566
  void 0,
4567
+ void 0,
3802
4568
  diagnostics
3803
4569
  );
3804
4570
  if (diagnostics.length > 0) {