@formspec/build 0.1.0-alpha.27 → 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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -2
  3. package/dist/analyzer/class-analyzer.d.ts +12 -6
  4. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  5. package/dist/analyzer/program.d.ts +3 -2
  6. package/dist/analyzer/program.d.ts.map +1 -1
  7. package/dist/browser.cjs +485 -76
  8. package/dist/browser.cjs.map +1 -1
  9. package/dist/browser.js +486 -77
  10. package/dist/browser.js.map +1 -1
  11. package/dist/build-alpha.d.ts +18 -2
  12. package/dist/build-beta.d.ts +18 -2
  13. package/dist/build-internal.d.ts +18 -2
  14. package/dist/build.d.ts +18 -2
  15. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  16. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  17. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  18. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  19. package/dist/cli.cjs +1460 -143
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +1458 -139
  22. package/dist/cli.js.map +1 -1
  23. package/dist/generators/class-schema.d.ts +6 -1
  24. package/dist/generators/class-schema.d.ts.map +1 -1
  25. package/dist/generators/method-schema.d.ts.map +1 -1
  26. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  27. package/dist/index.cjs +1425 -141
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1425 -139
  32. package/dist/index.js.map +1 -1
  33. package/dist/internals.cjs +1416 -178
  34. package/dist/internals.cjs.map +1 -1
  35. package/dist/internals.js +1416 -176
  36. package/dist/internals.js.map +1 -1
  37. package/dist/json-schema/generator.d.ts +3 -1
  38. package/dist/json-schema/generator.d.ts.map +1 -1
  39. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  40. package/dist/metadata/collision-guards.d.ts +3 -0
  41. package/dist/metadata/collision-guards.d.ts.map +1 -0
  42. package/dist/metadata/index.d.ts +7 -0
  43. package/dist/metadata/index.d.ts.map +1 -0
  44. package/dist/metadata/policy.d.ts +11 -0
  45. package/dist/metadata/policy.d.ts.map +1 -0
  46. package/dist/metadata/resolve.d.ts +20 -0
  47. package/dist/metadata/resolve.d.ts.map +1 -0
  48. package/dist/ui-schema/generator.d.ts +11 -2
  49. package/dist/ui-schema/generator.d.ts.map +1 -1
  50. package/dist/ui-schema/ir-generator.d.ts +2 -1
  51. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  52. package/package.json +7 -6
package/dist/cli.cjs CHANGED
@@ -30,6 +30,334 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
+ // src/metadata/policy.ts
34
+ function normalizePluralization(input) {
35
+ if (input?.mode === "infer-if-missing") {
36
+ return {
37
+ mode: "infer-if-missing",
38
+ infer: () => "",
39
+ inflect: input.inflect
40
+ };
41
+ }
42
+ if (input?.mode === "require-explicit") {
43
+ return {
44
+ mode: "require-explicit",
45
+ infer: () => "",
46
+ inflect: NOOP_INFLECT
47
+ };
48
+ }
49
+ return {
50
+ mode: "disabled",
51
+ infer: () => "",
52
+ inflect: NOOP_INFLECT
53
+ };
54
+ }
55
+ function normalizeScalarPolicy(input) {
56
+ if (input?.mode === "infer-if-missing") {
57
+ return {
58
+ mode: "infer-if-missing",
59
+ infer: input.infer,
60
+ pluralization: normalizePluralization(input.pluralization)
61
+ };
62
+ }
63
+ if (input?.mode === "require-explicit") {
64
+ return {
65
+ mode: "require-explicit",
66
+ infer: () => "",
67
+ pluralization: normalizePluralization(input.pluralization)
68
+ };
69
+ }
70
+ return {
71
+ mode: "disabled",
72
+ infer: () => "",
73
+ pluralization: normalizePluralization(input?.pluralization)
74
+ };
75
+ }
76
+ function normalizeDeclarationPolicy(input) {
77
+ return {
78
+ apiName: normalizeScalarPolicy(input?.apiName),
79
+ displayName: normalizeScalarPolicy(input?.displayName)
80
+ };
81
+ }
82
+ function normalizeMetadataPolicy(input) {
83
+ return {
84
+ type: normalizeDeclarationPolicy(input?.type),
85
+ field: normalizeDeclarationPolicy(input?.field),
86
+ method: normalizeDeclarationPolicy(input?.method)
87
+ };
88
+ }
89
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
90
+ return policy[declarationKind];
91
+ }
92
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
93
+ return {
94
+ surface,
95
+ declarationKind,
96
+ logicalName,
97
+ ...buildContext !== void 0 && { buildContext }
98
+ };
99
+ }
100
+ var NOOP_INFLECT;
101
+ var init_policy = __esm({
102
+ "src/metadata/policy.ts"() {
103
+ "use strict";
104
+ NOOP_INFLECT = () => "";
105
+ }
106
+ });
107
+
108
+ // src/metadata/resolve.ts
109
+ function toExplicitScalar(value) {
110
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
111
+ }
112
+ function toExplicitResolvedMetadata(explicit) {
113
+ if (explicit === void 0) {
114
+ return void 0;
115
+ }
116
+ const apiName = toExplicitScalar(explicit.apiName);
117
+ const displayName = toExplicitScalar(explicit.displayName);
118
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
119
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
120
+ const metadata = {
121
+ ...apiName !== void 0 && { apiName },
122
+ ...displayName !== void 0 && { displayName },
123
+ ...apiNamePlural !== void 0 && { apiNamePlural },
124
+ ...displayNamePlural !== void 0 && { displayNamePlural }
125
+ };
126
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
127
+ }
128
+ function resolveScalar(current, policy, context, metadataLabel) {
129
+ if (current !== void 0) {
130
+ return current;
131
+ }
132
+ if (policy.mode === "require-explicit") {
133
+ throw new Error(
134
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
135
+ );
136
+ }
137
+ if (policy.mode !== "infer-if-missing") {
138
+ return void 0;
139
+ }
140
+ const inferredValue = policy.infer(context);
141
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
142
+ }
143
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
144
+ if (current !== void 0) {
145
+ return current;
146
+ }
147
+ if (policy.mode === "require-explicit") {
148
+ throw new Error(
149
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
150
+ );
151
+ }
152
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
153
+ return void 0;
154
+ }
155
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
156
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
157
+ }
158
+ function resolveResolvedMetadata(current, policy, context) {
159
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
160
+ const displayName = resolveScalar(
161
+ current?.displayName,
162
+ policy.displayName,
163
+ context,
164
+ "displayName"
165
+ );
166
+ const apiNamePlural = resolvePlural(
167
+ current?.apiNamePlural,
168
+ apiName,
169
+ policy.apiName.pluralization,
170
+ context,
171
+ "apiNamePlural"
172
+ );
173
+ const displayNamePlural = resolvePlural(
174
+ current?.displayNamePlural,
175
+ displayName,
176
+ policy.displayName.pluralization,
177
+ context,
178
+ "displayNamePlural"
179
+ );
180
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
181
+ return void 0;
182
+ }
183
+ return {
184
+ ...apiName !== void 0 && { apiName },
185
+ ...displayName !== void 0 && { displayName },
186
+ ...apiNamePlural !== void 0 && { apiNamePlural },
187
+ ...displayNamePlural !== void 0 && { displayNamePlural }
188
+ };
189
+ }
190
+ function pickResolvedMetadataValue(baseValue, overlayValue) {
191
+ if (overlayValue?.source === "explicit") {
192
+ return overlayValue;
193
+ }
194
+ if (baseValue?.source === "explicit") {
195
+ return baseValue;
196
+ }
197
+ return baseValue ?? overlayValue;
198
+ }
199
+ function resolveTypeNodeMetadata(type, options) {
200
+ switch (type.kind) {
201
+ case "array":
202
+ return {
203
+ ...type,
204
+ items: resolveTypeNodeMetadata(type.items, options)
205
+ };
206
+ case "object":
207
+ return {
208
+ ...type,
209
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
210
+ };
211
+ case "record":
212
+ return {
213
+ ...type,
214
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
215
+ };
216
+ case "union":
217
+ return {
218
+ ...type,
219
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
220
+ };
221
+ case "reference":
222
+ case "primitive":
223
+ case "enum":
224
+ case "dynamic":
225
+ case "custom":
226
+ return type;
227
+ default: {
228
+ const _exhaustive = type;
229
+ return _exhaustive;
230
+ }
231
+ }
232
+ }
233
+ function resolveObjectPropertyMetadata(property, options) {
234
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
235
+ surface: options.surface,
236
+ declarationKind: "field",
237
+ logicalName: property.name,
238
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
239
+ });
240
+ return {
241
+ ...property,
242
+ ...metadata !== void 0 && { metadata },
243
+ type: resolveTypeNodeMetadata(property.type, options)
244
+ };
245
+ }
246
+ function resolveFieldMetadataNode(field, options) {
247
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
248
+ surface: options.surface,
249
+ declarationKind: "field",
250
+ logicalName: field.name,
251
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
252
+ });
253
+ return {
254
+ ...field,
255
+ ...metadata !== void 0 && { metadata },
256
+ type: resolveTypeNodeMetadata(field.type, options)
257
+ };
258
+ }
259
+ function resolveFormElementMetadata(element, options) {
260
+ switch (element.kind) {
261
+ case "field":
262
+ return resolveFieldMetadataNode(element, options);
263
+ case "group":
264
+ return {
265
+ ...element,
266
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
267
+ };
268
+ case "conditional":
269
+ return {
270
+ ...element,
271
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
272
+ };
273
+ default: {
274
+ const _exhaustive = element;
275
+ return _exhaustive;
276
+ }
277
+ }
278
+ }
279
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
280
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
281
+ surface: options.surface,
282
+ declarationKind: "type",
283
+ logicalName: typeDefinition.name,
284
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
285
+ });
286
+ return {
287
+ ...typeDefinition,
288
+ ...metadata !== void 0 && { metadata },
289
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
290
+ };
291
+ }
292
+ function resolveMetadata(explicit, policy, context) {
293
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
294
+ }
295
+ function mergeResolvedMetadata(baseMetadata, overlayMetadata) {
296
+ const apiName = pickResolvedMetadataValue(baseMetadata?.apiName, overlayMetadata?.apiName);
297
+ const displayName = pickResolvedMetadataValue(
298
+ baseMetadata?.displayName,
299
+ overlayMetadata?.displayName
300
+ );
301
+ const apiNamePlural = pickResolvedMetadataValue(
302
+ baseMetadata?.apiNamePlural,
303
+ overlayMetadata?.apiNamePlural
304
+ );
305
+ const displayNamePlural = pickResolvedMetadataValue(
306
+ baseMetadata?.displayNamePlural,
307
+ overlayMetadata?.displayNamePlural
308
+ );
309
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
310
+ return void 0;
311
+ }
312
+ return {
313
+ ...apiName !== void 0 && { apiName },
314
+ ...displayName !== void 0 && { displayName },
315
+ ...apiNamePlural !== void 0 && { apiNamePlural },
316
+ ...displayNamePlural !== void 0 && { displayNamePlural }
317
+ };
318
+ }
319
+ function getSerializedName(logicalName, metadata) {
320
+ return metadata?.apiName?.value ?? logicalName;
321
+ }
322
+ function getDisplayName(metadata) {
323
+ return metadata?.displayName?.value;
324
+ }
325
+ function resolveFormIRMetadata(ir, options) {
326
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
327
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
328
+ surface: options.surface,
329
+ declarationKind: "type",
330
+ logicalName: rootLogicalName,
331
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
332
+ });
333
+ return {
334
+ ...ir,
335
+ ...metadata !== void 0 && { metadata },
336
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
337
+ typeRegistry: Object.fromEntries(
338
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
339
+ name,
340
+ resolveTypeDefinitionMetadata(definition, options)
341
+ ])
342
+ )
343
+ };
344
+ }
345
+ var init_resolve = __esm({
346
+ "src/metadata/resolve.ts"() {
347
+ "use strict";
348
+ }
349
+ });
350
+
351
+ // src/metadata/index.ts
352
+ var init_metadata = __esm({
353
+ "src/metadata/index.ts"() {
354
+ "use strict";
355
+ init_policy();
356
+ init_resolve();
357
+ init_resolve();
358
+ }
359
+ });
360
+
33
361
  // src/canonicalize/chain-dsl-canonicalizer.ts
