@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/browser.cjs CHANGED
@@ -50,6 +50,169 @@ module.exports = __toCommonJS(browser_exports);
50
50
 
51
51
  // src/canonicalize/chain-dsl-canonicalizer.ts
52
52
  var import_internals = require("@formspec/core/internals");
53
+
54
+ // src/metadata/policy.ts
55
+ var NOOP_INFLECT = () => "";
56
+ function normalizePluralization(input) {
57
+ if (input?.mode === "infer-if-missing") {
58
+ return {
59
+ mode: "infer-if-missing",
60
+ infer: () => "",
61
+ inflect: input.inflect
62
+ };
63
+ }
64
+ if (input?.mode === "require-explicit") {
65
+ return {
66
+ mode: "require-explicit",
67
+ infer: () => "",
68
+ inflect: NOOP_INFLECT
69
+ };
70
+ }
71
+ return {
72
+ mode: "disabled",
73
+ infer: () => "",
74
+ inflect: NOOP_INFLECT
75
+ };
76
+ }
77
+ function normalizeScalarPolicy(input) {
78
+ if (input?.mode === "infer-if-missing") {
79
+ return {
80
+ mode: "infer-if-missing",
81
+ infer: input.infer,
82
+ pluralization: normalizePluralization(input.pluralization)
83
+ };
84
+ }
85
+ if (input?.mode === "require-explicit") {
86
+ return {
87
+ mode: "require-explicit",
88
+ infer: () => "",
89
+ pluralization: normalizePluralization(input.pluralization)
90
+ };
91
+ }
92
+ return {
93
+ mode: "disabled",
94
+ infer: () => "",
95
+ pluralization: normalizePluralization(input?.pluralization)
96
+ };
97
+ }
98
+ function normalizeDeclarationPolicy(input) {
99
+ return {
100
+ apiName: normalizeScalarPolicy(input?.apiName),
101
+ displayName: normalizeScalarPolicy(input?.displayName)
102
+ };
103
+ }
104
+ function normalizeMetadataPolicy(input) {
105
+ return {
106
+ type: normalizeDeclarationPolicy(input?.type),
107
+ field: normalizeDeclarationPolicy(input?.field),
108
+ method: normalizeDeclarationPolicy(input?.method)
109
+ };
110
+ }
111
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
112
+ return policy[declarationKind];
113
+ }
114
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
115
+ return {
116
+ surface,
117
+ declarationKind,
118
+ logicalName,
119
+ ...buildContext !== void 0 && { buildContext }
120
+ };
121
+ }
122
+
123
+ // src/metadata/resolve.ts
124
+ function toExplicitScalar(value) {
125
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
126
+ }
127
+ function toExplicitResolvedMetadata(explicit) {
128
+ if (explicit === void 0) {
129
+ return void 0;
130
+ }
131
+ const apiName = toExplicitScalar(explicit.apiName);
132
+ const displayName = toExplicitScalar(explicit.displayName);
133
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
134
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
135
+ const metadata = {
136
+ ...apiName !== void 0 && { apiName },
137
+ ...displayName !== void 0 && { displayName },
138
+ ...apiNamePlural !== void 0 && { apiNamePlural },
139
+ ...displayNamePlural !== void 0 && { displayNamePlural }
140
+ };
141
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
142
+ }
143
+ function resolveScalar(current, policy, context, metadataLabel) {
144
+ if (current !== void 0) {
145
+ return current;
146
+ }
147
+ if (policy.mode === "require-explicit") {
148
+ throw new Error(
149
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
150
+ );
151
+ }
152
+ if (policy.mode !== "infer-if-missing") {
153
+ return void 0;
154
+ }
155
+ const inferredValue = policy.infer(context);
156
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
157
+ }
158
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
159
+ if (current !== void 0) {
160
+ return current;
161
+ }
162
+ if (policy.mode === "require-explicit") {
163
+ throw new Error(
164
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
165
+ );
166
+ }
167
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
168
+ return void 0;
169
+ }
170
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
171
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
172
+ }
173
+ function resolveResolvedMetadata(current, policy, context) {
174
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
175
+ const displayName = resolveScalar(
176
+ current?.displayName,
177
+ policy.displayName,
178
+ context,
179
+ "displayName"
180
+ );
181
+ const apiNamePlural = resolvePlural(
182
+ current?.apiNamePlural,
183
+ apiName,
184
+ policy.apiName.pluralization,
185
+ context,
186
+ "apiNamePlural"
187
+ );
188
+ const displayNamePlural = resolvePlural(
189
+ current?.displayNamePlural,
190
+ displayName,
191
+ policy.displayName.pluralization,
192
+ context,
193
+ "displayNamePlural"
194
+ );
195
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
196
+ return void 0;
197
+ }
198
+ return {
199
+ ...apiName !== void 0 && { apiName },
200
+ ...displayName !== void 0 && { displayName },
201
+ ...apiNamePlural !== void 0 && { apiNamePlural },
202
+ ...displayNamePlural !== void 0 && { displayNamePlural }
203
+ };
204
+ }
205
+ function resolveMetadata(explicit, policy, context) {
206
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
207
+ }
208
+ function getSerializedName(logicalName, metadata) {
209
+ return metadata?.apiName?.value ?? logicalName;
210
+ }
211
+ function getDisplayName(metadata) {
212
+ return metadata?.displayName?.value;
213
+ }
214
+
215
+ // src/canonicalize/chain-dsl-canonicalizer.ts
53
216
  var CHAIN_DSL_PROVENANCE = {
54
217
  surface: "chain-dsl",
55
218
  file: "",
@@ -65,57 +228,60 @@ function isConditional(el) {
65
228
  function isField(el) {
66
229
  return el._type === "field";
67
230
  }
68
- function canonicalizeChainDSL(form) {
231
+ function canonicalizeChainDSL(form, options) {
232
+ const metadataPolicy = normalizeMetadataPolicy(
233
+ options?.metadata ?? (0, import_internals._getFormSpecMetadataPolicy)(form)
234
+ );
69
235
  return {
70
236
  kind: "form-ir",
71
237
  irVersion: import_internals.IR_VERSION,
72
- elements: canonicalizeElements(form.elements),
238
+ elements: canonicalizeElements(form.elements, metadataPolicy),
73
239
  rootAnnotations: [],
74
240
  typeRegistry: {},
75
241
  provenance: CHAIN_DSL_PROVENANCE
76
242
  };
77
243
  }
78
- function canonicalizeElements(elements) {
79
- return elements.map(canonicalizeElement);
244
+ function canonicalizeElements(elements, metadataPolicy) {
245
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
80
246
  }
81
- function canonicalizeElement(element) {
247
+ function canonicalizeElement(element, metadataPolicy) {
82
248
  if (isField(element)) {
83
- return canonicalizeField(element);
249
+ return canonicalizeField(element, metadataPolicy);
84
250
  }
85
251
  if (isGroup(element)) {
86
- return canonicalizeGroup(element);
252
+ return canonicalizeGroup(element, metadataPolicy);
87
253
  }
88
254
  if (isConditional(element)) {
89
- return canonicalizeConditional(element);
255
+ return canonicalizeConditional(element, metadataPolicy);
90
256
  }
91
257
  const _exhaustive = element;
92
258
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
93
259
  }
94
- function canonicalizeField(field) {
260
+ function canonicalizeField(field, metadataPolicy) {
95
261
  switch (field._field) {
96
262
  case "text":
97
- return canonicalizeTextField(field);
263
+ return canonicalizeTextField(field, metadataPolicy);
98
264
  case "number":
99
- return canonicalizeNumberField(field);
265
+ return canonicalizeNumberField(field, metadataPolicy);
100
266
  case "boolean":
101
- return canonicalizeBooleanField(field);
267
+ return canonicalizeBooleanField(field, metadataPolicy);
102
268
  case "enum":
103
- return canonicalizeStaticEnumField(field);
269
+ return canonicalizeStaticEnumField(field, metadataPolicy);
104
270
  case "dynamic_enum":
105
- return canonicalizeDynamicEnumField(field);
271
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
106
272
  case "dynamic_schema":
107
- return canonicalizeDynamicSchemaField(field);
273
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
108
274
  case "array":
109
- return canonicalizeArrayField(field);
275
+ return canonicalizeArrayField(field, metadataPolicy);
110
276
  case "object":
111
- return canonicalizeObjectField(field);
277
+ return canonicalizeObjectField(field, metadataPolicy);
112
278
  default: {
113
279
  const _exhaustive = field;
114
280
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
115
281
  }
116
282
  }
117
283
  }
118
- function canonicalizeTextField(field) {
284
+ function canonicalizeTextField(field, metadataPolicy) {
119
285
  const type = { kind: "primitive", primitiveKind: "string" };
120
286
  const constraints = [];
121
287
  if (field.minLength !== void 0) {
@@ -147,13 +313,14 @@ function canonicalizeTextField(field) {
147
313
  }
148
314
  return buildFieldNode(
149
315
  field.name,
316
+ resolveFieldMetadata(field.name, field, metadataPolicy),
150
317
  type,
151
318
  field.required,
152
- buildAnnotations(field.label, field.placeholder),
319
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
153
320
  constraints
154
321
  );
155
322
  }
156
- function canonicalizeNumberField(field) {
323
+ function canonicalizeNumberField(field, metadataPolicy) {
157
324
  const type = { kind: "primitive", primitiveKind: "number" };
158
325
  const constraints = [];
159
326
  if (field.min !== void 0) {
@@ -185,17 +352,24 @@ function canonicalizeNumberField(field) {
185
352
  }
186
353
  return buildFieldNode(
187
354
  field.name,
355
+ resolveFieldMetadata(field.name, field, metadataPolicy),
188
356
  type,
189
357
  field.required,
190
- buildAnnotations(field.label),
358
+ buildAnnotations(getExplicitDisplayName(field)),
191
359
  constraints
192
360
  );
193
361
  }
194
- function canonicalizeBooleanField(field) {
362
+ function canonicalizeBooleanField(field, metadataPolicy) {
195
363
  const type = { kind: "primitive", primitiveKind: "boolean" };
196
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
364
+ return buildFieldNode(
365
+ field.name,
366
+ resolveFieldMetadata(field.name, field, metadataPolicy),
367
+ type,
368
+ field.required,
369
+ buildAnnotations(getExplicitDisplayName(field))
370
+ );
197
371
  }
198
- function canonicalizeStaticEnumField(field) {
372
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
199
373
  const members = field.options.map((opt) => {
200
374
  if (typeof opt === "string") {
201
375
  return { value: opt };
@@ -203,28 +377,46 @@ function canonicalizeStaticEnumField(field) {
203
377
  return { value: opt.id, displayName: opt.label };
204
378
  });
205
379
  const type = { kind: "enum", members };
206
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
380
+ return buildFieldNode(
381
+ field.name,
382
+ resolveFieldMetadata(field.name, field, metadataPolicy),
383
+ type,
384
+ field.required,
385
+ buildAnnotations(getExplicitDisplayName(field))
386
+ );
207
387
  }
208
- function canonicalizeDynamicEnumField(field) {
388
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
209
389
  const type = {
210
390
  kind: "dynamic",
211
391
  dynamicKind: "enum",
212
392
  sourceKey: field.source,
213
393
  parameterFields: field.params ? [...field.params] : []
214
394
  };
215
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
395
+ return buildFieldNode(
396
+ field.name,
397
+ resolveFieldMetadata(field.name, field, metadataPolicy),
398
+ type,
399
+ field.required,
400
+ buildAnnotations(getExplicitDisplayName(field))
401
+ );
216
402
  }
217
- function canonicalizeDynamicSchemaField(field) {
403
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
218
404
  const type = {
219
405
  kind: "dynamic",
220
406
  dynamicKind: "schema",
221
407
  sourceKey: field.schemaSource,
222
408
  parameterFields: []
223
409
  };
224
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
410
+ return buildFieldNode(
411
+ field.name,
412
+ resolveFieldMetadata(field.name, field, metadataPolicy),
413
+ type,
414
+ field.required,
415
+ buildAnnotations(getExplicitDisplayName(field))
416
+ );
225
417
  }
226
- function canonicalizeArrayField(field) {
227
- const itemProperties = buildObjectProperties(field.items);
418
+ function canonicalizeArrayField(field, metadataPolicy) {
419
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
228
420
  const itemsType = {
229
421
  kind: "object",
230
422
  properties: itemProperties,
@@ -252,37 +444,44 @@ function canonicalizeArrayField(field) {
252
444
  }
253
445
  return buildFieldNode(
254
446
  field.name,
447
+ resolveFieldMetadata(field.name, field, metadataPolicy),
255
448
  type,
256
449
  field.required,
257
- buildAnnotations(field.label),
450
+ buildAnnotations(getExplicitDisplayName(field)),
258
451
  constraints
259
452
  );
260
453
  }
261
- function canonicalizeObjectField(field) {
262
- const properties = buildObjectProperties(field.properties);
454
+ function canonicalizeObjectField(field, metadataPolicy) {
455
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
263
456
  const type = {
264
457
  kind: "object",
265
458
  properties,
266
459
  additionalProperties: true
267
460
  };
268
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
461
+ return buildFieldNode(
462
+ field.name,
463
+ resolveFieldMetadata(field.name, field, metadataPolicy),
464
+ type,
465
+ field.required,
466
+ buildAnnotations(getExplicitDisplayName(field))
467
+ );
269
468
  }
270
- function canonicalizeGroup(g) {
469
+ function canonicalizeGroup(g, metadataPolicy) {
271
470
  return {
272
471
  kind: "group",
273
472
  label: g.label,
274
- elements: canonicalizeElements(g.elements),
473
+ elements: canonicalizeElements(g.elements, metadataPolicy),
275
474
  provenance: CHAIN_DSL_PROVENANCE
276
475
  };
277
476
  }
278
- function canonicalizeConditional(c) {
477
+ function canonicalizeConditional(c, metadataPolicy) {
279
478
  return {
280
479
  kind: "conditional",
281
480
  fieldName: c.field,
282
481
  // Conditional values from the chain DSL are JSON-serializable primitives
283
482
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
284
483
  value: assertJsonValue(c.value),
285
- elements: canonicalizeElements(c.elements),
484
+ elements: canonicalizeElements(c.elements, metadataPolicy),
286
485
  provenance: CHAIN_DSL_PROVENANCE
287
486
  };
288
487
  }
@@ -302,10 +501,11 @@ function assertJsonValue(v) {
302
501
  }
303
502
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
304
503
  }
305
- function buildFieldNode(name, type, required, annotations, constraints = []) {
504
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
306
505
  return {
307
506
  kind: "field",
308
507
  name,
508
+ ...metadata !== void 0 && { metadata },
309
509
  type,
310
510
  required: required === true,
311
511
  constraints,
@@ -335,13 +535,14 @@ function buildAnnotations(label, placeholder) {
335
535
  }
336
536
  return annotations;
337
537
  }
338
- function buildObjectProperties(elements, insideConditional = false) {
538
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
339
539
  const properties = [];
340
540
  for (const el of elements) {
341
541
  if (isField(el)) {
342
- const fieldNode = canonicalizeField(el);
542
+ const fieldNode = canonicalizeField(el, metadataPolicy);
343
543
  properties.push({
344
544
  name: fieldNode.name,
545
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
345
546
  type: fieldNode.type,
346
547
  // Fields inside a conditional branch are always optional in the
347
548
  // data schema, regardless of their `required` flag — the condition
@@ -352,17 +553,148 @@ function buildObjectProperties(elements, insideConditional = false) {
352
553
  provenance: CHAIN_DSL_PROVENANCE
353
554
  });
354
555
  } else if (isGroup(el)) {
355
- properties.push(...buildObjectProperties(el.elements, insideConditional));
556
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
356
557
  } else if (isConditional(el)) {
357
- properties.push(...buildObjectProperties(el.elements, true));
558
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
358
559
  }
359
560
  }
360
561
  return properties;
361
562
  }
563
+ function getExplicitDisplayName(field) {
564
+ if (field.label !== void 0 && field.displayName !== void 0) {
565
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
566
+ }
567
+ return field.displayName ?? field.label;
568
+ }
569
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
570
+ const displayName = getExplicitDisplayName(field);
571
+ return resolveMetadata(
572
+ {
573
+ ...field.apiName !== void 0 && { apiName: field.apiName },
574
+ ...displayName !== void 0 && { displayName }
575
+ },
576
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
577
+ makeMetadataContext("chain-dsl", "field", logicalName)
578
+ );
579
+ }
362
580
 
363
581
  // src/canonicalize/tsdoc-canonicalizer.ts
364
582
  var import_internals2 = require("@formspec/core/internals");
365
583
 
584
+ // src/metadata/collision-guards.ts
585
+ function assertUniqueSerializedNames(entries, scope) {
586
+ const seen = /* @__PURE__ */ new Map();
587
+ for (const entry of entries) {
588
+ const previous = seen.get(entry.serializedName);
589
+ if (previous !== void 0) {
590
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
591
+ continue;
592
+ }
593
+ throw new Error(
594
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
595
+ );
596
+ }
597
+ seen.set(entry.serializedName, entry);
598
+ }
599
+ }
600
+ function collectFlattenedFields(elements) {
601
+ const fields = [];
602
+ for (const element of elements) {
603
+ switch (element.kind) {
604
+ case "field":
605
+ fields.push(element);
606
+ break;
607
+ case "group":
608
+ case "conditional":
609
+ fields.push(...collectFlattenedFields(element.elements));
610
+ break;
611
+ default: {
612
+ const exhaustive = element;
613
+ void exhaustive;
614
+ }
615
+ }
616
+ }
617
+ return fields;
618
+ }
619
+ function validateObjectProperties(properties, scope) {
620
+ assertUniqueSerializedNames(
621
+ properties.map((property) => ({
622
+ logicalName: property.name,
623
+ serializedName: getSerializedName(property.name, property.metadata),
624
+ category: "object property"
625
+ })),
626
+ scope
627
+ );
628
+ for (const property of properties) {
629
+ validateTypeNode(
630
+ property.type,
631
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
632
+ );
633
+ }
634
+ }
635
+ function validateTypeNode(type, scope) {
636
+ switch (type.kind) {
637
+ case "array":
638
+ validateTypeNode(type.items, `${scope}[]`);
639
+ break;
640
+ case "object":
641
+ validateObjectProperties(type.properties, scope);
642
+ break;
643
+ case "record":
644
+ validateTypeNode(type.valueType, `${scope}.*`);
645
+ break;
646
+ case "union":
647
+ type.members.forEach((member, index) => {
648
+ validateTypeNode(member, `${scope}|${String(index)}`);
649
+ });
650
+ break;
651
+ case "reference":
652
+ case "primitive":
653
+ case "enum":
654
+ case "dynamic":
655
+ case "custom":
656
+ break;
657
+ default: {
658
+ const exhaustive = type;
659
+ void exhaustive;
660
+ }
661
+ }
662
+ }
663
+ function validateTypeDefinitions(typeRegistry) {
664
+ const definitions = Object.values(typeRegistry);
665
+ assertUniqueSerializedNames(
666
+ definitions.map((definition) => ({
667
+ logicalName: definition.name,
668
+ serializedName: getSerializedName(definition.name, definition.metadata),
669
+ category: "type definition"
670
+ })),
671
+ "$defs"
672
+ );
673
+ for (const definition of definitions) {
674
+ validateTypeDefinition(definition);
675
+ }
676
+ }
677
+ function validateTypeDefinition(definition) {
678
+ validateTypeNode(
679
+ definition.type,
680
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
681
+ );
682
+ }
683
+ function assertNoSerializedNameCollisions(ir) {
684
+ assertUniqueSerializedNames(
685
+ collectFlattenedFields(ir.elements).map((field) => ({
686
+ logicalName: field.name,
687
+ serializedName: getSerializedName(field.name, field.metadata),
688
+ category: "field"
689
+ })),
690
+ "form root"
691
+ );
692
+ for (const field of collectFlattenedFields(ir.elements)) {
693
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
694
+ }
695
+ validateTypeDefinitions(ir.typeRegistry);
696
+ }
697
+
366
698
  // src/json-schema/ir-generator.ts
367
699
  function makeContext(options) {
368
700
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -373,19 +705,33 @@ function makeContext(options) {
373
705
  }
374
706
  return {
375
707
  defs: {},
708
+ typeNameMap: {},
709
+ typeRegistry: {},
376
710
  extensionRegistry: options?.extensionRegistry,
377
711
  vendorPrefix
378
712
  };
379
713
  }
380
714
  function generateJsonSchemaFromIR(ir, options) {
381
- const ctx = makeContext(options);
715
+ assertNoSerializedNameCollisions(ir);
716
+ const ctx = {
717
+ ...makeContext(options),
718
+ typeRegistry: ir.typeRegistry,
719
+ typeNameMap: Object.fromEntries(
720
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
721
+ name,
722
+ getSerializedName(name, typeDef.metadata)
723
+ ])
724
+ )
725
+ };
382
726
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
383
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
727
+ const schemaName = ctx.typeNameMap[name] ?? name;
728
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
729
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
384
730
  if (typeDef.constraints && typeDef.constraints.length > 0) {
385
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
731
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
386
732
  }
387
733
  if (typeDef.annotations && typeDef.annotations.length > 0) {
388
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
734
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
389
735
  }
390
736
  }
391
737
  const properties = {};
@@ -398,6 +744,7 @@ function generateJsonSchemaFromIR(ir, options) {
398
744
  properties,
399
745
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
400
746
  };
747
+ applyResolvedMetadata(result, ir.metadata);
401
748
  if (ir.annotations && ir.annotations.length > 0) {
402
749
  applyAnnotations(result, ir.annotations, ctx);
403
750
  }
@@ -410,9 +757,9 @@ function collectFields(elements, properties, required, ctx) {
410
757
  for (const element of elements) {
411
758
  switch (element.kind) {
412
759
  case "field":
413
- properties[element.name] = generateFieldSchema(element, ctx);
760
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
414
761
  if (element.required) {
415
- required.push(element.name);
762
+ required.push(getSerializedName(element.name, element.metadata));
416
763
  }
417
764
  break;
418
765
  case "group":
@@ -456,6 +803,7 @@ function generateFieldSchema(field, ctx) {
456
803
  rootAnnotations.push(annotation);
457
804
  }
458
805
  }
806
+ applyResolvedMetadata(schema, field.metadata);
459
807
  applyAnnotations(schema, rootAnnotations, ctx);
460
808
  if (itemStringSchema !== void 0) {
461
809
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -463,7 +811,7 @@ function generateFieldSchema(field, ctx) {
463
811
  if (pathConstraints.length === 0) {
464
812
  return schema;
465
813
  }
466
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
814
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
467
815
  }
468
816
  function isStringItemConstraint(constraint) {
469
817
  switch (constraint.constraintKind) {
@@ -475,9 +823,11 @@ function isStringItemConstraint(constraint) {
475
823
  return false;
476
824
  }
477
825
  }
478
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
826
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
479
827
  if (schema.type === "array" && schema.items) {
480
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
828
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
829
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
830
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
481
831
  return schema;
482
832
  }
483
833
  const byTarget = /* @__PURE__ */ new Map();
@@ -492,7 +842,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
492
842
  for (const [target, constraints] of byTarget) {
493
843
  const subSchema = {};
494
844
  applyConstraints(subSchema, constraints, ctx);
495
- propertyOverrides[target] = subSchema;
845
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
496
846
  }
497
847
  if (schema.$ref) {
498
848
  const { $ref, ...rest } = schema;
@@ -540,7 +890,7 @@ function generateTypeNode(type, ctx) {
540
890
  case "union":
541
891
  return generateUnionType(type, ctx);
542
892
  case "reference":
543
- return generateReferenceType(type);
893
+ return generateReferenceType(type, ctx);
544
894
  case "dynamic":
545
895
  return generateDynamicType(type);
546
896
  case "custom":
@@ -581,9 +931,10 @@ function generateObjectType(type, ctx) {
581
931
  const properties = {};
582
932
  const required = [];
583
933
  for (const prop of type.properties) {
584
- properties[prop.name] = generatePropertySchema(prop, ctx);
934
+ const propertyName = getSerializedName(prop.name, prop.metadata);
935
+ properties[propertyName] = generatePropertySchema(prop, ctx);
585
936
  if (!prop.optional) {
586
- required.push(prop.name);
937
+ required.push(propertyName);
587
938
  }
588
939
  }
589
940
  const schema = { type: "object", properties };
@@ -604,6 +955,7 @@ function generateRecordType(type, ctx) {
604
955
  function generatePropertySchema(prop, ctx) {
605
956
  const schema = generateTypeNode(prop.type, ctx);
606
957
  applyConstraints(schema, prop.constraints, ctx);
958
+ applyResolvedMetadata(schema, prop.metadata);
607
959
  applyAnnotations(schema, prop.annotations, ctx);
608
960
  return schema;
609
961
  }
@@ -632,8 +984,28 @@ function isNullableUnion(type) {
632
984
  ).length;
633
985
  return nullCount === 1;
634
986
  }
635
- function generateReferenceType(type) {
636
- return { $ref: `#/$defs/${type.name}` };
987
+ function generateReferenceType(type, ctx) {
988
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
989
+ }
990
+ function applyResolvedMetadata(schema, metadata) {
991
+ const displayName = getDisplayName(metadata);
992
+ if (displayName !== void 0) {
993
+ schema.title = displayName;
994
+ }
995
+ }
996
+ function resolveReferencedType(type, ctx) {
997
+ return ctx.typeRegistry[type.name]?.type;
998
+ }
999
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
1000
+ if (typeNode?.kind === "object") {
1001
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
1002
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
1003
+ }
1004
+ if (typeNode?.kind === "reference") {
1005
+ const referencedType = resolveReferencedType(typeNode, ctx);
1006
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
1007
+ }
1008
+ return logicalName;
637
1009
  }
638
1010
  function generateDynamicType(type) {
639
1011
  if (type.dynamicKind === "enum") {
@@ -713,7 +1085,7 @@ function applyAnnotations(schema, annotations, ctx) {
713
1085
  for (const annotation of annotations) {
714
1086
  switch (annotation.annotationKind) {
715
1087
  case "displayName":
716
- schema.title = annotation.value;
1088
+ schema.title ??= annotation.value;
717
1089
  break;
718
1090
  case "description":
719
1091
  schema.description = annotation.value;
@@ -800,7 +1172,10 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
800
1172
 
801
1173
  // src/json-schema/generator.ts
802
1174
  function generateJsonSchema(form, options) {
803
- const ir = canonicalizeChainDSL(form);
1175
+ const ir = canonicalizeChainDSL(
1176
+ form,
1177
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1178
+ );
804
1179
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
805
1180
  return generateJsonSchemaFromIR(ir, internalOptions);
806
1181
  }
@@ -970,13 +1345,21 @@ function combineRules(parentRule, childRule) {
970
1345
  }
971
1346
  };
972
1347
  }
973
- function fieldNodeToControl(field, parentRule) {
974
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1348
+ function getFieldDisplayName(field) {
1349
+ const resolvedDisplayName = getDisplayName(field.metadata);
1350
+ if (resolvedDisplayName !== void 0) {
1351
+ return resolvedDisplayName;
1352
+ }
1353
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1354
+ }
1355
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
975
1356
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1357
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1358
+ const displayName = getFieldDisplayName(field);
976
1359
  const control = {
977
1360
  type: "Control",
978
- scope: fieldToScope(field.name),
979
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1361
+ scope: fieldToScope(serializedName),
1362
+ ...displayName !== void 0 && { label: displayName },
980
1363
  ...placeholderAnnotation !== void 0 && {
981
1364
  options: { placeholder: placeholderAnnotation.value }
982
1365
  },
@@ -984,30 +1367,30 @@ function fieldNodeToControl(field, parentRule) {
984
1367
  };
985
1368
  return control;
986
1369
  }
987
- function groupNodeToLayout(group, parentRule) {
1370
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
988
1371
  return {
989
1372
  type: "Group",
990
1373
  label: group.label,
991
- elements: irElementsToUiSchema(group.elements, parentRule),
1374
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
992
1375
  ...parentRule !== void 0 && { rule: parentRule }
993
1376
  };
994
1377
  }
995
- function irElementsToUiSchema(elements, parentRule) {
1378
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
996
1379
  const result = [];
997
1380
  for (const element of elements) {
998
1381
  switch (element.kind) {
999
1382
  case "field": {
1000
- result.push(fieldNodeToControl(element, parentRule));
1383
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
1001
1384
  break;
1002
1385
  }
1003
1386
  case "group": {
1004
- result.push(groupNodeToLayout(element, parentRule));
1387
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
1005
1388
  break;
1006
1389
  }
1007
1390
  case "conditional": {
1008
- const newRule = createShowRule(element.fieldName, element.value);
1391
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
1009
1392
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
1010
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1393
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
1011
1394
  result.push(...childElements);
1012
1395
  break;
1013
1396
  }
@@ -1021,16 +1404,42 @@ function irElementsToUiSchema(elements, parentRule) {
1021
1404
  return result;
1022
1405
  }
1023
1406
  function generateUiSchemaFromIR(ir) {
1407
+ assertNoSerializedNameCollisions(ir);
1408
+ const fieldNameMap = collectFieldNameMap(ir.elements);
1024
1409
  const result = {
1025
1410
  type: "VerticalLayout",
1026
- elements: irElementsToUiSchema(ir.elements)
1411
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
1027
1412
  };
1028
1413
  return parseOrThrow(uiSchema, result, "UI Schema");
1029
1414
  }
1415
+ function collectFieldNameMap(elements) {
1416
+ const map = /* @__PURE__ */ new Map();
1417
+ for (const element of elements) {
1418
+ switch (element.kind) {
1419
+ case "field":
1420
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1421
+ break;
1422
+ case "group":
1423
+ case "conditional":
1424
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1425
+ map.set(key, value);
1426
+ }
1427
+ break;
1428
+ default: {
1429
+ const _exhaustive = element;
1430
+ void _exhaustive;
1431
+ }
1432
+ }
1433
+ }
1434
+ return map;
1435
+ }
1030
1436
 
1031
1437
  // src/ui-schema/generator.ts
1032
- function generateUiSchema(form) {
1033
- const ir = canonicalizeChainDSL(form);
1438
+ function generateUiSchema(form, options) {
1439
+ const ir = canonicalizeChainDSL(
1440
+ form,
1441
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1442
+ );
1034
1443
  return generateUiSchemaFromIR(ir);
1035
1444
  }
1036
1445
 
@@ -1261,7 +1670,7 @@ function validateIR(ir, options) {
1261
1670
  function buildFormSchemas(form, options) {
1262
1671
  return {
1263
1672
  jsonSchema: generateJsonSchema(form, options),
1264
- uiSchema: generateUiSchema(form)
1673
+ uiSchema: generateUiSchema(form, options)
1265
1674
  };
1266
1675
  }
1267
1676
  // Annotate the CommonJS export names for ESM import in node: