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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -2
  3. package/dist/analyzer/class-analyzer.d.ts +12 -6
  4. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  5. package/dist/analyzer/program.d.ts +3 -2
  6. package/dist/analyzer/program.d.ts.map +1 -1
  7. package/dist/browser.cjs +485 -76
  8. package/dist/browser.cjs.map +1 -1
  9. package/dist/browser.js +486 -77
  10. package/dist/browser.js.map +1 -1
  11. package/dist/build-alpha.d.ts +18 -2
  12. package/dist/build-beta.d.ts +18 -2
  13. package/dist/build-internal.d.ts +18 -2
  14. package/dist/build.d.ts +18 -2
  15. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  16. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  17. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  18. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  19. package/dist/cli.cjs +1460 -143
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.js +1458 -139
  22. package/dist/cli.js.map +1 -1
  23. package/dist/generators/class-schema.d.ts +6 -1
  24. package/dist/generators/class-schema.d.ts.map +1 -1
  25. package/dist/generators/method-schema.d.ts.map +1 -1
  26. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  27. package/dist/index.cjs +1425 -141
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +4 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1425 -139
  32. package/dist/index.js.map +1 -1
  33. package/dist/internals.cjs +1416 -178
  34. package/dist/internals.cjs.map +1 -1
  35. package/dist/internals.js +1416 -176
  36. package/dist/internals.js.map +1 -1
  37. package/dist/json-schema/generator.d.ts +3 -1
  38. package/dist/json-schema/generator.d.ts.map +1 -1
  39. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  40. package/dist/metadata/collision-guards.d.ts +3 -0
  41. package/dist/metadata/collision-guards.d.ts.map +1 -0
  42. package/dist/metadata/index.d.ts +7 -0
  43. package/dist/metadata/index.d.ts.map +1 -0
  44. package/dist/metadata/policy.d.ts +11 -0
  45. package/dist/metadata/policy.d.ts.map +1 -0
  46. package/dist/metadata/resolve.d.ts +20 -0
  47. package/dist/metadata/resolve.d.ts.map +1 -0
  48. package/dist/ui-schema/generator.d.ts +11 -2
  49. package/dist/ui-schema/generator.d.ts.map +1 -1
  50. package/dist/ui-schema/ir-generator.d.ts +2 -1
  51. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  52. package/package.json +7 -6
package/dist/cli.js CHANGED
@@ -9,8 +9,336 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/metadata/policy.ts
13
+ function normalizePluralization(input) {
14
+ if (input?.mode === "infer-if-missing") {
15
+ return {
16
+ mode: "infer-if-missing",
17
+ infer: () => "",
18
+ inflect: input.inflect
19
+ };
20
+ }
21
+ if (input?.mode === "require-explicit") {
22
+ return {
23
+ mode: "require-explicit",
24
+ infer: () => "",
25
+ inflect: NOOP_INFLECT
26
+ };
27
+ }
28
+ return {
29
+ mode: "disabled",
30
+ infer: () => "",
31
+ inflect: NOOP_INFLECT
32
+ };
33
+ }
34
+ function normalizeScalarPolicy(input) {
35
+ if (input?.mode === "infer-if-missing") {
36
+ return {
37
+ mode: "infer-if-missing",
38
+ infer: input.infer,
39
+ pluralization: normalizePluralization(input.pluralization)
40
+ };
41
+ }
42
+ if (input?.mode === "require-explicit") {
43
+ return {
44
+ mode: "require-explicit",
45
+ infer: () => "",
46
+ pluralization: normalizePluralization(input.pluralization)
47
+ };
48
+ }
49
+ return {
50
+ mode: "disabled",
51
+ infer: () => "",
52
+ pluralization: normalizePluralization(input?.pluralization)
53
+ };
54
+ }
55
+ function normalizeDeclarationPolicy(input) {
56
+ return {
57
+ apiName: normalizeScalarPolicy(input?.apiName),
58
+ displayName: normalizeScalarPolicy(input?.displayName)
59
+ };
60
+ }
61
+ function normalizeMetadataPolicy(input) {
62
+ return {
63
+ type: normalizeDeclarationPolicy(input?.type),
64
+ field: normalizeDeclarationPolicy(input?.field),
65
+ method: normalizeDeclarationPolicy(input?.method)
66
+ };
67
+ }
68
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
69
+ return policy[declarationKind];
70
+ }
71
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
72
+ return {
73
+ surface,
74
+ declarationKind,
75
+ logicalName,
76
+ ...buildContext !== void 0 && { buildContext }
77
+ };
78
+ }
79
+ var NOOP_INFLECT;
80
+ var init_policy = __esm({
81
+ "src/metadata/policy.ts"() {
82
+ "use strict";
83
+ NOOP_INFLECT = () => "";
84
+ }
85
+ });
86
+
87
+ // src/metadata/resolve.ts
88
+ function toExplicitScalar(value) {
89
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
90
+ }
91
+ function toExplicitResolvedMetadata(explicit) {
92
+ if (explicit === void 0) {
93
+ return void 0;
94
+ }
95
+ const apiName = toExplicitScalar(explicit.apiName);
96
+ const displayName = toExplicitScalar(explicit.displayName);
97
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
98
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
99
+ const metadata = {
100
+ ...apiName !== void 0 && { apiName },
101
+ ...displayName !== void 0 && { displayName },
102
+ ...apiNamePlural !== void 0 && { apiNamePlural },
103
+ ...displayNamePlural !== void 0 && { displayNamePlural }
104
+ };
105
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
106
+ }
107
+ function resolveScalar(current, policy, context, metadataLabel) {
108
+ if (current !== void 0) {
109
+ return current;
110
+ }
111
+ if (policy.mode === "require-explicit") {
112
+ throw new Error(
113
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
114
+ );
115
+ }
116
+ if (policy.mode !== "infer-if-missing") {
117
+ return void 0;
118
+ }
119
+ const inferredValue = policy.infer(context);
120
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
121
+ }
122
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
123
+ if (current !== void 0) {
124
+ return current;
125
+ }
126
+ if (policy.mode === "require-explicit") {
127
+ throw new Error(
128
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
129
+ );
130
+ }
131
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
132
+ return void 0;
133
+ }
134
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
135
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
136
+ }
137
+ function resolveResolvedMetadata(current, policy, context) {
138
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
139
+ const displayName = resolveScalar(
140
+ current?.displayName,
141
+ policy.displayName,
142
+ context,
143
+ "displayName"
144
+ );
145
+ const apiNamePlural = resolvePlural(
146
+ current?.apiNamePlural,
147
+ apiName,
148
+ policy.apiName.pluralization,
149
+ context,
150
+ "apiNamePlural"
151
+ );
152
+ const displayNamePlural = resolvePlural(
153
+ current?.displayNamePlural,
154
+ displayName,
155
+ policy.displayName.pluralization,
156
+ context,
157
+ "displayNamePlural"
158
+ );
159
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
160
+ return void 0;
161
+ }
162
+ return {
163
+ ...apiName !== void 0 && { apiName },
164
+ ...displayName !== void 0 && { displayName },
165
+ ...apiNamePlural !== void 0 && { apiNamePlural },
166
+ ...displayNamePlural !== void 0 && { displayNamePlural }
167
+ };
168
+ }
169
+ function pickResolvedMetadataValue(baseValue, overlayValue) {
170
+ if (overlayValue?.source === "explicit") {
171
+ return overlayValue;
172
+ }
173
+ if (baseValue?.source === "explicit") {
174
+ return baseValue;
175
+ }
176
+ return baseValue ?? overlayValue;
177
+ }
178
+ function resolveTypeNodeMetadata(type, options) {
179
+ switch (type.kind) {
180
+ case "array":
181
+ return {
182
+ ...type,
183
+ items: resolveTypeNodeMetadata(type.items, options)
184
+ };
185
+ case "object":
186
+ return {
187
+ ...type,
188
+ properties: type.properties.map((property) => resolveObjectPropertyMetadata(property, options))
189
+ };
190
+ case "record":
191
+ return {
192
+ ...type,
193
+ valueType: resolveTypeNodeMetadata(type.valueType, options)
194
+ };
195
+ case "union":
196
+ return {
197
+ ...type,
198
+ members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
199
+ };
200
+ case "reference":
201
+ case "primitive":
202
+ case "enum":
203
+ case "dynamic":
204
+ case "custom":
205
+ return type;
206
+ default: {
207
+ const _exhaustive = type;
208
+ return _exhaustive;
209
+ }
210
+ }
211
+ }
212
+ function resolveObjectPropertyMetadata(property, options) {
213
+ const metadata = resolveResolvedMetadata(property.metadata, options.policy.field, {
214
+ surface: options.surface,
215
+ declarationKind: "field",
216
+ logicalName: property.name,
217
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
218
+ });
219
+ return {
220
+ ...property,
221
+ ...metadata !== void 0 && { metadata },
222
+ type: resolveTypeNodeMetadata(property.type, options)
223
+ };
224
+ }
225
+ function resolveFieldMetadataNode(field, options) {
226
+ const metadata = resolveResolvedMetadata(field.metadata, options.policy.field, {
227
+ surface: options.surface,
228
+ declarationKind: "field",
229
+ logicalName: field.name,
230
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
231
+ });
232
+ return {
233
+ ...field,
234
+ ...metadata !== void 0 && { metadata },
235
+ type: resolveTypeNodeMetadata(field.type, options)
236
+ };
237
+ }
238
+ function resolveFormElementMetadata(element, options) {
239
+ switch (element.kind) {
240
+ case "field":
241
+ return resolveFieldMetadataNode(element, options);
242
+ case "group":
243
+ return {
244
+ ...element,
245
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
246
+ };
247
+ case "conditional":
248
+ return {
249
+ ...element,
250
+ elements: element.elements.map((child) => resolveFormElementMetadata(child, options))
251
+ };
252
+ default: {
253
+ const _exhaustive = element;
254
+ return _exhaustive;
255
+ }
256
+ }
257
+ }
258
+ function resolveTypeDefinitionMetadata(typeDefinition, options) {
259
+ const metadata = resolveResolvedMetadata(typeDefinition.metadata, options.policy.type, {
260
+ surface: options.surface,
261
+ declarationKind: "type",
262
+ logicalName: typeDefinition.name,
263
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
264
+ });
265
+ return {
266
+ ...typeDefinition,
267
+ ...metadata !== void 0 && { metadata },
268
+ type: resolveTypeNodeMetadata(typeDefinition.type, options)
269
+ };
270
+ }
271
+ function resolveMetadata(explicit, policy, context) {
272
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
273
+ }
274
+ function mergeResolvedMetadata(baseMetadata, overlayMetadata) {
275
+ const apiName = pickResolvedMetadataValue(baseMetadata?.apiName, overlayMetadata?.apiName);
276
+ const displayName = pickResolvedMetadataValue(
277
+ baseMetadata?.displayName,
278
+ overlayMetadata?.displayName
279
+ );
280
+ const apiNamePlural = pickResolvedMetadataValue(
281
+ baseMetadata?.apiNamePlural,
282
+ overlayMetadata?.apiNamePlural
283
+ );
284
+ const displayNamePlural = pickResolvedMetadataValue(
285
+ baseMetadata?.displayNamePlural,
286
+ overlayMetadata?.displayNamePlural
287
+ );
288
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
289
+ return void 0;
290
+ }
291
+ return {
292
+ ...apiName !== void 0 && { apiName },
293
+ ...displayName !== void 0 && { displayName },
294
+ ...apiNamePlural !== void 0 && { apiNamePlural },
295
+ ...displayNamePlural !== void 0 && { displayNamePlural }
296
+ };
297
+ }
298
+ function getSerializedName(logicalName, metadata) {
299
+ return metadata?.apiName?.value ?? logicalName;
300
+ }
301
+ function getDisplayName(metadata) {
302
+ return metadata?.displayName?.value;
303
+ }
304
+ function resolveFormIRMetadata(ir, options) {
305
+ const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
306
+ const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
307
+ surface: options.surface,
308
+ declarationKind: "type",
309
+ logicalName: rootLogicalName,
310
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
311
+ });
312
+ return {
313
+ ...ir,
314
+ ...metadata !== void 0 && { metadata },
315
+ elements: ir.elements.map((element) => resolveFormElementMetadata(element, options)),
316
+ typeRegistry: Object.fromEntries(
317
+ Object.entries(ir.typeRegistry).map(([name, definition]) => [
318
+ name,
319
+ resolveTypeDefinitionMetadata(definition, options)
320
+ ])
321
+ )
322
+ };
323
+ }
324
+ var init_resolve = __esm({
325
+ "src/metadata/resolve.ts"() {
326
+ "use strict";
327
+ }
328
+ });
329
+
330
+ // src/metadata/index.ts
331
+ var init_metadata = __esm({
332
+ "src/metadata/index.ts"() {
333
+ "use strict";
334
+ init_policy();
335
+ init_resolve();
336
+ init_resolve();
337
+ }
338
+ });
339
+
12
340
  // src/canonicalize/chain-dsl-canonicalizer.ts