34
362
  function isGroup(el) {
35
363
  return el._type === "group";
@@ -40,57 +368,60 @@ function isConditional(el) {
40
368
  function isField(el) {
41
369
  return el._type === "field";
42
370
  }
43
- function canonicalizeChainDSL(form) {
371
+ function canonicalizeChainDSL(form, options) {
372
+ const metadataPolicy = normalizeMetadataPolicy(
373
+ options?.metadata ?? (0, import_internals._getFormSpecMetadataPolicy)(form)
374
+ );
44
375
  return {
45
376
  kind: "form-ir",
46
377
  irVersion: import_internals.IR_VERSION,
47
- elements: canonicalizeElements(form.elements),
378
+ elements: canonicalizeElements(form.elements, metadataPolicy),
48
379
  rootAnnotations: [],
49
380
  typeRegistry: {},
50
381
  provenance: CHAIN_DSL_PROVENANCE
51
382
  };
52
383
  }
53
- function canonicalizeElements(elements) {
54
- return elements.map(canonicalizeElement);
384
+ function canonicalizeElements(elements, metadataPolicy) {
385
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
55
386
  }
56
- function canonicalizeElement(element) {
387
+ function canonicalizeElement(element, metadataPolicy) {
57
388
  if (isField(element)) {
58
- return canonicalizeField(element);
389
+ return canonicalizeField(element, metadataPolicy);
59
390
  }
60
391
  if (isGroup(element)) {
61
- return canonicalizeGroup(element);
392
+ return canonicalizeGroup(element, metadataPolicy);
62
393
  }
63
394
  if (isConditional(element)) {
64
- return canonicalizeConditional(element);
395
+ return canonicalizeConditional(element, metadataPolicy);
65
396
  }
66
397
  const _exhaustive = element;
67
398
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
68
399
  }
69
- function canonicalizeField(field) {
400
+ function canonicalizeField(field, metadataPolicy) {
70
401
  switch (field._field) {
71
402
  case "text":
72
- return canonicalizeTextField(field);
403
+ return canonicalizeTextField(field, metadataPolicy);
73
404
  case "number":
74
- return canonicalizeNumberField(field);
405
+ return canonicalizeNumberField(field, metadataPolicy);
75
406
  case "boolean":
76
- return canonicalizeBooleanField(field);
407
+ return canonicalizeBooleanField(field, metadataPolicy);
77
408
  case "enum":
78
- return canonicalizeStaticEnumField(field);
409
+ return canonicalizeStaticEnumField(field, metadataPolicy);
79
410
  case "dynamic_enum":
80
- return canonicalizeDynamicEnumField(field);
411
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
81
412
  case "dynamic_schema":
82
- return canonicalizeDynamicSchemaField(field);
413
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
83
414
  case "array":
84
- return canonicalizeArrayField(field);
415
+ return canonicalizeArrayField(field, metadataPolicy);
85
416
  case "object":
86
- return canonicalizeObjectField(field);
417
+ return canonicalizeObjectField(field, metadataPolicy);
87
418
  default: {
88
419
  const _exhaustive = field;
89
420
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
90
421
  }
91
422
  }
92
423
  }
93
- function canonicalizeTextField(field) {
424
+ function canonicalizeTextField(field, metadataPolicy) {
94
425
  const type = { kind: "primitive", primitiveKind: "string" };
95
426
  const constraints = [];
96
427
  if (field.minLength !== void 0) {
@@ -122,13 +453,14 @@ function canonicalizeTextField(field) {
122
453
  }
123
454
  return buildFieldNode(
124
455
  field.name,
456
+ resolveFieldMetadata(field.name, field, metadataPolicy),
125
457
  type,
126
458
  field.required,
127
- buildAnnotations(field.label, field.placeholder),
459
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
128
460
  constraints
129
461
  );
130
462
  }
131
- function canonicalizeNumberField(field) {
463
+ function canonicalizeNumberField(field, metadataPolicy) {
132
464
  const type = { kind: "primitive", primitiveKind: "number" };
133
465
  const constraints = [];
134
466
  if (field.min !== void 0) {
@@ -160,17 +492,24 @@ function canonicalizeNumberField(field) {
160
492
  }
161
493
  return buildFieldNode(
162
494
  field.name,
495
+ resolveFieldMetadata(field.name, field, metadataPolicy),
163
496
  type,
164
497
  field.required,
165
- buildAnnotations(field.label),
498
+ buildAnnotations(getExplicitDisplayName(field)),
166
499
  constraints
167
500
  );
168
501
  }
169
- function canonicalizeBooleanField(field) {
502
+ function canonicalizeBooleanField(field, metadataPolicy) {
170
503
  const type = { kind: "primitive", primitiveKind: "boolean" };
171
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
504
+ return buildFieldNode(
505
+ field.name,
506
+ resolveFieldMetadata(field.name, field, metadataPolicy),
507
+ type,
508
+ field.required,
509
+ buildAnnotations(getExplicitDisplayName(field))
510
+ );
172
511
  }
173
- function canonicalizeStaticEnumField(field) {
512
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
174
513
  const members = field.options.map((opt) => {
175
514
  if (typeof opt === "string") {
176
515
  return { value: opt };
@@ -178,28 +517,46 @@ function canonicalizeStaticEnumField(field) {
178
517
  return { value: opt.id, displayName: opt.label };
179
518
  });
180
519
  const type = { kind: "enum", members };
181
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
520
+ return buildFieldNode(
521
+ field.name,
522
+ resolveFieldMetadata(field.name, field, metadataPolicy),
523
+ type,
524
+ field.required,
525
+ buildAnnotations(getExplicitDisplayName(field))
526
+ );
182
527
  }
183
- function canonicalizeDynamicEnumField(field) {
528
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
184
529
  const type = {
185
530
  kind: "dynamic",
186
531
  dynamicKind: "enum",
187
532
  sourceKey: field.source,
188
533
  parameterFields: field.params ? [...field.params] : []
189
534
  };
190
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
535
+ return buildFieldNode(
536
+ field.name,
537
+ resolveFieldMetadata(field.name, field, metadataPolicy),
538
+ type,
539
+ field.required,
540
+ buildAnnotations(getExplicitDisplayName(field))
541
+ );
191
542
  }
192
- function canonicalizeDynamicSchemaField(field) {
543
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
193
544
  const type = {
194
545
  kind: "dynamic",
195
546
  dynamicKind: "schema",
196
547
  sourceKey: field.schemaSource,
197
548
  parameterFields: []
198
549
  };
199
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
550
+ return buildFieldNode(
551
+ field.name,
552
+ resolveFieldMetadata(field.name, field, metadataPolicy),
553
+ type,
554
+ field.required,
555
+ buildAnnotations(getExplicitDisplayName(field))
556
+ );
200
557
  }
201
- function canonicalizeArrayField(field) {
202
- const itemProperties = buildObjectProperties(field.items);
558
+ function canonicalizeArrayField(field, metadataPolicy) {
559
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
203
560
  const itemsType = {
204
561
  kind: "object",
205
562
  properties: itemProperties,
@@ -227,37 +584,44 @@ function canonicalizeArrayField(field) {
227
584
  }
228
585
  return buildFieldNode(
229
586
  field.name,
587
+ resolveFieldMetadata(field.name, field, metadataPolicy),
230
588
  type,
231
589
  field.required,
232
- buildAnnotations(field.label),
590
+ buildAnnotations(getExplicitDisplayName(field)),
233
591
  constraints
234
592
  );
235
593
  }
236
- function canonicalizeObjectField(field) {
237
- const properties = buildObjectProperties(field.properties);
594
+ function canonicalizeObjectField(field, metadataPolicy) {
595
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
238
596
  const type = {
239
597
  kind: "object",
240
598
  properties,
241
599
  additionalProperties: true
242
600
  };
243
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
601
+ return buildFieldNode(
602
+ field.name,
603
+ resolveFieldMetadata(field.name, field, metadataPolicy),
604
+ type,
605
+ field.required,
606
+ buildAnnotations(getExplicitDisplayName(field))
607
+ );
244
608
  }
245
- function canonicalizeGroup(g) {
609
+ function canonicalizeGroup(g, metadataPolicy) {
246
610
  return {
247
611
  kind: "group",
248
612
  label: g.label,
249
- elements: canonicalizeElements(g.elements),
613
+ elements: canonicalizeElements(g.elements, metadataPolicy),
250
614
  provenance: CHAIN_DSL_PROVENANCE
251
615
  };
252
616
  }
253
- function canonicalizeConditional(c) {
617
+ function canonicalizeConditional(c, metadataPolicy) {
254
618
  return {
255
619
  kind: "conditional",
256
620
  fieldName: c.field,
257
621
  // Conditional values from the chain DSL are JSON-serializable primitives
258
622
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
259
623
  value: assertJsonValue(c.value),
260
- elements: canonicalizeElements(c.elements),
624
+ elements: canonicalizeElements(c.elements, metadataPolicy),
261
625
  provenance: CHAIN_DSL_PROVENANCE
262
626
  };
263
627
  }
@@ -277,10 +641,11 @@ function assertJsonValue(v) {
277
641
  }
278
642
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
279
643
  }
280
- function buildFieldNode(name, type, required, annotations, constraints = []) {
644
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
281
645
  return {
282
646
  kind: "field",
283
647
  name,
648
+ ...metadata !== void 0 && { metadata },
284
649
  type,
285
650
  required: required === true,
286
651
  constraints,
@@ -310,13 +675,14 @@ function buildAnnotations(label, placeholder) {
310
675
  }
311
676
  return annotations;
312
677
  }
313
- function buildObjectProperties(elements, insideConditional = false) {
678
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
314
679
  const properties = [];
315
680
  for (const el of elements) {
316
681
  if (isField(el)) {
317
- const fieldNode = canonicalizeField(el);
682
+ const fieldNode = canonicalizeField(el, metadataPolicy);
318
683
  properties.push({
319
684
  name: fieldNode.name,
685
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
320
686
  type: fieldNode.type,
321
687
  // Fields inside a conditional branch are always optional in the
322
688
  // data schema, regardless of their `required` flag — the condition
@@ -327,18 +693,36 @@ function buildObjectProperties(elements, insideConditional = false) {
327
693
  provenance: CHAIN_DSL_PROVENANCE
328
694
  });
329
695
  } else if (isGroup(el)) {
330
- properties.push(...buildObjectProperties(el.elements, insideConditional));
696
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
331
697
  } else if (isConditional(el)) {
332
- properties.push(...buildObjectProperties(el.elements, true));
698
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
333
699
  }
334
700
  }
335
701
  return properties;
336
702
  }
703
+ function getExplicitDisplayName(field) {
704
+ if (field.label !== void 0 && field.displayName !== void 0) {
705
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
706
+ }
707
+ return field.displayName ?? field.label;
708
+ }
709
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
710
+ const displayName = getExplicitDisplayName(field);
711
+ return resolveMetadata(
712
+ {
713
+ ...field.apiName !== void 0 && { apiName: field.apiName },
714
+ ...displayName !== void 0 && { displayName }
715
+ },
716
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
717
+ makeMetadataContext("chain-dsl", "field", logicalName)
718
+ );
719
+ }
337
720
  var import_internals, CHAIN_DSL_PROVENANCE;
338
721
  var init_chain_dsl_canonicalizer = __esm({
339
722
  "src/canonicalize/chain-dsl-canonicalizer.ts"() {
340
723
  "use strict";
341
724
  import_internals = require("@formspec/core/internals");
725
+ init_metadata();
342
726
  CHAIN_DSL_PROVENANCE = {
343
727
  surface: "chain-dsl",
344
728
  file: "",
@@ -349,7 +733,7 @@ var init_chain_dsl_canonicalizer = __esm({
349
733
  });
350
734
 
351
735
  // src/canonicalize/tsdoc-canonicalizer.ts
352
- function canonicalizeTSDoc(analysis, source) {
736
+ function canonicalizeTSDoc(analysis, source, options) {
353
737
  const file = source?.file ?? "";
354
738
  const provenance = {
355
739
  surface: "tsdoc",
@@ -358,15 +742,21 @@ function canonicalizeTSDoc(analysis, source) {
358
742
  column: 0
359
743
  };
360
744
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
361
- return {
745
+ const ir = {
362
746
  kind: "form-ir",
747
+ name: analysis.name,
363
748
  irVersion: import_internals2.IR_VERSION,
364
749
  elements,
750
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
365
751
  typeRegistry: analysis.typeRegistry,
366
752
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
367
753
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
368
754
  provenance
369
755
  };
756
+ return resolveFormIRMetadata(ir, {
757
+ policy: normalizeMetadataPolicy(options?.metadata),
758
+ surface: "tsdoc"
759
+ });
370
760
  }
371
761
  function assembleElements(fields, layouts, provenance) {
372
762
  const elements = [];
@@ -427,6 +817,7 @@ var init_tsdoc_canonicalizer = __esm({
427
817
  "src/canonicalize/tsdoc-canonicalizer.ts"() {
428
818
  "use strict";
429
819
  import_internals2 = require("@formspec/core/internals");
820
+ init_metadata();
430
821
  }
431
822
  });
432
823
 
@@ -439,6 +830,126 @@ var init_canonicalize = __esm({
439
830
  }
440
831
  });
441
832
 
833
+ // src/metadata/collision-guards.ts
834
+ function assertUniqueSerializedNames(entries, scope) {
835
+ const seen = /* @__PURE__ */ new Map();
836
+ for (const entry of entries) {
837
+ const previous = seen.get(entry.serializedName);
838
+ if (previous !== void 0) {
839
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
840
+ continue;
841
+ }
842
+ throw new Error(
843
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
844
+ );
845
+ }
846
+ seen.set(entry.serializedName, entry);
847
+ }
848
+ }
849
+ function collectFlattenedFields(elements) {
850
+ const fields = [];
851
+ for (const element of elements) {
852
+ switch (element.kind) {
853
+ case "field":
854
+ fields.push(element);
855
+ break;
856
+ case "group":
857
+ case "conditional":
858
+ fields.push(...collectFlattenedFields(element.elements));
859
+ break;
860
+ default: {
861
+ const exhaustive = element;
862
+ void exhaustive;
863
+ }
864
+ }
865
+ }
866
+ return fields;
867
+ }
868
+ function validateObjectProperties(properties, scope) {
869
+ assertUniqueSerializedNames(
870
+ properties.map((property) => ({
871
+ logicalName: property.name,
872
+ serializedName: getSerializedName(property.name, property.metadata),
873
+ category: "object property"
874
+ })),
875
+ scope
876
+ );
877
+ for (const property of properties) {
878
+ validateTypeNode(
879
+ property.type,
880
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
881
+ );
882
+ }
883
+ }
884
+ function validateTypeNode(type, scope) {
885
+ switch (type.kind) {
886
+ case "array":
887
+ validateTypeNode(type.items, `${scope}[]`);
888
+ break;
889
+ case "object":
890
+ validateObjectProperties(type.properties, scope);
891
+ break;
892
+ case "record":
893
+ validateTypeNode(type.valueType, `${scope}.*`);
894
+ break;
895
+ case "union":
896
+ type.members.forEach((member, index) => {
897
+ validateTypeNode(member, `${scope}|${String(index)}`);
898
+ });
899
+ break;
900
+ case "reference":
901
+ case "primitive":
902
+ case "enum":
903
+ case "dynamic":
904
+ case "custom":
905
+ break;
906
+ default: {
907
+ const exhaustive = type;
908
+ void exhaustive;
909
+ }
910
+ }
911
+ }
912
+ function validateTypeDefinitions(typeRegistry) {
913
+ const definitions = Object.values(typeRegistry);
914
+ assertUniqueSerializedNames(
915
+ definitions.map((definition) => ({
916
+ logicalName: definition.name,
917
+ serializedName: getSerializedName(definition.name, definition.metadata),
918
+ category: "type definition"
919
+ })),
920
+ "$defs"
921
+ );
922
+ for (const definition of definitions) {
923
+ validateTypeDefinition(definition);
924
+ }
925
+ }
926
+ function validateTypeDefinition(definition) {
927
+ validateTypeNode(
928
+ definition.type,
929
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
930
+ );
931
+ }
932
+ function assertNoSerializedNameCollisions(ir) {
933
+ assertUniqueSerializedNames(
934
+ collectFlattenedFields(ir.elements).map((field) => ({
935
+ logicalName: field.name,
936
+ serializedName: getSerializedName(field.name, field.metadata),
937
+ category: "field"
938
+ })),
939
+ "form root"
940
+ );
941
+ for (const field of collectFlattenedFields(ir.elements)) {
942
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
943
+ }
944
+ validateTypeDefinitions(ir.typeRegistry);
945
+ }
946
+ var init_collision_guards = __esm({
947
+ "src/metadata/collision-guards.ts"() {
948
+ "use strict";
949
+ init_resolve();
950
+ }
951
+ });
952
+
442
953
  // src/json-schema/ir-generator.ts
443
954
  function makeContext(options) {
444
955
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -449,19 +960,33 @@ function makeContext(options) {
449
960
  }
450
961
  return {
451
962
  defs: {},
963
+ typeNameMap: {},
964
+ typeRegistry: {},
452
965
  extensionRegistry: options?.extensionRegistry,
453
966
  vendorPrefix
454
967
  };
455
968
  }
456
969
  function generateJsonSchemaFromIR(ir, options) {
457
- const ctx = makeContext(options);
970
+ assertNoSerializedNameCollisions(ir);
971
+ const ctx = {
972
+ ...makeContext(options),
973
+ typeRegistry: ir.typeRegistry,
974
+ typeNameMap: Object.fromEntries(
975
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
976
+ name,
977
+ getSerializedName(name, typeDef.metadata)
978
+ ])
979
+ )
980
+ };
458
981
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
459
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
982
+ const schemaName = ctx.typeNameMap[name] ?? name;
983
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
984
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
460
985
  if (typeDef.constraints && typeDef.constraints.length > 0) {
461
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
986
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
462
987
  }
463
988
  if (typeDef.annotations && typeDef.annotations.length > 0) {
464
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
989
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
465
990
  }
466
991
  }
467
992
  const properties = {};
@@ -474,6 +999,7 @@ function generateJsonSchemaFromIR(ir, options) {
474
999
  properties,
475
1000
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
476
1001
  };
1002
+ applyResolvedMetadata(result, ir.metadata);
477
1003
  if (ir.annotations && ir.annotations.length > 0) {
478
1004
  applyAnnotations(result, ir.annotations, ctx);
479
1005
  }
@@ -486,9 +1012,9 @@ function collectFields(elements, properties, required, ctx) {
486
1012
  for (const element of elements) {
487
1013
  switch (element.kind) {
488
1014
  case "field":
489
- properties[element.name] = generateFieldSchema(element, ctx);
1015
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
490
1016
  if (element.required) {
491
- required.push(element.name);
1017
+ required.push(getSerializedName(element.name, element.metadata));
492
1018
  }
493
1019
  break;
494
1020
  case "group":
@@ -532,6 +1058,7 @@ function generateFieldSchema(field, ctx) {
532
1058
  rootAnnotations.push(annotation);
533
1059
  }
534
1060
  }
1061
+ applyResolvedMetadata(schema, field.metadata);
535
1062
  applyAnnotations(schema, rootAnnotations, ctx);
536
1063
  if (itemStringSchema !== void 0) {
537
1064
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -539,7 +1066,7 @@ function generateFieldSchema(field, ctx) {
539
1066
  if (pathConstraints.length === 0) {
540
1067
  return schema;
541
1068
  }
542
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
1069
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
543
1070
  }
544
1071
  function isStringItemConstraint(constraint) {
545
1072
  switch (constraint.constraintKind) {
@@ -551,9 +1078,11 @@ function isStringItemConstraint(constraint) {
551
1078
  return false;
552
1079
  }
553
1080
  }
554
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
1081
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
555
1082
  if (schema.type === "array" && schema.items) {
556
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
1083
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
1084
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
1085
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
557
1086
  return schema;
558
1087
  }
559
1088
  const byTarget = /* @__PURE__ */ new Map();
@@ -568,7 +1097,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
568
1097
  for (const [target, constraints] of byTarget) {
569
1098
  const subSchema = {};
570
1099
  applyConstraints(subSchema, constraints, ctx);
571
- propertyOverrides[target] = subSchema;
1100
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
572
1101
  }
573
1102
  if (schema.$ref) {
574
1103
  const { $ref, ...rest } = schema;
@@ -616,7 +1145,7 @@ function generateTypeNode(type, ctx) {
616
1145
  case "union":
617
1146
  return generateUnionType(type, ctx);
618
1147
  case "reference":
619
- return generateReferenceType(type);
1148
+ return generateReferenceType(type, ctx);
620
1149
  case "dynamic":
621
1150
  return generateDynamicType(type);
622
1151
  case "custom":
@@ -657,9 +1186,10 @@ function generateObjectType(type, ctx) {
657
1186
  const properties = {};
658
1187
  const required = [];
659
1188
  for (const prop of type.properties) {
660
- properties[prop.name] = generatePropertySchema(prop, ctx);
1189
+ const propertyName = getSerializedName(prop.name, prop.metadata);
1190
+ properties[propertyName] = generatePropertySchema(prop, ctx);
661
1191
  if (!prop.optional) {
662
- required.push(prop.name);
1192
+ required.push(propertyName);
663
1193
  }
664
1194
  }
665
1195
  const schema = { type: "object", properties };
@@ -680,6 +1210,7 @@ function generateRecordType(type, ctx) {
680
1210
  function generatePropertySchema(prop, ctx) {
681
1211
  const schema = generateTypeNode(prop.type, ctx);
682
1212
  applyConstraints(schema, prop.constraints, ctx);
1213
+ applyResolvedMetadata(schema, prop.metadata);
683
1214
  applyAnnotations(schema, prop.annotations, ctx);
684
1215
  return schema;
685
1216
  }
@@ -708,8 +1239,28 @@ function isNullableUnion(type) {
708
1239
  ).length;
709
1240
  return nullCount === 1;
710
1241
  }
711
- function generateReferenceType(type) {
712
- return { $ref: `#/$defs/${type.name}` };
1242
+ function generateReferenceType(type, ctx) {
1243
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
1244
+ }
1245
+ function applyResolvedMetadata(schema, metadata) {
1246
+ const displayName = getDisplayName(metadata);
1247
+ if (displayName !== void 0) {
1248
+ schema.title = displayName;
1249
+ }
1250
+ }
1251
+ function resolveReferencedType(type, ctx) {
1252
+ return ctx.typeRegistry[type.name]?.type;
1253
+ }
1254
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1255
+ if (typeNode?.kind === "object") {
1256
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1257
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1258
+ }
1259
+ if (typeNode?.kind === "reference") {
1260
+ const referencedType = resolveReferencedType(typeNode, ctx);
1261
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1262
+ }
1263
+ return logicalName;
713
1264
  }
714
1265
  function generateDynamicType(type) {
715
1266
  if (type.dynamicKind === "enum") {
@@ -789,7 +1340,7 @@ function applyAnnotations(schema, annotations, ctx) {
789
1340
  for (const annotation of annotations) {
790
1341
  switch (annotation.annotationKind) {
791
1342
  case "displayName":
792
- schema.title = annotation.value;
1343
+ schema.title ??= annotation.value;
793
1344
  break;
794
1345
  case "description":
795
1346
  schema.description = annotation.value;
@@ -876,12 +1427,17 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
876
1427
  var init_ir_generator = __esm({
877
1428
  "src/json-schema/ir-generator.ts"() {
878
1429
  "use strict";
1430
+ init_metadata();
1431
+ init_collision_guards();
879
1432
  }
880
1433
  });
881
1434
 
882
1435
  // src/json-schema/generator.ts
883
1436
  function generateJsonSchema(form, options) {
884
- const ir = canonicalizeChainDSL(form);
1437
+ const ir = canonicalizeChainDSL(
1438
+ form,
1439
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1440
+ );
885
1441
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
886
1442
  return generateJsonSchemaFromIR(ir, internalOptions);
887
1443
  }
@@ -1063,13 +1619,21 @@ function combineRules(parentRule, childRule) {
1063
1619
  }
1064
1620
  };
1065
1621
  }
1066
- function fieldNodeToControl(field, parentRule) {
1067
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1622
+ function getFieldDisplayName(field) {
1623
+ const resolvedDisplayName = getDisplayName(field.metadata);
1624
+ if (resolvedDisplayName !== void 0) {
1625
+ return resolvedDisplayName;
1626
+ }
1627
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1628
+ }
1629
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
1068
1630
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1631
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1632
+ const displayName = getFieldDisplayName(field);
1069
1633
  const control = {
1070
1634
  type: "Control",
1071
- scope: fieldToScope(field.name),
1072
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1635
+ scope: fieldToScope(serializedName),
1636
+ ...displayName !== void 0 && { label: displayName },
1073
1637
  ...placeholderAnnotation !== void 0 && {
1074
1638
  options: { placeholder: placeholderAnnotation.value }
1075
1639
  },
@@ -1077,30 +1641,30 @@ function fieldNodeToControl(field, parentRule) {
1077
1641
  };
1078
1642
  return control;
1079
1643
  }
1080
- function groupNodeToLayout(group, parentRule) {
1644
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
1081
1645
  return {
1082
1646
  type: "Group",
1083
1647
  label: group.label,
1084
- elements: irElementsToUiSchema(group.elements, parentRule),
1648
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
1085
1649
  ...parentRule !== void 0 && { rule: parentRule }
1086
1650
  };
1087
1651
  }
1088
- function irElementsToUiSchema(elements, parentRule) {
1652
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1089
1653
  const result = [];
1090
1654
  for (const element of elements) {
1091
1655
  switch (element.kind) {
1092
1656
  case "field": {
1093
- result.push(fieldNodeToControl(element, parentRule));
1657
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1094
1658
  break;
1095
1659
  }
1096
1660
  case "group": {
1097
- result.push(groupNodeToLayout(element, parentRule));
1661
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1098
1662
  break;
1099
1663
  }
1100
1664
  case "conditional": {
1101
- const newRule = createShowRule(element.fieldName, element.value);
1665
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1102
1666
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1103
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1667
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1104
1668
  result.push(...childElements);
1105
1669
  break;
1106
1670
  }
@@ -1114,24 +1678,52 @@ function irElementsToUiSchema(elements, parentRule) {
1114
1678
  return result;
1115
1679
  }
1116
1680
  function generateUiSchemaFromIR(ir) {
1681
+ assertNoSerializedNameCollisions(ir);
1682
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1117
1683
  const result = {
1118
1684
  type: "VerticalLayout",
1119
- elements: irElementsToUiSchema(ir.elements)
1685
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1120
1686
  };
1121
1687
  return parseOrThrow(uiSchema, result, "UI Schema");
1122
1688
  }
1689
+ function collectFieldNameMap(elements) {
1690
+ const map = /* @__PURE__ */ new Map();
1691
+ for (const element of elements) {
1692
+ switch (element.kind) {
1693
+ case "field":
1694
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1695
+ break;
1696
+ case "group":
1697
+ case "conditional":
1698
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1699
+ map.set(key, value);
1700
+ }
1701
+ break;
1702
+ default: {
1703
+ const _exhaustive = element;
1704
+ void _exhaustive;
1705
+ }
1706
+ }
1707
+ }
1708
+ return map;
1709
+ }
1123
1710
  var import_zod2;
1124
1711
  var init_ir_generator2 = __esm({
1125
1712
  "src/ui-schema/ir-generator.ts"() {
1126
1713
  "use strict";
1714
+ init_metadata();
1715
+ init_collision_guards();
1127
1716
  init_schema();
1128
1717
  import_zod2 = require("zod");
1129
1718
  }
1130
1719
  });
1131
1720
 
1132
1721
  // src/ui-schema/generator.ts
1133
- function generateUiSchema(form) {
1134
- const ir = canonicalizeChainDSL(form);
1722
+ function generateUiSchema(form, options) {
1723
+ const ir = canonicalizeChainDSL(
1724
+ form,
1725
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1726
+ );
1135
1727
  return generateUiSchemaFromIR(ir);
1136
1728
  }
1137
1729
  var init_generator2 = __esm({
@@ -2185,7 +2777,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
2185
2777
  ...hostType !== void 0 && { hostType }
2186
2778
  };
2187
2779
  }
2188
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2780
+ function makeExplicitScalarMetadata(value) {
2781
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
2782
+ }
2783
+ function extractExplicitMetadata(node) {
2784
+ let apiName;
2785
+ let displayName;
2786
+ let apiNamePlural;
2787
+ let displayNamePlural;
2788
+ for (const tag of getLeadingParsedTags(node)) {
2789
+ const value = tag.argumentText.trim();
2790
+ if (value === "") {
2791
+ continue;
2792
+ }
2793
+ if (tag.normalizedTagName === "apiName") {
2794
+ if (tag.target === null) {
2795
+ apiName ??= value;
2796
+ } else if (tag.target.kind === "variant") {
2797
+ if (tag.target.rawText === "singular") {
2798
+ apiName ??= value;
2799
+ } else if (tag.target.rawText === "plural") {
2800
+ apiNamePlural ??= value;
2801
+ }
2802
+ }
2803
+ continue;
2804
+ }
2805
+ if (tag.normalizedTagName === "displayName") {
2806
+ if (tag.target === null) {
2807
+ displayName ??= value;
2808
+ } else if (tag.target.kind === "variant") {
2809
+ if (tag.target.rawText === "singular") {
2810
+ displayName ??= value;
2811
+ } else if (tag.target.rawText === "plural") {
2812
+ displayNamePlural ??= value;
2813
+ }
2814
+ }
2815
+ }
2816
+ }
2817
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
2818
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
2819
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
2820
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
2821
+ const metadata = {
2822
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
2823
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
2824
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
2825
+ ...resolvedDisplayNamePlural !== void 0 && {
2826
+ displayNamePlural: resolvedDisplayNamePlural
2827
+ }
2828
+ };
2829
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
2830
+ }
2831
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
2832
+ const explicit = extractExplicitMetadata(node);
2833
+ return resolveMetadata(
2834
+ {
2835
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
2836
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
2837
+ ...explicit?.apiNamePlural !== void 0 && {
2838
+ apiNamePlural: explicit.apiNamePlural.value
2839
+ },
2840
+ ...explicit?.displayNamePlural !== void 0 && {
2841
+ displayNamePlural: explicit.displayNamePlural.value
2842
+ }
2843
+ },
2844
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
2845
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
2846
+ );
2847
+ }
2848
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2849
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2189
2850
  const name = classDecl.name?.text ?? "AnonymousClass";
2190
2851
  const fields = [];
2191
2852
  const fieldLayouts = [];
@@ -2212,6 +2873,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2212
2873
  visiting,
2213
2874
  diagnostics,
2214
2875
  classType,
2876
+ normalizedMetadataPolicy,
2215
2877
  extensionRegistry
2216
2878
  );
2217
2879
  if (fieldNode) {
@@ -2230,9 +2892,25 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2230
2892
  }
2231
2893
  }
2232
2894
  }
2895
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2896
+ fields,
2897
+ classDecl,
2898
+ classType,
2899
+ checker,
2900
+ file,
2901
+ diagnostics,
2902
+ normalizedMetadataPolicy
2903
+ );
2904
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2905
+ checker,
2906
+ declaration: classDecl,
2907
+ subjectType: classType,
2908
+ hostType: classType
2909
+ });
2233
2910
  return {
2234
2911
  name,
2235
- fields,
2912
+ ...metadata !== void 0 && { metadata },
2913
+ fields: specializedFields,
2236
2914
  fieldLayouts,
2237
2915
  typeRegistry,
2238
2916
  ...annotations.length > 0 && { annotations },
@@ -2241,7 +2919,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2241
2919
  staticMethods
2242
2920
  };
2243
2921
  }
2244
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2922
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2923
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2245
2924
  const name = interfaceDecl.name.text;
2246
2925
  const fields = [];
2247
2926
  const typeRegistry = {};
@@ -2265,6 +2944,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2265
2944
  visiting,
2266
2945
  diagnostics,
2267
2946
  interfaceType,
2947
+ normalizedMetadataPolicy,
2268
2948
  extensionRegistry
2269
2949
  );
2270
2950
  if (fieldNode) {
@@ -2272,10 +2952,26 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2272
2952
  }
2273
2953
  }
2274
2954
  }
2275
- const fieldLayouts = fields.map(() => ({}));
2955
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2956
+ fields,
2957
+ interfaceDecl,
2958
+ interfaceType,
2959
+ checker,
2960
+ file,
2961
+ diagnostics,
2962
+ normalizedMetadataPolicy
2963
+ );
2964
+ const fieldLayouts = specializedFields.map(() => ({}));
2965
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
2966
+ checker,
2967
+ declaration: interfaceDecl,
2968
+ subjectType: interfaceType,
2969
+ hostType: interfaceType
2970
+ });
2276
2971
  return {
2277
2972
  name,
2278
- fields,
2973
+ ...metadata !== void 0 && { metadata },
2974
+ fields: specializedFields,
2279
2975
  fieldLayouts,
2280
2976
  typeRegistry,
2281
2977
  ...annotations.length > 0 && { annotations },
@@ -2284,7 +2980,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2284
2980
  staticMethods: []
2285
2981
  };
2286
2982
  }
2287
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2983
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2288
2984
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2289
2985
  const sourceFile = typeAlias.getSourceFile();
2290
2986
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2294,6 +2990,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2294
2990
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2295
2991
  };
2296
2992
  }
2993
+ const typeLiteral = typeAlias.type;
2994
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2297
2995
  const name = typeAlias.name.text;
2298
2996
  const fields = [];
2299
2997
  const typeRegistry = {};
@@ -2307,7 +3005,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2307
3005
  const annotations = [...typeAliasDoc.annotations];
2308
3006
  diagnostics.push(...typeAliasDoc.diagnostics);
2309
3007
  const visiting = /* @__PURE__ */ new Set();
2310
- for (const member of typeAlias.type.members) {
3008
+ for (const member of typeLiteral.members) {
2311
3009
  if (ts3.isPropertySignature(member)) {
2312
3010
  const fieldNode = analyzeInterfacePropertyToIR(
2313
3011
  member,
@@ -2317,6 +3015,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2317
3015
  visiting,
2318
3016
  diagnostics,
2319
3017
  aliasType,
3018
+ normalizedMetadataPolicy,
2320
3019
  extensionRegistry
2321
3020
  );
2322
3021
  if (fieldNode) {
@@ -2324,12 +3023,28 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2324
3023
  }
2325
3024
  }
2326
3025
  }
3026
+ const specializedFields = applyDeclarationDiscriminatorToFields(
3027
+ fields,
3028
+ typeAlias,
3029
+ aliasType,
3030
+ checker,
3031
+ file,
3032
+ diagnostics,
3033
+ normalizedMetadataPolicy
3034
+ );
3035
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
3036
+ checker,
3037
+ declaration: typeAlias,
3038
+ subjectType: aliasType,
3039
+ hostType: aliasType
3040
+ });
2327
3041
  return {
2328
3042
  ok: true,
2329
3043
  analysis: {
2330
3044
  name,
2331
- fields,
2332
- fieldLayouts: fields.map(() => ({})),
3045
+ ...metadata !== void 0 && { metadata },
3046
+ fields: specializedFields,
3047
+ fieldLayouts: specializedFields.map(() => ({})),
2333
3048
  typeRegistry,
2334
3049
  ...annotations.length > 0 && { annotations },
2335
3050
  ...diagnostics.length > 0 && { diagnostics },
@@ -2338,7 +3053,444 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2338
3053
  }
2339
3054
  };
2340
3055
  }
2341
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3056
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
3057
+ return {
3058
+ code,
3059
+ message,
3060
+ severity: "error",
3061
+ primaryLocation,
3062
+ relatedLocations
3063
+ };
3064
+ }
3065
+ function getLeadingParsedTags(node) {
3066
+ const sourceFile = node.getSourceFile();
3067
+ const sourceText = sourceFile.getFullText();
3068
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
3069
+ if (commentRanges === void 0) {
3070
+ return [];
3071
+ }
3072
+ const parsedTags = [];
3073
+ for (const range of commentRanges) {
3074
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
3075
+ continue;
3076
+ }
3077
+ const commentText = sourceText.slice(range.pos, range.end);
3078
+ if (!commentText.startsWith("/**")) {
3079
+ continue;
3080
+ }
3081
+ parsedTags.push(...(0, import_internal2.parseCommentBlock)(commentText, { offset: range.pos }).tags);
3082
+ }
3083
+ return parsedTags;
3084
+ }
3085
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
3086
+ const subjectType = checker.getTypeAtLocation(node);
3087
+ const propertySymbol = subjectType.getProperty(fieldName);
3088
+ if (propertySymbol === void 0) {
3089
+ return null;
3090
+ }
3091
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
3092
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
3093
+ ) ?? propertySymbol.declarations?.[0];
3094
+ return {
3095
+ declaration,
3096
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
3097
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
3098
+ };
3099
+ }
3100
+ function isLocalTypeParameterName(node, typeParameterName) {
3101
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
3102
+ }
3103
+ function isNullishSemanticType(type) {
3104
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
3105
+ return true;
3106
+ }
3107
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
3108
+ }
3109
+ function isStringLikeSemanticType(type) {
3110
+ if (type.flags & ts3.TypeFlags.StringLike) {
3111
+ return true;
3112
+ }
3113
+ if (type.isUnion()) {
3114
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
3115
+ }
3116
+ return false;
3117
+ }
3118
+ function extractDiscriminatorDirective(node, file, diagnostics) {
3119
+ const discriminatorTags = getLeadingParsedTags(node).filter(
3120
+ (tag) => tag.normalizedTagName === "discriminator"
3121
+ );
3122
+ if (discriminatorTags.length === 0) {
3123
+ return null;
3124
+ }
3125
+ const [firstTag, ...duplicateTags] = discriminatorTags;
3126
+ for (const _duplicateTag of duplicateTags) {
3127
+ diagnostics.push(
3128
+ makeAnalysisDiagnostic(
3129
+ "DUPLICATE_TAG",
3130
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
3131
+ provenanceForNode(node, file)
3132
+ )
3133
+ );
3134
+ }
3135
+ if (firstTag === void 0) {
3136
+ return null;
3137
+ }
3138
+ const firstTarget = firstTag.target;
3139
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
3140
+ diagnostics.push(
3141
+ makeAnalysisDiagnostic(
3142
+ "INVALID_TAG_ARGUMENT",
3143
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
3144
+ provenanceForNode(node, file)
3145
+ )
3146
+ );
3147
+ return null;
3148
+ }
3149
+ if (firstTarget.path.segments.length !== 1) {
3150
+ diagnostics.push(
3151
+ makeAnalysisDiagnostic(
3152
+ "INVALID_TAG_ARGUMENT",
3153
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
3154
+ provenanceForNode(node, file)
3155
+ )
3156
+ );
3157
+ return null;
3158
+ }
3159
+ const typeParameterName = firstTag.argumentText.trim();
3160
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
3161
+ diagnostics.push(
3162
+ makeAnalysisDiagnostic(
3163
+ "INVALID_TAG_ARGUMENT",
3164
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
3165
+ provenanceForNode(node, file)
3166
+ )
3167
+ );
3168
+ return null;
3169
+ }
3170
+ return {
3171
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
3172
+ typeParameterName,
3173
+ provenance: provenanceForNode(node, file)
3174
+ };
3175
+ }
3176
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
3177
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
3178
+ if (directive === null) {
3179
+ return null;
3180
+ }
3181
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
3182
+ diagnostics.push(
3183
+ makeAnalysisDiagnostic(
3184
+ "INVALID_TAG_ARGUMENT",
3185
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
3186
+ directive.provenance
3187
+ )
3188
+ );
3189
+ return null;
3190
+ }
3191
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3192
+ if (property === null) {
3193
+ diagnostics.push(
3194
+ makeAnalysisDiagnostic(
3195
+ "UNKNOWN_PATH_TARGET",
3196
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
3197
+ directive.provenance
3198
+ )
3199
+ );
3200
+ return null;
3201
+ }
3202
+ if (property.optional) {
3203
+ diagnostics.push(
3204
+ makeAnalysisDiagnostic(
3205
+ "TYPE_MISMATCH",
3206
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
3207
+ directive.provenance,
3208
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3209
+ )
3210
+ );
3211
+ return null;
3212
+ }
3213
+ if (isNullishSemanticType(property.type)) {
3214
+ diagnostics.push(
3215
+ makeAnalysisDiagnostic(
3216
+ "TYPE_MISMATCH",
3217
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
3218
+ directive.provenance,
3219
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3220
+ )
3221
+ );
3222
+ return null;
3223
+ }
3224
+ if (!isStringLikeSemanticType(property.type)) {
3225
+ diagnostics.push(
3226
+ makeAnalysisDiagnostic(
3227
+ "TYPE_MISMATCH",
3228
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
3229
+ directive.provenance,
3230
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3231
+ )
3232
+ );
3233
+ return null;
3234
+ }
3235
+ return directive;
3236
+ }
3237
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
3238
+ const typeParameterIndex = node.typeParameters?.findIndex(
3239
+ (typeParameter) => typeParameter.name.text === typeParameterName
3240
+ ) ?? -1;
3241
+ if (typeParameterIndex < 0) {
3242
+ return null;
3243
+ }
3244
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
3245
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
3246
+ return referenceTypeArguments[typeParameterIndex] ?? null;
3247
+ }
3248
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
3249
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
3250
+ }
3251
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
3252
+ const propertySymbol = boundType.getProperty(fieldName);
3253
+ if (propertySymbol === void 0) {
3254
+ return void 0;
3255
+ }
3256
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
3257
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
3258
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
3259
+ if (resolvedAnchorNode === null) {
3260
+ return void 0;
3261
+ }
3262
+ const propertyType = checker.getTypeOfSymbolAtLocation(
3263
+ propertySymbol,
3264
+ resolvedAnchorNode
3265
+ );
3266
+ if (propertyType.isStringLiteral()) {
3267
+ return propertyType.value;
3268
+ }
3269
+ if (propertyType.isUnion()) {
3270
+ const nonNullMembers = propertyType.types.filter(
3271
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3272
+ );
3273
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
3274
+ diagnostics.push(
3275
+ makeAnalysisDiagnostic(
3276
+ "INVALID_TAG_ARGUMENT",
3277
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
3278
+ provenance
3279
+ )
3280
+ );
3281
+ return null;
3282
+ }
3283
+ }
3284
+ return void 0;
3285
+ }
3286
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
3287
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
3288
+ if (declaration === null) {
3289
+ return void 0;
3290
+ }
3291
+ const metadata = resolveNodeMetadata(
3292
+ metadataPolicy,
3293
+ "type",
3294
+ getDiscriminatorLogicalName(boundType, declaration, checker),
3295
+ declaration,
3296
+ {
3297
+ checker,
3298
+ declaration,
3299
+ subjectType: boundType
3300
+ }
3301
+ );
3302
+ return metadata?.apiName;
3303
+ }
3304
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
3305
+ if (seen.has(type)) {
3306
+ return null;
3307
+ }
3308
+ seen.add(type);
3309
+ const symbol = type.aliasSymbol ?? type.getSymbol();
3310
+ if (symbol !== void 0) {
3311
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
3312
+ const targetSymbol = aliased ?? symbol;
3313
+ const declaration = targetSymbol.declarations?.find(
3314
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
3315
+ );
3316
+ if (declaration !== void 0) {
3317
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
3318
+ return resolveNamedDiscriminatorDeclaration(
3319
+ checker.getTypeFromTypeNode(declaration.type),
3320
+ checker,
3321
+ seen
3322
+ );
3323
+ }
3324
+ return declaration;
3325
+ }
3326
+ }
3327
+ return null;
3328
+ }
3329
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
3330
+ if (boundType === null) {
3331
+ diagnostics.push(
3332
+ makeAnalysisDiagnostic(
3333
+ "INVALID_TAG_ARGUMENT",
3334
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
3335
+ provenance
3336
+ )
3337
+ );
3338
+ return null;
3339
+ }
3340
+ if (boundType.isStringLiteral()) {
3341
+ return boundType.value;
3342
+ }
3343
+ if (boundType.isUnion()) {
3344
+ const nonNullMembers = boundType.types.filter(
3345
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3346
+ );
3347
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
3348
+ diagnostics.push(
3349
+ makeAnalysisDiagnostic(
3350
+ "INVALID_TAG_ARGUMENT",
3351
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
3352
+ provenance
3353
+ )
3354
+ );
3355
+ return null;
3356
+ }
3357
+ }
3358
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
3359
+ boundType,
3360
+ fieldName,
3361
+ checker,
3362
+ provenance,
3363
+ diagnostics
3364
+ );
3365
+ if (literalIdentityValue !== void 0) {
3366
+ return literalIdentityValue;
3367
+ }
3368
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
3369
+ if (apiName?.source === "explicit") {
3370
+ return apiName.value;
3371
+ }
3372
+ if (apiName?.source === "inferred") {
3373
+ return apiName.value;
3374
+ }
3375
+ diagnostics.push(
3376
+ makeAnalysisDiagnostic(
3377
+ "INVALID_TAG_ARGUMENT",
3378
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
3379
+ provenance
3380
+ )
3381
+ );
3382
+ return null;
3383
+ }
3384
+ function getDeclarationName(node) {
3385
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
3386
+ return node.name?.text ?? "anonymous";
3387
+ }
3388
+ return "anonymous";
3389
+ }
3390
+ function getResolvedTypeArguments(type) {
3391
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
3392
+ }
3393
+ function getDiscriminatorLogicalName(type, declaration, checker) {
3394
+ const baseName = getDeclarationName(declaration);
3395
+ const typeArguments = getResolvedTypeArguments(type);
3396
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
3397
+ }
3398
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3399
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3400
+ if (directive === null) {
3401
+ return [...fields];
3402
+ }
3403
+ const discriminatorValue = resolveDiscriminatorValue(
3404
+ getConcreteTypeArgumentForDiscriminator(
3405
+ node,
3406
+ subjectType,
3407
+ checker,
3408
+ directive.typeParameterName
3409
+ ),
3410
+ directive.fieldName,
3411
+ checker,
3412
+ directive.provenance,
3413
+ diagnostics,
3414
+ metadataPolicy
3415
+ );
3416
+ if (discriminatorValue === null) {
3417
+ return [...fields];
3418
+ }
3419
+ return fields.map(
3420
+ (field) => field.name === directive.fieldName ? {
3421
+ ...field,
3422
+ type: {
3423
+ kind: "enum",
3424
+ members: [{ value: discriminatorValue }]
3425
+ }
3426
+ } : field
3427
+ );
3428
+ }
3429
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
3430
+ const renderedArguments = typeArguments.map(
3431
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
3432
+ ).filter((value) => value !== "");
3433
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
3434
+ }
3435
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
3436
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
3437
+ if (typeNode === void 0) {
3438
+ return [];
3439
+ }
3440
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
3441
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
3442
+ return [];
3443
+ }
3444
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
3445
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
3446
+ return {
3447
+ tsType: argumentType,
3448
+ typeNode: resolveTypeNode(
3449
+ argumentType,
3450
+ checker,
3451
+ file,
3452
+ typeRegistry,
3453
+ visiting,
3454
+ argumentNode,
3455
+ metadataPolicy,
3456
+ extensionRegistry,
3457
+ diagnostics
3458
+ )
3459
+ };
3460
+ });
3461
+ }
3462
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3463
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3464
+ if (directive === null) {
3465
+ return properties;
3466
+ }
3467
+ const discriminatorValue = resolveDiscriminatorValue(
3468
+ getConcreteTypeArgumentForDiscriminator(
3469
+ node,
3470
+ subjectType,
3471
+ checker,
3472
+ directive.typeParameterName
3473
+ ),
3474
+ directive.fieldName,
3475
+ checker,
3476
+ directive.provenance,
3477
+ diagnostics,
3478
+ metadataPolicy
3479
+ );
3480
+ if (discriminatorValue === null) {
3481
+ return properties;
3482
+ }
3483
+ return properties.map(
3484
+ (property) => property.name === directive.fieldName ? {
3485
+ ...property,
3486
+ type: {
3487
+ kind: "enum",
3488
+ members: [{ value: discriminatorValue }]
3489
+ }
3490
+ } : property
3491
+ );
3492
+ }
3493
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2342
3494
  if (!ts3.isIdentifier(prop.name)) {
2343
3495
  return null;
2344
3496
  }
