@formspec/build 0.1.0-alpha.40 → 0.1.0-alpha.42

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.
package/dist/cli.js CHANGED
@@ -58,11 +58,35 @@ function normalizeDeclarationPolicy(input) {
58
58
  displayName: normalizeScalarPolicy(input?.displayName)
59
59
  };
60
60
  }
61
+ function normalizeEnumMemberDisplayNamePolicy(input) {
62
+ if (input?.mode === "infer-if-missing") {
63
+ return {
64
+ mode: "infer-if-missing",
65
+ infer: input.infer
66
+ };
67
+ }
68
+ if (input?.mode === "require-explicit") {
69
+ return {
70
+ mode: "require-explicit",
71
+ infer: () => ""
72
+ };
73
+ }
74
+ return {
75
+ mode: "disabled",
76
+ infer: () => ""
77
+ };
78
+ }
79
+ function normalizeEnumMemberPolicy(input) {
80
+ return {
81
+ displayName: normalizeEnumMemberDisplayNamePolicy(input?.displayName)
82
+ };
83
+ }
61
84
  function normalizeMetadataPolicy(input) {
62
85
  return {
63
86
  type: normalizeDeclarationPolicy(input?.type),
64
87
  field: normalizeDeclarationPolicy(input?.field),
65
- method: normalizeDeclarationPolicy(input?.method)
88
+ method: normalizeDeclarationPolicy(input?.method),
89
+ enumMember: normalizeEnumMemberPolicy(input?.enumMember)
66
90
  };
67
91
  }
68
92
  function getDeclarationMetadataPolicy(policy, declarationKind) {
@@ -175,6 +199,40 @@ function pickResolvedMetadataValue(baseValue, overlayValue) {
175
199
  }
176
200
  return baseValue ?? overlayValue;
177
201
  }