13
- import { IR_VERSION } from "@formspec/core/internals";
341
+ import { IR_VERSION, _getFormSpecMetadataPolicy } from "@formspec/core/internals";
14
342
  function isGroup(el) {
15
343
  return el._type === "group";
16
344
  }
@@ -20,57 +348,60 @@ function isConditional(el) {
20
348
  function isField(el) {
21
349
  return el._type === "field";
22
350
  }
23
- function canonicalizeChainDSL(form) {
351
+ function canonicalizeChainDSL(form, options) {
352
+ const metadataPolicy = normalizeMetadataPolicy(
353
+ options?.metadata ?? _getFormSpecMetadataPolicy(form)
354
+ );
24
355
  return {
25
356
  kind: "form-ir",
26
357
  irVersion: IR_VERSION,
27
- elements: canonicalizeElements(form.elements),
358
+ elements: canonicalizeElements(form.elements, metadataPolicy),
28
359
  rootAnnotations: [],
29
360
  typeRegistry: {},
30
361
  provenance: CHAIN_DSL_PROVENANCE
31
362
  };
32
363
  }
33
- function canonicalizeElements(elements) {
34
- return elements.map(canonicalizeElement);
364
+ function canonicalizeElements(elements, metadataPolicy) {
365
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
35
366
  }
36
- function canonicalizeElement(element) {
367
+ function canonicalizeElement(element, metadataPolicy) {
37
368
  if (isField(element)) {
38
- return canonicalizeField(element);
369
+ return canonicalizeField(element, metadataPolicy);
39
370
  }
40
371
  if (isGroup(element)) {
41
- return canonicalizeGroup(element);
372
+ return canonicalizeGroup(element, metadataPolicy);
42
373
  }
43
374
  if (isConditional(element)) {
44
- return canonicalizeConditional(element);
375
+ return canonicalizeConditional(element, metadataPolicy);
45
376
  }
46
377
  const _exhaustive = element;
47
378
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
48
379
  }
49
- function canonicalizeField(field) {
380
+ function canonicalizeField(field, metadataPolicy) {
50
381
  switch (field._field) {
51
382
  case "text":
52
- return canonicalizeTextField(field);
383
+ return canonicalizeTextField(field, metadataPolicy);
53
384
  case "number":
54
- return canonicalizeNumberField(field);
385
+ return canonicalizeNumberField(field, metadataPolicy);
55
386
  case "boolean":
56
- return canonicalizeBooleanField(field);
387
+ return canonicalizeBooleanField(field, metadataPolicy);
57
388
  case "enum":
58
- return canonicalizeStaticEnumField(field);
389
+ return canonicalizeStaticEnumField(field, metadataPolicy);
59
390
  case "dynamic_enum":
60
- return canonicalizeDynamicEnumField(field);
391
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
61
392
  case "dynamic_schema":
62
- return canonicalizeDynamicSchemaField(field);
393
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
63
394
  case "array":
64
- return canonicalizeArrayField(field);
395
+ return canonicalizeArrayField(field, metadataPolicy);
65
396
  case "object":
66
- return canonicalizeObjectField(field);
397
+ return canonicalizeObjectField(field, metadataPolicy);
67
398
  default: {
68
399
  const _exhaustive = field;
69
400
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
70
401
  }
71
402
  }
72
403
  }
73
- function canonicalizeTextField(field) {
404
+ function canonicalizeTextField(field, metadataPolicy) {
74
405
  const type = { kind: "primitive", primitiveKind: "string" };
75
406
  const constraints = [];
76
407
  if (field.minLength !== void 0) {
@@ -102,13 +433,14 @@ function canonicalizeTextField(field) {
102
433
  }
103
434
  return buildFieldNode(
104
435
  field.name,
436
+ resolveFieldMetadata(field.name, field, metadataPolicy),
105
437
  type,
106
438
  field.required,
107
- buildAnnotations(field.label, field.placeholder),
439
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
108
440
  constraints
109
441
  );
110
442
  }
111
- function canonicalizeNumberField(field) {
443
+ function canonicalizeNumberField(field, metadataPolicy) {
112
444
  const type = { kind: "primitive", primitiveKind: "number" };
113
445
  const constraints = [];
114
446
  if (field.min !== void 0) {
@@ -140,17 +472,24 @@ function canonicalizeNumberField(field) {
140
472
  }
141
473
  return buildFieldNode(
142
474
  field.name,
475
+ resolveFieldMetadata(field.name, field, metadataPolicy),
143
476
  type,
144
477
  field.required,
145
- buildAnnotations(field.label),
478
+ buildAnnotations(getExplicitDisplayName(field)),
146
479
  constraints
147
480
  );
148
481
  }
149
- function canonicalizeBooleanField(field) {
482
+ function canonicalizeBooleanField(field, metadataPolicy) {
150
483
  const type = { kind: "primitive", primitiveKind: "boolean" };
151
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
484
+ return buildFieldNode(
485
+ field.name,
486
+ resolveFieldMetadata(field.name, field, metadataPolicy),
487
+ type,
488
+ field.required,
489
+ buildAnnotations(getExplicitDisplayName(field))
490
+ );
152
491
  }
153
- function canonicalizeStaticEnumField(field) {
492
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
154
493
  const members = field.options.map((opt) => {
155
494
  if (typeof opt === "string") {
156
495
  return { value: opt };
@@ -158,28 +497,46 @@ function canonicalizeStaticEnumField(field) {
158
497
  return { value: opt.id, displayName: opt.label };
159
498
  });
160
499
  const type = { kind: "enum", members };
161
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
500
+ return buildFieldNode(
501
+ field.name,
502
+ resolveFieldMetadata(field.name, field, metadataPolicy),
503
+ type,
504
+ field.required,
505
+ buildAnnotations(getExplicitDisplayName(field))
506
+ );
162
507
  }
163
- function canonicalizeDynamicEnumField(field) {
508
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
164
509
  const type = {
165
510
  kind: "dynamic",
166
511
  dynamicKind: "enum",
167
512
  sourceKey: field.source,
168
513
  parameterFields: field.params ? [...field.params] : []
169
514
  };
170
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
515
+ return buildFieldNode(
516
+ field.name,
517
+ resolveFieldMetadata(field.name, field, metadataPolicy),
518
+ type,
519
+ field.required,
520
+ buildAnnotations(getExplicitDisplayName(field))
521
+ );
171
522
  }
172
- function canonicalizeDynamicSchemaField(field) {
523
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
173
524
  const type = {
174
525
  kind: "dynamic",
175
526
  dynamicKind: "schema",
176
527
  sourceKey: field.schemaSource,
177
528
  parameterFields: []
178
529
  };
179
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
530
+ return buildFieldNode(
531
+ field.name,
532
+ resolveFieldMetadata(field.name, field, metadataPolicy),
533
+ type,
534
+ field.required,
535
+ buildAnnotations(getExplicitDisplayName(field))
536
+ );
180
537
  }
181
- function canonicalizeArrayField(field) {
182
- const itemProperties = buildObjectProperties(field.items);
538
+ function canonicalizeArrayField(field, metadataPolicy) {
539
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
183
540
  const itemsType = {
184
541
  kind: "object",
185
542
  properties: itemProperties,
@@ -207,37 +564,44 @@ function canonicalizeArrayField(field) {
207
564
  }
208
565
  return buildFieldNode(
209
566
  field.name,
567
+ resolveFieldMetadata(field.name, field, metadataPolicy),
210
568
  type,
211
569
  field.required,
212
- buildAnnotations(field.label),
570
+ buildAnnotations(getExplicitDisplayName(field)),
213
571
  constraints
214
572
  );
215
573
  }
216
- function canonicalizeObjectField(field) {
217
- const properties = buildObjectProperties(field.properties);
574
+ function canonicalizeObjectField(field, metadataPolicy) {
575
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
218
576
  const type = {
219
577
  kind: "object",
220
578
  properties,
221
579
  additionalProperties: true
222
580
  };
223
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
581
+ return buildFieldNode(
582
+ field.name,
583
+ resolveFieldMetadata(field.name, field, metadataPolicy),
584
+ type,
585
+ field.required,
586
+ buildAnnotations(getExplicitDisplayName(field))
587
+ );
224
588
  }
225
- function canonicalizeGroup(g) {
589
+ function canonicalizeGroup(g, metadataPolicy) {
226
590
  return {
227
591
  kind: "group",
228
592
  label: g.label,
229
- elements: canonicalizeElements(g.elements),
593
+ elements: canonicalizeElements(g.elements, metadataPolicy),
230
594
  provenance: CHAIN_DSL_PROVENANCE
231
595
  };
232
596
  }
233
- function canonicalizeConditional(c) {
597
+ function canonicalizeConditional(c, metadataPolicy) {
234
598
  return {
235
599
  kind: "conditional",
236
600
  fieldName: c.field,
237
601
  // Conditional values from the chain DSL are JSON-serializable primitives
238
602
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
239
603
  value: assertJsonValue(c.value),
240
- elements: canonicalizeElements(c.elements),
604
+ elements: canonicalizeElements(c.elements, metadataPolicy),
241
605
  provenance: CHAIN_DSL_PROVENANCE
242
606
  };
243
607
  }
@@ -257,10 +621,11 @@ function assertJsonValue(v) {
257
621
  }
258
622
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
259
623
  }
260
- function buildFieldNode(name, type, required, annotations, constraints = []) {
624
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
261
625
  return {
262
626
  kind: "field",
263
627
  name,
628
+ ...metadata !== void 0 && { metadata },
264
629
  type,
265
630
  required: required === true,
266
631
  constraints,
@@ -290,13 +655,14 @@ function buildAnnotations(label, placeholder) {
290
655
  }
291
656
  return annotations;
292
657
  }
293
- function buildObjectProperties(elements, insideConditional = false) {
658
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
294
659
  const properties = [];
295
660
  for (const el of elements) {
296
661
  if (isField(el)) {
297
- const fieldNode = canonicalizeField(el);
662
+ const fieldNode = canonicalizeField(el, metadataPolicy);
298
663
  properties.push({
299
664
  name: fieldNode.name,
665
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
300
666
  type: fieldNode.type,
301
667
  // Fields inside a conditional branch are always optional in the
302
668
  // data schema, regardless of their `required` flag — the condition
@@ -307,17 +673,35 @@ function buildObjectProperties(elements, insideConditional = false) {
307
673
  provenance: CHAIN_DSL_PROVENANCE
308
674
  });
309
675
  } else if (isGroup(el)) {
310
- properties.push(...buildObjectProperties(el.elements, insideConditional));
676
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
311
677
  } else if (isConditional(el)) {
312
- properties.push(...buildObjectProperties(el.elements, true));
678
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
313
679
  }
314
680
  }
315
681
  return properties;
316
682
  }
683
+ function getExplicitDisplayName(field) {
684
+ if (field.label !== void 0 && field.displayName !== void 0) {
685
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
686
+ }
687
+ return field.displayName ?? field.label;
688
+ }
689
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
690
+ const displayName = getExplicitDisplayName(field);
691
+ return resolveMetadata(
692
+ {
693
+ ...field.apiName !== void 0 && { apiName: field.apiName },
694
+ ...displayName !== void 0 && { displayName }
695
+ },
696
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
697
+ makeMetadataContext("chain-dsl", "field", logicalName)
698
+ );
699
+ }
317
700
  var CHAIN_DSL_PROVENANCE;
318
701
  var init_chain_dsl_canonicalizer = __esm({
319
702
  "src/canonicalize/chain-dsl-canonicalizer.ts"() {
320
703
  "use strict";
704
+ init_metadata();
321
705
  CHAIN_DSL_PROVENANCE = {
322
706
  surface: "chain-dsl",
323
707
  file: "",
@@ -329,7 +713,7 @@ var init_chain_dsl_canonicalizer = __esm({
329
713
 
330
714
  // src/canonicalize/tsdoc-canonicalizer.ts
331
715
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
332
- function canonicalizeTSDoc(analysis, source) {
716
+ function canonicalizeTSDoc(analysis, source, options) {
333
717
  const file = source?.file ?? "";
334
718
  const provenance = {
335
719
  surface: "tsdoc",
@@ -338,15 +722,21 @@ function canonicalizeTSDoc(analysis, source) {
338
722
  column: 0
339
723
  };
340
724
  const elements = assembleElements(analysis.fields, analysis.fieldLayouts, provenance);
341
- return {
725
+ const ir = {
342
726
  kind: "form-ir",
727
+ name: analysis.name,
343
728
  irVersion: IR_VERSION2,
344
729
  elements,
730
+ ...analysis.metadata !== void 0 && { metadata: analysis.metadata },
345
731
  typeRegistry: analysis.typeRegistry,
346
732
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { rootAnnotations: analysis.annotations },
347
733
  ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
348
734
  provenance
349
735
  };
736
+ return resolveFormIRMetadata(ir, {
737
+ policy: normalizeMetadataPolicy(options?.metadata),
738
+ surface: "tsdoc"
739
+ });
350
740
  }
351
741
  function assembleElements(fields, layouts, provenance) {
352
742
  const elements = [];
@@ -405,6 +795,7 @@ function wrapInConditional(field, layout, provenance) {
405
795
  var init_tsdoc_canonicalizer = __esm({
406
796
  "src/canonicalize/tsdoc-canonicalizer.ts"() {
407
797
  "use strict";
798
+ init_metadata();
408
799
  }
409
800
  });
410
801
 
@@ -417,6 +808,126 @@ var init_canonicalize = __esm({
417
808
  }
418
809
  });
419
810
 
811
+ // src/metadata/collision-guards.ts
812
+ function assertUniqueSerializedNames(entries, scope) {
813
+ const seen = /* @__PURE__ */ new Map();
814
+ for (const entry of entries) {
815
+ const previous = seen.get(entry.serializedName);
816
+ if (previous !== void 0) {
817
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
818
+ continue;
819
+ }
820
+ throw new Error(
821
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
822
+ );
823
+ }
824
+ seen.set(entry.serializedName, entry);
825
+ }
826
+ }
827
+ function collectFlattenedFields(elements) {
828
+ const fields = [];
829
+ for (const element of elements) {
830
+ switch (element.kind) {
831
+ case "field":
832
+ fields.push(element);
833
+ break;
834
+ case "group":
835
+ case "conditional":
836
+ fields.push(...collectFlattenedFields(element.elements));
837
+ break;
838
+ default: {
839
+ const exhaustive = element;
840
+ void exhaustive;
841
+ }
842
+ }
843
+ }
844
+ return fields;
845
+ }
846
+ function validateObjectProperties(properties, scope) {
847
+ assertUniqueSerializedNames(
848
+ properties.map((property) => ({
849
+ logicalName: property.name,
850
+ serializedName: getSerializedName(property.name, property.metadata),
851
+ category: "object property"
852
+ })),
853
+ scope
854
+ );
855
+ for (const property of properties) {
856
+ validateTypeNode(
857
+ property.type,
858
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
859
+ );
860
+ }
861
+ }
862
+ function validateTypeNode(type, scope) {
863
+ switch (type.kind) {
864
+ case "array":
865
+ validateTypeNode(type.items, `${scope}[]`);
866
+ break;
867
+ case "object":
868
+ validateObjectProperties(type.properties, scope);
869
+ break;
870
+ case "record":
871
+ validateTypeNode(type.valueType, `${scope}.*`);
872
+ break;
873
+ case "union":
874
+ type.members.forEach((member, index) => {
875
+ validateTypeNode(member, `${scope}|${String(index)}`);
876
+ });
877
+ break;
878
+ case "reference":
879
+ case "primitive":
880
+ case "enum":
881
+ case "dynamic":
882
+ case "custom":
883
+ break;
884
+ default: {
885
+ const exhaustive = type;
886
+ void exhaustive;
887
+ }
888
+ }
889
+ }
890
+ function validateTypeDefinitions(typeRegistry) {
891
+ const definitions = Object.values(typeRegistry);
892
+ assertUniqueSerializedNames(
893
+ definitions.map((definition) => ({
894
+ logicalName: definition.name,
895
+ serializedName: getSerializedName(definition.name, definition.metadata),
896
+ category: "type definition"
897
+ })),
898
+ "$defs"
899
+ );
900
+ for (const definition of definitions) {
901
+ validateTypeDefinition(definition);
902
+ }
903
+ }
904
+ function validateTypeDefinition(definition) {
905
+ validateTypeNode(
906
+ definition.type,
907
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
908
+ );
909
+ }
910
+ function assertNoSerializedNameCollisions(ir) {
911
+ assertUniqueSerializedNames(
912
+ collectFlattenedFields(ir.elements).map((field) => ({
913
+ logicalName: field.name,
914
+ serializedName: getSerializedName(field.name, field.metadata),
915
+ category: "field"
916
+ })),
917
+ "form root"
918
+ );
919
+ for (const field of collectFlattenedFields(ir.elements)) {
920
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
921
+ }
922
+ validateTypeDefinitions(ir.typeRegistry);
923
+ }
924
+ var init_collision_guards = __esm({
925
+ "src/metadata/collision-guards.ts"() {
926
+ "use strict";
927
+ init_resolve();
928
+ }
929
+ });
930
+
420
931
  // src/json-schema/ir-generator.ts
421
932
  function makeContext(options) {
422
933
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -427,19 +938,33 @@ function makeContext(options) {
427
938
  }
428
939
  return {
429
940
  defs: {},
941
+ typeNameMap: {},
942
+ typeRegistry: {},
430
943
  extensionRegistry: options?.extensionRegistry,
431
944
  vendorPrefix
432
945
  };
433
946
  }
434
947
  function generateJsonSchemaFromIR(ir, options) {
435
- const ctx = makeContext(options);
948
+ assertNoSerializedNameCollisions(ir);
949
+ const ctx = {
950
+ ...makeContext(options),
951
+ typeRegistry: ir.typeRegistry,
952
+ typeNameMap: Object.fromEntries(
953
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
954
+ name,
955
+ getSerializedName(name, typeDef.metadata)
956
+ ])
957
+ )
958
+ };
436
959
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
437
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
960
+ const schemaName = ctx.typeNameMap[name] ?? name;
961
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
962
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
438
963
  if (typeDef.constraints && typeDef.constraints.length > 0) {
439
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
964
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
440
965
  }
441
966
  if (typeDef.annotations && typeDef.annotations.length > 0) {
442
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
967
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
443
968
  }
444
969
  }
445
970
  const properties = {};
@@ -452,6 +977,7 @@ function generateJsonSchemaFromIR(ir, options) {
452
977
  properties,
453
978
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
454
979
  };
980
+ applyResolvedMetadata(result, ir.metadata);
455
981
  if (ir.annotations && ir.annotations.length > 0) {
456
982
  applyAnnotations(result, ir.annotations, ctx);
457
983
  }
@@ -464,9 +990,9 @@ function collectFields(elements, properties, required, ctx) {
464
990
  for (const element of elements) {
465
991
  switch (element.kind) {
466
992
  case "field":
467
- properties[element.name] = generateFieldSchema(element, ctx);
993
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
468
994
  if (element.required) {
469
- required.push(element.name);
995
+ required.push(getSerializedName(element.name, element.metadata));
470
996
  }
471
997
  break;
472
998
  case "group":
@@ -510,6 +1036,7 @@ function generateFieldSchema(field, ctx) {
510
1036
  rootAnnotations.push(annotation);
511
1037
  }
512
1038
  }
1039
+ applyResolvedMetadata(schema, field.metadata);
513
1040
  applyAnnotations(schema, rootAnnotations, ctx);
514
1041
  if (itemStringSchema !== void 0) {
515
1042
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -517,7 +1044,7 @@ function generateFieldSchema(field, ctx) {
517
1044
  if (pathConstraints.length === 0) {
518
1045
  return schema;
519
1046
  }
520
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
1047
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
521
1048
  }
522
1049
  function isStringItemConstraint(constraint) {
523
1050
  switch (constraint.constraintKind) {
@@ -529,9 +1056,11 @@ function isStringItemConstraint(constraint) {
529
1056
  return false;
530
1057
  }
531
1058
  }
532
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
1059
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
533
1060
  if (schema.type === "array" && schema.items) {
534
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
1061
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
1062
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
1063
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
535
1064
  return schema;
536
1065
  }
537
1066
  const byTarget = /* @__PURE__ */ new Map();
@@ -546,7 +1075,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
546
1075
  for (const [target, constraints] of byTarget) {
547
1076
  const subSchema = {};
548
1077
  applyConstraints(subSchema, constraints, ctx);
549
- propertyOverrides[target] = subSchema;
1078
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
550
1079
  }
551
1080
  if (schema.$ref) {
552
1081
  const { $ref, ...rest } = schema;
@@ -594,7 +1123,7 @@ function generateTypeNode(type, ctx) {
594
1123
  case "union":
595
1124
  return generateUnionType(type, ctx);
596
1125
  case "reference":
597
- return generateReferenceType(type);
1126
+ return generateReferenceType(type, ctx);
598
1127
  case "dynamic":
599
1128
  return generateDynamicType(type);
600
1129
  case "custom":
@@ -635,9 +1164,10 @@ function generateObjectType(type, ctx) {
635
1164
  const properties = {};
636
1165
  const required = [];
637
1166
  for (const prop of type.properties) {
638
- properties[prop.name] = generatePropertySchema(prop, ctx);
1167
+ const propertyName = getSerializedName(prop.name, prop.metadata);
1168
+ properties[propertyName] = generatePropertySchema(prop, ctx);
639
1169
  if (!prop.optional) {
640
- required.push(prop.name);
1170
+ required.push(propertyName);
641
1171
  }
642
1172
  }
643
1173
  const schema = { type: "object", properties };
@@ -658,6 +1188,7 @@ function generateRecordType(type, ctx) {
658
1188
  function generatePropertySchema(prop, ctx) {
659
1189
  const schema = generateTypeNode(prop.type, ctx);
660
1190
  applyConstraints(schema, prop.constraints, ctx);
1191
+ applyResolvedMetadata(schema, prop.metadata);
661
1192
  applyAnnotations(schema, prop.annotations, ctx);
662
1193
  return schema;
663
1194
  }
@@ -686,8 +1217,28 @@ function isNullableUnion(type) {
686
1217
  ).length;
687
1218
  return nullCount === 1;
688
1219
  }
689
- function generateReferenceType(type) {
690
- return { $ref: `#/$defs/${type.name}` };
1220
+ function generateReferenceType(type, ctx) {
1221
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
1222
+ }
1223
+ function applyResolvedMetadata(schema, metadata) {
1224
+ const displayName = getDisplayName(metadata);
1225
+ if (displayName !== void 0) {
1226
+ schema.title = displayName;
1227
+ }
1228
+ }
1229
+ function resolveReferencedType(type, ctx) {
1230
+ return ctx.typeRegistry[type.name]?.type;
1231
+ }
1232
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1233
+ if (typeNode?.kind === "object") {
1234
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1235
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1236
+ }
1237
+ if (typeNode?.kind === "reference") {
1238
+ const referencedType = resolveReferencedType(typeNode, ctx);
1239
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1240
+ }
1241
+ return logicalName;
691
1242
  }
692
1243
  function generateDynamicType(type) {
693
1244
  if (type.dynamicKind === "enum") {
@@ -767,7 +1318,7 @@ function applyAnnotations(schema, annotations, ctx) {
767
1318
  for (const annotation of annotations) {
768
1319
  switch (annotation.annotationKind) {
769
1320
  case "displayName":
770
- schema.title = annotation.value;
1321
+ schema.title ??= annotation.value;
771
1322
  break;
772
1323
  case "description":
773
1324
  schema.description = annotation.value;
@@ -854,12 +1405,17 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
854
1405
  var init_ir_generator = __esm({
855
1406
  "src/json-schema/ir-generator.ts"() {
856
1407
  "use strict";
1408
+ init_metadata();
1409
+ init_collision_guards();
857
1410
  }
858
1411
  });
859
1412
 
860
1413
  // src/json-schema/generator.ts
861
1414
  function generateJsonSchema(form, options) {
862
- const ir = canonicalizeChainDSL(form);
1415
+ const ir = canonicalizeChainDSL(
1416
+ form,
1417
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1418
+ );
863
1419
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
864
1420
  return generateJsonSchemaFromIR(ir, internalOptions);
865
1421
  }
@@ -1042,13 +1598,21 @@ function combineRules(parentRule, childRule) {
1042
1598
  }
1043
1599
  };
1044
1600
  }
1045
- function fieldNodeToControl(field, parentRule) {
1046
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1601
+ function getFieldDisplayName(field) {
1602
+ const resolvedDisplayName = getDisplayName(field.metadata);
1603
+ if (resolvedDisplayName !== void 0) {
1604
+ return resolvedDisplayName;
1605
+ }
1606
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1607
+ }
1608
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
1047
1609
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1610
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1611
+ const displayName = getFieldDisplayName(field);
1048
1612
  const control = {
1049
1613
  type: "Control",
1050
- scope: fieldToScope(field.name),
1051
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1614
+ scope: fieldToScope(serializedName),
1615
+ ...displayName !== void 0 && { label: displayName },
1052
1616
  ...placeholderAnnotation !== void 0 && {
1053
1617
  options: { placeholder: placeholderAnnotation.value }
1054
1618
  },
@@ -1056,30 +1620,30 @@ function fieldNodeToControl(field, parentRule) {
1056
1620
  };
1057
1621
  return control;
1058
1622
  }
1059
- function groupNodeToLayout(group, parentRule) {
1623
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
1060
1624
  return {
1061
1625
  type: "Group",
1062
1626
  label: group.label,
1063
- elements: irElementsToUiSchema(group.elements, parentRule),
1627
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
1064
1628
  ...parentRule !== void 0 && { rule: parentRule }
1065
1629
  };
1066
1630
  }
1067
- function irElementsToUiSchema(elements, parentRule) {
1631
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
1068
1632
  const result = [];
1069
1633
  for (const element of elements) {
1070
1634
  switch (element.kind) {
1071
1635
  case "field": {
1072
- result.push(fieldNodeToControl(element, parentRule));
1636
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1073
1637
  break;
1074
1638
  }
1075
1639
  case "group": {
1076
- result.push(groupNodeToLayout(element, parentRule));
1640
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1077
1641
  break;
1078
1642
  }
1079
1643
  case "conditional": {
1080
- const newRule = createShowRule(element.fieldName, element.value);
1644
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1081
1645
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1082
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1646
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1083
1647
  result.push(...childElements);
1084
1648
  break;
1085
1649
  }
@@ -1093,22 +1657,50 @@ function irElementsToUiSchema(elements, parentRule) {
1093
1657
  return result;
1094
1658
  }
1095
1659
  function generateUiSchemaFromIR(ir) {
1660
+ assertNoSerializedNameCollisions(ir);
1661
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1096
1662
  const result = {
1097
1663
  type: "VerticalLayout",
1098
- elements: irElementsToUiSchema(ir.elements)
1664
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1099
1665
  };
1100
1666
  return parseOrThrow(uiSchema, result, "UI Schema");
1101
1667
  }
1668
+ function collectFieldNameMap(elements) {
1669
+ const map = /* @__PURE__ */ new Map();
1670
+ for (const element of elements) {
1671
+ switch (element.kind) {
1672
+ case "field":
1673
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1674
+ break;
1675
+ case "group":
1676
+ case "conditional":
1677
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1678
+ map.set(key, value);
1679
+ }
1680
+ break;
1681
+ default: {
1682
+ const _exhaustive = element;
1683
+ void _exhaustive;
1684
+ }
1685
+ }
1686
+ }
1687
+ return map;
1688
+ }
1102
1689
  var init_ir_generator2 = __esm({
1103
1690
  "src/ui-schema/ir-generator.ts"() {
1104
1691
  "use strict";
1692
+ init_metadata();
1693
+ init_collision_guards();
1105
1694
  init_schema();
1106
1695
  }
1107
1696
  });
1108
1697
 
1109
1698
  // src/ui-schema/generator.ts
1110
- function generateUiSchema(form) {
1111
- const ir = canonicalizeChainDSL(form);
1699
+ function generateUiSchema(form, options) {
1700
+ const ir = canonicalizeChainDSL(
1701
+ form,
1702
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1703
+ );
1112
1704
  return generateUiSchemaFromIR(ir);
1113
1705
  }
1114
1706
  var init_generator2 = __esm({
@@ -2169,6 +2761,9 @@ var init_jsdoc_constraints = __esm({
2169
2761
 
2170
2762
  // src/analyzer/class-analyzer.ts
2171
2763
  import * as ts3 from "typescript";
2764
+ import {
2765
+ parseCommentBlock as parseCommentBlock2
2766
+ } from "@formspec/analysis/internal";
2172
2767
  function isObjectType(type) {
2173
2768
  return !!(type.flags & ts3.TypeFlags.Object);
2174
2769
  }
@@ -2187,7 +2782,76 @@ function makeParseOptions(extensionRegistry, fieldType, checker, subjectType, ho
2187
2782
  ...hostType !== void 0 && { hostType }
2188
2783
  };
2189
2784
  }
2190
- function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2785
+ function makeExplicitScalarMetadata(value) {
2786
+ return value === void 0 || value === "" ? void 0 : { value, source: "explicit" };
2787
+ }
2788
+ function extractExplicitMetadata(node) {
2789
+ let apiName;
2790
+ let displayName;
2791
+ let apiNamePlural;
2792
+ let displayNamePlural;
2793
+ for (const tag of getLeadingParsedTags(node)) {
2794
+ const value = tag.argumentText.trim();
2795
+ if (value === "") {
2796
+ continue;
2797
+ }
2798
+ if (tag.normalizedTagName === "apiName") {
2799
+ if (tag.target === null) {
2800
+ apiName ??= value;
2801
+ } else if (tag.target.kind === "variant") {
2802
+ if (tag.target.rawText === "singular") {
2803
+ apiName ??= value;
2804
+ } else if (tag.target.rawText === "plural") {
2805
+ apiNamePlural ??= value;
2806
+ }
2807
+ }
2808
+ continue;
2809
+ }
2810
+ if (tag.normalizedTagName === "displayName") {
2811
+ if (tag.target === null) {
2812
+ displayName ??= value;
2813
+ } else if (tag.target.kind === "variant") {
2814
+ if (tag.target.rawText === "singular") {
2815
+ displayName ??= value;
2816
+ } else if (tag.target.rawText === "plural") {
2817
+ displayNamePlural ??= value;
2818
+ }
2819
+ }
2820
+ }
2821
+ }
2822
+ const resolvedApiName = makeExplicitScalarMetadata(apiName);
2823
+ const resolvedDisplayName = makeExplicitScalarMetadata(displayName);
2824
+ const resolvedApiNamePlural = makeExplicitScalarMetadata(apiNamePlural);
2825
+ const resolvedDisplayNamePlural = makeExplicitScalarMetadata(displayNamePlural);
2826
+ const metadata = {
2827
+ ...resolvedApiName !== void 0 && { apiName: resolvedApiName },
2828
+ ...resolvedDisplayName !== void 0 && { displayName: resolvedDisplayName },
2829
+ ...resolvedApiNamePlural !== void 0 && { apiNamePlural: resolvedApiNamePlural },
2830
+ ...resolvedDisplayNamePlural !== void 0 && {
2831
+ displayNamePlural: resolvedDisplayNamePlural
2832
+ }
2833
+ };
2834
+ return Object.keys(metadata).length === 0 ? void 0 : metadata;
2835
+ }
2836
+ function resolveNodeMetadata(metadataPolicy, declarationKind, logicalName, node, buildContext) {
2837
+ const explicit = extractExplicitMetadata(node);
2838
+ return resolveMetadata(
2839
+ {
2840
+ ...explicit?.apiName !== void 0 && { apiName: explicit.apiName.value },
2841
+ ...explicit?.displayName !== void 0 && { displayName: explicit.displayName.value },
2842
+ ...explicit?.apiNamePlural !== void 0 && {
2843
+ apiNamePlural: explicit.apiNamePlural.value
2844
+ },
2845
+ ...explicit?.displayNamePlural !== void 0 && {
2846
+ displayNamePlural: explicit.displayNamePlural.value
2847
+ }
2848
+ },
2849
+ getDeclarationMetadataPolicy(metadataPolicy, declarationKind),
2850
+ makeMetadataContext("tsdoc", declarationKind, logicalName, buildContext)
2851
+ );
2852
+ }
2853
+ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2854
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2191
2855
  const name = classDecl.name?.text ?? "AnonymousClass";
2192
2856
  const fields = [];
2193
2857
  const fieldLayouts = [];
@@ -2214,6 +2878,7 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2214
2878
  visiting,
2215
2879
  diagnostics,
2216
2880
  classType,
2881
+ normalizedMetadataPolicy,
2217
2882
  extensionRegistry
2218
2883
  );
2219
2884
  if (fieldNode) {
@@ -2232,9 +2897,25 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2232
2897
  }
2233
2898
  }
2234
2899
  }
2900
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2901
+ fields,
2902
+ classDecl,
2903
+ classType,
2904
+ checker,
2905
+ file,
2906
+ diagnostics,
2907
+ normalizedMetadataPolicy
2908
+ );
2909
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, classDecl, {
2910
+ checker,
2911
+ declaration: classDecl,
2912
+ subjectType: classType,
2913
+ hostType: classType
2914
+ });
2235
2915
  return {
2236
2916
  name,
2237
- fields,
2917
+ ...metadata !== void 0 && { metadata },
2918
+ fields: specializedFields,
2238
2919
  fieldLayouts,
2239
2920
  typeRegistry,
2240
2921
  ...annotations.length > 0 && { annotations },
@@ -2243,7 +2924,8 @@ function analyzeClassToIR(classDecl, checker, file = "", extensionRegistry) {
2243
2924
  staticMethods
2244
2925
  };
2245
2926
  }
2246
- function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry) {
2927
+ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegistry, metadataPolicy) {
2928
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2247
2929
  const name = interfaceDecl.name.text;
2248
2930
  const fields = [];
2249
2931
  const typeRegistry = {};
@@ -2267,6 +2949,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2267
2949
  visiting,
2268
2950
  diagnostics,
2269
2951
  interfaceType,
2952
+ normalizedMetadataPolicy,
2270
2953
  extensionRegistry
2271
2954
  );
2272
2955
  if (fieldNode) {
@@ -2274,10 +2957,26 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2274
2957
  }
2275
2958
  }
2276
2959
  }
2277
- const fieldLayouts = fields.map(() => ({}));
2960
+ const specializedFields = applyDeclarationDiscriminatorToFields(
2961
+ fields,
2962
+ interfaceDecl,
2963
+ interfaceType,
2964
+ checker,
2965
+ file,
2966
+ diagnostics,
2967
+ normalizedMetadataPolicy
2968
+ );
2969
+ const fieldLayouts = specializedFields.map(() => ({}));
2970
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, interfaceDecl, {
2971
+ checker,
2972
+ declaration: interfaceDecl,
2973
+ subjectType: interfaceType,
2974
+ hostType: interfaceType
2975
+ });
2278
2976
  return {
2279
2977
  name,
2280
- fields,
2978
+ ...metadata !== void 0 && { metadata },
2979
+ fields: specializedFields,
2281
2980
  fieldLayouts,
2282
2981
  typeRegistry,
2283
2982
  ...annotations.length > 0 && { annotations },
@@ -2286,7 +2985,7 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "", extensionRegist
2286
2985
  staticMethods: []
2287
2986
  };
2288
2987
  }
2289
- function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry) {
2988
+ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry, metadataPolicy) {
2290
2989
  if (!ts3.isTypeLiteralNode(typeAlias.type)) {
2291
2990
  const sourceFile = typeAlias.getSourceFile();
2292
2991
  const { line } = sourceFile.getLineAndCharacterOfPosition(typeAlias.getStart());
@@ -2296,6 +2995,8 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2296
2995
  error: `Type alias "${typeAlias.name.text}" at line ${String(line + 1)} is not an object type literal (found ${kindDesc})`
2297
2996
  };
2298
2997
  }
2998
+ const typeLiteral = typeAlias.type;
2999
+ const normalizedMetadataPolicy = normalizeMetadataPolicy(metadataPolicy);
2299
3000
  const name = typeAlias.name.text;
2300
3001
  const fields = [];
2301
3002
  const typeRegistry = {};
@@ -2309,7 +3010,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2309
3010
  const annotations = [...typeAliasDoc.annotations];
2310
3011
  diagnostics.push(...typeAliasDoc.diagnostics);
2311
3012
  const visiting = /* @__PURE__ */ new Set();
2312
- for (const member of typeAlias.type.members) {
3013
+ for (const member of typeLiteral.members) {
2313
3014
  if (ts3.isPropertySignature(member)) {
2314
3015
  const fieldNode = analyzeInterfacePropertyToIR(
2315
3016
  member,
@@ -2319,6 +3020,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2319
3020
  visiting,
2320
3021
  diagnostics,
2321
3022
  aliasType,
3023
+ normalizedMetadataPolicy,
2322
3024
  extensionRegistry
2323
3025
  );
2324
3026
  if (fieldNode) {
@@ -2326,12 +3028,28 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2326
3028
  }
2327
3029
  }
2328
3030
  }
3031
+ const specializedFields = applyDeclarationDiscriminatorToFields(
3032
+ fields,
3033
+ typeAlias,
3034
+ aliasType,
3035
+ checker,
3036
+ file,
3037
+ diagnostics,
3038
+ normalizedMetadataPolicy
3039
+ );
3040
+ const metadata = resolveNodeMetadata(normalizedMetadataPolicy, "type", name, typeAlias, {
3041
+ checker,
3042
+ declaration: typeAlias,
3043
+ subjectType: aliasType,
3044
+ hostType: aliasType
3045
+ });
2329
3046
  return {
2330
3047
  ok: true,
2331
3048
  analysis: {
2332
3049
  name,
2333
- fields,
2334
- fieldLayouts: fields.map(() => ({})),
3050
+ ...metadata !== void 0 && { metadata },
3051
+ fields: specializedFields,
3052
+ fieldLayouts: specializedFields.map(() => ({})),
2335
3053
  typeRegistry,
2336
3054
  ...annotations.length > 0 && { annotations },
2337
3055
  ...diagnostics.length > 0 && { diagnostics },
@@ -2340,7 +3058,444 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "", extensionRegistry)
2340
3058
  }
2341
3059
  };
2342
3060
  }
2343
- function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3061
+ function makeAnalysisDiagnostic(code, message, primaryLocation, relatedLocations = []) {
3062
+ return {
3063
+ code,
3064
+ message,
3065
+ severity: "error",
3066
+ primaryLocation,
3067
+ relatedLocations
3068
+ };
3069
+ }
3070
+ function getLeadingParsedTags(node) {
3071
+ const sourceFile = node.getSourceFile();
3072
+ const sourceText = sourceFile.getFullText();
3073
+ const commentRanges = ts3.getLeadingCommentRanges(sourceText, node.getFullStart());
3074
+ if (commentRanges === void 0) {
3075
+ return [];
3076
+ }
3077
+ const parsedTags = [];
3078
+ for (const range of commentRanges) {
3079
+ if (range.kind !== ts3.SyntaxKind.MultiLineCommentTrivia) {
3080
+ continue;
3081
+ }
3082
+ const commentText = sourceText.slice(range.pos, range.end);
3083
+ if (!commentText.startsWith("/**")) {
3084
+ continue;
3085
+ }
3086
+ parsedTags.push(...parseCommentBlock2(commentText, { offset: range.pos }).tags);
3087
+ }
3088
+ return parsedTags;
3089
+ }
3090
+ function resolveDiscriminatorProperty(node, checker, fieldName) {
3091
+ const subjectType = checker.getTypeAtLocation(node);
3092
+ const propertySymbol = subjectType.getProperty(fieldName);
3093
+ if (propertySymbol === void 0) {
3094
+ return null;
3095
+ }
3096
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.find(
3097
+ (candidate) => ts3.isPropertyDeclaration(candidate) || ts3.isPropertySignature(candidate)
3098
+ ) ?? propertySymbol.declarations?.[0];
3099
+ return {
3100
+ declaration,
3101
+ type: checker.getTypeOfSymbolAtLocation(propertySymbol, declaration ?? node),
3102
+ optional: !!(propertySymbol.flags & ts3.SymbolFlags.Optional) || declaration !== void 0 && "questionToken" in declaration && declaration.questionToken !== void 0
3103
+ };
3104
+ }
3105
+ function isLocalTypeParameterName(node, typeParameterName) {
3106
+ return node.typeParameters?.some((typeParameter) => typeParameter.name.text === typeParameterName) ?? false;
3107
+ }
3108
+ function isNullishSemanticType(type) {
3109
+ if (type.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined | ts3.TypeFlags.Void | ts3.TypeFlags.Unknown | ts3.TypeFlags.Any)) {
3110
+ return true;
3111
+ }
3112
+ return type.isUnion() && type.types.some((member) => isNullishSemanticType(member));
3113
+ }
3114
+ function isStringLikeSemanticType(type) {
3115
+ if (type.flags & ts3.TypeFlags.StringLike) {
3116
+ return true;
3117
+ }
3118
+ if (type.isUnion()) {
3119
+ return type.types.length > 0 && type.types.every((member) => isStringLikeSemanticType(member));
3120
+ }
3121
+ return false;
3122
+ }
3123
+ function extractDiscriminatorDirective(node, file, diagnostics) {
3124
+ const discriminatorTags = getLeadingParsedTags(node).filter(
3125
+ (tag) => tag.normalizedTagName === "discriminator"
3126
+ );
3127
+ if (discriminatorTags.length === 0) {
3128
+ return null;
3129
+ }
3130
+ const [firstTag, ...duplicateTags] = discriminatorTags;
3131
+ for (const _duplicateTag of duplicateTags) {
3132
+ diagnostics.push(
3133
+ makeAnalysisDiagnostic(
3134
+ "DUPLICATE_TAG",
3135
+ 'Duplicate "@discriminator" tag. Only one discriminator declaration is allowed per declaration.',
3136
+ provenanceForNode(node, file)
3137
+ )
3138
+ );
3139
+ }
3140
+ if (firstTag === void 0) {
3141
+ return null;
3142
+ }
3143
+ const firstTarget = firstTag.target;
3144
+ if (firstTarget?.path === null || firstTarget?.valid !== true) {
3145
+ diagnostics.push(
3146
+ makeAnalysisDiagnostic(
3147
+ "INVALID_TAG_ARGUMENT",
3148
+ 'Tag "@discriminator" requires a direct path target like ":kind".',
3149
+ provenanceForNode(node, file)
3150
+ )
3151
+ );
3152
+ return null;
3153
+ }
3154
+ if (firstTarget.path.segments.length !== 1) {
3155
+ diagnostics.push(
3156
+ makeAnalysisDiagnostic(
3157
+ "INVALID_TAG_ARGUMENT",
3158
+ 'Tag "@discriminator" only supports direct property targets in v1; nested paths are out of scope.',
3159
+ provenanceForNode(node, file)
3160
+ )
3161
+ );
3162
+ return null;
3163
+ }
3164
+ const typeParameterName = firstTag.argumentText.trim();
3165
+ if (!/^[A-Za-z_$][\w$]*$/u.test(typeParameterName)) {
3166
+ diagnostics.push(
3167
+ makeAnalysisDiagnostic(
3168
+ "INVALID_TAG_ARGUMENT",
3169
+ 'Tag "@discriminator" requires a local type parameter name as its source operand.',
3170
+ provenanceForNode(node, file)
3171
+ )
3172
+ );
3173
+ return null;
3174
+ }
3175
+ return {
3176
+ fieldName: firstTarget.path.segments[0] ?? firstTarget.rawText,
3177
+ typeParameterName,
3178
+ provenance: provenanceForNode(node, file)
3179
+ };
3180
+ }
3181
+ function validateDiscriminatorDirective(node, checker, file, diagnostics) {
3182
+ const directive = extractDiscriminatorDirective(node, file, diagnostics);
3183
+ if (directive === null) {
3184
+ return null;
3185
+ }
3186
+ if (!isLocalTypeParameterName(node, directive.typeParameterName)) {
3187
+ diagnostics.push(
3188
+ makeAnalysisDiagnostic(
3189
+ "INVALID_TAG_ARGUMENT",
3190
+ `Tag "@discriminator" references "${directive.typeParameterName}", but the source operand must be a type parameter declared on the same declaration.`,
3191
+ directive.provenance
3192
+ )
3193
+ );
3194
+ return null;
3195
+ }
3196
+ const property = resolveDiscriminatorProperty(node, checker, directive.fieldName);
3197
+ if (property === null) {
3198
+ diagnostics.push(
3199
+ makeAnalysisDiagnostic(
3200
+ "UNKNOWN_PATH_TARGET",
3201
+ `Tag "@discriminator" targets "${directive.fieldName}", but no direct property with that name exists on this declaration.`,
3202
+ directive.provenance
3203
+ )
3204
+ );
3205
+ return null;
3206
+ }
3207
+ if (property.optional) {
3208
+ diagnostics.push(
3209
+ makeAnalysisDiagnostic(
3210
+ "TYPE_MISMATCH",
3211
+ `Discriminator field "${directive.fieldName}" must be required; optional discriminator fields are not supported.`,
3212
+ directive.provenance,
3213
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3214
+ )
3215
+ );
3216
+ return null;
3217
+ }
3218
+ if (isNullishSemanticType(property.type)) {
3219
+ diagnostics.push(
3220
+ makeAnalysisDiagnostic(
3221
+ "TYPE_MISMATCH",
3222
+ `Discriminator field "${directive.fieldName}" must not be nullable.`,
3223
+ directive.provenance,
3224
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3225
+ )
3226
+ );
3227
+ return null;
3228
+ }
3229
+ if (!isStringLikeSemanticType(property.type)) {
3230
+ diagnostics.push(
3231
+ makeAnalysisDiagnostic(
3232
+ "TYPE_MISMATCH",
3233
+ `Discriminator field "${directive.fieldName}" must be string-like.`,
3234
+ directive.provenance,
3235
+ property.declaration !== void 0 ? [provenanceForNode(property.declaration, file)] : []
3236
+ )
3237
+ );
3238
+ return null;
3239
+ }
3240
+ return directive;
3241
+ }
3242
+ function getConcreteTypeArgumentForDiscriminator(node, subjectType, checker, typeParameterName) {
3243
+ const typeParameterIndex = node.typeParameters?.findIndex(
3244
+ (typeParameter) => typeParameter.name.text === typeParameterName
3245
+ ) ?? -1;
3246
+ if (typeParameterIndex < 0) {
3247
+ return null;
3248
+ }
3249
+ const referenceTypeArguments = (isTypeReference(subjectType) ? subjectType.typeArguments : void 0) ?? subjectType.aliasTypeArguments;
3250
+ if (referenceTypeArguments?.[typeParameterIndex] !== void 0) {
3251
+ return referenceTypeArguments[typeParameterIndex] ?? null;
3252
+ }
3253
+ const localTypeParameter = node.typeParameters?.[typeParameterIndex];
3254
+ return localTypeParameter === void 0 ? null : checker.getTypeAtLocation(localTypeParameter);
3255
+ }
3256
+ function resolveLiteralDiscriminatorPropertyValue(boundType, fieldName, checker, provenance, diagnostics) {
3257
+ const propertySymbol = boundType.getProperty(fieldName);
3258
+ if (propertySymbol === void 0) {
3259
+ return void 0;
3260
+ }
3261
+ const declaration = propertySymbol.valueDeclaration ?? propertySymbol.declarations?.[0];
3262
+ const anchorNode = declaration ?? boundType.symbol.declarations?.[0] ?? null;
3263
+ const resolvedAnchorNode = anchorNode ?? resolveNamedDiscriminatorDeclaration(boundType, checker);
3264
+ if (resolvedAnchorNode === null) {
3265
+ return void 0;
3266
+ }
3267
+ const propertyType = checker.getTypeOfSymbolAtLocation(
3268
+ propertySymbol,
3269
+ resolvedAnchorNode
3270
+ );
3271
+ if (propertyType.isStringLiteral()) {
3272
+ return propertyType.value;
3273
+ }
3274
+ if (propertyType.isUnion()) {
3275
+ const nonNullMembers = propertyType.types.filter(
3276
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3277
+ );
3278
+ if (nonNullMembers.length > 0 && nonNullMembers.every((member) => member.isStringLiteral())) {
3279
+ diagnostics.push(
3280
+ makeAnalysisDiagnostic(
3281
+ "INVALID_TAG_ARGUMENT",
3282
+ "Discriminator resolution for union-valued identity properties is out of scope for v1.",
3283
+ provenance
3284
+ )
3285
+ );
3286
+ return null;
3287
+ }
3288
+ }
3289
+ return void 0;
3290
+ }
3291
+ function resolveDiscriminatorApiName(boundType, checker, metadataPolicy) {
3292
+ const declaration = resolveNamedDiscriminatorDeclaration(boundType, checker);
3293
+ if (declaration === null) {
3294
+ return void 0;
3295
+ }
3296
+ const metadata = resolveNodeMetadata(
3297
+ metadataPolicy,
3298
+ "type",
3299
+ getDiscriminatorLogicalName(boundType, declaration, checker),
3300
+ declaration,
3301
+ {
3302
+ checker,
3303
+ declaration,
3304
+ subjectType: boundType
3305
+ }
3306
+ );
3307
+ return metadata?.apiName;
3308
+ }
3309
+ function resolveNamedDiscriminatorDeclaration(type, checker, seen = /* @__PURE__ */ new Set()) {
3310
+ if (seen.has(type)) {
3311
+ return null;
3312
+ }
3313
+ seen.add(type);
3314
+ const symbol = type.aliasSymbol ?? type.getSymbol();
3315
+ if (symbol !== void 0) {
3316
+ const aliased = symbol.flags & ts3.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : void 0;
3317
+ const targetSymbol = aliased ?? symbol;
3318
+ const declaration = targetSymbol.declarations?.find(
3319
+ (candidate) => ts3.isClassDeclaration(candidate) || ts3.isInterfaceDeclaration(candidate) || ts3.isTypeAliasDeclaration(candidate) || ts3.isEnumDeclaration(candidate)
3320
+ );
3321
+ if (declaration !== void 0) {
3322
+ if (ts3.isTypeAliasDeclaration(declaration) && ts3.isTypeReferenceNode(declaration.type) && checker.getTypeFromTypeNode(declaration.type) !== type) {
3323
+ return resolveNamedDiscriminatorDeclaration(
3324
+ checker.getTypeFromTypeNode(declaration.type),
3325
+ checker,
3326
+ seen
3327
+ );
3328
+ }
3329
+ return declaration;
3330
+ }
3331
+ }
3332
+ return null;
3333
+ }
3334
+ function resolveDiscriminatorValue(boundType, fieldName, checker, provenance, diagnostics, metadataPolicy) {
3335
+ if (boundType === null) {
3336
+ diagnostics.push(
3337
+ makeAnalysisDiagnostic(
3338
+ "INVALID_TAG_ARGUMENT",
3339
+ "Discriminator resolution failed because no concrete type argument is available for the referenced type parameter.",
3340
+ provenance
3341
+ )
3342
+ );
3343
+ return null;
3344
+ }
3345
+ if (boundType.isStringLiteral()) {
3346
+ return boundType.value;
3347
+ }
3348
+ if (boundType.isUnion()) {
3349
+ const nonNullMembers = boundType.types.filter(
3350
+ (member) => !(member.flags & (ts3.TypeFlags.Null | ts3.TypeFlags.Undefined))
3351
+ );
3352
+ if (nonNullMembers.every((member) => member.isStringLiteral())) {
3353
+ diagnostics.push(
3354
+ makeAnalysisDiagnostic(
3355
+ "INVALID_TAG_ARGUMENT",
3356
+ "Discriminator resolution for unions of string literals is out of scope for v1.",
3357
+ provenance
3358
+ )
3359
+ );
3360
+ return null;
3361
+ }
3362
+ }
3363
+ const literalIdentityValue = resolveLiteralDiscriminatorPropertyValue(
3364
+ boundType,
3365
+ fieldName,
3366
+ checker,
3367
+ provenance,
3368
+ diagnostics
3369
+ );
3370
+ if (literalIdentityValue !== void 0) {
3371
+ return literalIdentityValue;
3372
+ }
3373
+ const apiName = resolveDiscriminatorApiName(boundType, checker, metadataPolicy);
3374
+ if (apiName?.source === "explicit") {
3375
+ return apiName.value;
3376
+ }
3377
+ if (apiName?.source === "inferred") {
3378
+ return apiName.value;
3379
+ }
3380
+ diagnostics.push(
3381
+ makeAnalysisDiagnostic(
3382
+ "INVALID_TAG_ARGUMENT",
3383
+ "Discriminator resolution could not derive a JSON-facing discriminator value from the referenced type argument.",
3384
+ provenance
3385
+ )
3386
+ );
3387
+ return null;
3388
+ }
3389
+ function getDeclarationName(node) {
3390
+ if (ts3.isClassDeclaration(node) || ts3.isInterfaceDeclaration(node) || ts3.isTypeAliasDeclaration(node) || ts3.isEnumDeclaration(node)) {
3391
+ return node.name?.text ?? "anonymous";
3392
+ }
3393
+ return "anonymous";
3394
+ }
3395
+ function getResolvedTypeArguments(type) {
3396
+ return (isTypeReference(type) ? type.typeArguments : void 0) ?? type.aliasTypeArguments ?? [];
3397
+ }
3398
+ function getDiscriminatorLogicalName(type, declaration, checker) {
3399
+ const baseName = getDeclarationName(declaration);
3400
+ const typeArguments = getResolvedTypeArguments(type);
3401
+ return typeArguments.length === 0 ? baseName : buildInstantiatedReferenceName(baseName, typeArguments, checker);
3402
+ }
3403
+ function applyDeclarationDiscriminatorToFields(fields, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3404
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3405
+ if (directive === null) {
3406
+ return [...fields];
3407
+ }
3408
+ const discriminatorValue = resolveDiscriminatorValue(
3409
+ getConcreteTypeArgumentForDiscriminator(
3410
+ node,
3411
+ subjectType,
3412
+ checker,
3413
+ directive.typeParameterName
3414
+ ),
3415
+ directive.fieldName,
3416
+ checker,
3417
+ directive.provenance,
3418
+ diagnostics,
3419
+ metadataPolicy
3420
+ );
3421
+ if (discriminatorValue === null) {
3422
+ return [...fields];
3423
+ }
3424
+ return fields.map(
3425
+ (field) => field.name === directive.fieldName ? {
3426
+ ...field,
3427
+ type: {
3428
+ kind: "enum",
3429
+ members: [{ value: discriminatorValue }]
3430
+ }
3431
+ } : field
3432
+ );
3433
+ }
3434
+ function buildInstantiatedReferenceName(baseName, typeArguments, checker) {
3435
+ const renderedArguments = typeArguments.map(
3436
+ (typeArgument) => checker.typeToString(typeArgument).replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "")
3437
+ ).filter((value) => value !== "");
3438
+ return renderedArguments.length === 0 ? baseName : `${baseName}__${renderedArguments.join("__")}`;
3439
+ }
3440
+ function extractReferenceTypeArguments(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy, extensionRegistry, diagnostics) {
3441
+ const typeNode = sourceNode === void 0 ? void 0 : extractTypeNodeFromSource(sourceNode);
3442
+ if (typeNode === void 0) {
3443
+ return [];
3444
+ }
3445
+ const resolvedTypeNode = resolveAliasedTypeNode(typeNode, checker);
3446
+ if (!ts3.isTypeReferenceNode(resolvedTypeNode) || resolvedTypeNode.typeArguments === void 0) {
3447
+ return [];
3448
+ }
3449
+ return resolvedTypeNode.typeArguments.map((argumentNode) => {
3450
+ const argumentType = checker.getTypeFromTypeNode(argumentNode);
3451
+ return {
3452
+ tsType: argumentType,
3453
+ typeNode: resolveTypeNode(
3454
+ argumentType,
3455
+ checker,
3456
+ file,
3457
+ typeRegistry,
3458
+ visiting,
3459
+ argumentNode,
3460
+ metadataPolicy,
3461
+ extensionRegistry,
3462
+ diagnostics
3463
+ )
3464
+ };
3465
+ });
3466
+ }
3467
+ function applyDiscriminatorToObjectProperties(properties, node, subjectType, checker, file, diagnostics, metadataPolicy) {
3468
+ const directive = validateDiscriminatorDirective(node, checker, file, diagnostics);
3469
+ if (directive === null) {
3470
+ return properties;
3471
+ }
3472
+ const discriminatorValue = resolveDiscriminatorValue(
3473
+ getConcreteTypeArgumentForDiscriminator(
3474
+ node,
3475
+ subjectType,
3476
+ checker,
3477
+ directive.typeParameterName
3478
+ ),
3479
+ directive.fieldName,
3480
+ checker,
3481
+ directive.provenance,
3482
+ diagnostics,
3483
+ metadataPolicy
3484
+ );
3485
+ if (discriminatorValue === null) {
3486
+ return properties;
3487
+ }
3488
+ return properties.map(
3489
+ (property) => property.name === directive.fieldName ? {
3490
+ ...property,
3491
+ type: {
3492
+ kind: "enum",
3493
+ members: [{ value: discriminatorValue }]
3494
+ }
3495
+ } : property
3496
+ );
3497
+ }
3498
+ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2344
3499
  if (!ts3.isIdentifier(prop.name)) {
2345
3500
  return null;
2346
3501
  }
@@ -2355,6 +3510,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2355
3510
  typeRegistry,
2356
3511
  visiting,
2357
3512
  prop,
3513
+ metadataPolicy,
2358
3514
  extensionRegistry,
2359
3515
  diagnostics
2360
3516
  );
@@ -2378,9 +3534,16 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2378
3534
  annotations.push(defaultAnnotation);
2379
3535
  }
2380
3536
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3537
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3538
+ checker,
3539
+ declaration: prop,
3540
+ subjectType: tsType,
3541
+ hostType
3542
+ });
2381
3543
  return {
2382
3544
  kind: "field",
2383
3545
  name,
3546
+ ...metadata !== void 0 && { metadata },
2384
3547
  type,
2385
3548
  required: !optional,
2386
3549
  constraints,
@@ -2388,7 +3551,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting, diagnosti
2388
3551
  provenance
2389
3552
  };
2390
3553
  }
2391
- function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, extensionRegistry) {
3554
+ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visiting, diagnostics, hostType, metadataPolicy, extensionRegistry) {
2392
3555
  if (!ts3.isIdentifier(prop.name)) {
2393
3556
  return null;
2394
3557
  }
@@ -2403,6 +3566,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2403
3566
  typeRegistry,
2404
3567
  visiting,
2405
3568
  prop,
3569
+ metadataPolicy,
2406
3570
  extensionRegistry,
2407
3571
  diagnostics
2408
3572
  );
@@ -2422,9 +3586,16 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
2422
3586
  let annotations = [];
2423
3587
  annotations.push(...docResult.annotations);
2424
3588
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
3589
+ const metadata = resolveNodeMetadata(metadataPolicy, "field", name, prop, {
3590
+ checker,
3591
+ declaration: prop,
3592
+ subjectType: tsType,
3593
+ hostType
3594
+ });
2425
3595
  return {
2426
3596
  kind: "field",
2427
3597
  name,
3598
+ ...metadata !== void 0 && { metadata },
2428
3599
  type,
2429
3600
  required: !optional,
2430
3601
  constraints,
@@ -2549,7 +3720,7 @@ function getTypeNodeRegistrationName(typeNode) {
2549
3720
  }
2550
3721
  return null;
2551
3722
  }
2552
- function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3723
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2553
3724
  const customType = resolveRegisteredCustomType(sourceNode, extensionRegistry, checker);
2554
3725
  if (customType) {
2555
3726
  return customType;
@@ -2561,6 +3732,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2561
3732
  typeRegistry,
2562
3733
  visiting,
2563
3734
  sourceNode,
3735
+ metadataPolicy,
2564
3736
  extensionRegistry,
2565
3737
  diagnostics
2566
3738
  );
@@ -2605,6 +3777,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2605
3777
  typeRegistry,
2606
3778
  visiting,
2607
3779
  sourceNode,
3780
+ metadataPolicy,
2608
3781
  extensionRegistry,
2609
3782
  diagnostics
2610
3783
  );
@@ -2617,6 +3790,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2617
3790
  typeRegistry,
2618
3791
  visiting,
2619
3792
  sourceNode,
3793
+ metadataPolicy,
2620
3794
  extensionRegistry,
2621
3795
  diagnostics
2622
3796
  );
@@ -2628,13 +3802,15 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode
2628
3802
  file,
2629
3803
  typeRegistry,
2630
3804
  visiting,
3805
+ sourceNode,
3806
+ metadataPolicy,
2631
3807
  extensionRegistry,
2632
3808
  diagnostics
2633
3809
  );
2634
3810
  }
2635
3811
  return { kind: "primitive", primitiveKind: "string" };
2636
3812
  }
2637
- function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3813
+ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2638
3814
  if (!(type.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null))) {
2639
3815
  return null;
2640
3816
  }
@@ -2654,14 +3830,21 @@ function tryResolveNamedPrimitiveAlias(type, checker, file, typeRegistry, visiti
2654
3830
  file,
2655
3831
  makeParseOptions(extensionRegistry)
2656
3832
  );
3833
+ const metadata = resolveNodeMetadata(metadataPolicy, "type", aliasName, aliasDecl, {
3834
+ checker,
3835
+ declaration: aliasDecl,
3836
+ subjectType: aliasType
3837
+ });
2657
3838
  typeRegistry[aliasName] = {
2658
3839
  name: aliasName,
3840
+ ...metadata !== void 0 && { metadata },
2659
3841
  type: resolveAliasedPrimitiveTarget(
2660
3842
  aliasType,
2661
3843
  checker,
2662
3844
  file,
2663
3845
  typeRegistry,
2664
3846
  visiting,
3847
+ metadataPolicy,
2665
3848
  extensionRegistry,
2666
3849
  diagnostics
2667
3850
  ),
@@ -2690,7 +3873,7 @@ function shouldEmitPrimitiveAliasDefinition(typeNode, checker) {
2690
3873
  const resolved = checker.getTypeFromTypeNode(aliasDecl.type);
2691
3874
  return !!(resolved.flags & (ts3.TypeFlags.String | ts3.TypeFlags.Number | ts3.TypeFlags.BigInt | ts3.TypeFlags.BigIntLiteral | ts3.TypeFlags.Boolean | ts3.TypeFlags.Null));
2692
3875
  }
2693
- function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
3876
+ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2694
3877
  const nestedAliasDecl = type.aliasSymbol?.declarations?.find(ts3.isTypeAliasDeclaration);
2695
3878
  if (nestedAliasDecl !== void 0) {
2696
3879
  return resolveAliasedPrimitiveTarget(
@@ -2699,6 +3882,7 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2699
3882
  file,
2700
3883
  typeRegistry,
2701
3884
  visiting,
3885
+ metadataPolicy,
2702
3886
  extensionRegistry,
2703
3887
  diagnostics
2704
3888
  );
@@ -2710,11 +3894,12 @@ function resolveAliasedPrimitiveTarget(type, checker, file, typeRegistry, visiti
2710
3894
  typeRegistry,
2711
3895
  visiting,
2712
3896
  void 0,
3897
+ metadataPolicy,
2713
3898
  extensionRegistry,
2714
3899
  diagnostics
2715
3900
  );
2716
3901
  }
2717
- function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
3902
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2718
3903
  const typeName = getNamedTypeName(type);
2719
3904
  const namedDecl = getNamedTypeDeclaration(type);
2720
3905
  if (typeName && typeName in typeRegistry) {
@@ -2749,8 +3934,14 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2749
3934
  return result;
2750
3935
  }
2751
3936
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
3937
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", typeName, namedDecl, {
3938
+ checker,
3939
+ declaration: namedDecl,
3940
+ subjectType: type
3941
+ }) : void 0;
2752
3942
  typeRegistry[typeName] = {
2753
3943
  name: typeName,
3944
+ ...metadata !== void 0 && { metadata },
2754
3945
  type: result,
2755
3946
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2756
3947
  provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
@@ -2804,6 +3995,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2804
3995
  typeRegistry,
2805
3996
  visiting,
2806
3997
  nonNullMembers[0].sourceNode ?? sourceNode,
3998
+ metadataPolicy,
2807
3999
  extensionRegistry,
2808
4000
  diagnostics
2809
4001
  );
@@ -2821,6 +4013,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2821
4013
  typeRegistry,
2822
4014
  visiting,
2823
4015
  memberSourceNode ?? sourceNode,
4016
+ metadataPolicy,
2824
4017
  extensionRegistry,
2825
4018
  diagnostics
2826
4019
  )
@@ -2830,7 +4023,7 @@ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNod
2830
4023
  }
2831
4024
  return registerNamed({ kind: "union", members });
2832
4025
  }
2833
- function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, extensionRegistry, diagnostics) {
4026
+ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2834
4027
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
2835
4028
  const elementType = typeArgs?.[0];
2836
4029
  const elementSourceNode = extractArrayElementTypeNode(sourceNode, checker);
@@ -2841,12 +4034,13 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting, sourceNod
2841
4034
  typeRegistry,
2842
4035
  visiting,
2843
4036
  elementSourceNode,
4037
+ metadataPolicy,
2844
4038
  extensionRegistry,
2845
4039
  diagnostics
2846
4040
  ) : { kind: "primitive", primitiveKind: "string" };
2847
4041
  return { kind: "array", items };
2848
4042
  }
2849
- function tryResolveRecordType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
4043
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
2850
4044
  if (type.getProperties().length > 0) {
2851
4045
  return null;
2852
4046
  }
@@ -2861,6 +4055,7 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting, exten
2861
4055
  typeRegistry,
2862
4056
  visiting,
2863
4057
  void 0,
4058
+ metadataPolicy,
2864
4059
  extensionRegistry,
2865
4060
  diagnostics
2866
4061
  );
@@ -2891,35 +4086,76 @@ function typeNodeContainsReference(type, targetName) {
2891
4086
  }
2892
4087
  }
2893
4088
  }
2894
- function resolveObjectType(type, checker, file, typeRegistry, visiting, extensionRegistry, diagnostics) {
4089
+ function shouldEmitResolvedObjectProperty(property, declaration) {
4090
+ if (property.name.startsWith("__@")) {
4091
+ return false;
4092
+ }
4093
+ if (declaration !== void 0 && "name" in declaration && declaration.name !== void 0) {
4094
+ const name = declaration.name;
4095
+ if (ts3.isComputedPropertyName(name) || ts3.isPrivateIdentifier(name)) {
4096
+ return false;
4097
+ }
4098
+ if (!ts3.isIdentifier(name) && !ts3.isStringLiteral(name) && !ts3.isNumericLiteral(name)) {
4099
+ return false;
4100
+ }
4101
+ }
4102
+ return true;
4103
+ }
4104
+ function resolveObjectType(type, checker, file, typeRegistry, visiting, sourceNode, metadataPolicy = normalizeMetadataPolicy(void 0), extensionRegistry, diagnostics) {
4105
+ const collectedDiagnostics = diagnostics ?? [];
2895
4106
  const typeName = getNamedTypeName(type);
2896
4107
  const namedTypeName = typeName ?? void 0;
2897
4108
  const namedDecl = getNamedTypeDeclaration(type);
2898
- const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
4109
+ const referenceTypeArguments = extractReferenceTypeArguments(
4110
+ type,
4111
+ checker,
4112
+ file,
4113
+ typeRegistry,
4114
+ visiting,
4115
+ sourceNode,
4116
+ metadataPolicy,
4117
+ extensionRegistry,
4118
+ collectedDiagnostics
4119
+ );
4120
+ const instantiatedTypeName = namedTypeName !== void 0 && referenceTypeArguments.length > 0 ? buildInstantiatedReferenceName(
4121
+ namedTypeName,
4122
+ referenceTypeArguments.map((argument) => argument.tsType),
4123
+ checker
4124
+ ) : void 0;
4125
+ const registryTypeName = instantiatedTypeName ?? namedTypeName;
4126
+ const shouldRegisterNamedType = registryTypeName !== void 0 && !(registryTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2899
4127
  const clearNamedTypeRegistration = () => {
2900
- if (namedTypeName === void 0 || !shouldRegisterNamedType) {
4128
+ if (registryTypeName === void 0 || !shouldRegisterNamedType) {
2901
4129
  return;
2902
4130
  }
2903
- Reflect.deleteProperty(typeRegistry, namedTypeName);
4131
+ Reflect.deleteProperty(typeRegistry, registryTypeName);
2904
4132
  };
2905
4133
  if (visiting.has(type)) {
2906
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2907
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4134
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4135
+ return {
4136
+ kind: "reference",
4137
+ name: registryTypeName,
4138
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4139
+ };
2908
4140
  }
2909
4141
  return { kind: "object", properties: [], additionalProperties: false };
2910
4142
  }
2911
- if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2912
- typeRegistry[namedTypeName] = {
2913
- name: namedTypeName,
4143
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[registryTypeName]) {
4144
+ typeRegistry[registryTypeName] = {
4145
+ name: registryTypeName,
2914
4146
  type: RESOLVING_TYPE_PLACEHOLDER,
2915
4147
  provenance: provenanceForDeclaration(namedDecl, file)
2916
4148
  };
2917
4149
  }
2918
4150
  visiting.add(type);
2919
- if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2920
- if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
4151
+ if (registryTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[registryTypeName]?.type !== void 0) {
4152
+ if (typeRegistry[registryTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2921
4153
  visiting.delete(type);
2922
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4154
+ return {
4155
+ kind: "reference",
4156
+ name: registryTypeName,
4157
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4158
+ };
2923
4159
  }
2924
4160
  }
2925
4161
  const recordNode = tryResolveRecordType(
@@ -2928,25 +4164,36 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2928
4164
  file,
2929
4165
  typeRegistry,
2930
4166
  visiting,
4167
+ metadataPolicy,
2931
4168
  extensionRegistry,
2932
- diagnostics
4169
+ collectedDiagnostics
2933
4170
  );
2934
4171
  if (recordNode) {
2935
4172
  visiting.delete(type);
2936
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2937
- const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
4173
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
4174
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, registryTypeName);
2938
4175
  if (!isRecursiveRecord) {
2939
4176
  clearNamedTypeRegistration();
2940
4177
  return recordNode;
2941
4178
  }
2942
4179
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2943
- typeRegistry[namedTypeName] = {
2944
- name: namedTypeName,
4180
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4181
+ checker,
4182
+ declaration: namedDecl,
4183
+ subjectType: type
4184
+ }) : void 0;
4185
+ typeRegistry[registryTypeName] = {
4186
+ name: registryTypeName,
4187
+ ...metadata !== void 0 && { metadata },
2945
4188
  type: recordNode,
2946
4189
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
2947
4190
  provenance: provenanceForDeclaration(namedDecl, file)
2948
4191
  };
2949
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4192
+ return {
4193
+ kind: "reference",
4194
+ name: registryTypeName,
4195
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4196
+ };
2950
4197
  }
2951
4198
  return recordNode;
2952
4199
  }
@@ -2957,12 +4204,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2957
4204
  file,
2958
4205
  typeRegistry,
2959
4206
  visiting,
2960
- diagnostics ?? [],
4207
+ metadataPolicy,
4208
+ collectedDiagnostics,
2961
4209
  extensionRegistry
2962
4210
  );
2963
4211
  for (const prop of type.getProperties()) {
2964
4212
  const declaration = prop.valueDeclaration ?? prop.declarations?.[0];
2965
4213
  if (!declaration) continue;
4214
+ if (!shouldEmitResolvedObjectProperty(prop, declaration)) continue;
2966
4215
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
2967
4216
  const optional = !!(prop.flags & ts3.SymbolFlags.Optional);
2968
4217
  const propTypeNode = resolveTypeNode(
@@ -2972,12 +4221,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2972
4221
  typeRegistry,
2973
4222
  visiting,
2974
4223
  declaration,
4224
+ metadataPolicy,
2975
4225
  extensionRegistry,
2976
- diagnostics
4226
+ collectedDiagnostics
2977
4227
  );
2978
4228
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
2979
4229
  properties.push({
2980
4230
  name: prop.name,
4231
+ ...fieldNodeInfo?.metadata !== void 0 && { metadata: fieldNodeInfo.metadata },
2981
4232
  type: propTypeNode,
2982
4233
  optional,
2983
4234
  constraints: fieldNodeInfo?.constraints ?? [],
@@ -2988,22 +4239,40 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting, extensio
2988
4239
  visiting.delete(type);
2989
4240
  const objectNode = {
2990
4241
  kind: "object",
2991
- properties,
4242
+ properties: namedDecl !== void 0 && (ts3.isClassDeclaration(namedDecl) || ts3.isInterfaceDeclaration(namedDecl) || ts3.isTypeAliasDeclaration(namedDecl)) ? applyDiscriminatorToObjectProperties(
4243
+ properties,
4244
+ namedDecl,
4245
+ type,
4246
+ checker,
4247
+ file,
4248
+ collectedDiagnostics,
4249
+ metadataPolicy
4250
+ ) : properties,
2992
4251
  additionalProperties: true
2993
4252
  };
2994
- if (namedTypeName !== void 0 && shouldRegisterNamedType) {
4253
+ if (registryTypeName !== void 0 && shouldRegisterNamedType) {
2995
4254
  const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file, makeParseOptions(extensionRegistry)) : void 0;
2996
- typeRegistry[namedTypeName] = {
2997
- name: namedTypeName,
4255
+ const metadata = namedDecl !== void 0 ? resolveNodeMetadata(metadataPolicy, "type", registryTypeName, namedDecl, {
4256
+ checker,
4257
+ declaration: namedDecl,
4258
+ subjectType: type
4259
+ }) : void 0;
4260
+ typeRegistry[registryTypeName] = {
4261
+ name: registryTypeName,
4262
+ ...metadata !== void 0 && { metadata },
2998
4263
  type: objectNode,
2999
4264
  ...annotations !== void 0 && annotations.length > 0 && { annotations },
3000
4265
  provenance: provenanceForDeclaration(namedDecl, file)
3001
4266
  };
3002
- return { kind: "reference", name: namedTypeName, typeArguments: [] };
4267
+ return {
4268
+ kind: "reference",
4269
+ name: registryTypeName,
4270
+ typeArguments: referenceTypeArguments.map((argument) => argument.typeNode)
4271
+ };
3003
4272
  }
3004
4273
  return objectNode;
3005
4274
  }
3006
- function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, diagnostics, extensionRegistry) {
4275
+ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting, metadataPolicy, diagnostics, extensionRegistry) {
3007
4276
  const symbols = [type.getSymbol(), type.aliasSymbol].filter(
3008
4277
  (s) => s?.declarations != null && s.declarations.length > 0
3009
4278
  );
@@ -3024,10 +4293,12 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3024
4293
  visiting,
3025
4294
  diagnostics,
3026
4295
  hostType,
4296
+ metadataPolicy,
3027
4297
  extensionRegistry
3028
4298
  );
3029
4299
  if (fieldNode) {
3030
4300
  map.set(fieldNode.name, {
4301
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3031
4302
  constraints: [...fieldNode.constraints],
3032
4303
  annotations: [...fieldNode.annotations],
3033
4304
  provenance: fieldNode.provenance
@@ -3045,6 +4316,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3045
4316
  file,
3046
4317
  typeRegistry,
3047
4318
  visiting,
4319
+ metadataPolicy,
3048
4320
  checker.getTypeAtLocation(interfaceDecl),
3049
4321
  diagnostics,
3050
4322
  extensionRegistry
@@ -3058,6 +4330,7 @@ function getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visitin
3058
4330
  file,
3059
4331
  typeRegistry,
3060
4332
  visiting,
4333
+ metadataPolicy,
3061
4334
  checker.getTypeAtLocation(typeAliasDecl),
3062
4335
  diagnostics,
3063
4336
  extensionRegistry
@@ -3109,7 +4382,7 @@ function isNullishTypeNode(typeNode) {
3109
4382
  }
3110
4383
  return ts3.isLiteralTypeNode(typeNode) && (typeNode.literal.kind === ts3.SyntaxKind.NullKeyword || typeNode.literal.kind === ts3.SyntaxKind.UndefinedKeyword);
3111
4384
  }
3112
- function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, hostType, diagnostics, extensionRegistry) {
4385
+ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, metadataPolicy, hostType, diagnostics, extensionRegistry) {
3113
4386
  const map = /* @__PURE__ */ new Map();
3114
4387
  for (const member of members) {
3115
4388
  if (ts3.isPropertySignature(member)) {
@@ -3121,10 +4394,12 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting, h
3121
4394
  visiting,
3122
4395
  diagnostics,
3123
4396
  hostType,
4397
+ metadataPolicy,
3124
4398
  extensionRegistry
3125
4399
  );
3126
4400
  if (fieldNode) {
3127
4401
  map.set(fieldNode.name, {
4402
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
3128
4403
  constraints: [...fieldNode.constraints],
3129
4404
  annotations: [...fieldNode.annotations],
3130
4405
  provenance: fieldNode.provenance
@@ -3154,6 +4429,7 @@ function extractTypeAliasConstraintNodes(typeNode, checker, file, extensionRegis
3154
4429
  {},
3155
4430
  /* @__PURE__ */ new Set(),
3156
4431
  aliasDecl.type,
4432
+ void 0,
3157
4433
  extensionRegistry
3158
4434
  );
3159
4435
  const constraints = extractJSDocConstraintNodes(
@@ -3263,6 +4539,7 @@ var init_class_analyzer = __esm({
3263
4539
  "use strict";
3264
4540
  init_jsdoc_constraints();
3265
4541
  init_tsdoc_parser();
4542
+ init_metadata();
3266
4543
  RESOLVING_TYPE_PLACEHOLDER = {
3267
4544
  kind: "object",
3268
4545
  properties: [],
@@ -3355,19 +4632,37 @@ function findInterfaceByName(sourceFile, interfaceName) {
3355
4632
  function findTypeAliasByName(sourceFile, aliasName) {
3356
4633
  return findNodeByName(sourceFile, aliasName, ts4.isTypeAliasDeclaration, (n) => n.name.text);
3357
4634
  }
3358
- function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry) {
4635
+ function analyzeNamedTypeToIR(filePath, typeName, extensionRegistry, metadataPolicy) {
3359
4636
  const ctx = createProgramContext(filePath);
3360
- return analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry);
4637
+ return analyzeNamedTypeToIRFromProgramContext(
4638
+ ctx,
4639
+ filePath,
4640
+ typeName,
4641
+ extensionRegistry,
4642
+ metadataPolicy
4643
+ );
3361
4644
  }
3362
- function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry) {
4645
+ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensionRegistry, metadataPolicy) {
3363
4646
  const analysisFilePath = path.resolve(filePath);
3364
4647
  const classDecl = findClassByName(ctx.sourceFile, typeName);
3365
4648
  if (classDecl !== null) {
3366
- return analyzeClassToIR(classDecl, ctx.checker, analysisFilePath, extensionRegistry);
4649
+ return analyzeClassToIR(
4650
+ classDecl,
4651
+ ctx.checker,
4652
+ analysisFilePath,
4653
+ extensionRegistry,
4654
+ metadataPolicy
4655
+ );
3367
4656
  }
3368
4657
  const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
3369
4658
  if (interfaceDecl !== null) {
3370
- return analyzeInterfaceToIR(interfaceDecl, ctx.checker, analysisFilePath, extensionRegistry);
4659
+ return analyzeInterfaceToIR(
4660
+ interfaceDecl,
4661
+ ctx.checker,
4662
+ analysisFilePath,
4663
+ extensionRegistry,
4664
+ metadataPolicy
4665
+ );
3371
4666
  }
3372
4667
  const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
3373
4668
  if (typeAlias !== null) {
@@ -3375,7 +4670,8 @@ function analyzeNamedTypeToIRFromProgramContext(ctx, filePath, typeName, extensi
3375
4670
  typeAlias,
3376
4671
  ctx.checker,
3377
4672
  analysisFilePath,
3378
- extensionRegistry
4673
+ extensionRegistry,
4674
+ metadataPolicy
3379
4675
  );
3380
4676
  if (result.ok) {
3381
4677
  return result.analysis;
@@ -3490,7 +4786,11 @@ function generateClassSchemas(analysis, source, options) {
3490
4786
  if (errorDiagnostics !== void 0 && errorDiagnostics.length > 0) {
3491
4787
  throw new Error(formatValidationError(errorDiagnostics));
3492
4788
  }
3493
- const ir = canonicalizeTSDoc(analysis, source);
4789
+ const ir = canonicalizeTSDoc(
4790
+ analysis,
4791
+ source,
4792
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
4793
+ );
3494
4794
  const validationResult = validateIR(ir, {
3495
4795
  ...options?.extensionRegistry !== void 0 && {
3496
4796
  extensionRegistry: options.extensionRegistry
@@ -3527,13 +4827,15 @@ function generateSchemasFromClass(options) {
3527
4827
  classDecl,
3528
4828
  ctx.checker,
3529
4829
  options.filePath,
3530
- options.extensionRegistry
4830
+ options.extensionRegistry,
4831
+ options.metadata
3531
4832
  );
3532
4833
  return generateClassSchemas(
3533
4834
  analysis,
3534
4835
  { file: options.filePath },
3535
4836
  {
3536
4837
  extensionRegistry: options.extensionRegistry,
4838
+ metadata: options.metadata,
3537
4839
  vendorPrefix: options.vendorPrefix
3538
4840
  }
3539
4841
  );
@@ -3551,13 +4853,15 @@ function generateSchemasFromProgram(options) {
3551
4853
  ctx,
3552
4854
  options.filePath,
3553
4855
  options.typeName,
3554
- options.extensionRegistry
4856
+ options.extensionRegistry,
4857
+ options.metadata
3555
4858
  );
3556
4859
  return generateClassSchemas(
3557
4860
  analysis,
3558
4861
  { file: options.filePath },
3559
4862
  {
3560
4863
  extensionRegistry: options.extensionRegistry,
4864
+ metadata: options.metadata,
3561
4865
  vendorPrefix: options.vendorPrefix
3562
4866
  }
3563
4867
  );
@@ -3577,16 +4881,28 @@ var init_class_schema = __esm({
3577
4881
  // src/generators/mixed-authoring.ts
3578
4882
  function buildMixedAuthoringSchemas(options) {
3579
4883
  const { filePath, typeName, overlays, ...schemaOptions } = options;
3580
- const analysis = analyzeNamedTypeToIR(filePath, typeName, schemaOptions.extensionRegistry);
3581
- const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
3582
- const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
4884
+ const analysis = analyzeNamedTypeToIR(
4885
+ filePath,
4886
+ typeName,
4887
+ schemaOptions.extensionRegistry,
4888
+ schemaOptions.metadata
4889
+ );
4890
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays, schemaOptions.metadata);
4891
+ const ir = canonicalizeTSDoc(
4892
+ composedAnalysis,
4893
+ { file: filePath },
4894
+ schemaOptions.metadata !== void 0 ? { metadata: schemaOptions.metadata } : void 0
4895
+ );
3583
4896
  return {
3584
4897
  jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
3585
4898
  uiSchema: generateUiSchemaFromIR(ir)
3586
4899
  };
3587
4900
  }
3588
- function composeAnalysisWithOverlays(analysis, overlays) {
3589
- const overlayIR = canonicalizeChainDSL(overlays);
4901
+ function composeAnalysisWithOverlays(analysis, overlays, metadata) {
4902
+ const overlayIR = canonicalizeChainDSL(
4903
+ overlays,
4904
+ metadata !== void 0 ? { metadata } : void 0
4905
+ );
3590
4906
  const overlayFields = collectOverlayFields(overlayIR.elements);
3591
4907
  if (overlayFields.length === 0) {
3592
4908
  return analysis;
@@ -3642,8 +4958,10 @@ function collectOverlayFields(elements) {
3642
4958
  }
3643
4959
  function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
3644
4960
  assertSupportedOverlayField(baseField, overlayField);
4961
+ const metadata = mergeResolvedMetadata(baseField.metadata, overlayField.metadata);
3645
4962
  return {
3646
4963
  ...baseField,
4964
+ ...metadata !== void 0 && { metadata },
3647
4965
  type: mergeFieldType(baseField, overlayField, typeRegistry),
3648
4966
  annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
3649
4967
  };
@@ -3756,6 +5074,7 @@ var init_mixed_authoring = __esm({
3756
5074
  init_ir_generator2();
3757
5075
  init_canonicalize();
3758
5076
  init_program();
5077
+ init_metadata();
3759
5078
  }
3760
5079
  });
3761
5080
 
@@ -3779,12 +5098,12 @@ import * as path2 from "path";
3779
5098
  function buildFormSchemas(form, options) {
3780
5099
  return {
3781
5100
  jsonSchema: generateJsonSchema(form, options),
3782
- uiSchema: generateUiSchema(form)
5101
+ uiSchema: generateUiSchema(form, options)
3783
5102
  };
3784
5103
  }
3785
5104
  function writeSchemas(form, options) {
3786
- const { outDir, name = "schema", indent = 2, vendorPrefix } = options;
3787
- const buildOptions = vendorPrefix === void 0 ? void 0 : { vendorPrefix };
5105
+ const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
5106
+ const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
3788
5107
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
3789
5108
  if (!fs.existsSync(outDir)) {
3790
5109
  fs.mkdirSync(outDir, { recursive: true });