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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/analyzer/class-analyzer.d.ts +11 -5
  2. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  3. package/dist/analyzer/program.d.ts +3 -2
  4. package/dist/analyzer/program.d.ts.map +1 -1
  5. package/dist/browser.cjs +485 -76
  6. package/dist/browser.cjs.map +1 -1
  7. package/dist/browser.js +486 -77
  8. package/dist/browser.js.map +1 -1
  9. package/dist/build-alpha.d.ts +18 -2
  10. package/dist/build-beta.d.ts +18 -2
  11. package/dist/build-internal.d.ts +18 -2
  12. package/dist/build.d.ts +18 -2
  13. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  14. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  15. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  16. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  17. package/dist/cli.cjs +1031 -170
  18. package/dist/cli.cjs.map +1 -1
  19. package/dist/cli.js +1032 -171
  20. package/dist/cli.js.map +1 -1
  21. package/dist/generators/class-schema.d.ts +6 -1
  22. package/dist/generators/class-schema.d.ts.map +1 -1
  23. package/dist/generators/method-schema.d.ts.map +1 -1
  24. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  25. package/dist/index.cjs +998 -170
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +4 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +999 -171
  30. package/dist/index.js.map +1 -1
  31. package/dist/internals.cjs +921 -155
  32. package/dist/internals.cjs.map +1 -1
  33. package/dist/internals.js +922 -156
  34. package/dist/internals.js.map +1 -1
  35. package/dist/json-schema/generator.d.ts +3 -1
  36. package/dist/json-schema/generator.d.ts.map +1 -1
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/metadata/collision-guards.d.ts +3 -0
  39. package/dist/metadata/collision-guards.d.ts.map +1 -0
  40. package/dist/metadata/index.d.ts +7 -0
  41. package/dist/metadata/index.d.ts.map +1 -0
  42. package/dist/metadata/policy.d.ts +11 -0
  43. package/dist/metadata/policy.d.ts.map +1 -0
  44. package/dist/metadata/resolve.d.ts +20 -0
  45. package/dist/metadata/resolve.d.ts.map +1 -0
  46. package/dist/ui-schema/generator.d.ts +11 -2
  47. package/dist/ui-schema/generator.d.ts.map +1 -1
  48. package/dist/ui-schema/ir-generator.d.ts +2 -1
  49. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  50. package/package.json +4 -4
package/dist/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) {
@@ -2236,10 +2898,18 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2236
2898
  classType,
2237
2899
  checker,
2238
2900
  file,
2239
- diagnostics
2901
+ diagnostics,
2902
+ normalizedMetadataPolicy
2240
2903
  );
2904
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2905
+ checker,
2906
+ declaration: classDecl,
2907
+ subjectType: classType,
2908
+ hostType: classType
2909
+ });
2241
2910
  return {
2242
2911
  name,
2912
+ ...metadata !== void 0 && { metadata },
2243
2913
  fields: specializedFields,
2244
2914
  fieldLayouts,
2245
2915
  typeRegistry,
@@ -2249,7 +2919,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2249
2919
  staticMethods
2250
2920
  };
2251
2921
  }
2252
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2922
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2923
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2253
2924
  const name = interfaceDecl.name.text;
2254
2925
  const fields = [];
2255
2926
  const typeRegistry = {};
@@ -2273,6 +2944,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2273
2944
  visiting,
2274
2945
  diagnostics,
2275
2946
  interfaceType,
2947
+ normalizedMetadataPolicy,
2276
2948
  extensionRegistry
2277
2949
  );
2278
2950
  if (fieldNode) {
@@ -2286,11 +2958,19 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2286
2958
  interfaceType,
2287
2959
  checker,
2288
2960
  file,
2289
- diagnostics
2961
+ diagnostics,
2962
+ normalizedMetadataPolicy
2290
2963
  );
2291
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
+ });
2292
2971
  return {
2293
2972
  name,
2973
+ ...metadata !== void 0 && { metadata },
2294
2974
  fields: specializedFields,
2295
2975
  fieldLayouts,
2296
2976
  typeRegistry,
@@ -2300,7 +2980,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2300
2980
  staticMethods: []
2301
2981
  };
