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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/analyzer/class-analyzer.d.ts +11 -5
  2. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  3. package/dist/analyzer/program.d.ts +3 -2
  4. package/dist/analyzer/program.d.ts.map +1 -1
  5. package/dist/browser.cjs +485 -76
  6. package/dist/browser.cjs.map +1 -1
  7. package/dist/browser.js +486 -77
  8. package/dist/browser.js.map +1 -1
  9. package/dist/build-alpha.d.ts +18 -2
  10. package/dist/build-beta.d.ts +18 -2
  11. package/dist/build-internal.d.ts +18 -2
  12. package/dist/build.d.ts +18 -2
  13. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +5 -2
  14. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -1
  15. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +5 -1
  16. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  17. package/dist/cli.cjs +1031 -170
  18. package/dist/cli.cjs.map +1 -1
  19. package/dist/cli.js +1032 -171
  20. package/dist/cli.js.map +1 -1
  21. package/dist/generators/class-schema.d.ts +6 -1
  22. package/dist/generators/class-schema.d.ts.map +1 -1
  23. package/dist/generators/method-schema.d.ts.map +1 -1
  24. package/dist/generators/mixed-authoring.d.ts.map +1 -1
  25. package/dist/index.cjs +998 -170
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.ts +4 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +999 -171
  30. package/dist/index.js.map +1 -1
  31. package/dist/internals.cjs +921 -155
  32. package/dist/internals.cjs.map +1 -1
  33. package/dist/internals.js +922 -156
  34. package/dist/internals.js.map +1 -1
  35. package/dist/json-schema/generator.d.ts +3 -1
  36. package/dist/json-schema/generator.d.ts.map +1 -1
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/metadata/collision-guards.d.ts +3 -0
  39. package/dist/metadata/collision-guards.d.ts.map +1 -0
  40. package/dist/metadata/index.d.ts +7 -0
  41. package/dist/metadata/index.d.ts.map +1 -0
  42. package/dist/metadata/policy.d.ts +11 -0
  43. package/dist/metadata/policy.d.ts.map +1 -0
  44. package/dist/metadata/resolve.d.ts +20 -0
  45. package/dist/metadata/resolve.d.ts.map +1 -0
  46. package/dist/ui-schema/generator.d.ts +11 -2
  47. package/dist/ui-schema/generator.d.ts.map +1 -1
  48. package/dist/ui-schema/ir-generator.d.ts +2 -1
  49. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  50. package/package.json +4 -4
package/dist/browser.js CHANGED
@@ -1,5 +1,168 @@
1
1
  // src/canonicalize/chain-dsl-canonicalizer.ts