@@ -2353,6 +3505,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2353
3505
  typeRegistry,
2354
3506
  visiting,
2355
3507
  prop,
3508
+ metadataPolicy,
2356
3509
  extensionRegistry,
2357
3510
  diagnostics
2358
3511
  );
@@ -2376,9 +3529,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2376
3529
  annotations.push(defaultAnnotation);
2377
3530
  }
2378
3531
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3532
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3533
+ checker,
3534
+ declaration: prop,
3535
+ subjectType: tsType,
3536
+ hostType
3537
+ });
2379
3538
  return {
2380
3539
  kind: "field",
2381
3540
  name,
3541
+ ...metadata !== void 0 && { metadata },
2382
3542
  type,
2383
3543
  required: !optional,
2384
3544
  constraints,
@@ -2386,7 +3546,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2386
3546
  provenance
2387
3547
  };
2388
3548
  }
2389
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3549
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2390
3550
  if (!ts3.isIdentifier(prop.name)) {
2391
3551
  return null;
2392
3552
  }
@@ -2401,6 +3561,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2401
3561
  typeRegistry,
2402
3562
  visiting,
2403
3563
  prop,
3564
+ metadataPolicy,
2404
3565
  extensionRegistry,
2405
3566
  diagnostics
2406
3567
  );
@@ -2420,9 +3581,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2420
3581
  let annotations = [];
