@formspec/build 0.1.0-alpha.13 → 0.1.0-alpha.15

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 (71) hide show
  1. package/README.md +20 -20
  2. package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
  3. package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
  4. package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
  5. package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
  6. package/dist/__tests__/fixtures/alias-chains.d.ts +37 -0
  7. package/dist/__tests__/fixtures/alias-chains.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
  9. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  10. package/dist/__tests__/fixtures/example-a-builtins.d.ts +13 -13
  11. package/dist/__tests__/fixtures/example-interface-types.d.ts +33 -33
  12. package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
  13. package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -5
  14. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
  15. package/dist/__tests__/json-utils.test.d.ts +5 -0
  16. package/dist/__tests__/json-utils.test.d.ts.map +1 -0
  17. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
  18. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
  19. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
  20. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
  21. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
  22. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
  23. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
  24. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
  25. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
  26. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
  27. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
  28. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
  29. package/dist/__tests__/parity/utils.d.ts +6 -1
  30. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  31. package/dist/__tests__/path-target-parser.test.d.ts +9 -0
  32. package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
  33. package/dist/analyzer/class-analyzer.d.ts +1 -1
  34. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  35. package/dist/analyzer/jsdoc-constraints.d.ts +8 -52
  36. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  37. package/dist/analyzer/json-utils.d.ts +22 -0
  38. package/dist/analyzer/json-utils.d.ts.map +1 -0
  39. package/dist/analyzer/tsdoc-parser.d.ts +24 -12
  40. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  41. package/dist/browser.cjs +452 -94
  42. package/dist/browser.cjs.map +1 -1
  43. package/dist/browser.d.ts +15 -2
  44. package/dist/browser.d.ts.map +1 -1
  45. package/dist/browser.js +450 -94
  46. package/dist/browser.js.map +1 -1
  47. package/dist/build.d.ts +132 -5
  48. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
  49. package/dist/cli.cjs +406 -104
  50. package/dist/cli.cjs.map +1 -1
  51. package/dist/cli.js +407 -104
  52. package/dist/cli.js.map +1 -1
  53. package/dist/index.cjs +386 -102
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.ts +20 -3
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +386 -104
  58. package/dist/index.js.map +1 -1
  59. package/dist/internals.cjs +597 -172
  60. package/dist/internals.cjs.map +1 -1
  61. package/dist/internals.js +597 -172
  62. package/dist/internals.js.map +1 -1
  63. package/dist/json-schema/generator.d.ts +8 -2
  64. package/dist/json-schema/generator.d.ts.map +1 -1
  65. package/dist/json-schema/ir-generator.d.ts +25 -2
  66. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  67. package/dist/json-schema/types.d.ts +1 -1
  68. package/dist/json-schema/types.d.ts.map +1 -1
  69. package/dist/validate/constraint-validator.d.ts +3 -7
  70. package/dist/validate/constraint-validator.d.ts.map +1 -1
  71. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -202,7 +202,7 @@ function canonicalizeArrayField(field) {
202
202
  const itemsType = {
203
203
  kind: "object",
204
204
  properties: itemProperties,
205
- additionalProperties: false
205
+ additionalProperties: true
206
206
  };
207
207
  const type = { kind: "array", items: itemsType };
208
208
  const constraints = [];
@@ -237,7 +237,7 @@ function canonicalizeObjectField(field) {
237
237
  const type = {
238
238
  kind: "object",
239
239
  properties,
240
- additionalProperties: false
240
+ additionalProperties: true
241
241
  };
242
242
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
243
243
  }
@@ -437,11 +437,21 @@ var init_canonicalize = __esm({
437
437
  });
438
438
 
439
439
  // src/json-schema/ir-generator.ts
440
- function makeContext() {
441
- return { defs: {} };
440
+ function makeContext(options) {
441
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
442
+ if (!vendorPrefix.startsWith("x-")) {
443
+ throw new Error(
444
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
445
+ );
446
+ }
447
+ return {
448
+ defs: {},
449
+ extensionRegistry: options?.extensionRegistry,
450
+ vendorPrefix
451
+ };
442
452
  }
443
- function generateJsonSchemaFromIR(ir) {
444
- const ctx = makeContext();
453
+ function generateJsonSchemaFromIR(ir, options) {
454
+ const ctx = makeContext(options);
445
455
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
446
456
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
447
457
  }
@@ -484,8 +494,70 @@ function collectFields(elements, properties, required, ctx) {
484
494
  }
485
495
  function generateFieldSchema(field, ctx) {
486
496
  const schema = generateTypeNode(field.type, ctx);
487
- applyConstraints(schema, field.constraints);
488
- applyAnnotations(schema, field.annotations);
497
+ const directConstraints = [];
498
+ const pathConstraints = [];
499
+ for (const c of field.constraints) {
500
+ if (c.path) {
501
+ pathConstraints.push(c);
502
+ } else {
503
+ directConstraints.push(c);
504
+ }
505
+ }
506
+ applyConstraints(schema, directConstraints, ctx);
507
+ applyAnnotations(schema, field.annotations, ctx);
508
+ if (pathConstraints.length === 0) {
509
+ return schema;
510
+ }
511
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
512
+ }
513
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
514
+ if (schema.type === "array" && schema.items) {
515
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
516
+ return schema;
517
+ }
518
+ const byTarget = /* @__PURE__ */ new Map();
519
+ for (const c of pathConstraints) {
520
+ const target = c.path?.segments[0];
521
+ if (!target) continue;
522
+ const group = byTarget.get(target) ?? [];
523
+ group.push(c);
524
+ byTarget.set(target, group);
525
+ }
526
+ const propertyOverrides = {};
527
+ for (const [target, constraints] of byTarget) {
528
+ const subSchema = {};
529
+ applyConstraints(subSchema, constraints, ctx);
530
+ propertyOverrides[target] = subSchema;
531
+ }
532
+ if (schema.$ref) {
533
+ const { $ref, ...rest } = schema;
534
+ const refPart = { $ref };
535
+ const overridePart = {
536
+ properties: propertyOverrides,
537
+ ...rest
538
+ };
539
+ return { allOf: [refPart, overridePart] };
540
+ }
541
+ if (schema.type === "object" && schema.properties) {
542
+ const missingOverrides = {};
543
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
544
+ if (schema.properties[target]) {
545
+ Object.assign(schema.properties[target], overrideSchema);
546
+ } else {
547
+ missingOverrides[target] = overrideSchema;
548
+ }
549
+ }
550
+ if (Object.keys(missingOverrides).length === 0) {
551
+ return schema;
552
+ }
553
+ return {
554
+ allOf: [schema, { properties: missingOverrides }]
555
+ };
556
+ }
557
+ if (schema.allOf) {
558
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
559
+ return schema;
560
+ }
489
561
  return schema;
490
562
  }
491
563
  function generateTypeNode(type, ctx) {
@@ -498,6 +570,8 @@ function generateTypeNode(type, ctx) {
498
570
  return generateArrayType(type, ctx);
499
571
  case "object":
500
572
  return generateObjectType(type, ctx);
573
+ case "record":
574
+ return generateRecordType(type, ctx);
501
575
  case "union":
502
576
  return generateUnionType(type, ctx);
503
577
  case "reference":
@@ -505,7 +579,7 @@ function generateTypeNode(type, ctx) {
505
579
  case "dynamic":
506
580
  return generateDynamicType(type);
507
581
  case "custom":
508
- return generateCustomType(type);
582
+ return generateCustomType(type, ctx);
509
583
  default: {
510
584
  const _exhaustive = type;
511
585
  return _exhaustive;
@@ -554,16 +628,27 @@ function generateObjectType(type, ctx) {
554
628
  }
555
629
  return schema;
556
630
  }
631
+ function generateRecordType(type, ctx) {
632
+ return {
633
+ type: "object",
634
+ additionalProperties: generateTypeNode(type.valueType, ctx)
635
+ };
636
+ }
557
637
  function generatePropertySchema(prop, ctx) {
558
638
  const schema = generateTypeNode(prop.type, ctx);
559
- applyConstraints(schema, prop.constraints);
560
- applyAnnotations(schema, prop.annotations);
639
+ applyConstraints(schema, prop.constraints, ctx);
640
+ applyAnnotations(schema, prop.annotations, ctx);
561
641
  return schema;
562
642
  }
563
643
  function generateUnionType(type, ctx) {
564
644
  if (isBooleanUnion(type)) {
565
645
  return { type: "boolean" };
566
646
  }
647
+ if (isNullableUnion(type)) {
648
+ return {
649
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
650
+ };
651
+ }
567
652
  return {
568
653
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
569
654
  };
@@ -573,6 +658,13 @@ function isBooleanUnion(type) {
573
658
  const kinds = type.members.map((m) => m.kind);
574
659
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
575
660
  }
661
+ function isNullableUnion(type) {
662
+ if (type.members.length !== 2) return false;
663
+ const nullCount = type.members.filter(
664
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
665
+ ).length;
666
+ return nullCount === 1;
667
+ }
576
668
  function generateReferenceType(type) {
577
669
  return { $ref: `#/$defs/${type.name}` };
578
670
  }
@@ -593,10 +685,7 @@ function generateDynamicType(type) {
593
685
  "x-formspec-schemaSource": type.sourceKey
594
686
  };
595
687
  }
596
- function generateCustomType(_type) {
597
- return { type: "object" };
598
- }
599
- function applyConstraints(schema, constraints) {
688
+ function applyConstraints(schema, constraints, ctx) {
600
689
  for (const constraint of constraints) {
601
690
  switch (constraint.constraintKind) {
602
691
  case "minimum":
@@ -641,6 +730,7 @@ function applyConstraints(schema, constraints) {
641
730
  case "allowedMembers":
642
731
  break;
643
732
  case "custom":
733
+ applyCustomConstraint(schema, constraint, ctx);
644
734
  break;
645
735
  default: {
646
736
  const _exhaustive = constraint;
@@ -649,7 +739,7 @@ function applyConstraints(schema, constraints) {
649
739
  }
650
740
  }
651
741
  }
652
- function applyAnnotations(schema, annotations) {
742
+ function applyAnnotations(schema, annotations, ctx) {
653
743
  for (const annotation of annotations) {
654
744
  switch (annotation.annotationKind) {
655
745
  case "displayName":
@@ -669,6 +759,7 @@ function applyAnnotations(schema, annotations) {
669
759
  case "formatHint":
670
760
  break;
671
761
  case "custom":
762
+ applyCustomAnnotation(schema, annotation, ctx);
672
763
  break;
673
764
  default: {
674
765
  const _exhaustive = annotation;
@@ -677,6 +768,36 @@ function applyAnnotations(schema, annotations) {
677
768
  }
678
769
  }
679
770
  }
771
+ function generateCustomType(type, ctx) {
772
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
773
+ if (registration === void 0) {
774
+ throw new Error(
775
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
776
+ );
777
+ }
778
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
779
+ }
780
+ function applyCustomConstraint(schema, constraint, ctx) {
781
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
782
+ if (registration === void 0) {
783
+ throw new Error(
784
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
785
+ );
786
+ }
787
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
788
+ }
789
+ function applyCustomAnnotation(schema, annotation, ctx) {
790
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
791
+ if (registration === void 0) {
792
+ throw new Error(
793
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
794
+ );
795
+ }
796
+ if (registration.toJsonSchema === void 0) {
797
+ return;
798
+ }
799
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
800
+ }
680
801
  var init_ir_generator = __esm({
681
802
  "src/json-schema/ir-generator.ts"() {
682
803
  "use strict";
@@ -684,9 +805,9 @@ var init_ir_generator = __esm({
684
805
  });
685
806
 
686
807
  // src/json-schema/generator.ts
687
- function generateJsonSchema(form) {
808
+ function generateJsonSchema(form, options) {
688
809
  const ir = canonicalizeChainDSL(form);
689
- return generateJsonSchemaFromIR(ir);
810
+ return generateJsonSchemaFromIR(ir, options);
690
811
  }
691
812
  var init_generator = __esm({
692
813
  "src/json-schema/generator.ts"() {
@@ -948,6 +1069,61 @@ var init_types = __esm({
948
1069
  }
949
1070
  });
950
1071
 
1072
+ // src/extensions/registry.ts
1073
+ function createExtensionRegistry(extensions) {
1074
+ const typeMap = /* @__PURE__ */ new Map();
1075
+ const constraintMap = /* @__PURE__ */ new Map();
1076
+ const annotationMap = /* @__PURE__ */ new Map();
1077
+ for (const ext of extensions) {
1078
+ if (ext.types !== void 0) {
1079
+ for (const type of ext.types) {
1080
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1081
+ if (typeMap.has(qualifiedId)) {
1082
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1083
+ }
1084
+ typeMap.set(qualifiedId, type);
1085
+ }
1086
+ }
1087
+ if (ext.constraints !== void 0) {
1088
+ for (const constraint of ext.constraints) {
1089
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1090
+ if (constraintMap.has(qualifiedId)) {
1091
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1092
+ }
1093
+ constraintMap.set(qualifiedId, constraint);
1094
+ }
1095
+ }
1096
+ if (ext.annotations !== void 0) {
1097
+ for (const annotation of ext.annotations) {
1098
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1099
+ if (annotationMap.has(qualifiedId)) {
1100
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1101
+ }
1102
+ annotationMap.set(qualifiedId, annotation);
1103
+ }
1104
+ }
1105
+ }
1106
+ return {
1107
+ extensions,
1108
+ findType: (typeId) => typeMap.get(typeId),
1109
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1110
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1111
+ };
1112
+ }
1113
+ var init_registry = __esm({
1114
+ "src/extensions/registry.ts"() {
1115
+ "use strict";
1116
+ }
1117
+ });
1118
+
1119
+ // src/extensions/index.ts
1120
+ var init_extensions = __esm({
1121
+ "src/extensions/index.ts"() {
1122
+ "use strict";
1123
+ init_registry();
1124
+ }
1125
+ });
1126
+
951
1127
  // src/json-schema/schema.ts
952
1128
  var import_zod3, jsonSchemaTypeSchema, jsonSchema7Schema;
953
1129
  var init_schema2 = __esm({
@@ -1094,10 +1270,21 @@ var init_program = __esm({
1094
1270
  }
1095
1271
  });
1096
1272
 
1097
- // src/analyzer/tsdoc-parser.ts
1098
- function isBuiltinConstraintName(tagName) {
1099
- return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
1273
+ // src/analyzer/json-utils.ts
1274
+ function tryParseJson(text) {
1275
+ try {
1276
+ return JSON.parse(text);
1277
+ } catch {
1278
+ return null;
1279
+ }
1100
1280
  }
1281
+ var init_json_utils = __esm({
1282
+ "src/analyzer/json-utils.ts"() {
1283
+ "use strict";
1284
+ }
1285
+ });
1286
+
1287
+ // src/analyzer/tsdoc-parser.ts
1101
1288
  function createFormSpecTSDocConfig() {
1102
1289
  const config = new import_tsdoc.TSDocConfiguration();
1103
1290
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1109,6 +1296,15 @@ function createFormSpecTSDocConfig() {
1109
1296
  })
1110
1297
  );
1111
1298
  }
1299
+ for (const tagName of ["displayName", "description"]) {
1300
+ config.addTagDefinition(
1301
+ new import_tsdoc.TSDocTagDefinition({
1302
+ tagName: "@" + tagName,
1303
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1304
+ allowMultiple: true
1305
+ })
1306
+ );
1307
+ }
1112
1308
  return config;
1113
1309
  }
1114
1310
  function getParser() {
@@ -1136,7 +1332,28 @@ function parseTSDocTags(node, file = "") {
1136
1332
  );
1137
1333
  const docComment = parserContext.docComment;
1138
1334
  for (const block of docComment.customBlocks) {
1139
- const tagName = block.blockTag.tagName.substring(1);
1335
+ const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1336
+ if (tagName === "displayName" || tagName === "description") {
1337
+ const text2 = extractBlockText(block).trim();
1338
+ if (text2 === "") continue;
1339
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1340
+ if (tagName === "displayName") {
1341
+ annotations.push({
1342
+ kind: "annotation",
1343
+ annotationKind: "displayName",
1344
+ value: text2,
1345
+ provenance: provenance2
1346
+ });
1347
+ } else {
1348
+ annotations.push({
1349
+ kind: "annotation",
1350
+ annotationKind: "description",
1351
+ value: text2,
1352
+ provenance: provenance2
1353
+ });
1354
+ }
1355
+ continue;
1356
+ }
1140
1357
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1141
1358
  const text = extractBlockText(block).trim();
1142
1359
  if (text === "") continue;
@@ -1157,7 +1374,7 @@ function parseTSDocTags(node, file = "") {
1157
1374
  }
1158
1375
  const jsDocTagsAll = ts2.getJSDocTags(node);
1159
1376
  for (const tag of jsDocTagsAll) {
1160
- const tagName = tag.tagName.text;
1377
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1161
1378
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1162
1379
  const commentText = getTagCommentText(tag);
1163
1380
  if (commentText === void 0 || commentText.trim() === "") continue;
@@ -1168,43 +1385,17 @@ function parseTSDocTags(node, file = "") {
1168
1385
  constraints.push(constraintNode);
1169
1386
  }
1170
1387
  }
1171
- let displayName;
1172
- let description;
1173
- let displayNameTag;
1174
- let descriptionTag;
1175
- for (const tag of jsDocTagsAll) {
1176
- const tagName = tag.tagName.text;
1177
- const commentText = getTagCommentText(tag);
1178
- if (commentText === void 0 || commentText.trim() === "") {
1179
- continue;
1180
- }
1181
- const trimmed = commentText.trim();
1182
- if (tagName === "Field_displayName") {
1183
- displayName = trimmed;
1184
- displayNameTag = tag;
1185
- } else if (tagName === "Field_description") {
1186
- description = trimmed;
1187
- descriptionTag = tag;
1188
- }
1189
- }
1190
- if (displayName !== void 0 && displayNameTag) {
1191
- annotations.push({
1192
- kind: "annotation",
1193
- annotationKind: "displayName",
1194
- value: displayName,
1195
- provenance: provenanceForJSDocTag(displayNameTag, file)
1196
- });
1197
- }
1198
- if (description !== void 0 && descriptionTag) {
1199
- annotations.push({
1200
- kind: "annotation",
1201
- annotationKind: "description",
1202
- value: description,
1203
- provenance: provenanceForJSDocTag(descriptionTag, file)
1204
- });
1205
- }
1206
1388
  return { constraints, annotations };
1207
1389
  }
1390
+ function extractPathTarget(text) {
1391
+ const trimmed = text.trimStart();
1392
+ const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1393
+ if (!match?.[1] || !match[2]) return null;
1394
+ return {
1395
+ path: { segments: [match[1]] },
1396
+ remainingText: match[2]
1397
+ };
1398
+ }
1208
1399
  function extractBlockText(block) {
1209
1400
  return extractPlainText(block.content);
1210
1401
  }
@@ -1224,12 +1415,15 @@ function extractPlainText(node) {
1224
1415
  return result;
1225
1416
  }
1226
1417
  function parseConstraintValue(tagName, text, provenance) {
1227
- if (!isBuiltinConstraintName(tagName)) {
1418
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1228
1419
  return null;
1229
1420
  }
1421
+ const pathResult = extractPathTarget(text);
1422
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1423
+ const path4 = pathResult?.path;
1230
1424
  const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1231
1425
  if (expectedType === "number") {
1232
- const value = Number(text);
1426
+ const value = Number(effectiveText);
1233
1427
  if (Number.isNaN(value)) {
1234
1428
  return null;
1235
1429
  }
@@ -1239,6 +1433,7 @@ function parseConstraintValue(tagName, text, provenance) {
1239
1433
  kind: "constraint",
1240
1434
  constraintKind: numericKind,
1241
1435
  value,
1436
+ ...path4 && { path: path4 },
1242
1437
  provenance
1243
1438
  };
1244
1439
  }
@@ -1248,42 +1443,41 @@ function parseConstraintValue(tagName, text, provenance) {
1248
1443
  kind: "constraint",
1249
1444
  constraintKind: lengthKind,
1250
1445
  value,
1446
+ ...path4 && { path: path4 },
1251
1447
  provenance
1252
1448
  };
1253
1449
  }
1254
1450
  return null;
1255
1451
  }
1256
1452
  if (expectedType === "json") {
1257
- try {
1258
- const parsed = JSON.parse(text);
1259
- if (!Array.isArray(parsed)) {
1260
- return null;
1261
- }
1262
- const members = [];
1263
- for (const item of parsed) {
1264
- if (typeof item === "string" || typeof item === "number") {
1265
- members.push(item);
1266
- } else if (typeof item === "object" && item !== null && "id" in item) {
1267
- const id = item["id"];
1268
- if (typeof id === "string" || typeof id === "number") {
1269
- members.push(id);
1270
- }
1453
+ const parsed = tryParseJson(effectiveText);
1454
+ if (!Array.isArray(parsed)) {
1455
+ return null;
1456
+ }
1457
+ const members = [];
1458
+ for (const item of parsed) {
1459
+ if (typeof item === "string" || typeof item === "number") {
1460
+ members.push(item);
1461
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1462
+ const id = item["id"];
1463
+ if (typeof id === "string" || typeof id === "number") {
1464
+ members.push(id);
1271
1465
  }
1272
1466
  }
1273
- return {
1274
- kind: "constraint",
1275
- constraintKind: "allowedMembers",
1276
- members,
1277
- provenance
1278
- };
1279
- } catch {
1280
- return null;
1281
1467
  }
1468
+ return {
1469
+ kind: "constraint",
1470
+ constraintKind: "allowedMembers",
1471
+ members,
1472
+ ...path4 && { path: path4 },
1473
+ provenance
1474
+ };
1282
1475
  }
1283
1476
  return {
1284
1477
  kind: "constraint",
1285
1478
  constraintKind: "pattern",
1286
- pattern: text,
1479
+ pattern: effectiveText,
1480
+ ...path4 && { path: path4 },
1287
1481
  provenance
1288
1482
  };
1289
1483
  }
@@ -1324,17 +1518,21 @@ var init_tsdoc_parser = __esm({
1324
1518
  ts2 = __toESM(require("typescript"), 1);
1325
1519
  import_tsdoc = require("@microsoft/tsdoc");
1326
1520
  import_core3 = require("@formspec/core");
1521
+ init_json_utils();
1327
1522
  NUMERIC_CONSTRAINT_MAP = {
1328
- Minimum: "minimum",
1329
- Maximum: "maximum",
1330
- ExclusiveMinimum: "exclusiveMinimum",
1331
- ExclusiveMaximum: "exclusiveMaximum"
1523
+ minimum: "minimum",
1524
+ maximum: "maximum",
1525
+ exclusiveMinimum: "exclusiveMinimum",
1526
+ exclusiveMaximum: "exclusiveMaximum",
1527
+ multipleOf: "multipleOf"
1332
1528
  };
1333
1529
  LENGTH_CONSTRAINT_MAP = {
1334
- MinLength: "minLength",
1335
- MaxLength: "maxLength"
1530
+ minLength: "minLength",
1531
+ maxLength: "maxLength",
1532
+ minItems: "minItems",
1533
+ maxItems: "maxItems"
1336
1534
  };
1337
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1535
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1338
1536
  }
1339
1537
  });
1340
1538
 
@@ -1380,12 +1578,11 @@ function extractDefaultValueAnnotation(initializer, file = "") {
1380
1578
  }
1381
1579
  };
1382
1580
  }
1383
- var ts3, import_core4;
1581
+ var ts3;
1384
1582
  var init_jsdoc_constraints = __esm({
1385
1583
  "src/analyzer/jsdoc-constraints.ts"() {
1386
1584
  "use strict";
1387
1585
  ts3 = __toESM(require("typescript"), 1);
1388
- import_core4 = require("@formspec/core");
1389
1586
  init_tsdoc_parser();
1390
1587
  }
1391
1588
  });
@@ -1484,18 +1681,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1484
1681
  const tsType = checker.getTypeAtLocation(prop);
1485
1682
  const optional = prop.questionToken !== void 0;
1486
1683
  const provenance = provenanceForNode(prop, file);
1487
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1684
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1488
1685
  const constraints = [];
1489
1686
  if (prop.type) {
1490
1687
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1491
1688
  }
1492
1689
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1493
- const annotations = [];
1690
+ let annotations = [];
1494
1691
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1495
1692
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1496
1693
  if (defaultAnnotation) {
1497
1694
  annotations.push(defaultAnnotation);
1498
1695
  }
1696
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1499
1697
  return {
1500
1698
  kind: "field",
1501
1699
  name,
@@ -1514,14 +1712,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1514
1712
  const tsType = checker.getTypeAtLocation(prop);
1515
1713
  const optional = prop.questionToken !== void 0;
1516
1714
  const provenance = provenanceForNode(prop, file);
1517
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1715
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1518
1716
  const constraints = [];
1519
1717
  if (prop.type) {
1520
1718
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1521
1719
  }
1522
1720
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1523
- const annotations = [];
1721
+ let annotations = [];
1524
1722
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1723
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1525
1724
  return {
1526
1725
  kind: "field",
1527
1726
  name,
@@ -1532,6 +1731,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1532
1731
  provenance
1533
1732
  };
1534
1733
  }
1734
+ function applyEnumMemberDisplayNames(type, annotations) {
1735
+ if (!annotations.some(
1736
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1737
+ )) {
1738
+ return { type, annotations: [...annotations] };
1739
+ }
1740
+ const consumed = /* @__PURE__ */ new Set();
1741
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1742
+ if (consumed.size === 0) {
1743
+ return { type, annotations: [...annotations] };
1744
+ }
1745
+ return {
1746
+ type: nextType,
1747
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1748
+ };
1749
+ }
1750
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1751
+ switch (type.kind) {
1752
+ case "enum":
1753
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1754
+ case "union": {
1755
+ return {
1756
+ ...type,
1757
+ members: type.members.map(
1758
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1759
+ )
1760
+ };
1761
+ }
1762
+ default:
1763
+ return type;
1764
+ }
1765
+ }
1766
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1767
+ const displayNames = /* @__PURE__ */ new Map();
1768
+ for (const annotation of annotations) {
1769
+ if (annotation.annotationKind !== "displayName") continue;
1770
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1771
+ if (!parsed) continue;
1772
+ consumed.add(annotation);
1773
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1774
+ if (!member) continue;
1775
+ displayNames.set(String(member.value), parsed.label);
1776
+ }
1777
+ if (displayNames.size === 0) {
1778
+ return type;
1779
+ }
1780
+ return {
1781
+ ...type,
1782
+ members: type.members.map((member) => {
1783
+ const displayName = displayNames.get(String(member.value));
1784
+ return displayName !== void 0 ? { ...member, displayName } : member;
1785
+ })
1786
+ };
1787
+ }
1788
+ function parseEnumMemberDisplayName(value) {
1789
+ const trimmed = value.trim();
1790
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1791
+ if (!match?.[1] || !match[2]) return null;
1792
+ const label = match[2].trim();
1793
+ if (label === "") return null;
1794
+ return { value: match[1], label };
1795
+ }
1535
1796
  function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1536
1797
  if (type.flags & ts4.TypeFlags.String) {
1537
1798
  return { kind: "primitive", primitiveKind: "string" };
@@ -1642,7 +1903,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1642
1903
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1643
1904
  return { kind: "array", items };
1644
1905
  }
1906
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1907
+ if (type.getProperties().length > 0) {
1908
+ return null;
1909
+ }
1910
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
1911
+ if (!indexInfo) {
1912
+ return null;
1913
+ }
1914
+ if (visiting.has(type)) {
1915
+ return null;
1916
+ }
1917
+ visiting.add(type);
1918
+ try {
1919
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1920
+ return { kind: "record", valueType };
1921
+ } finally {
1922
+ visiting.delete(type);
1923
+ }
1924
+ }
1645
1925
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1926
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1927
+ if (recordNode) {
1928
+ return recordNode;
1929
+ }
1646
1930
  if (visiting.has(type)) {
1647
1931
  return { kind: "object", properties: [], additionalProperties: false };
1648
1932
  }
@@ -1674,7 +1958,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1674
1958
  const objectNode = {
1675
1959
  kind: "object",
1676
1960
  properties,
1677
- additionalProperties: false
1961
+ additionalProperties: true
1678
1962
  };
1679
1963
  if (typeName) {
1680
1964
  typeRegistry[typeName] = {
@@ -1743,14 +2027,22 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1743
2027
  }
1744
2028
  return map;
1745
2029
  }
1746
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
2030
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1747
2031
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
2032
+ if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
2033
+ const aliasName = typeNode.typeName.getText();
2034
+ throw new Error(
2035
+ `Type alias chain exceeds maximum depth of ${String(MAX_ALIAS_CHAIN_DEPTH)} at alias "${aliasName}" in ${file}. Simplify the alias chain or check for circular references.`
2036
+ );
2037
+ }
1748
2038
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1749
2039
  if (!symbol?.declarations) return [];
1750
2040
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1751
2041
  if (!aliasDecl) return [];
1752
2042
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1753
- return extractJSDocConstraintNodes(aliasDecl, file);
2043
+ const constraints = extractJSDocConstraintNodes(aliasDecl, file);
2044
+ constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
2045
+ return constraints;
1754
2046
  }
1755
2047
  function provenanceForNode(node, file) {
1756
2048
  const sourceFile = node.getSourceFile();
@@ -1823,12 +2115,13 @@ function detectFormSpecReference(typeNode) {
1823
2115
  }
1824
2116
  return null;
1825
2117
  }
1826
- var ts4;
2118
+ var ts4, MAX_ALIAS_CHAIN_DEPTH;
1827
2119
  var init_class_analyzer = __esm({
1828
2120
  "src/analyzer/class-analyzer.ts"() {
1829
2121
  "use strict";
1830
2122
  ts4 = __toESM(require("typescript"), 1);
1831
2123
  init_jsdoc_constraints();
2124
+ MAX_ALIAS_CHAIN_DEPTH = 8;
1832
2125
  }
1833
2126
  });
1834
2127
 
@@ -1892,7 +2185,9 @@ __export(index_exports, {
1892
2185
  categorizationSchema: () => categorizationSchema,
1893
2186
  categorySchema: () => categorySchema,
1894
2187
  controlSchema: () => controlSchema,
2188
+ createExtensionRegistry: () => createExtensionRegistry,
1895
2189
  generateJsonSchema: () => generateJsonSchema,
2190
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
1896
2191
  generateSchemas: () => generateSchemas,
1897
2192
  generateSchemasFromClass: () => generateSchemasFromClass,
1898
2193
  generateUiSchema: () => generateUiSchema,
@@ -1913,15 +2208,19 @@ __export(index_exports, {
1913
2208
  verticalLayoutSchema: () => verticalLayoutSchema,
1914
2209
  writeSchemas: () => writeSchemas
1915
2210
  });
1916
- function buildFormSchemas(form) {
2211
+ function buildFormSchemas(form, options) {
1917
2212
  return {
1918
- jsonSchema: generateJsonSchema(form),
2213
+ jsonSchema: generateJsonSchema(form, options),
1919
2214
  uiSchema: generateUiSchema(form)
1920
2215
  };
1921
2216
  }
1922
2217
  function writeSchemas(form, options) {
1923
- const { outDir, name = "schema", indent = 2 } = options;
1924
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2218
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2219
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2220
+ extensionRegistry,
2221
+ vendorPrefix
2222
+ };
2223
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
1925
2224
  if (!fs.existsSync(outDir)) {
1926
2225
  fs.mkdirSync(outDir, { recursive: true });
1927
2226
  }
@@ -1937,12 +2236,15 @@ var init_index = __esm({
1937
2236
  "use strict";
1938
2237
  init_generator();
1939
2238
  init_generator2();
2239
+ init_ir_generator();
1940
2240
  fs = __toESM(require("fs"), 1);
1941
2241
  path2 = __toESM(require("path"), 1);
1942
2242
  init_types();
2243
+ init_extensions();
1943
2244
  init_schema();
1944
2245
  init_schema2();
1945
2246
  init_generator();
2247
+ init_ir_generator();
1946
2248
  init_generator2();
1947
2249
  init_class_schema();
1948
2250
  }