2302
2982
  }
2303
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2983
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2304
2984
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2305
2985
  const sourceFile = typeAlias.getSourceFile();
2306
2986
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2310,6 +2990,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2310
2990
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2311
2991
  };
2312
2992
  }
2993
+ const typeLiteral = typeAlias.type;
2994
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2313
2995
  const name = typeAlias.name.text;
2314
2996
  const fields = [];
2315
2997
  const typeRegistry = {};
@@ -2323,7 +3005,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2323
3005
  const annotations = [...typeAliasDoc.annotations];
2324
3006
  diagnostics.push(...typeAliasDoc.diagnostics);
2325
3007
  const visiting = /* @__PURE__ */ new Set();
2326
- for (const member of typeAlias.type.members) {
3008
+ for (const member of typeLiteral.members) {
2327
3009
  if (ts3.isPropertySignature(member)) {
2328
3010
  const fieldNode = analyzeInterfacePropertyToIR(
2329
3011
  member,
@@ -2333,6 +3015,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2333
3015
  visiting,
2334
3016
  diagnostics,
2335
3017
  aliasType,
3018
+ normalizedMetadataPolicy,
2336
3019
  extensionRegistry
2337
3020
  );
2338
3021
  if (fieldNode) {
@@ -2346,12 +3029,20 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2346
3029
  aliasType,
2347
3030
  checker,
2348
3031
  file,
2349
- diagnostics
3032
+ diagnostics,
3033
+ normalizedMetadataPolicy
2350
3034
  );
3035
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
3036
+ checker,
3037
+ declaration: typeAlias,
3038
+ subjectType: aliasType,
3039
+ hostType: aliasType
3040
+ });
2351
3041
  return {
2352
3042
  ok: true,
2353
3043
  analysis: {
2354
3044
  name,
3045
+ ...metadata !== void 0 && { metadata },
2355
3046
  fields: specializedFields,
2356
3047
  fieldLayouts: specializedFields.map(() => ({})),
2357
3048
  typeRegistry,
@@ -2391,31 +3082,20 @@ function getLeadingParsedTags(node) {
2391
3082
  }
2392
3083
  return parsedTags;
2393
3084
  }
2394
- function findDiscriminatorProperty(node, fieldName) {
2395
- if (ts3.isClassDeclaration(node)) {
2396
- for (const member of node.members) {
2397
- if (ts3.isPropertyDeclaration(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2398
- return member;
2399
- }
2400
- }
2401
- return null;
2402
- }
2403
- if (ts3.isInterfaceDeclaration(node)) {
2404
- for (const member of node.members) {
2405
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2406
- return member;
2407
- }
2408
- }
3085
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
3086
+ const subjectType = checker.getTypeAtLocation(node);
3087
+ const propertySymbol = subjectType.getProperty(fieldName);
3088
+ if (propertySymbol === void 0) {
2409
3089
  return null;
2410
3090
  }
2411
- if (ts3.isTypeLiteralNode(node.type)) {
2412
- for (const member of node.type.members) {
2413
- if (ts3.isPropertySignature(member) && ts3.isIdentifier(member.name) && member.name.text === fieldName) {
2414
- return member;
2415
- }
2416
- }
2417
- }
2418
- return null;
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
+ };
2419
3099
  }
2420
3100
  function isLocalTypeParameterName(node, typeParameterName) {
2421
3101
  return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
@@ -2508,8 +3188,8 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2508
3188
  );
2509
3189
  return null;
2510
3190
  }
2511
- const propertyDecl = findDiscriminatorProperty(node, directive.fieldName);
2512
- if (propertyDecl === null) {
3191
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3192
+ if (property === null) {
2513
3193
  diagnostics.push(
2514
3194
  makeAnalysisDiagnostic(
2515
3195
  "UNKNOWN_PATH_TARGET",
@@ -2519,36 +3199,35 @@ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
2519
3199
  );
2520
3200
  return null;
2521
3201
  }
2522
- if (propertyDecl.questionToken !== void 0) {
3202
+ if (property.optional) {
2523
3203
  diagnostics.push(
2524
3204
  makeAnalysisDiagnostic(
2525
3205
  "TYPE_MISMATCH",
2526
3206
  `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
2527
3207
  directive.provenance,
2528
- [provenanceForNode(propertyDecl, file)]
3208
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2529
3209
  )
2530
3210
  );
2531
3211
  return null;
2532
3212
  }
2533
- const propertyType = checker.getTypeAtLocation(propertyDecl);
2534
- if (isNullishSemanticType(propertyType)) {
3213
+ if (isNullishSemanticType(property.type)) {
2535
3214
  diagnostics.push(
2536
3215
  makeAnalysisDiagnostic(
2537
3216
  "TYPE_MISMATCH",
2538
3217
  `Discriminator field "${directive.fieldName}" must not be nullable.`,
2539
3218
  directive.provenance,
2540
- [provenanceForNode(propertyDecl, file)]
3219
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2541
3220
  )
2542
3221
  );
2543
3222
  return null;
2544
3223
  }
2545
- if (!isStringLikeSemanticType(propertyType)) {
3224
+ if (!isStringLikeSemanticType(property.type)) {
2546
3225
  diagnostics.push(
2547
3226
  makeAnalysisDiagnostic(
2548
3227
  "TYPE_MISMATCH",
2549
3228
  `Discriminator field "${directive.fieldName}" must be string-like.`,
2550
3229
  directive.provenance,
2551
- [provenanceForNode(propertyDecl, file)]
3230
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
2552
3231
  )
2553
3232
  );
2554
3233
  return null;
@@ -2569,25 +3248,58 @@ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typ
2569
3248
  const localTypeParameter = node.typeParameters?.[typeParameterIndex];
2570
3249
  return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
2571
3250
  }
2572
- function extractDeclarationApiName(node) {
2573
- for (const tag of getLeadingParsedTags(node)) {
2574
- if (tag.normalizedTagName !== "apiName") {
2575
- continue;
2576
- }
2577
- if (tag.target === null && tag.argumentText.trim() !== "") {
2578
- return tag.argumentText.trim();
2579
- }
2580
- if (tag.target?.kind === "variant" && tag.target.rawText === "singular") {
2581
- const value = tag.argumentText.trim();
2582
- if (value !== "") {
2583
- return value;
2584
- }
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;
2585
3282
  }
2586
3283
  }
2587
- return null;
3284
+ return void 0;
2588
3285
  }
2589
- function inferJsonFacingName(name) {
2590
- return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
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;
2591
3303
  }
2592
3304
  function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
2593
3305
  if (seen.has(type)) {
@@ -2614,7 +3326,7 @@ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__
2614
3326
  }
2615
3327
  return null;
2616
3328
  }
2617
- function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics) {
3329
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
2618
3330
  if (boundType === null) {
2619
3331
  diagnostics.push(
2620
3332
  makeAnalysisDiagnostic(
@@ -2643,9 +3355,22 @@ function resolveDiscriminatorValue(boundType, checker, provenance, diagnostics)
2643
3355
  return null;
2644
3356
  }
2645
3357
  }
2646
- const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
2647
- if (declaration !== null) {
2648
- return extractDeclarationApiName(declaration) ?? inferJsonFacingName(getDeclarationName(declaration));
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;
2649
3374
  }
2650
3375
  diagnostics.push(
2651
3376
  makeAnalysisDiagnostic(
@@ -2662,7 +3387,15 @@ function getDeclarationName(node) {
2662
3387
  }
2663
3388
  return "anonymous";
2664
3389
  }
2665
- function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics) {
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) {
2666
3399
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2667
3400
  if (directive === null) {
2668
3401
  return [...fields];
@@ -2674,9 +3407,11 @@ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checke
2674
3407
  checker,
2675
3408
  directive.typeParameterName
2676
3409
  ),
3410
+ directive.fieldName,
2677
3411
  checker,
2678
3412
  directive.provenance,
2679
- diagnostics
3413
+ diagnostics,
3414
+ metadataPolicy
2680
3415
  );
2681
3416
  if (discriminatorValue === null) {
2682
3417
  return [...fields];
@@ -2697,7 +3432,7 @@ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
2697
3432
  ).filter((value) => value !== "");
2698
3433
  return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
2699
3434
  }
2700
- function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3435
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
2701
3436
  const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
2702
3437
  if (typeNode === void 0) {
2703
3438
  return [];
@@ -2717,13 +3452,14 @@ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiti
2717
3452
  typeRegistry,
2718
3453
  visiting,
2719
3454
  argumentNode,
3455
+ metadataPolicy,
2720
3456
  extensionRegistry,
2721
3457
  diagnostics
2722
3458
  )
2723
3459
  };
2724
3460
  });
2725
3461
  }
2726
- function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics) {
3462
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
2727
3463
  const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
2728
3464
  if (directive === null) {
2729
3465
  return properties;
@@ -2735,9 +3471,11 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2735
3471
  checker,
2736
3472
  directive.typeParameterName
2737
3473
  ),
3474
+ directive.fieldName,
2738
3475
  checker,
2739
3476
  directive.provenance,
2740
- diagnostics
3477
+ diagnostics,
3478
+ metadataPolicy
2741
3479
  );
2742
3480
  if (discriminatorValue === null) {
2743
3481
  return properties;
@@ -2752,7 +3490,7 @@ function applyDiscriminatorToObjectProperties(properties, node, subjectType, che
2752
3490
  } : property
2753
3491
  );
2754
3492
  }
2755
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3493
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2756
3494
  if (!ts3.isIdentifier(prop.name)) {
2757
3495
  return null;
2758
3496
  }
@@ -2767,6 +3505,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2767
3505
  typeRegistry,
2768
3506
  visiting,
2769
3507
  prop,
3508
+ metadataPolicy,
2770
3509
  extensionRegistry,
2771
3510
  diagnostics
2772
3511
  );
@@ -2790,9 +3529,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2790
3529
  annotations.push(defaultAnnotation);
2791
3530
  }
2792
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
+ });
2793
3538
  return {
2794
3539
  kind: "field",
2795
3540
  name,
3541
+ ...metadata !== void 0 && { metadata },
2796
3542
  type,
2797
3543
  required: !optional,
2798
3544
  constraints,
@@ -2800,7 +3546,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2800
3546
  provenance
2801
3547
  };
2802
3548
  }
2803
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3549
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2804
3550
  if (!ts3.isIdentifier(prop.name)) {
2805
3551
  return null;
2806
3552
  }
@@ -2815,6 +3561,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2815
3561
  typeRegistry,
2816
3562
  visiting,
2817
3563
  prop,
3564
+ metadataPolicy,
2818
3565
  extensionRegistry,
2819
3566
  diagnostics
2820
3567
  );
@@ -2834,9 +3581,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2834
3581
  let annotations = [];
2835
3582
  annotations.push(...docResult.annotations);
2836
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
+ });
2837
3590
  return {
2838
3591
  kind: "field",
2839
3592
  name,
3593
+ ...metadata !== void 0 && { metadata },
2840
3594
  type,
2841
3595
  required: !optional,
2842
3596
  constraints,
@@ -2961,7 +3715,7 @@ function getTypeNodeRegistrationName(typeNode) {
2961
3715
  }
2962
3716
  return null;
2963
3717
  }
2964
- 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) {
2965
3719
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2966
3720
  if (customType) {
2967
3721
  return customType;
@@ -2973,6 +3727,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2973
3727
  typeRegistry,
2974
3728
  visiting,
2975
3729
  sourceNode,
3730
+ metadataPolicy,
2976
3731
  extensionRegistry,
2977
3732
  diagnostics
2978
3733
  );
@@ -3017,6 +3772,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
3017
3772
  typeRegistry,
3018
3773
  visiting,
3019
3774
  sourceNode,
3775
+ metadataPolicy,
3020
3776
  extensionRegistry,
3021
3777
  diagnostics
3022
3778
  );
@@ -3029,6 +3785,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
3029
3785
  typeRegistry,
3030
3786
  visiting,
3031
3787
  sourceNode,
3788
+ metadataPolicy,
3032
3789
  extensionRegistry,
3033
3790
  diagnostics
3034
3791
  );
@@ -3041,13 +3798,14 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
3041
3798
  typeRegistry,
3042
3799
  visiting,
3043
3800
  sourceNode,
3801
+ metadataPolicy,
3044
3802
  extensionRegistry,
3045
3803
  diagnostics
3046
3804
  );
3047
3805
  }
3048
3806
  return { kind: "primitive", primitiveKind: "string" };
3049
3807
  }
3050
- 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) {
3051
3809
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
3052
3810
  return null;
3053
3811
  }
@@ -3067,14 +3825,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
3067
3825
  file,
3068
3826
  makeParseOptions(extensionRegistry)
3069
3827
  );
3828
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3829
+ checker,
3830
+ declaration: aliasDecl,
3831
+ subjectType: aliasType
3832
+ });
3070
3833
  typeRegistry[aliasName] = {
3071
3834
  name: aliasName,
3835
+ ...metadata !== void 0 && { metadata },
3072
3836
  type: resolveAliasedPrimitiveTarget(
3073
3837
  aliasType,
3074
3838
  checker,
3075
3839
  file,
3076
3840
  typeRegistry,
3077
3841
  visiting,
3842
+ metadataPolicy,
3078
3843
  extensionRegistry,
3079
3844
  diagnostics
3080
3845
  ),
@@ -3103,7 +3868,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
3103
3868
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
3104
3869
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
3105
3870
  }
3106
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3871
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3107
3872
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
3108
3873
  if (nestedAliasDecl !== void 0) {
3109
3874
  return resolveAliasedPrimitiveTarget(
@@ -3112,6 +3877,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3112
3877
  file,
3113
3878
  typeRegistry,
3114
3879
  visiting,
3880
+ metadataPolicy,
3115
3881
  extensionRegistry,
3116
3882
  diagnostics
3117
3883
  );
@@ -3123,11 +3889,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
3123
3889
  typeRegistry,
3124
3890
  visiting,
3125
3891
  void 0,
3892
+ metadataPolicy,
3126
3893
  extensionRegistry,
3127
3894
  diagnostics
3128
3895
  );
3129
3896
  }
3130
- 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) {
3131
3898
  const typeName = getNamedTypeName(type);
3132
3899
  const namedDecl = getNamedTypeDeclaration(type);
3133
3900
  if (typeName && typeName in typeRegistry) {
@@ -3162,8 +3929,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3162
3929
  return result;
3163
3930
  }
3164
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;
3165
3937
  typeRegistry[typeName] = {
3166
3938
  name: typeName,
3939
+ ...metadata !== void 0 && { metadata },
3167
3940
  type: result,
3168
3941
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3169
3942
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -3217,6 +3990,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3217
3990
  typeRegistry,
3218
3991
  visiting,
3219
3992
  nonNullMembers[0].sourceNode ?? sourceNode,
3993
+ metadataPolicy,
3220
3994
  extensionRegistry,
3221
3995
  diagnostics
3222
3996
  );
@@ -3234,6 +4008,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3234
4008
  typeRegistry,
3235
4009
  visiting,
3236
4010
  memberSourceNode ?? sourceNode,
4011
+ metadataPolicy,
3237
4012
  extensionRegistry,
3238
4013
  diagnostics
3239
4014
  )
@@ -3243,7 +4018,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
3243
4018
  }
3244
4019
  return registerNamed({ kind: "union", members });
3245
4020
  }
3246
- 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) {
3247
4022
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
3248
4023
  const elementType = typeArgs?.[0];
3249
4024
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -3254,12 +4029,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
3254
4029
  typeRegistry,
3255
4030
  visiting,
3256
4031
  elementSourceNode,
4032
+ metadataPolicy,
3257
4033
  extensionRegistry,
3258
4034
  diagnostics
3259
4035
  ) : { kind: "primitive", primitiveKind: "string" };
3260
4036
  return { kind: "array", items };
3261
4037
  }
3262
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
4038
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
3263
4039
  if (type.getProperties().length > 0) {
3264
4040
  return null;
3265
4041
  }
@@ -3274,6 +4050,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
3274
4050
  typeRegistry,
3275
4051
  visiting,
3276
4052
  void 0,
4053
+ metadataPolicy,
3277
4054
  extensionRegistry,
3278
4055
  diagnostics
3279
4056
  );
@@ -3304,7 +4081,22 @@ function typeNodeContainsReference(type, targetName) {
3304
4081
  }
3305
4082
  }
3306
4083
  }
3307
- function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, 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) {
3308
4100
  const collectedDiagnostics = diagnostics ?? [];
3309
4101
  const typeName = getNamedTypeName(type);
3310
4102
  const namedTypeName = typeName ?? void 0;
@@ -3316,6 +4108,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3316
4108
  typeRegistry,
3317
4109
  visiting,
3318
4110
  sourceNode,
4111
+ metadataPolicy,
3319
4112
  extensionRegistry,
3320
4113
  collectedDiagnostics
3321
4114
  );
@@ -3366,6 +4159,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3366
4159
  file,
3367
4160
  typeRegistry,
3368
4161
  visiting,
4162
+ metadataPolicy,
3369
4163
  extensionRegistry,
3370
4164
  collectedDiagnostics
3371
4165
  );
@@ -3378,8 +4172,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3378
4172
  return recordNode;
3379
4173
  }
3380
4174
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4175
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4176
+ checker,
4177
+ declaration: namedDecl,
4178
+ subjectType: type
4179
+ }) : void 0;
3381
4180
  typeRegistry[registryTypeName] = {
3382
4181
  name: registryTypeName,
4182
+ ...metadata !== void 0 && { metadata },
3383
4183
  type: recordNode,
3384
4184
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3385
4185
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3399,12 +4199,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3399
4199
  file,
3400
4200
  typeRegistry,
3401
4201
  visiting,
4202
+ metadataPolicy,
3402
4203
  collectedDiagnostics,
3403
4204
  extensionRegistry
3404
4205
  );
3405
4206
  for (const prop of type.getProperties()) {
3406
4207
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
3407
4208
  if (!declaration) continue;
4209
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
3408
4210
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
3409
4211
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
3410
4212
  const propTypeNode = resolveTypeNode(
@@ -3414,12 +4216,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3414
4216
  typeRegistry,
3415
4217
  visiting,
3416
4218
  declaration,
4219
+ metadataPolicy,
3417
4220
  extensionRegistry,
3418
4221
  collectedDiagnostics
3419
4222
  );
3420
4223
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
3421
4224
  properties.push({
3422
4225
  name: prop.name,
4226
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
3423
4227
  type: propTypeNode,
3424
4228
  optional,
3425
4229
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -3436,14 +4240,21 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3436
4240
  type,
3437
4241
  checker,
3438
4242
  file,
3439
- collectedDiagnostics
4243
+ collectedDiagnostics,
4244
+ metadataPolicy
3440
4245
  ) : properties,
3441
4246
  additionalProperties: true
3442
4247
  };
3443
4248
  if (registryTypeName !== void 0 && shouldRegisterNamedType) {
3444
4249
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
4250
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4251
+ checker,
4252
+ declaration: namedDecl,
4253
+ subjectType: type
4254
+ }) : void 0;
3445
4255
  typeRegistry[registryTypeName] = {
3446
4256
  name: registryTypeName,
4257
+ ...metadata !== void 0 && { metadata },
3447
4258
  type: objectNode,
3448
4259
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3449
4260
  provenance: provenanceForDeclaration(namedDecl, file)
@@ -3456,7 +4267,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNo
3456
4267
  }
3457
4268
  return objectNode;
3458
4269
  }
3459
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4270
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
3460
4271
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
3461
4272
  (s) => s?.declarations != null && s.declarations.length > 0
3462
4273
  );
@@ -3477,10 +4288,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3477
4288
  visiting,
3478
4289
  diagnostics,
3479
4290
  hostType,
4291
+ metadataPolicy,
3480
4292
  extensionRegistry
3481
4293
  );
3482
4294
  if (fieldNode) {
3483
4295
  map.set(fieldNode.name, {
4296
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3484
4297
  constraints: [...fieldNode.constraints],
3485
4298
  annotations: [...fieldNode.annotations],
3486
4299
  provenance: fieldNode.provenance
@@ -3498,6 +4311,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3498
4311
  file,
3499
4312
  typeRegistry,
3500
4313
  visiting,
4314
+ metadataPolicy,
3501
4315
  checker.getTypeAtLocation(interfaceDecl),
3502
4316
  diagnostics,
3503
4317
  extensionRegistry
@@ -3511,6 +4325,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3511
4325
  file,
3512
4326
  typeRegistry,
3513
4327
  visiting,
4328
+ metadataPolicy,
3514
4329
  checker.getTypeAtLocation(typeAliasDecl),
3515
4330
  diagnostics,
3516
4331
  extensionRegistry
@@ -3562,7 +4377,7 @@ function isNullishTypeNode(typeNode) {
3562
4377
  }
3563
4378
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3564
4379
  }
3565
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4380
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3566
4381
  const map = /* @__PURE__ */ new Map();
3567
4382
  for (const member of members) {
3568
4383
  if (ts3.isPropertySignature(member)) {
@@ -3574,10 +4389,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3574
4389
  visiting,
3575
4390
  diagnostics,
3576
4391
  hostType,
4392
+ metadataPolicy,
3577
4393
  extensionRegistry
3578
4394
  );
3579
4395
  if (fieldNode) {
3580
4396
  map.set(fieldNode.name, {
4397
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3581
4398
  constraints: [...fieldNode.constraints],
3582
4399
  annotations: [...fieldNode.annotations],
3583
4400
  provenance: fieldNode.provenance
@@ -3607,6 +4424,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3607
4424
  {},
3608
4425
  /* @__PURE__ */ new Set(),
3609
4426
  aliasDecl.type,
4427
+ void 0,
3610
4428
  extensionRegistry
3611
4429
  );
3612
4430
  const constraints = extractJSDocConstraintNodes(
@@ -3718,6 +4536,7 @@ var init_class_analyzer = __esm({
3718
4536
  import_internal2 = require("@formspec/analysis/internal");
3719
4537
  init_jsdoc_constraints();
3720
4538
  init_tsdoc_parser();
4539
+ init_metadata();
3721
4540
  RESOLVING_TYPE_PLACEHOLDER = {
3722
4541
  kind: "object",
3723
4542
  properties: [],
@@ -3808,19 +4627,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3808
4627
  function findTypeAliasByName(sourceFile, aliasName) {
3809
4628
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3810
4629
  }
3811
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4630
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3812
4631
  const ctx = createProgramContext(filePath);
3813
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4632
+ return analyzeNamedTypeToIRFromProgramContext(
4633
+ ctx,
4634
+ filePath,
4635
+ typeName,
4636
+ extensionRegistry,
4637
+ metadataPolicy
4638
+ );
3814
4639
  }
3815
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4640
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3816
4641
  const analysisFilePath = path.resolve(filePath);
3817
4642
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3818
4643
  if (classDecl !== null) {
3819
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4644
+ return analyzeClassToIR(
4645
+ classDecl,
4646
+ ctx.checker,
4647
+ analysisFilePath,
4648
+ extensionRegistry,
4649
+ metadataPolicy
4650
+ );
3820
4651
  }
3821
4652
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3822
4653
  if (interfaceDecl !== null) {
3823
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4654
+ return analyzeInterfaceToIR(
4655
+ interfaceDecl,
4656
+ ctx.checker,
4657
+ analysisFilePath,
4658
+ extensionRegistry,
4659
+ metadataPolicy
4660
+ );
3824
4661
  }
3825
4662
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3826
4663
  if (typeAlias !== null) {
@@ -3828,7 +4665,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3828
4665
  typeAlias,
3829
4666
  ctx.checker,
3830
4667
  analysisFilePath,
3831
- extensionRegistry
4668
+ extensionRegistry,
4669
+ metadataPolicy
3832
4670
  );
3833
4671
  if (result.ok) {
3834
4672
  return result.analysis;
@@ -3944,7 +4782,11 @@ function generateClassSchemas(analysis, source, options) {
3944
4782
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3945
4783
  throw new Error(formatValidationError(errorDiagnostics));
3946
4784
  }
3947
- const ir = canonicalizeTSDoc(analysis, source);
4785
+ const ir = canonicalizeTSDoc(
4786
+ analysis,
4787
+ source,
4788
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4789
+ );
3948
4790
  const validationResult = validateIR(ir, {
3949
4791
  ...options?.extensionRegistry !== void 0 && {
3950
4792
  extensionRegistry: options.extensionRegistry
@@ -3981,13 +4823,15 @@ function generateSchemasFromClass(options) {
3981
4823
  classDecl,
3982
4824
  ctx.checker,
3983
4825
  options.filePath,
3984
- options.extensionRegistry
4826
+ options.extensionRegistry,
4827
+ options.metadata
3985
4828
  );
3986
4829
  return generateClassSchemas(
3987
4830
  analysis,
3988
4831
  { file: options.filePath },
3989
4832
  {
3990
4833
  extensionRegistry: options.extensionRegistry,
4834
+ metadata: options.metadata,
3991
4835
  vendorPrefix: options.vendorPrefix
3992
4836
  }
3993
4837
  );
@@ -4005,13 +4849,15 @@ function generateSchemasFromProgram(options) {
4005
4849
  ctx,
4006
4850
  options.filePath,
4007
4851
  options.typeName,
4008
- options.extensionRegistry
4852
+ options.extensionRegistry,
4853
+ options.metadata
4009
4854
  );
4010
4855
  return generateClassSchemas(
4011
4856
  analysis,
4012
4857
  { file: options.filePath },
4013
4858
  {
4014
4859
  extensionRegistry: options.extensionRegistry,
4860
+ metadata: options.metadata,
4015
4861
  vendorPrefix: options.vendorPrefix
4016
4862
  }
4017
4863
  );
@@ -4033,16 +4879,28 @@ var init_class_schema = __esm({
4033
4879
  // src/generators/mixed-authoring.ts
4034
4880
  function buildMixedAuthoringSchemas(options) {
4035
4881
  const { filePath, typeName, overlays, ...schemaOptions } = options;
4036
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
4037
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
4038
- 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
+ );
4039
4894
  return {
4040
4895
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
4041
4896
  uiSchema: generateUiSchemaFromIR(ir)
4042
4897
  };
4043
4898
  }
4044
- function composeAnalysisWithOverlays(analysis, overlays) {
4045
- const overlayIR = canonicalizeChainDSL(overlays);
4899
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4900
+ const overlayIR = canonicalizeChainDSL(
4901
+ overlays,
4902
+ metadata !== void 0 ? { metadata } : void 0
4903
+ );
4046
4904
  const overlayFields = collectOverlayFields(overlayIR.elements);
4047
4905
  if (overlayFields.length === 0) {
4048
4906
  return analysis;
@@ -4098,8 +4956,10 @@ function collectOverlayFields(elements) {
4098
4956
  }
4099
4957
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
4100
4958
  assertSupportedOverlayField(baseField, overlayField);
4959
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
4101
4960
  return {
4102
4961
  ...baseField,
4962
+ ...metadata !== void 0 && { metadata },
4103
4963
  type: mergeFieldType(baseField, overlayField, typeRegistry),
4104
4964
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
4105
4965
  };
@@ -4212,6 +5072,7 @@ var init_mixed_authoring = __esm({
4212
5072
  init_ir_generator2();
4213
5073
  init_canonicalize();
4214
5074
  init_program();
5075
+ init_metadata();
4215
5076
  }
4216
5077
  });
4217
5078
 
@@ -4233,12 +5094,12 @@ __export(index_exports, {
4233
5094
  function buildFormSchemas(form, options) {
4234
5095
  return {
4235
5096
  jsonSchema: generateJsonSchema(form, options),
4236
- uiSchema: generateUiSchema(form)
5097
+ uiSchema: generateUiSchema(form, options)
4237
5098
  };
4238
5099
  }
4239
5100
  function writeSchemas(form, options) {
4240
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
4241
- 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 };
4242
5103
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
4243
5104
  if (!fs.existsSync(outDir)) {
4244
5105
  fs.mkdirSync(outDir, { recursive: true });