2421
3582
  annotations.push(...docResult.annotations);
2422
3583
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3584
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3585
+ checker,
3586
+ declaration: prop,
3587
+ subjectType: tsType,
3588
+ hostType
3589
+ });
2423
3590
  return {
2424
3591
  kind: "field",
2425
3592
  name,
3593
+ ...metadata !== void 0 && { metadata },
2426
3594
  type,
2427
3595
  required: !optional,
2428
3596
  constraints,
@@ -2547,7 +3715,7 @@ function getTypeNodeRegistrationName(typeNode) {
2547
3715
  }
2548
3716
  return null;
2549
3717
  }
2550
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3718
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2551
3719
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2552
3720
  if (customType) {
2553
3721
  return customType;
@@ -2559,6 +3727,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2559
3727
  typeRegistry,
2560
3728
  visiting,
2561
3729
  sourceNode,
3730
+ metadataPolicy,
2562
3731
  extensionRegistry,
2563
3732
  diagnostics
2564
3733
  );
@@ -2603,6 +3772,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2603
3772
  typeRegistry,
2604
3773
  visiting,
2605
3774
  sourceNode,
3775
+ metadataPolicy,
2606
3776
  extensionRegistry,
2607
3777
  diagnostics
2608
3778
  );
@@ -2615,6 +3785,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2615
3785
  typeRegistry,