2
- import { IR_VERSION } from "@formspec/core/internals";
2
+ import { IR_VERSION, _getFormSpecMetadataPolicy } from "@formspec/core/internals";
3
+
4
+ // src/metadata/policy.ts
5
+ var NOOP_INFLECT = () => "";
6
+ function normalizePluralization(input) {
7
+ if (input?.mode === "infer-if-missing") {
8
+ return {
9
+ mode: "infer-if-missing",
10
+ infer: () => "",
11
+ inflect: input.inflect
12
+ };
13
+ }
14
+ if (input?.mode === "require-explicit") {
15
+ return {
16
+ mode: "require-explicit",
17
+ infer: () => "",
18
+ inflect: NOOP_INFLECT
19
+ };
20
+ }
21
+ return {
22
+ mode: "disabled",
23
+ infer: () => "",
24
+ inflect: NOOP_INFLECT
25
+ };
26
+ }
27
+ function normalizeScalarPolicy(input) {
28
+ if (input?.mode === "infer-if-missing") {
29
+ return {
30
+ mode: "infer-if-missing",
31
+ infer: input.infer,
32
+ pluralization: normalizePluralization(input.pluralization)
33
+ };
34
+ }
35
+ if (input?.mode === "require-explicit") {
36
+ return {
37
+ mode: "require-explicit",
38
+ infer: () => "",
39
+ pluralization: normalizePluralization(input.pluralization)
40
+ };
41
+ }
42
+ return {
43
+ mode: "disabled",
44
+ infer: () => "",
45
+ pluralization: normalizePluralization(input?.pluralization)
46
+ };
47
+ }
48
+ function normalizeDeclarationPolicy(input) {
49
+ return {
50
+ apiName: normalizeScalarPolicy(input?.apiName),
51
+ displayName: normalizeScalarPolicy(input?.displayName)
52
+ };
53
+ }
54
+ function normalizeMetadataPolicy(input) {
55
+ return {
56
+ type: normalizeDeclarationPolicy(input?.type),
57
+ field: normalizeDeclarationPolicy(input?.field),
58
+ method: normalizeDeclarationPolicy(input?.method)
59
+ };
60
+ }
61
+ function getDeclarationMetadataPolicy(policy, declarationKind) {
62
+ return policy[declarationKind];
63
+ }
64
+ function makeMetadataContext(surface, declarationKind, logicalName, buildContext) {
65
+ return {
66
+ surface,
67
+ declarationKind,
68
+ logicalName,
69
+ ...buildContext !== void 0 && { buildContext }
70
+ };
71
+ }
72
+
73
+ // src/metadata/resolve.ts
74
+ function toExplicitScalar(value) {
75
+ return value !== void 0 && value.trim() !== "" ? { value, source: "explicit" } : void 0;
76
+ }
77
+ function toExplicitResolvedMetadata(explicit) {
78
+ if (explicit === void 0) {
79
+ return void 0;
80
+ }
81
+ const apiName = toExplicitScalar(explicit.apiName);
82
+ const displayName = toExplicitScalar(explicit.displayName);
83
+ const apiNamePlural = toExplicitScalar(explicit.apiNamePlural);
84
+ const displayNamePlural = toExplicitScalar(explicit.displayNamePlural);
85
+ const metadata = {
86
+ ...apiName !== void 0 && { apiName },
87
+ ...displayName !== void 0 && { displayName },
88
+ ...apiNamePlural !== void 0 && { apiNamePlural },
89
+ ...displayNamePlural !== void 0 && { displayNamePlural }
90
+ };
91
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
92
+ }
93
+ function resolveScalar(current, policy, context, metadataLabel) {
94
+ if (current !== void 0) {
95
+ return current;
96
+ }
97
+ if (policy.mode === "require-explicit") {
98
+ throw new Error(
99
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
100
+ );
101
+ }
102
+ if (policy.mode !== "infer-if-missing") {
103
+ return void 0;
104
+ }
105
+ const inferredValue = policy.infer(context);
106
+ return inferredValue.trim() !== "" ? { value: inferredValue, source: "inferred" } : void 0;
107
+ }
108
+ function resolvePlural(current, singular, policy, context, metadataLabel) {
109
+ if (current !== void 0) {
110
+ return current;
111
+ }
112
+ if (policy.mode === "require-explicit") {
113
+ throw new Error(
114
+ `Metadata policy requires explicit ${metadataLabel} for ${context.declarationKind} "${context.logicalName}" on the ${context.surface} surface.`
115
+ );
116
+ }
117
+ if (singular === void 0 || policy.mode !== "infer-if-missing") {
118
+ return void 0;
119
+ }
120
+ const pluralValue = policy.inflect({ ...context, singular: singular.value });
121
+ return pluralValue.trim() !== "" ? { value: pluralValue, source: "inferred" } : void 0;
122
+ }
123
+ function resolveResolvedMetadata(current, policy, context) {
124
+ const apiName = resolveScalar(current?.apiName, policy.apiName, context, "apiName");
125
+ const displayName = resolveScalar(
126
+ current?.displayName,
127
+ policy.displayName,
128
+ context,
129
+ "displayName"
130
+ );
131
+ const apiNamePlural = resolvePlural(
132
+ current?.apiNamePlural,
133
+ apiName,
134
+ policy.apiName.pluralization,
135
+ context,
136
+ "apiNamePlural"
137
+ );
138
+ const displayNamePlural = resolvePlural(
139
+ current?.displayNamePlural,
140
+ displayName,
141
+ policy.displayName.pluralization,
142
+ context,
143
+ "displayNamePlural"
144
+ );
145
+ if (apiName === void 0 && displayName === void 0 && apiNamePlural === void 0 && displayNamePlural === void 0) {
146
+ return void 0;
147
+ }
148
+ return {
149
+ ...apiName !== void 0 && { apiName },
150
+ ...displayName !== void 0 && { displayName },
151
+ ...apiNamePlural !== void 0 && { apiNamePlural },
152
+ ...displayNamePlural !== void 0 && { displayNamePlural }
153
+ };
154
+ }
155
+ function resolveMetadata(explicit, policy, context) {
156
+ return resolveResolvedMetadata(toExplicitResolvedMetadata(explicit), policy, context);
157
+ }
158
+ function getSerializedName(logicalName, metadata) {
159
+ return metadata?.apiName?.value ?? logicalName;
160
+ }
161
+ function getDisplayName(metadata) {
162
+ return metadata?.displayName?.value;
163
+ }
164
+
165
+ // src/canonicalize/chain-dsl-canonicalizer.ts
3
166
  var CHAIN_DSL_PROVENANCE = {
4
167
  surface: "chain-dsl",
5
168
  file: "",
@@ -15,57 +178,60 @@ function isConditional(el) {
15
178
  function isField(el) {
16
179
  return el._type === "field";
17
180
  }
18
- function canonicalizeChainDSL(form) {
181
+ function canonicalizeChainDSL(form, options) {
182
+ const metadataPolicy = normalizeMetadataPolicy(
183
+ options?.metadata ?? _getFormSpecMetadataPolicy(form)
184
+ );
19
185
  return {
20
186
  kind: "form-ir",
21
187
  irVersion: IR_VERSION,
22
- elements: canonicalizeElements(form.elements),
188
+ elements: canonicalizeElements(form.elements, metadataPolicy),
23
189
  rootAnnotations: [],
24
190
  typeRegistry: {},
25
191
  provenance: CHAIN_DSL_PROVENANCE
26
192
  };
27
193
  }
28
- function canonicalizeElements(elements) {
29
- return elements.map(canonicalizeElement);
194
+ function canonicalizeElements(elements, metadataPolicy) {
195
+ return elements.map((element) => canonicalizeElement(element, metadataPolicy));
30
196
  }
31
- function canonicalizeElement(element) {
197
+ function canonicalizeElement(element, metadataPolicy) {
32
198
  if (isField(element)) {
33
- return canonicalizeField(element);
199
+ return canonicalizeField(element, metadataPolicy);
34
200
  }
35
201
  if (isGroup(element)) {
36
- return canonicalizeGroup(element);
202
+ return canonicalizeGroup(element, metadataPolicy);
37
203
  }
38
204
  if (isConditional(element)) {
39
- return canonicalizeConditional(element);
205
+ return canonicalizeConditional(element, metadataPolicy);
40
206
  }
41
207
  const _exhaustive = element;
42
208
  throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
43
209
  }
44
- function canonicalizeField(field) {
210
+ function canonicalizeField(field, metadataPolicy) {
45
211
  switch (field._field) {
46
212
  case "text":
47
- return canonicalizeTextField(field);
213
+ return canonicalizeTextField(field, metadataPolicy);
48
214
  case "number":
49
- return canonicalizeNumberField(field);
215
+ return canonicalizeNumberField(field, metadataPolicy);
50
216
  case "boolean":
51
- return canonicalizeBooleanField(field);
217
+ return canonicalizeBooleanField(field, metadataPolicy);
52
218
  case "enum":
53
- return canonicalizeStaticEnumField(field);
219
+ return canonicalizeStaticEnumField(field, metadataPolicy);
54
220
  case "dynamic_enum":
55
- return canonicalizeDynamicEnumField(field);
221
+ return canonicalizeDynamicEnumField(field, metadataPolicy);
56
222
  case "dynamic_schema":
57
- return canonicalizeDynamicSchemaField(field);
223
+ return canonicalizeDynamicSchemaField(field, metadataPolicy);
58
224
  case "array":
59
- return canonicalizeArrayField(field);
225
+ return canonicalizeArrayField(field, metadataPolicy);
60
226
  case "object":
61
- return canonicalizeObjectField(field);
227
+ return canonicalizeObjectField(field, metadataPolicy);
62
228
  default: {
63
229
  const _exhaustive = field;
64
230
  throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
65
231
  }
66
232
  }
67
233
  }
68
- function canonicalizeTextField(field) {
234
+ function canonicalizeTextField(field, metadataPolicy) {
69
235
  const type = { kind: "primitive", primitiveKind: "string" };
70
236
  const constraints = [];
71
237
  if (field.minLength !== void 0) {
@@ -97,13 +263,14 @@ function canonicalizeTextField(field) {
97
263
  }
98
264
  return buildFieldNode(
99
265
  field.name,
266
+ resolveFieldMetadata(field.name, field, metadataPolicy),
100
267
  type,
101
268
  field.required,
102
- buildAnnotations(field.label, field.placeholder),
269
+ buildAnnotations(getExplicitDisplayName(field), field.placeholder),
103
270
  constraints
104
271
  );
105
272
  }
106
- function canonicalizeNumberField(field) {
273
+ function canonicalizeNumberField(field, metadataPolicy) {
107
274
  const type = { kind: "primitive", primitiveKind: "number" };
108
275
  const constraints = [];
109
276
  if (field.min !== void 0) {
@@ -135,17 +302,24 @@ function canonicalizeNumberField(field) {
135
302
  }
136
303
  return buildFieldNode(
137
304
  field.name,
305
+ resolveFieldMetadata(field.name, field, metadataPolicy),
138
306
  type,
139
307
  field.required,
140
- buildAnnotations(field.label),
308
+ buildAnnotations(getExplicitDisplayName(field)),
141
309
  constraints
142
310
  );
143
311
  }
144
- function canonicalizeBooleanField(field) {
312
+ function canonicalizeBooleanField(field, metadataPolicy) {
145
313
  const type = { kind: "primitive", primitiveKind: "boolean" };
146
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
314
+ return buildFieldNode(
315
+ field.name,
316
+ resolveFieldMetadata(field.name, field, metadataPolicy),
317
+ type,
318
+ field.required,
319
+ buildAnnotations(getExplicitDisplayName(field))
320
+ );
147
321
  }
148
- function canonicalizeStaticEnumField(field) {
322
+ function canonicalizeStaticEnumField(field, metadataPolicy) {
149
323
  const members = field.options.map((opt) => {
150
324
  if (typeof opt === "string") {
151
325
  return { value: opt };
@@ -153,28 +327,46 @@ function canonicalizeStaticEnumField(field) {
153
327
  return { value: opt.id, displayName: opt.label };
154
328
  });
155
329
  const type = { kind: "enum", members };
156
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
330
+ return buildFieldNode(
331
+ field.name,
332
+ resolveFieldMetadata(field.name, field, metadataPolicy),
333
+ type,
334
+ field.required,
335
+ buildAnnotations(getExplicitDisplayName(field))
336
+ );
157
337
  }
158
- function canonicalizeDynamicEnumField(field) {
338
+ function canonicalizeDynamicEnumField(field, metadataPolicy) {
159
339
  const type = {
160
340
  kind: "dynamic",
161
341
  dynamicKind: "enum",
162
342
  sourceKey: field.source,
163
343
  parameterFields: field.params ? [...field.params] : []
164
344
  };
165
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
345
+ return buildFieldNode(
346
+ field.name,
347
+ resolveFieldMetadata(field.name, field, metadataPolicy),
348
+ type,
349
+ field.required,
350
+ buildAnnotations(getExplicitDisplayName(field))
351
+ );
166
352
  }
167
- function canonicalizeDynamicSchemaField(field) {
353
+ function canonicalizeDynamicSchemaField(field, metadataPolicy) {
168
354
  const type = {
169
355
  kind: "dynamic",
170
356
  dynamicKind: "schema",
171
357
  sourceKey: field.schemaSource,
172
358
  parameterFields: []
173
359
  };
174
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
360
+ return buildFieldNode(
361
+ field.name,
362
+ resolveFieldMetadata(field.name, field, metadataPolicy),
363
+ type,
364
+ field.required,
365
+ buildAnnotations(getExplicitDisplayName(field))
366
+ );
175
367
  }
176
- function canonicalizeArrayField(field) {
177
- const itemProperties = buildObjectProperties(field.items);
368
+ function canonicalizeArrayField(field, metadataPolicy) {
369
+ const itemProperties = buildObjectProperties(field.items, metadataPolicy);
178
370
  const itemsType = {
179
371
  kind: "object",
180
372
  properties: itemProperties,
@@ -202,37 +394,44 @@ function canonicalizeArrayField(field) {
202
394
  }
203
395
  return buildFieldNode(
204
396
  field.name,
397
+ resolveFieldMetadata(field.name, field, metadataPolicy),
205
398
  type,
206
399
  field.required,
207
- buildAnnotations(field.label),
400
+ buildAnnotations(getExplicitDisplayName(field)),
208
401
  constraints
209
402
  );
210
403
  }
211
- function canonicalizeObjectField(field) {
212
- const properties = buildObjectProperties(field.properties);
404
+ function canonicalizeObjectField(field, metadataPolicy) {
405
+ const properties = buildObjectProperties(field.properties, metadataPolicy);
213
406
  const type = {
214
407
  kind: "object",
215
408
  properties,
216
409
  additionalProperties: true
217
410
  };
218
- return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
411
+ return buildFieldNode(
412
+ field.name,
413
+ resolveFieldMetadata(field.name, field, metadataPolicy),
414
+ type,
415
+ field.required,
416
+ buildAnnotations(getExplicitDisplayName(field))
417
+ );
219
418
  }
220
- function canonicalizeGroup(g) {
419
+ function canonicalizeGroup(g, metadataPolicy) {
221
420
  return {
222
421
  kind: "group",
223
422
  label: g.label,
224
- elements: canonicalizeElements(g.elements),
423
+ elements: canonicalizeElements(g.elements, metadataPolicy),
225
424
  provenance: CHAIN_DSL_PROVENANCE
226
425
  };
227
426
  }
228
- function canonicalizeConditional(c) {
427
+ function canonicalizeConditional(c, metadataPolicy) {
229
428
  return {
230
429
  kind: "conditional",
231
430
  fieldName: c.field,
232
431
  // Conditional values from the chain DSL are JSON-serializable primitives
233
432
  // (strings, numbers, booleans) produced by the `is()` predicate helper.
234
433
  value: assertJsonValue(c.value),
235
- elements: canonicalizeElements(c.elements),
434
+ elements: canonicalizeElements(c.elements, metadataPolicy),
236
435
  provenance: CHAIN_DSL_PROVENANCE
237
436
  };
238
437
  }
@@ -252,10 +451,11 @@ function assertJsonValue(v) {
252
451
  }
253
452
  throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
254
453
  }
255
- function buildFieldNode(name, type, required, annotations, constraints = []) {
454
+ function buildFieldNode(name, metadata, type, required, annotations, constraints = []) {
256
455
  return {
257
456
  kind: "field",
258
457
  name,
458
+ ...metadata !== void 0 && { metadata },
259
459
  type,
260
460
  required: required === true,
261
461
  constraints,
@@ -285,13 +485,14 @@ function buildAnnotations(label, placeholder) {
285
485
  }
286
486
  return annotations;
287
487
  }
288
- function buildObjectProperties(elements, insideConditional = false) {
488
+ function buildObjectProperties(elements, metadataPolicy, insideConditional = false) {
289
489
  const properties = [];
290
490
  for (const el of elements) {
291
491
  if (isField(el)) {
292
- const fieldNode = canonicalizeField(el);
492
+ const fieldNode = canonicalizeField(el, metadataPolicy);
293
493
  properties.push({
294
494
  name: fieldNode.name,
495
+ ...fieldNode.metadata !== void 0 && { metadata: fieldNode.metadata },
295
496
  type: fieldNode.type,
296
497
  // Fields inside a conditional branch are always optional in the
297
498
  // data schema, regardless of their `required` flag — the condition
@@ -302,17 +503,148 @@ function buildObjectProperties(elements, insideConditional = false) {
302
503
  provenance: CHAIN_DSL_PROVENANCE
303
504
  });
304
505
  } else if (isGroup(el)) {
305
- properties.push(...buildObjectProperties(el.elements, insideConditional));
506
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, insideConditional));
306
507
  } else if (isConditional(el)) {
307
- properties.push(...buildObjectProperties(el.elements, true));
508
+ properties.push(...buildObjectProperties(el.elements, metadataPolicy, true));
308
509
  }
309
510
  }
310
511
  return properties;
311
512
  }
513
+ function getExplicitDisplayName(field) {
514
+ if (field.label !== void 0 && field.displayName !== void 0) {
515
+ throw new Error('Chain DSL fields cannot specify both "label" and "displayName".');
516
+ }
517
+ return field.displayName ?? field.label;
518
+ }
519
+ function resolveFieldMetadata(logicalName, field, metadataPolicy) {
520
+ const displayName = getExplicitDisplayName(field);
521
+ return resolveMetadata(
522
+ {
523
+ ...field.apiName !== void 0 && { apiName: field.apiName },
524
+ ...displayName !== void 0 && { displayName }
525
+ },
526
+ getDeclarationMetadataPolicy(metadataPolicy, "field"),
527
+ makeMetadataContext("chain-dsl", "field", logicalName)
528
+ );
529
+ }
312
530
 
313
531
  // src/canonicalize/tsdoc-canonicalizer.ts
314
532
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core/internals";
315
533
 
534
+ // src/metadata/collision-guards.ts
535
+ function assertUniqueSerializedNames(entries, scope) {
536
+ const seen = /* @__PURE__ */ new Map();
537
+ for (const entry of entries) {
538
+ const previous = seen.get(entry.serializedName);
539
+ if (previous !== void 0) {
540
+ if (previous.logicalName === entry.logicalName && previous.category === entry.category) {
541
+ continue;
542
+ }
543
+ throw new Error(
544
+ `Serialized name collision in ${scope}: ${previous.category} "${previous.logicalName}" and ${entry.category} "${entry.logicalName}" both resolve to "${entry.serializedName}".`
545
+ );
546
+ }
547
+ seen.set(entry.serializedName, entry);
548
+ }
549
+ }
550
+ function collectFlattenedFields(elements) {
551
+ const fields = [];
552
+ for (const element of elements) {
553
+ switch (element.kind) {
554
+ case "field":
555
+ fields.push(element);
556
+ break;
557
+ case "group":
558
+ case "conditional":
559
+ fields.push(...collectFlattenedFields(element.elements));
560
+ break;
561
+ default: {
562
+ const exhaustive = element;
563
+ void exhaustive;
564
+ }
565
+ }
566
+ }
567
+ return fields;
568
+ }
569
+ function validateObjectProperties(properties, scope) {
570
+ assertUniqueSerializedNames(
571
+ properties.map((property) => ({
572
+ logicalName: property.name,
573
+ serializedName: getSerializedName(property.name, property.metadata),
574
+ category: "object property"
575
+ })),
576
+ scope
577
+ );
578
+ for (const property of properties) {
579
+ validateTypeNode(
580
+ property.type,
581
+ `${scope}.${getSerializedName(property.name, property.metadata)}`
582
+ );
583
+ }
584
+ }
585
+ function validateTypeNode(type, scope) {
586
+ switch (type.kind) {
587
+ case "array":
588
+ validateTypeNode(type.items, `${scope}[]`);
589
+ break;
590
+ case "object":
591
+ validateObjectProperties(type.properties, scope);
592
+ break;
593
+ case "record":
594
+ validateTypeNode(type.valueType, `${scope}.*`);
595
+ break;
596
+ case "union":
597
+ type.members.forEach((member, index) => {
598
+ validateTypeNode(member, `${scope}|${String(index)}`);
599
+ });
600
+ break;
601
+ case "reference":
602
+ case "primitive":
603
+ case "enum":
604
+ case "dynamic":
605
+ case "custom":
606
+ break;
607
+ default: {
608
+ const exhaustive = type;
609
+ void exhaustive;
610
+ }
611
+ }
612
+ }
613
+ function validateTypeDefinitions(typeRegistry) {
614
+ const definitions = Object.values(typeRegistry);
615
+ assertUniqueSerializedNames(
616
+ definitions.map((definition) => ({
617
+ logicalName: definition.name,
618
+ serializedName: getSerializedName(definition.name, definition.metadata),
619
+ category: "type definition"
620
+ })),
621
+ "$defs"
622
+ );
623
+ for (const definition of definitions) {
624
+ validateTypeDefinition(definition);
625
+ }
626
+ }
627
+ function validateTypeDefinition(definition) {
628
+ validateTypeNode(
629
+ definition.type,
630
+ `type "${getSerializedName(definition.name, definition.metadata)}"`
631
+ );
632
+ }
633
+ function assertNoSerializedNameCollisions(ir) {
634
+ assertUniqueSerializedNames(
635
+ collectFlattenedFields(ir.elements).map((field) => ({
636
+ logicalName: field.name,
637
+ serializedName: getSerializedName(field.name, field.metadata),
638
+ category: "field"
639
+ })),
640
+ "form root"
641
+ );
642
+ for (const field of collectFlattenedFields(ir.elements)) {
643
+ validateTypeNode(field.type, `field "${getSerializedName(field.name, field.metadata)}"`);
644
+ }
645
+ validateTypeDefinitions(ir.typeRegistry);
646
+ }
647
+
316
648
  // src/json-schema/ir-generator.ts
317
649
  function makeContext(options) {
318
650
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
@@ -323,19 +655,33 @@ function makeContext(options) {
323
655
  }
324
656
  return {
325
657
  defs: {},
658
+ typeNameMap: {},
659
+ typeRegistry: {},
326
660
  extensionRegistry: options?.extensionRegistry,
327
661
  vendorPrefix
328
662
  };
329
663
  }
330
664
  function generateJsonSchemaFromIR(ir, options) {
331
- const ctx = makeContext(options);
665
+ assertNoSerializedNameCollisions(ir);
666
+ const ctx = {
667
+ ...makeContext(options),
668
+ typeRegistry: ir.typeRegistry,
669
+ typeNameMap: Object.fromEntries(
670
+ Object.entries(ir.typeRegistry).map(([name, typeDef]) => [
671
+ name,
672
+ getSerializedName(name, typeDef.metadata)
673
+ ])
674
+ )
675
+ };
332
676
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
333
- ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
677
+ const schemaName = ctx.typeNameMap[name] ?? name;
678
+ ctx.defs[schemaName] = generateTypeNode(typeDef.type, ctx);
679
+ applyResolvedMetadata(ctx.defs[schemaName], typeDef.metadata);
334
680
  if (typeDef.constraints && typeDef.constraints.length > 0) {
335
- applyConstraints(ctx.defs[name], typeDef.constraints, ctx);
681
+ applyConstraints(ctx.defs[schemaName], typeDef.constraints, ctx);
336
682
  }
337
683
  if (typeDef.annotations && typeDef.annotations.length > 0) {
338
- applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
684
+ applyAnnotations(ctx.defs[schemaName], typeDef.annotations, ctx);
339
685
  }
340
686
  }
341
687
  const properties = {};
@@ -348,6 +694,7 @@ function generateJsonSchemaFromIR(ir, options) {
348
694
  properties,
349
695
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
350
696
  };
697
+ applyResolvedMetadata(result, ir.metadata);
351
698
  if (ir.annotations && ir.annotations.length > 0) {
352
699
  applyAnnotations(result, ir.annotations, ctx);
353
700
  }
@@ -360,9 +707,9 @@ function collectFields(elements, properties, required, ctx) {
360
707
  for (const element of elements) {
361
708
  switch (element.kind) {
362
709
  case "field":
363
- properties[element.name] = generateFieldSchema(element, ctx);
710
+ properties[getSerializedName(element.name, element.metadata)] = generateFieldSchema(element, ctx);
364
711
  if (element.required) {
365
- required.push(element.name);
712
+ required.push(getSerializedName(element.name, element.metadata));
366
713
  }
367
714
  break;
368
715
  case "group":
@@ -406,6 +753,7 @@ function generateFieldSchema(field, ctx) {
406
753
  rootAnnotations.push(annotation);
407
754
  }
408
755
  }
756
+ applyResolvedMetadata(schema, field.metadata);
409
757
  applyAnnotations(schema, rootAnnotations, ctx);
410
758
  if (itemStringSchema !== void 0) {
411
759
  applyAnnotations(itemStringSchema, itemAnnotations, ctx);
@@ -413,7 +761,7 @@ function generateFieldSchema(field, ctx) {
413
761
  if (pathConstraints.length === 0) {
414
762
  return schema;
415
763
  }
416
- return applyPathTargetedConstraints(schema, pathConstraints, ctx);
764
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx, field.type);
417
765
  }
418
766
  function isStringItemConstraint(constraint) {
419
767
  switch (constraint.constraintKind) {
@@ -425,9 +773,11 @@ function isStringItemConstraint(constraint) {
425
773
  return false;
426
774
  }
427
775
  }
428
- function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
776
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx, typeNode) {
429
777
  if (schema.type === "array" && schema.items) {
430
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
778
+ const referencedType = typeNode?.kind === "reference" ? resolveReferencedType(typeNode, ctx) : void 0;
779
+ const nestedType = typeNode?.kind === "array" ? typeNode.items : referencedType?.kind === "array" ? referencedType.items : void 0;
780
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx, nestedType);
431
781
  return schema;
432
782
  }
433
783
  const byTarget = /* @__PURE__ */ new Map();
@@ -442,7 +792,7 @@ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
442
792
  for (const [target, constraints] of byTarget) {
443
793
  const subSchema = {};
444
794
  applyConstraints(subSchema, constraints, ctx);
445
- propertyOverrides[target] = subSchema;
795
+ propertyOverrides[resolveSerializedPropertyName(target, typeNode, ctx)] = subSchema;
446
796
  }
447
797
  if (schema.$ref) {
448
798
  const { $ref, ...rest } = schema;
@@ -490,7 +840,7 @@ function generateTypeNode(type, ctx) {
490
840
  case "union":
491
841
  return generateUnionType(type, ctx);
492
842
  case "reference":
493
- return generateReferenceType(type);
843
+ return generateReferenceType(type, ctx);
494
844
  case "dynamic":
495
845
  return generateDynamicType(type);
496
846
  case "custom":
@@ -531,9 +881,10 @@ function generateObjectType(type, ctx) {
531
881
  const properties = {};
532
882
  const required = [];
533
883
  for (const prop of type.properties) {
534
- properties[prop.name] = generatePropertySchema(prop, ctx);
884
+ const propertyName = getSerializedName(prop.name, prop.metadata);
885
+ properties[propertyName] = generatePropertySchema(prop, ctx);
535
886
  if (!prop.optional) {
536
- required.push(prop.name);
887
+ required.push(propertyName);
537
888
  }
538
889
  }
539
890
  const schema = { type: "object", properties };
@@ -554,6 +905,7 @@ function generateRecordType(type, ctx) {
554
905
  function generatePropertySchema(prop, ctx) {
555
906
  const schema = generateTypeNode(prop.type, ctx);
556
907
  applyConstraints(schema, prop.constraints, ctx);
908
+ applyResolvedMetadata(schema, prop.metadata);
557
909
  applyAnnotations(schema, prop.annotations, ctx);
558
910
  return schema;
559
911
  }
@@ -582,8 +934,28 @@ function isNullableUnion(type) {
582
934
  ).length;
583
935
  return nullCount === 1;
584
936
  }
585
- function generateReferenceType(type) {
586
- return { $ref: `#/$defs/${type.name}` };
937
+ function generateReferenceType(type, ctx) {
938
+ return { $ref: `#/$defs/${ctx.typeNameMap[type.name] ?? type.name}` };
939
+ }
940
+ function applyResolvedMetadata(schema, metadata) {
941
+ const displayName = getDisplayName(metadata);
942
+ if (displayName !== void 0) {
943
+ schema.title = displayName;
944
+ }
945
+ }
946
+ function resolveReferencedType(type, ctx) {
947
+ return ctx.typeRegistry[type.name]?.type;
948
+ }
949
+ function resolveSerializedPropertyName(logicalName, typeNode, ctx) {
950
+ if (typeNode?.kind === "object") {
951
+ const property = typeNode.properties.find((candidate) => candidate.name === logicalName);
952
+ return property === void 0 ? logicalName : getSerializedName(property.name, property.metadata);
953
+ }
954
+ if (typeNode?.kind === "reference") {
955
+ const referencedType = resolveReferencedType(typeNode, ctx);
956
+ return referencedType === void 0 ? logicalName : resolveSerializedPropertyName(logicalName, referencedType, ctx);
957
+ }
958
+ return logicalName;
587
959
  }
588
960
  function generateDynamicType(type) {
589
961
  if (type.dynamicKind === "enum") {
@@ -663,7 +1035,7 @@ function applyAnnotations(schema, annotations, ctx) {
663
1035
  for (const annotation of annotations) {
664
1036
  switch (annotation.annotationKind) {
665
1037
  case "displayName":
666
- schema.title = annotation.value;
1038
+ schema.title ??= annotation.value;
667
1039
  break;
668
1040
  case "description":
669
1041
  schema.description = annotation.value;
@@ -750,7 +1122,10 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
750
1122
 
751
1123
  // src/json-schema/generator.ts
752
1124
  function generateJsonSchema(form, options) {
753
- const ir = canonicalizeChainDSL(form);
1125
+ const ir = canonicalizeChainDSL(
1126
+ form,
1127
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1128
+ );
754
1129
  const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
755
1130
  return generateJsonSchemaFromIR(ir, internalOptions);
756
1131
  }
@@ -920,13 +1295,21 @@ function combineRules(parentRule, childRule) {
920
1295
  }
921
1296
  };
922
1297
  }
923
- function fieldNodeToControl(field, parentRule) {
924
- const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1298
+ function getFieldDisplayName(field) {
1299
+ const resolvedDisplayName = getDisplayName(field.metadata);
1300
+ if (resolvedDisplayName !== void 0) {
1301
+ return resolvedDisplayName;
1302
+ }
1303
+ return field.annotations.find((annotation) => annotation.annotationKind === "displayName")?.value;
1304
+ }
1305
+ function fieldNodeToControl(field, fieldNameMap, parentRule) {
925
1306
  const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
1307
+ const serializedName = fieldNameMap.get(field.name) ?? getSerializedName(field.name, field.metadata);
1308
+ const displayName = getFieldDisplayName(field);
926
1309
  const control = {
927
1310
  type: "Control",
928
- scope: fieldToScope(field.name),
929
- ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1311
+ scope: fieldToScope(serializedName),
1312
+ ...displayName !== void 0 && { label: displayName },
930
1313
  ...placeholderAnnotation !== void 0 && {
931
1314
  options: { placeholder: placeholderAnnotation.value }
932
1315
  },
@@ -934,30 +1317,30 @@ function fieldNodeToControl(field, parentRule) {
934
1317
  };
935
1318
  return control;
936
1319
  }
937
- function groupNodeToLayout(group, parentRule) {
1320
+ function groupNodeToLayout(group, fieldNameMap, parentRule) {
938
1321
  return {
939
1322
  type: "Group",
940
1323
  label: group.label,
941
- elements: irElementsToUiSchema(group.elements, parentRule),
1324
+ elements: irElementsToUiSchema(group.elements, fieldNameMap, parentRule),
942
1325
  ...parentRule !== void 0 && { rule: parentRule }
943
1326
  };
944
1327
  }
945
- function irElementsToUiSchema(elements, parentRule) {
1328
+ function irElementsToUiSchema(elements, fieldNameMap, parentRule) {
946
1329
  const result = [];
947
1330
  for (const element of elements) {
948
1331
  switch (element.kind) {
949
1332
  case "field": {
950
- result.push(fieldNodeToControl(element, parentRule));
1333
+ result.push(fieldNodeToControl(element, fieldNameMap, parentRule));
951
1334
  break;
952
1335
  }
953
1336
  case "group": {
954
- result.push(groupNodeToLayout(element, parentRule));
1337
+ result.push(groupNodeToLayout(element, fieldNameMap, parentRule));
955
1338
  break;
956
1339
  }
957
1340
  case "conditional": {
958
- const newRule = createShowRule(element.fieldName, element.value);
1341
+ const newRule = createShowRule(fieldNameMap.get(element.fieldName) ?? element.fieldName, element.value);
959
1342
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
960
- const childElements = irElementsToUiSchema(element.elements, combinedRule);
1343
+ const childElements = irElementsToUiSchema(element.elements, fieldNameMap, combinedRule);
961
1344
  result.push(...childElements);
962
1345
  break;
963
1346
  }
@@ -971,16 +1354,42 @@ function irElementsToUiSchema(elements, parentRule) {
971
1354
  return result;
972
1355
  }
973
1356
  function generateUiSchemaFromIR(ir) {
1357
+ assertNoSerializedNameCollisions(ir);
1358
+ const fieldNameMap = collectFieldNameMap(ir.elements);
974
1359
  const result = {
975
1360
  type: "VerticalLayout",
976
- elements: irElementsToUiSchema(ir.elements)
1361
+ elements: irElementsToUiSchema(ir.elements, fieldNameMap)
977
1362
  };
978
1363
  return parseOrThrow(uiSchema, result, "UI Schema");
979
1364
  }
1365
+ function collectFieldNameMap(elements) {
1366
+ const map = /* @__PURE__ */ new Map();
1367
+ for (const element of elements) {
1368
+ switch (element.kind) {
1369
+ case "field":
1370
+ map.set(element.name, getSerializedName(element.name, element.metadata));
1371
+ break;
1372
+ case "group":
1373
+ case "conditional":
1374
+ for (const [key, value] of collectFieldNameMap(element.elements)) {
1375
+ map.set(key, value);
1376
+ }
1377
+ break;
1378
+ default: {
1379
+ const _exhaustive = element;
1380
+ void _exhaustive;
1381
+ }
1382
+ }
1383
+ }
1384
+ return map;
1385
+ }
980
1386
 
981
1387
  // src/ui-schema/generator.ts
982
- function generateUiSchema(form) {
983
- const ir = canonicalizeChainDSL(form);
1388
+ function generateUiSchema(form, options) {
1389
+ const ir = canonicalizeChainDSL(
1390
+ form,
1391
+ options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1392
+ );
984
1393
  return generateUiSchemaFromIR(ir);
985
1394
  }
986
1395
 
@@ -1213,7 +1622,7 @@ function validateIR(ir, options) {
1213
1622
  function buildFormSchemas(form, options) {
1214
1623
  return {
1215
1624
  jsonSchema: generateJsonSchema(form, options),
1216
- uiSchema: generateUiSchema(form)
1625
+ uiSchema: generateUiSchema(form, options)
1217
1626
  };
1218
1627
  }
1219
1628
  export {