202
+ function resolveEnumMemberDisplayName(current, policy, context) {
203
+ if (current !== void 0) {
204
+ return current;
205
+ }
206
+ if (policy.mode === "require-explicit") {
207
+ throw new Error(
208
+ `Metadata policy requires explicit displayName for enum member "${context.logicalName}" on the ${context.surface} surface.`
209
+ );
210
+ }
211
+ if (policy.mode !== "infer-if-missing") {
212
+ return void 0;
213
+ }
214
+ const inferredValue = policy.infer(context).trim();
215
+ return inferredValue !== "" ? inferredValue : void 0;
216
+ }
217
+ function resolveEnumTypeMetadata(type, options) {
218
+ const members = type.members.map((member) => {
219
+ const displayName = resolveEnumMemberDisplayName(
220
+ member.displayName,
221
+ options.policy.enumMember.displayName,
222
+ {
223
+ surface: options.surface,
224
+ logicalName: String(member.value),
225
+ memberValue: member.value,
226
+ ...options.buildContext !== void 0 && { buildContext: options.buildContext }
227
+ }
228
+ );
229
+ if (displayName === member.displayName) {
230
+ return member;
231
+ }
232
+ return displayName === void 0 ? { value: member.value } : { value: member.value, displayName };
233
+ });
234
+ return members.some((member, index) => member !== type.members[index]) ? { ...type, members } : type;
235
+ }
178
236
  function resolveTypeNodeMetadata(type, options) {
179
237
  switch (type.kind) {
180
238
  case "array":
@@ -197,9 +255,10 @@ function resolveTypeNodeMetadata(type, options) {
197
255
  ...type,
198
256
  members: type.members.map((member) => resolveTypeNodeMetadata(member, options))
199
257
  };
258
+ case "enum":
259
+ return resolveEnumTypeMetadata(type, options);
200
260
  case "reference":
201
261
  case "primitive":
202
- case "enum":
203
262
  case "dynamic":
204
263
  case "custom":
205
264
  return type;
@@ -302,11 +361,10 @@ function getDisplayName(metadata) {
302
361
  return metadata?.displayName?.value;
303
362
  }
304
363
  function resolveFormIRMetadata(ir, options) {
305
- const rootLogicalName = options.rootLogicalName ?? ir.name ?? "FormSpec";
306
- const metadata = resolveResolvedMetadata(ir.metadata, options.policy.type, {
364
+ const metadata = options.resolveRootTypeMetadata === false ? ir.metadata : resolveResolvedMetadata(ir.metadata, options.policy.type, {
307
365
  surface: options.surface,
308
366
  declarationKind: "type",
309
- logicalName: rootLogicalName,
367
+ logicalName: options.rootLogicalName ?? ir.name ?? "FormSpec",
310
368
  ...options.buildContext !== void 0 && { buildContext: options.buildContext }
311
369
  });
312
370
  return {
@@ -352,7 +410,7 @@ function canonicalizeChainDSL(form, options) {
352
410
  const metadataPolicy = normalizeMetadataPolicy(
353
411
  options?.metadata ?? _getFormSpecMetadataPolicy(form)
354
412
  );
355
- return {
413
+ const ir = {
356
414
  kind: "form-ir",
357
415
  irVersion: IR_VERSION,
358
416
  elements: canonicalizeElements(form.elements, metadataPolicy),
@@ -360,6 +418,13 @@ function canonicalizeChainDSL(form, options) {
360
418
  typeRegistry: {},
361
419
  provenance: CHAIN_DSL_PROVENANCE
362
420
  };
421
+ return resolveFormIRMetadata(ir, {
422
+ policy: metadataPolicy,
423
+ surface: "chain-dsl",
424
+ // Chain DSL has no root/type-metadata authoring surface, so only resolve
425
+ // field/type-registry metadata and enum-member labels here.
426
+ resolveRootTypeMetadata: false
427
+ });
363
428
  }
364
429
  function canonicalizeElements(elements, metadataPolicy) {
365
430
  return elements.map((element) => canonicalizeElement(element, metadataPolicy));
@@ -931,17 +996,25 @@ var init_collision_guards = __esm({
931
996
  // src/json-schema/ir-generator.ts
932
997
  function makeContext(options) {
933
998
  const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
999
+ const rawEnumSerialization = options?.enumSerialization;
934
1000
  if (!vendorPrefix.startsWith("x-")) {
935
1001
  throw new Error(
936
1002
  `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
937
1003
  );
938
1004
  }
1005
+ if (rawEnumSerialization !== void 0 && rawEnumSerialization !== "enum" && rawEnumSerialization !== "oneOf") {
1006
+ throw new Error(
1007
+ `Invalid enumSerialization "${rawEnumSerialization}". Expected "enum" or "oneOf".`
1008
+ );
1009
+ }
1010
+ const enumSerialization = rawEnumSerialization ?? "enum";
939
1011
  return {
940
1012
  defs: {},
941
1013
  typeNameMap: {},
942
1014
  typeRegistry: {},
943
1015
  extensionRegistry: options?.extensionRegistry,
944
- vendorPrefix
1016
+ vendorPrefix,
1017
+ enumSerialization
945
1018
  };
946
1019
  }
947
1020
  function generateJsonSchemaFromIR(ir, options) {
@@ -1115,7 +1188,7 @@ function generateTypeNode(type, ctx) {
1115
1188
  case "primitive":
1116
1189
  return generatePrimitiveType(type);
1117
1190
  case "enum":
1118
- return generateEnumType(type);
1191
+ return generateEnumType(type, ctx);
1119
1192
  case "array":
1120
1193
  return generateArrayType(type, ctx);
1121
1194
  case "object":
@@ -1141,20 +1214,37 @@ function generatePrimitiveType(type) {
1141
1214
  type: type.primitiveKind === "integer" || type.primitiveKind === "bigint" ? "integer" : type.primitiveKind
1142
1215
  };
1143
1216
  }
1144
- function generateEnumType(type) {
1145
- const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
1146
- if (hasDisplayNames) {
1217
+ function generateEnumType(type, ctx) {
1218
+ if (ctx.enumSerialization === "oneOf") {
1147
1219
  return {
1148
- oneOf: type.members.map((m) => {
1149
- const entry = { const: m.value };
1150
- if (m.displayName !== void 0) {
1151
- entry.title = m.displayName;
1152
- }
1153
- return entry;
1154
- })
1220
+ oneOf: type.members.map((m) => ({
1221
+ const: m.value,
1222
+ title: m.displayName ?? String(m.value)
1223
+ }))
1155
1224
  };
1156
1225
  }
1157
- return { enum: type.members.map((m) => m.value) };
1226
+ const schema = { enum: type.members.map((m) => m.value) };
1227
+ const displayNames = buildEnumDisplayNameExtension(type);
1228
+ if (displayNames !== void 0) {
1229
+ schema[`${ctx.vendorPrefix}-display-names`] = displayNames;
1230
+ }
1231
+ return schema;
1232
+ }
1233
+ function buildEnumDisplayNameExtension(type) {
1234
+ if (!type.members.some((member) => member.displayName !== void 0)) {
1235
+ return void 0;
1236
+ }
1237
+ const displayNames = /* @__PURE__ */ Object.create(null);
1238
+ for (const member of type.members) {
1239
+ const key = String(member.value);
1240
+ if (Object.hasOwn(displayNames, key)) {
1241
+ throw new Error(
1242
+ `Enum display-name key "${key}" is ambiguous after stringification. Use oneOf serialization for mixed string/number enum values that collide.`
1243
+ );
1244
+ }
1245
+ displayNames[key] = member.displayName ?? key;
1246
+ }
1247
+ return displayNames;
1158
1248
  }
1159
1249
  function generateArrayType(type, ctx) {
1160
1250
  return {
@@ -1510,12 +1600,25 @@ function applyCustomConstraint(schema, constraint, ctx) {
1510
1600
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
1511
1601
  );
1512
1602
  }
1513
- assignVendorPrefixedExtensionKeywords(
1514
- schema,
1515
- registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
1516
- ctx.vendorPrefix,
1517
- `custom constraint "${constraint.constraintId}"`
1518
- );
1603
+ const extensionSchema = registration.toJsonSchema(constraint.payload, ctx.vendorPrefix);
1604
+ if (registration.emitsVocabularyKeywords) {
1605
+ const target = schema;
1606
+ for (const [key, value] of Object.entries(extensionSchema)) {
1607
+ if (JSON_SCHEMA_STRUCTURAL_KEYWORDS.has(key)) {
1608
+ throw new Error(
1609
+ `Custom constraint "${constraint.constraintId}" with emitsVocabularyKeywords must not overwrite standard JSON Schema keyword "${key}"`
1610
+ );
1611
+ }
1612
+ target[key] = value;
1613
+ }
1614
+ } else {
1615
+ assignVendorPrefixedExtensionKeywords(
1616
+ schema,
1617
+ extensionSchema,
1618
+ ctx.vendorPrefix,
1619
+ `custom constraint "${constraint.constraintId}"`
1620
+ );
1621
+ }
1519
1622
  }
1520
1623
  function applyCustomAnnotation(schema, annotation, ctx) {
1521
1624
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -1544,21 +1647,88 @@ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPr
1544
1647
  schema[key] = value;
1545
1648
  }
1546
1649
  }
1650
+ var JSON_SCHEMA_STRUCTURAL_KEYWORDS;
1547
1651
  var init_ir_generator = __esm({
1548
1652
  "src/json-schema/ir-generator.ts"() {
1549
1653
  "use strict";
1550
1654
  init_metadata();
1551
1655
  init_collision_guards();
1656
+ JSON_SCHEMA_STRUCTURAL_KEYWORDS = /* @__PURE__ */ new Set([
1657
+ "$schema",
1658
+ "$ref",
1659
+ "$defs",
1660
+ "$id",
1661
+ "$anchor",
1662
+ "$dynamicRef",
1663
+ "$dynamicAnchor",
1664
+ "$vocabulary",
1665
+ "$comment",
1666
+ "type",
1667
+ "enum",
1668
+ "const",
1669
+ "properties",
1670
+ "patternProperties",
1671
+ "additionalProperties",
1672
+ "required",
1673
+ "items",
1674
+ "prefixItems",
1675
+ "additionalItems",
1676
+ "contains",
1677
+ "allOf",
1678
+ "oneOf",
1679
+ "anyOf",
1680
+ "not",
1681
+ "if",
1682
+ "then",
1683
+ "else",
1684
+ "minimum",
1685
+ "maximum",
1686
+ "exclusiveMinimum",
1687
+ "exclusiveMaximum",
1688
+ "multipleOf",
1689
+ "minLength",
1690
+ "maxLength",
1691
+ "pattern",
1692
+ "minItems",
1693
+ "maxItems",
1694
+ "uniqueItems",
1695
+ "minProperties",
1696
+ "maxProperties",
1697
+ "minContains",
1698
+ "maxContains",
1699
+ "format",
1700
+ "title",
1701
+ "description",
1702
+ "default",
1703
+ "deprecated",
1704
+ "readOnly",
1705
+ "writeOnly",
1706
+ "examples",
1707
+ "dependentRequired",
1708
+ "dependentSchemas",
1709
+ "propertyNames",
1710
+ "unevaluatedItems",
1711
+ "unevaluatedProperties",
1712
+ "contentEncoding",
1713
+ "contentMediaType",
1714
+ "contentSchema"
1715
+ ]);
1552
1716
  }
1553
1717
  });
1554
1718
 
1555
1719
  // src/json-schema/generator.ts
1556
1720
  function generateJsonSchema(form, options) {
1721
+ const metadata = options?.metadata;
1722
+ const vendorPrefix = options?.vendorPrefix;
1723
+ const enumSerialization = options?.enumSerialization;
1557
1724
  const ir = canonicalizeChainDSL(
1558
1725
  form,
1559
- options?.metadata !== void 0 ? { metadata: options.metadata } : void 0
1726
+ metadata !== void 0 ? { metadata } : void 0
1560
1727
  );
1561
- const internalOptions = options?.vendorPrefix === void 0 ? void 0 : { vendorPrefix: options.vendorPrefix };
1728
+ const internalOptions = vendorPrefix === void 0 && enumSerialization === void 0 ? void 0 : {
1729
+ ...vendorPrefix !== void 0 && { vendorPrefix },
1730
+ ...enumSerialization !== void 0 && { enumSerialization }
1731
+ };
1562
1732
  return generateJsonSchemaFromIR(ir, internalOptions);
1563
1733
  }
1564
1734
  var init_generator = __esm({
@@ -5606,6 +5776,7 @@ function generateSchemasFromClass(options) {
5606
5776
  {
5607
5777
  extensionRegistry: options.extensionRegistry,
5608
5778
  metadata: options.metadata,
5779
+ enumSerialization: options.enumSerialization,
5609
5780
  vendorPrefix: options.vendorPrefix
5610
5781
  }
5611
5782
  );
@@ -5735,6 +5906,7 @@ function generateSchemasFromDetailedProgramContext(ctx, filePath, typeName, opti
5735
5906
  {
5736
5907
  extensionRegistry: options.extensionRegistry,
5737
5908
  metadata: options.metadata,
5909
+ enumSerialization: options.enumSerialization,
5738
5910
  vendorPrefix: options.vendorPrefix
5739
5911
  }
5740
5912
  );
@@ -5951,7 +6123,7 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5951
6123
  column: 0
5952
6124
  }
5953
6125
  };
5954
- const schema = generateJsonSchemaFromIR(
6126
+ const ir = resolveFormIRMetadata(
5955
6127
  {
5956
6128
  kind: "form-ir",
5957
6129
  name: root.name,
@@ -5962,8 +6134,17 @@ function toStandaloneJsonSchema(root, typeRegistry, options) {
5962
6134
  typeRegistry,
5963
6135
  provenance: syntheticField.provenance
5964
6136
  },
6137
+ {
6138
+ policy: normalizeMetadataPolicy(options?.metadata),
6139
+ surface: "tsdoc",
6140
+ rootLogicalName: root.name
6141
+ }
6142
+ );
6143
+ const schema = generateJsonSchemaFromIR(
6144
+ ir,
5965
6145
  {
5966
6146
  extensionRegistry: options?.extensionRegistry,
6147
+ enumSerialization: options?.enumSerialization,
5967
6148
  vendorPrefix: options?.vendorPrefix
5968
6149
  }
5969
6150
  );
@@ -5990,6 +6171,7 @@ function generateSchemasFromAnalysis(analysis, filePath, options) {
5990
6171
  { file: filePath },
5991
6172
  {
5992
6173
  extensionRegistry: options?.extensionRegistry,
6174
+ enumSerialization: options?.enumSerialization,
5993
6175
  metadata: options?.metadata,
5994
6176
  vendorPrefix: options?.vendorPrefix
5995
6177
  }
@@ -6435,8 +6617,19 @@ function buildFormSchemas(form, options) {
6435
6617
  };
6436
6618
  }
6437
6619
  function writeSchemas(form, options) {
6438
- const { outDir, name = "schema", indent = 2, vendorPrefix, metadata } = options;
6439
- const buildOptions = vendorPrefix === void 0 && metadata === void 0 ? void 0 : { vendorPrefix, metadata };
6620
+ const {
6621
+ outDir,
6622
+ name = "schema",
6623
+ indent = 2,
6624
+ vendorPrefix,
6625
+ enumSerialization,
6626
+ metadata
6627
+ } = options;
6628
+ const buildOptions = vendorPrefix === void 0 && enumSerialization === void 0 && metadata === void 0 ? void 0 : {
6629
+ ...vendorPrefix !== void 0 && { vendorPrefix },
6630
+ ...enumSerialization !== void 0 && { enumSerialization },
6631
+ ...metadata !== void 0 && { metadata }
6632
+ };
6440
6633
  const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
6441
6634
  if (!fs.existsSync(outDir)) {
6442
6635
  fs.mkdirSync(outDir, { recursive: true });
@@ -6481,6 +6674,8 @@ Usage:
6481
6674
  Options:
6482
6675
  -o, --out-dir <dir> Output directory (default: ./generated)
6483
6676
  -n, --name <name> Base name for output files (default: derived from input)
6677
+ --enum-serialization <enum|oneOf>
6678
+ Enum JSON Schema representation (default: enum)
6484
6679
  -h, --help Show this help message
6485
6680
 
6486
6681
  Example:
@@ -6497,6 +6692,7 @@ function parseArgs(args) {
6497
6692
  const positional = [];
6498
6693
  let outDir = "./generated";
6499
6694
  let name = "";
6695
+ let enumSerialization = "enum";
6500
6696
  for (let i = 0; i < args.length; i++) {
6501
6697
  const arg = args[i];
6502
6698
  if (arg === void 0) continue;
@@ -6524,6 +6720,20 @@ function parseArgs(args) {
6524
6720
  i++;
6525
6721
  continue;
6526
6722
  }
6723
+ if (arg === "--enum-serialization") {
6724
+ const nextArg = args[i + 1];
6725
+ if (!nextArg) {
6726
+ console.error("Error: --enum-serialization requires a value");
6727
+ return null;
6728
+ }
6729
+ if (nextArg !== "enum" && nextArg !== "oneOf") {
6730
+ console.error('Error: --enum-serialization must be "enum" or "oneOf"');
6731
+ return null;
6732
+ }
6733
+ enumSerialization = nextArg;
6734
+ i++;
6735
+ continue;
6736
+ }
6527
6737
  if (arg.startsWith("-")) {
6528
6738
  console.error(`Error: Unknown option: ${arg}`);
6529
6739
  return null;
@@ -6543,7 +6753,7 @@ function parseArgs(args) {
6543
6753
  if (!name) {
6544
6754
  name = path3.basename(inputFile, path3.extname(inputFile));
6545
6755
  }
6546
- return { inputFile, outDir, name };
6756
+ return { inputFile, outDir, name, enumSerialization };
6547
6757
  }
6548
6758
  async function main() {
6549
6759
  const args = process.argv.slice(2);
@@ -6551,7 +6761,7 @@ async function main() {
6551
6761
  if (!options) {
6552
6762
  process.exit(1);
6553
6763
  }
6554
- const { inputFile, outDir, name } = options;
6764
+ const { inputFile, outDir, name, enumSerialization } = options;
6555
6765
  const absoluteInput = path3.resolve(process.cwd(), inputFile);
6556
6766
  try {
6557
6767
  const fileUrl = pathToFileURL(absoluteInput).href;
@@ -6568,7 +6778,7 @@ async function main() {
6568
6778
  const { writeSchemas: writeSchemas2 } = await Promise.resolve().then(() => (init_index(), index_exports));
6569
6779
  const { jsonSchemaPath, uiSchemaPath } = writeSchemas2(
6570
6780
  form,
6571
- { outDir, name }
6781
+ { outDir, name, enumSerialization }
6572
6782
  );
6573
6783
  console.log("Generated:");
6574
6784
  console.log(` ${jsonSchemaPath}`);