2616
3786
  visiting,
2617
3787
  sourceNode,
3788
+ metadataPolicy,
2618
3789
  extensionRegistry,
2619
3790
  diagnostics
2620
3791
  );
@@ -2626,13 +3797,15 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2626
3797
  file,
2627
3798
  typeRegistry,
2628
3799
  visiting,
3800
+ sourceNode,
3801
+ metadataPolicy,
2629
3802
  extensionRegistry,
2630
3803
  diagnostics
2631
3804
  );
2632
3805
  }
2633
3806
  return { kind: "primitive", primitiveKind: "string" };
2634
3807
  }
2635
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3808
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2636
3809
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2637
3810
  return null;
2638
3811
  }
@@ -2652,14 +3825,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2652
3825
  file,
2653
3826
  makeParseOptions(extensionRegistry)
2654
3827
  );
3828
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3829
+ checker,
3830
+ declaration: aliasDecl,
3831
+ subjectType: aliasType
3832
+ });
2655
3833
  typeRegistry[aliasName] = {
2656
3834
  name: aliasName,
3835
+ ...metadata !== void 0 && { metadata },
2657
3836
  type: resolveAliasedPrimitiveTarget(
2658
3837
  aliasType,
2659
3838
  checker,
2660
3839
  file,
2661
3840
  typeRegistry,
2662
3841
  visiting,
3842
+ metadataPolicy,
2663
3843
  extensionRegistry,
2664
3844
  diagnostics
2665
3845
  ),
@@ -2688,7 +3868,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2688
3868
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2689
3869
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2690
3870
  }
2691
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3871
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2692
3872
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2693
3873
  if (nestedAliasDecl !== void 0) {
2694
3874
  return resolveAliasedPrimitiveTarget(
@@ -2697,6 +3877,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2697
3877
  file,
2698
3878
  typeRegistry,
2699
3879
  visiting,
3880
+ metadataPolicy,
2700
3881
  extensionRegistry,
2701
3882
  diagnostics
2702
3883
  );
@@ -2708,11 +3889,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2708
3889
  typeRegistry,
2709
3890
  visiting,
2710
3891
  void 0,
3892
+ metadataPolicy,
2711
3893
  extensionRegistry,
2712
3894
  diagnostics
2713
3895
  );
2714
3896
  }
2715
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3897
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2716
3898
  const typeName = getNamedTypeName(type);
2717
3899
  const namedDecl = getNamedTypeDeclaration(type);
2718
3900
  if (typeName && typeName in typeRegistry) {
@@ -2747,8 +3929,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2747
3929
  return result;
2748
3930
  }
2749
3931
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3932
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
3933
+ checker,
3934
+ declaration: namedDecl,
3935
+ subjectType: type
3936
+ }) : void 0;
2750
3937
  typeRegistry[typeName] = {
2751
3938
  name: typeName,
3939
+ ...metadata !== void 0 && { metadata },
2752
3940
  type: result,
2753
3941
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2754
3942
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -2802,6 +3990,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2802
3990
  typeRegistry,
2803
3991
  visiting,
2804
3992
  nonNullMembers[0].sourceNode ?? sourceNode,
3993
+ metadataPolicy,
2805
3994
  extensionRegistry,
2806
3995
  diagnostics
2807
3996
  );
@@ -2819,6 +4008,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2819
4008
  typeRegistry,
2820
4009
  visiting,
2821
4010
  memberSourceNode ?? sourceNode,
4011
+ metadataPolicy,
2822
4012
  extensionRegistry,
2823
4013
  diagnostics
2824
4014
  )
@@ -2828,7 +4018,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2828
4018
  }
2829
4019
  return registerNamed({ kind: "union", members });
2830
4020
  }
2831
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
4021
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2832
4022
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2833
4023
  const elementType = typeArgs?.[0];
2834
4024
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2839,12 +4029,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2839
4029
  typeRegistry,
2840
4030
  visiting,
2841
4031
  elementSourceNode,
4032
+ metadataPolicy,
2842
4033
  extensionRegistry,
2843
4034
  diagnostics
2844
4035
  ) : { kind: "primitive", primitiveKind: "string" };
2845
4036
  return { kind: "array", items };
2846
4037
  }
2847
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
4038
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2848
4039
  if (type.getProperties().length > 0) {
2849
4040
  return null;
2850
4041
  }
@@ -2859,6 +4050,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2859
4050
  typeRegistry,
2860
4051
  visiting,
2861
4052
  void 0,
4053
+ metadataPolicy,
2862
4054
  extensionRegistry,
2863
4055
  diagnostics
2864
4056
  );
@@ -2889,35 +4081,76 @@ function typeNodeContainsReference(type, targetName) {
2889
4081
  }
2890
4082
  }
2891
4083
  }
2892
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
4084
+ function shouldEmitResolvedObjectProperty(property, declaration) {
4085
+ if (property.name.startsWith("__@")) {
4086
+ return false;
4087
+ }
4088
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
4089
+ const name = declaration.name;
4090
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
4091
+ return false;
4092
+ }
4093
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
4094
+ return false;
4095
+ }
4096
+ }
4097
+ return true;
4098
+ }
4099
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4100
+ const collectedDiagnostics = diagnostics ?? [];
2893
4101
  const typeName = getNamedTypeName(type);
2894
4102
  const namedTypeName = typeName ?? void 0;
2895
4103
  const namedDecl = getNamedTypeDeclaration(type);
2896
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
4104
+ const referenceTypeArguments = extractReferenceTypeArguments(
4105
+ type,
4106
+ checker,
4107
+ file,
4108
+ typeRegistry,
4109
+ visiting,
4110
+ sourceNode,
4111
+ metadataPolicy,
4112
+ extensionRegistry,
4113
+ collectedDiagnostics
4114
+ );
4115
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
4116
+ namedTypeName,
4117
+ referenceTypeArguments.map((argument) => argument.tsType),
4118
+ checker
4119
+ ) : void 0;
4120
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
4121
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2897
4122
  const clearNamedTypeRegistration = () => {
2898
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
4123
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2899
4124
  return;
2900
4125
  }
2901
- Reflect.deleteProperty(typeRegistry, namedTypeName);
4126
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2902
4127
  };
2903
4128
  if (visiting.has(type)) {
2904
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2905
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4129
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4130
+ return {
4131
+ kind: "reference",
4132
+ name: registryTypeName,
4133
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4134
+ };
2906
4135
  }
2907
4136
  return { kind: "object", properties: [], additionalProperties: false };
2908
4137
  }
2909
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2910
- typeRegistry[namedTypeName] = {
2911
- name: namedTypeName,
4138
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
4139
+ typeRegistry[registryTypeName] = {
4140
+ name: registryTypeName,
2912
4141
  type: RESOLVING_TYPE_PLACEHOLDER,
2913
4142
  provenance: provenanceForDeclaration(namedDecl, file)
2914
4143
  };
2915
4144
  }
2916
4145
  visiting.add(type);
2917
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2918
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
4146
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
4147
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2919
4148
  visiting.delete(type);
2920
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4149
+ return {
4150
+ kind: "reference",
4151
+ name: registryTypeName,
4152
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4153
+ };
2921
4154
  }
2922
4155
  }
2923
4156
  const recordNode = tryResolveRecordType(
@@ -2926,25 +4159,36 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2926
4159
  file,
2927
4160
  typeRegistry,
2928
4161
  visiting,
4162
+ metadataPolicy,
2929
4163
  extensionRegistry,
2930
- diagnostics
4164
+ collectedDiagnostics
2931
4165
  );
2932
4166
  if (recordNode) {
2933
4167
  visiting.delete(type);
2934
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2935
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
4168
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4169
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2936
4170
  if (!isRecursiveRecord) {
2937
4171
  clearNamedTypeRegistration();
2938
4172
  return recordNode;
2939
4173
  }
2940
4174
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2941
- typeRegistry[namedTypeName] = {
2942
- name: namedTypeName,
4175
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4176
+ checker,
4177
+ declaration: namedDecl,
4178
+ subjectType: type
4179
+ }) : void 0;
4180
+ typeRegistry[registryTypeName] = {
4181
+ name: registryTypeName,
4182
+ ...metadata !== void 0 && { metadata },
2943
4183
  type: recordNode,
2944
4184
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2945
4185
  provenance: provenanceForDeclaration(namedDecl, file)
2946
4186
  };
2947
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4187
+ return {
4188
+ kind: "reference",
4189
+ name: registryTypeName,
4190
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4191
+ };
2948
4192
  }
2949
4193
  return recordNode;
2950
4194
  }
@@ -2955,12 +4199,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2955
4199
  file,
2956
4200
  typeRegistry,
2957
4201
  visiting,
2958
- diagnostics ?? [],
4202
+ metadataPolicy,
4203
+ collectedDiagnostics,
2959
4204
  extensionRegistry
2960
4205
  );
2961
4206
  for (const prop of type.getProperties()) {
2962
4207
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2963
4208
  if (!declaration) continue;
4209
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
2964
4210
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2965
4211
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2966
4212
  const propTypeNode = resolveTypeNode(
@@ -2970,12 +4216,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2970
4216
  typeRegistry,
2971
4217
  visiting,
2972
4218
  declaration,
4219
+ metadataPolicy,
2973
4220
  extensionRegistry,
2974
- diagnostics
4221
+ collectedDiagnostics
2975
4222
  );
2976
4223
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2977
4224
  properties.push({
2978
4225
  name: prop.name,
4226
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
2979
4227
  type: propTypeNode,
2980
4228
  optional,
2981
4229
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -2986,22 +4234,40 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2986
4234
  visiting.delete(type);
2987
4235
  const objectNode = {
2988
4236
  kind: "object",
2989
- properties,
4237
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
4238
+ properties,
4239
+ namedDecl,
4240
+ type,
4241
+ checker,
4242
+ file,
4243
+ collectedDiagnostics,
4244
+ metadataPolicy
4245
+ ) : properties,
2990
4246
  additionalProperties: true
2991
4247
  };
2992
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
4248
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2993
4249
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2994
- typeRegistry[namedTypeName] = {
2995
- name: namedTypeName,
4250
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4251
+ checker,
4252
+ declaration: namedDecl,
4253
+ subjectType: type
4254
+ }) : void 0;
4255
+ typeRegistry[registryTypeName] = {
4256
+ name: registryTypeName,
4257
+ ...metadata !== void 0 && { metadata },
2996
4258
  type: objectNode,
2997
4259
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2998
4260
  provenance: provenanceForDeclaration(namedDecl, file)
2999
4261
  };
3000
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4262
+ return {
4263
+ kind: "reference",
4264
+ name: registryTypeName,
4265
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4266
+ };
3001
4267
  }
3002
4268
  return objectNode;
3003
4269
  }
3004
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4270
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
3005
4271
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
3006
4272
  (s) => s?.declarations != null && s.declarations.length > 0
3007
4273
  );
@@ -3022,10 +4288,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3022
4288
  visiting,
3023
4289
  diagnostics,
3024
4290
  hostType,
4291
+ metadataPolicy,
3025
4292
  extensionRegistry
3026
4293
  );
3027
4294
  if (fieldNode) {
3028
4295
  map.set(fieldNode.name, {
4296
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3029
4297
  constraints: [...fieldNode.constraints],
3030
4298
  annotations: [...fieldNode.annotations],
3031
4299
  provenance: fieldNode.provenance
@@ -3043,6 +4311,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3043
4311
  file,
3044
4312
  typeRegistry,
3045
4313
  visiting,
4314
+ metadataPolicy,
3046
4315
  checker.getTypeAtLocation(interfaceDecl),
3047
4316
  diagnostics,
3048
4317
  extensionRegistry
@@ -3056,6 +4325,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3056
4325
  file,
3057
4326
  typeRegistry,
3058
4327
  visiting,
4328
+ metadataPolicy,
3059
4329
  checker.getTypeAtLocation(typeAliasDecl),
3060
4330
  diagnostics,
3061
4331
  extensionRegistry
@@ -3107,7 +4377,7 @@ function isNullishTypeNode(typeNode) {
3107
4377
  }
3108
4378
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3109
4379
  }
3110
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4380
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3111
4381
  const map = /* @__PURE__ */ new Map();
3112
4382
  for (const member of members) {
3113
4383
  if (ts3.isPropertySignature(member)) {
@@ -3119,10 +4389,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3119
4389
  visiting,
3120
4390
  diagnostics,
3121
4391
  hostType,
4392
+ metadataPolicy,
3122
4393
  extensionRegistry
3123
4394
  );
3124
4395
  if (fieldNode) {
3125
4396
  map.set(fieldNode.name, {
4397
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3126
4398
  constraints: [...fieldNode.constraints],
3127
4399
  annotations: [...fieldNode.annotations],
3128
4400
  provenance: fieldNode.provenance
@@ -3152,6 +4424,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3152
4424
  {},
3153
4425
  /* @__PURE__ */ new Set(),
3154
4426
  aliasDecl.type,
4427
+ void 0,
3155
4428
  extensionRegistry
3156
4429
  );
3157
4430
  const constraints = extractJSDocConstraintNodes(
@@ -3255,13 +4528,15 @@ function detectFormSpecReference(typeNode) {
3255
4528
  }
3256
4529
  return null;
3257
4530
  }
3258
- var ts3, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
4531
+ var ts3, import_internal2, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
3259
4532
  var init_class_analyzer = __esm({
3260
4533
  "src/analyzer/class-analyzer.ts"() {
3261
4534
  "use strict";
3262
4535
  ts3 = __toESM(require("typescript"), 1);
4536
+ import_internal2 = require("@formspec/analysis/internal");
3263
4537
  init_jsdoc_constraints();
3264
4538
  init_tsdoc_parser();
4539
+ init_metadata();
3265
4540
  RESOLVING_TYPE_PLACEHOLDER = {
3266
4541
  kind: "object",
3267
4542
  properties: [],
@@ -3352,19 +4627,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3352
4627
  function findTypeAliasByName(sourceFile, aliasName) {
3353
4628
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3354
4629
  }
3355
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4630
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3356
4631
  const ctx = createProgramContext(filePath);
3357
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4632
+ return analyzeNamedTypeToIRFromProgramContext(
4633
+ ctx,
4634
+ filePath,
4635
+ typeName,
4636
+ extensionRegistry,
4637
+ metadataPolicy
4638
+ );
3358
4639
  }
3359
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4640
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3360
4641
  const analysisFilePath = path.resolve(filePath);
3361
4642
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3362
4643
  if (classDecl !== null) {
3363
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4644
+ return analyzeClassToIR(
4645
+ classDecl,
4646
+ ctx.checker,
4647
+ analysisFilePath,
4648
+ extensionRegistry,
4649
+ metadataPolicy
4650
+ );
3364
4651
  }
3365
4652
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3366
4653
  if (interfaceDecl !== null) {
3367
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4654
+ return analyzeInterfaceToIR(
4655
+ interfaceDecl,
4656
+ ctx.checker,
4657
+ analysisFilePath,
4658
+ extensionRegistry,
4659
+ metadataPolicy
4660
+ );
3368
4661
  }
3369
4662
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3370
4663
  if (typeAlias !== null) {
@@ -3372,7 +4665,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3372
4665
  typeAlias,
3373
4666
  ctx.checker,
3374
4667
  analysisFilePath,
3375
- extensionRegistry
4668
+ extensionRegistry,
4669
+ metadataPolicy
3376
4670
  );
3377
4671
  if (result.ok) {
3378
4672
  return result.analysis;
@@ -3395,7 +4689,7 @@ var init_program = __esm({
3395
4689
 
3396
4690
  // src/validate/constraint-validator.ts
3397
4691
  function validateFieldNode(ctx, field) {
3398
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
4692
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3399
4693
  field.name,
3400
4694
  field.type,
3401
4695
  field.constraints,
@@ -3413,7 +4707,7 @@ function validateFieldNode(ctx, field) {
3413
4707
  }
3414
4708
  function validateObjectProperty(ctx, parentName, property) {
3415
4709
  const qualifiedName = `${parentName}.${property.name}`;
3416
- const analysis = (0, import_internal2.analyzeConstraintTargets)(
4710
+ const analysis = (0, import_internal3.analyzeConstraintTargets)(
3417
4711
  qualifiedName,
3418
4712
  property.type,
3419
4713
  property.constraints,
@@ -3464,11 +4758,11 @@ function validateIR(ir, options) {
3464
4758
  valid: ctx.diagnostics.every((diagnostic) => diagnostic.severity !== "error")
3465
4759
  };
3466
4760
  }
3467
- var import_internal2;
4761
+ var import_internal3;
3468
4762
  var init_constraint_validator = __esm({
3469
4763
  "src/validate/constraint-validator.ts"() {
3470
4764
  "use strict";
3471
- import_internal2 = require("@formspec/analysis/internal");
4765
+ import_internal3 = require("@formspec/analysis/internal");
3472
4766
  }
3473
4767
  });
3474
4768
 
@@ -3488,7 +4782,11 @@ function generateClassSchemas(analysis, source, options) {
3488
4782
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3489
4783
  throw new Error(formatValidationError(errorDiagnostics));
3490
4784
  }
3491
- const ir = canonicalizeTSDoc(analysis, source);
4785
+ const ir = canonicalizeTSDoc(
4786
+ analysis,
4787
+ source,
4788
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4789
+ );
3492
4790
  const validationResult = validateIR(ir, {
3493
4791
  ...options?.extensionRegistry !== void 0 && {
3494
4792
  extensionRegistry: options.extensionRegistry
@@ -3525,13 +4823,15 @@ function generateSchemasFromClass(options) {
3525
4823
  classDecl,
3526
4824
  ctx.checker,
3527
4825
  options.filePath,
3528
- options.extensionRegistry
4826
+ options.extensionRegistry,
4827
+ options.metadata
3529
4828
  );
3530
4829
  return generateClassSchemas(
3531
4830
  analysis,
3532
4831
  { file: options.filePath },
3533
4832
  {
3534
4833
  extensionRegistry: options.extensionRegistry,
4834
+ metadata: options.metadata,
3535
4835
  vendorPrefix: options.vendorPrefix
3536
4836
  }
3537
4837
  );
@@ -3549,13 +4849,15 @@ function generateSchemasFromProgram(options) {
3549
4849
  ctx,
3550
4850
  options.filePath,
3551
4851
  options.typeName,
3552
- options.extensionRegistry
4852
+ options.extensionRegistry,
4853
+ options.metadata
3553
4854
  );
3554
4855
  return generateClassSchemas(
3555
4856
  analysis,
3556
4857
  { file: options.filePath },
3557
4858
  {
3558
4859
  extensionRegistry: options.extensionRegistry,
4860
+ metadata: options.metadata,
3559
4861
  vendorPrefix: options.vendorPrefix
3560
4862
  }
3561
4863
  );
@@ -3577,16 +4879,28 @@ var init_class_schema = __esm({
3577
4879
  // src/generators/mixed-authoring.ts
3578
4880
  function buildMixedAuthoringSchemas(options) {
3579
4881
  const { filePath, typeName, overlays, ...schemaOptions } = options;
3580
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
3581
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
3582
- const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
4882
+ const analysis = analyzeNamedTypeToIR(
4883
+ filePath,
4884
+ typeName,
4885
+ schemaOptions.extensionRegistry,
4886
+ schemaOptions.metadata
4887
+ );
4888
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays, schemaOptions.metadata);
4889
+ const ir = canonicalizeTSDoc(
4890
+ composedAnalysis,
4891
+ { file: filePath },
4892
+ schemaOptions.metadata !== void 0 ? { metadata: schemaOptions.metadata } : void 0
4893
+ );
3583
4894
  return {
3584
4895
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
3585
4896
  uiSchema: generateUiSchemaFromIR(ir)
3586
4897
  };
3587
4898
  }
3588
- function composeAnalysisWithOverlays(analysis, overlays) {
3589
- const overlayIR = canonicalizeChainDSL(overlays);
4899
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4900
+ const overlayIR = canonicalizeChainDSL(
4901
+ overlays,
4902
+ metadata !== void 0 ? { metadata } : void 0
4903
+ );
3590
4904
  const overlayFields = collectOverlayFields(overlayIR.elements);
3591
4905
  if (overlayFields.length === 0) {
3592
4906
  return analysis;
@@ -3642,8 +4956,10 @@ function collectOverlayFields(elements) {
3642
4956
  }
3643
4957
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3644
4958
  assertSupportedOverlayField(baseField, overlayField);
4959
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
3645
4960
  return {
3646
4961
  ...baseField,
4962
+ ...metadata !== void 0 && { metadata },
3647
4963
  type: mergeFieldType(baseField, overlayField, typeRegistry),
3648
4964
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3649
4965
  };
@@ -3756,6 +5072,7 @@ var init_mixed_authoring = __esm({
3756
5072
  init_ir_generator2();
3757
5073
  init_canonicalize();
3758
5074
  init_program();
5075
+ init_metadata();
3759
5076
  }
3760
5077
  });
3761
5078
 
@@ -3777,12 +5094,12 @@ __export(index_exports, {
3777
5094
  function buildFormSchemas(form, options) {
3778
5095
  return {
3779
5096
  jsonSchema: generateJsonSchema(form, options),
3780
- uiSchema: generateUiSchema(form)
5097
+ uiSchema: generateUiSchema(form, options)
3781
5098
  };
3782
5099
  }
3783
5100
  function writeSchemas(form, options) {
3784
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
3785
- const buildOptions = vendorPrefix === void 0 ? void 0 : { vendorPrefix };
5101
+ const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
5102
+ const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
3786
5103
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
3787
5104
  if (!fs.existsSync(outDir)) {
3788
5105
  fs.mkdirSync(outDir, { recursive: true });