@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/index.cjs CHANGED
@@ -34,7 +34,9 @@ __export(index_exports, {
34
34
  categorizationSchema: () => categorizationSchema,
35
35
  categorySchema: () => categorySchema,
36
36
  controlSchema: () => controlSchema,
37
+ createExtensionRegistry: () => createExtensionRegistry,
37
38
  generateJsonSchema: () => generateJsonSchema,
39
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
38
40
  generateSchemas: () => generateSchemas,
39
41
  generateSchemasFromClass: () => generateSchemasFromClass,
40
42
  generateUiSchema: () => generateUiSchema,
@@ -236,7 +238,7 @@ function canonicalizeArrayField(field) {
236
238
  const itemsType = {
237
239
  kind: "object",
238
240
  properties: itemProperties,
239
- additionalProperties: false
241
+ additionalProperties: true
240
242
  };
241
243
  const type = { kind: "array", items: itemsType };
242
244
  const constraints = [];
@@ -271,7 +273,7 @@ function canonicalizeObjectField(field) {
271
273
  const type = {
272
274
  kind: "object",
273
275
  properties,
274
- additionalProperties: false
276
+ additionalProperties: true
275
277
  };
276
278
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
277
279
  }
@@ -443,11 +445,21 @@ function wrapInConditional(field, layout, provenance) {
443
445
  }
444
446
 
445
447
  // src/json-schema/ir-generator.ts
446
- function makeContext() {
447
- return { defs: {} };
448
+ function makeContext(options) {
449
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
450
+ if (!vendorPrefix.startsWith("x-")) {
451
+ throw new Error(
452
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
453
+ );
454
+ }
455
+ return {
456
+ defs: {},
457
+ extensionRegistry: options?.extensionRegistry,
458
+ vendorPrefix
459
+ };
448
460
  }
449
- function generateJsonSchemaFromIR(ir) {
450
- const ctx = makeContext();
461
+ function generateJsonSchemaFromIR(ir, options) {
462
+ const ctx = makeContext(options);
451
463
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
452
464
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
453
465
  }
@@ -490,8 +502,70 @@ function collectFields(elements, properties, required, ctx) {
490
502
  }
491
503
  function generateFieldSchema(field, ctx) {
492
504
  const schema = generateTypeNode(field.type, ctx);
493
- applyConstraints(schema, field.constraints);
494
- applyAnnotations(schema, field.annotations);
505
+ const directConstraints = [];
506
+ const pathConstraints = [];
507
+ for (const c of field.constraints) {
508
+ if (c.path) {
509
+ pathConstraints.push(c);
510
+ } else {
511
+ directConstraints.push(c);
512
+ }
513
+ }
514
+ applyConstraints(schema, directConstraints, ctx);
515
+ applyAnnotations(schema, field.annotations, ctx);
516
+ if (pathConstraints.length === 0) {
517
+ return schema;
518
+ }
519
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
520
+ }
521
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
522
+ if (schema.type === "array" && schema.items) {
523
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
524
+ return schema;
525
+ }
526
+ const byTarget = /* @__PURE__ */ new Map();
527
+ for (const c of pathConstraints) {
528
+ const target = c.path?.segments[0];
529
+ if (!target) continue;
530
+ const group = byTarget.get(target) ?? [];
531
+ group.push(c);
532
+ byTarget.set(target, group);
533
+ }
534
+ const propertyOverrides = {};
535
+ for (const [target, constraints] of byTarget) {
536
+ const subSchema = {};
537
+ applyConstraints(subSchema, constraints, ctx);
538
+ propertyOverrides[target] = subSchema;
539
+ }
540
+ if (schema.$ref) {
541
+ const { $ref, ...rest } = schema;
542
+ const refPart = { $ref };
543
+ const overridePart = {
544
+ properties: propertyOverrides,
545
+ ...rest
546
+ };
547
+ return { allOf: [refPart, overridePart] };
548
+ }
549
+ if (schema.type === "object" && schema.properties) {
550
+ const missingOverrides = {};
551
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
552
+ if (schema.properties[target]) {
553
+ Object.assign(schema.properties[target], overrideSchema);
554
+ } else {
555
+ missingOverrides[target] = overrideSchema;
556
+ }
557
+ }
558
+ if (Object.keys(missingOverrides).length === 0) {
559
+ return schema;
560
+ }
561
+ return {
562
+ allOf: [schema, { properties: missingOverrides }]
563
+ };
564
+ }
565
+ if (schema.allOf) {
566
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
567
+ return schema;
568
+ }
495
569
  return schema;
496
570
  }
497
571
  function generateTypeNode(type, ctx) {
@@ -504,6 +578,8 @@ function generateTypeNode(type, ctx) {
504
578
  return generateArrayType(type, ctx);
505
579
  case "object":
506
580
  return generateObjectType(type, ctx);
581
+ case "record":
582
+ return generateRecordType(type, ctx);
507
583
  case "union":
508
584
  return generateUnionType(type, ctx);
509
585
  case "reference":
@@ -511,7 +587,7 @@ function generateTypeNode(type, ctx) {
511
587
  case "dynamic":
512
588
  return generateDynamicType(type);
513
589
  case "custom":
514
- return generateCustomType(type);
590
+ return generateCustomType(type, ctx);
515
591
  default: {
516
592
  const _exhaustive = type;
517
593
  return _exhaustive;
@@ -560,16 +636,27 @@ function generateObjectType(type, ctx) {
560
636
  }
561
637
  return schema;
562
638
  }
639
+ function generateRecordType(type, ctx) {
640
+ return {
641
+ type: "object",
642
+ additionalProperties: generateTypeNode(type.valueType, ctx)
643
+ };
644
+ }
563
645
  function generatePropertySchema(prop, ctx) {
564
646
  const schema = generateTypeNode(prop.type, ctx);
565
- applyConstraints(schema, prop.constraints);
566
- applyAnnotations(schema, prop.annotations);
647
+ applyConstraints(schema, prop.constraints, ctx);
648
+ applyAnnotations(schema, prop.annotations, ctx);
567
649
  return schema;
568
650
  }
569
651
  function generateUnionType(type, ctx) {
570
652
  if (isBooleanUnion(type)) {
571
653
  return { type: "boolean" };
572
654
  }
655
+ if (isNullableUnion(type)) {
656
+ return {
657
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
658
+ };
659
+ }
573
660
  return {
574
661
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
575
662
  };
@@ -579,6 +666,13 @@ function isBooleanUnion(type) {
579
666
  const kinds = type.members.map((m) => m.kind);
580
667
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
581
668
  }
669
+ function isNullableUnion(type) {
670
+ if (type.members.length !== 2) return false;
671
+ const nullCount = type.members.filter(
672
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
673
+ ).length;
674
+ return nullCount === 1;
675
+ }
582
676
  function generateReferenceType(type) {
583
677
  return { $ref: `#/$defs/${type.name}` };
584
678
  }
@@ -599,10 +693,7 @@ function generateDynamicType(type) {
599
693
  "x-formspec-schemaSource": type.sourceKey
600
694
  };
601
695
  }
602
- function generateCustomType(_type) {
603
- return { type: "object" };
604
- }
605
- function applyConstraints(schema, constraints) {
696
+ function applyConstraints(schema, constraints, ctx) {
606
697
  for (const constraint of constraints) {
607
698
  switch (constraint.constraintKind) {
608
699
  case "minimum":
@@ -647,6 +738,7 @@ function applyConstraints(schema, constraints) {
647
738
  case "allowedMembers":
648
739
  break;
649
740
  case "custom":
741
+ applyCustomConstraint(schema, constraint, ctx);
650
742
  break;
651
743
  default: {
652
744
  const _exhaustive = constraint;
@@ -655,7 +747,7 @@ function applyConstraints(schema, constraints) {
655
747
  }
656
748
  }
657
749
  }
658
- function applyAnnotations(schema, annotations) {
750
+ function applyAnnotations(schema, annotations, ctx) {
659
751
  for (const annotation of annotations) {
660
752
  switch (annotation.annotationKind) {
661
753
  case "displayName":
@@ -675,6 +767,7 @@ function applyAnnotations(schema, annotations) {
675
767
  case "formatHint":
676
768
  break;
677
769
  case "custom":
770
+ applyCustomAnnotation(schema, annotation, ctx);
678
771
  break;
679
772
  default: {
680
773
  const _exhaustive = annotation;
@@ -683,11 +776,41 @@ function applyAnnotations(schema, annotations) {
683
776
  }
684
777
  }
685
778
  }
779
+ function generateCustomType(type, ctx) {
780
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
781
+ if (registration === void 0) {
782
+ throw new Error(
783
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
784
+ );
785
+ }
786
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
787
+ }
788
+ function applyCustomConstraint(schema, constraint, ctx) {
789
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
790
+ if (registration === void 0) {
791
+ throw new Error(
792
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
793
+ );
794
+ }
795
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
796
+ }
797
+ function applyCustomAnnotation(schema, annotation, ctx) {
798
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
799
+ if (registration === void 0) {
800
+ throw new Error(
801
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
802
+ );
803
+ }
804
+ if (registration.toJsonSchema === void 0) {
805
+ return;
806
+ }
807
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
808
+ }
686
809
 
687
810
  // src/json-schema/generator.ts
688
- function generateJsonSchema(form) {
811
+ function generateJsonSchema(form, options) {
689
812
  const ir = canonicalizeChainDSL(form);
690
- return generateJsonSchemaFromIR(ir);
813
+ return generateJsonSchemaFromIR(ir, options);
691
814
  }
692
815
 
693
816
  // src/ui-schema/schema.ts
@@ -921,6 +1044,48 @@ function getSchemaExtension(schema, key) {
921
1044
  return schema[key];
922
1045
  }
923
1046
 
1047
+ // src/extensions/registry.ts
1048
+ function createExtensionRegistry(extensions) {
1049
+ const typeMap = /* @__PURE__ */ new Map();
1050
+ const constraintMap = /* @__PURE__ */ new Map();
1051
+ const annotationMap = /* @__PURE__ */ new Map();
1052
+ for (const ext of extensions) {
1053
+ if (ext.types !== void 0) {
1054
+ for (const type of ext.types) {
1055
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
1056
+ if (typeMap.has(qualifiedId)) {
1057
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
1058
+ }
1059
+ typeMap.set(qualifiedId, type);
1060
+ }
1061
+ }
1062
+ if (ext.constraints !== void 0) {
1063
+ for (const constraint of ext.constraints) {
1064
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1065
+ if (constraintMap.has(qualifiedId)) {
1066
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1067
+ }
1068
+ constraintMap.set(qualifiedId, constraint);
1069
+ }
1070
+ }
1071
+ if (ext.annotations !== void 0) {
1072
+ for (const annotation of ext.annotations) {
1073
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1074
+ if (annotationMap.has(qualifiedId)) {
1075
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1076
+ }
1077
+ annotationMap.set(qualifiedId, annotation);
1078
+ }
1079
+ }
1080
+ }
1081
+ return {
1082
+ extensions,
1083
+ findType: (typeId) => typeMap.get(typeId),
1084
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1085
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1086
+ };
1087
+ }
1088
+
924
1089
  // src/json-schema/schema.ts
925
1090
  var import_zod3 = require("zod");
926
1091
  var jsonSchemaTypeSchema = import_zod3.z.enum([
@@ -1060,26 +1225,36 @@ var ts4 = __toESM(require("typescript"), 1);
1060
1225
 
1061
1226
  // src/analyzer/jsdoc-constraints.ts
1062
1227
  var ts3 = __toESM(require("typescript"), 1);
1063
- var import_core4 = require("@formspec/core");
1064
1228
 
1065
1229
  // src/analyzer/tsdoc-parser.ts
1066
1230
  var ts2 = __toESM(require("typescript"), 1);
1067
1231
  var import_tsdoc = require("@microsoft/tsdoc");
1068
1232
  var import_core3 = require("@formspec/core");
1233
+
1234
+ // src/analyzer/json-utils.ts
1235
+ function tryParseJson(text) {
1236
+ try {
1237
+ return JSON.parse(text);
1238
+ } catch {
1239
+ return null;
1240
+ }
1241
+ }
1242
+
1243
+ // src/analyzer/tsdoc-parser.ts
1069
1244
  var NUMERIC_CONSTRAINT_MAP = {
1070
- Minimum: "minimum",
1071
- Maximum: "maximum",
1072
- ExclusiveMinimum: "exclusiveMinimum",
1073
- ExclusiveMaximum: "exclusiveMaximum"
1245
+ minimum: "minimum",
1246
+ maximum: "maximum",
1247
+ exclusiveMinimum: "exclusiveMinimum",
1248
+ exclusiveMaximum: "exclusiveMaximum",
1249
+ multipleOf: "multipleOf"
1074
1250
  };
1075
1251
  var LENGTH_CONSTRAINT_MAP = {
1076
- MinLength: "minLength",
1077
- MaxLength: "maxLength"
1252
+ minLength: "minLength",
1253
+ maxLength: "maxLength",
1254
+ minItems: "minItems",
1255
+ maxItems: "maxItems"
1078
1256
  };
1079
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1080
- function isBuiltinConstraintName(tagName) {
1081
- return tagName in import_core3.BUILTIN_CONSTRAINT_DEFINITIONS;
1082
- }
1257
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1083
1258
  function createFormSpecTSDocConfig() {
1084
1259
  const config = new import_tsdoc.TSDocConfiguration();
1085
1260
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1091,6 +1266,15 @@ function createFormSpecTSDocConfig() {
1091
1266
  })
1092
1267
  );
1093
1268
  }
1269
+ for (const tagName of ["displayName", "description"]) {
1270
+ config.addTagDefinition(
1271
+ new import_tsdoc.TSDocTagDefinition({
1272
+ tagName: "@" + tagName,
1273
+ syntaxKind: import_tsdoc.TSDocTagSyntaxKind.BlockTag,
1274
+ allowMultiple: true
1275
+ })
1276
+ );
1277
+ }
1094
1278
  return config;
1095
1279
  }
1096
1280
  var sharedParser;
@@ -1119,7 +1303,28 @@ function parseTSDocTags(node, file = "") {
1119
1303
  );
1120
1304
  const docComment = parserContext.docComment;
1121
1305
  for (const block of docComment.customBlocks) {
1122
- const tagName = block.blockTag.tagName.substring(1);
1306
+ const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1307
+ if (tagName === "displayName" || tagName === "description") {
1308
+ const text2 = extractBlockText(block).trim();
1309
+ if (text2 === "") continue;
1310
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1311
+ if (tagName === "displayName") {
1312
+ annotations.push({
1313
+ kind: "annotation",
1314
+ annotationKind: "displayName",
1315
+ value: text2,
1316
+ provenance: provenance2
1317
+ });
1318
+ } else {
1319
+ annotations.push({
1320
+ kind: "annotation",
1321
+ annotationKind: "description",
1322
+ value: text2,
1323
+ provenance: provenance2
1324
+ });
1325
+ }
1326
+ continue;
1327
+ }
1123
1328
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1124
1329
  const text = extractBlockText(block).trim();
1125
1330
  if (text === "") continue;
@@ -1140,7 +1345,7 @@ function parseTSDocTags(node, file = "") {
1140
1345
  }
1141
1346
  const jsDocTagsAll = ts2.getJSDocTags(node);
1142
1347
  for (const tag of jsDocTagsAll) {
1143
- const tagName = tag.tagName.text;
1348
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1144
1349
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1145
1350
  const commentText = getTagCommentText(tag);
1146
1351
  if (commentText === void 0 || commentText.trim() === "") continue;
@@ -1151,43 +1356,17 @@ function parseTSDocTags(node, file = "") {
1151
1356
  constraints.push(constraintNode);
1152
1357
  }
1153
1358
  }
1154
- let displayName;
1155
- let description;
1156
- let displayNameTag;
1157
- let descriptionTag;
1158
- for (const tag of jsDocTagsAll) {
1159
- const tagName = tag.tagName.text;
1160
- const commentText = getTagCommentText(tag);
1161
- if (commentText === void 0 || commentText.trim() === "") {
1162
- continue;
1163
- }
1164
- const trimmed = commentText.trim();
1165
- if (tagName === "Field_displayName") {
1166
- displayName = trimmed;
1167
- displayNameTag = tag;
1168
- } else if (tagName === "Field_description") {
1169
- description = trimmed;
1170
- descriptionTag = tag;
1171
- }
1172
- }
1173
- if (displayName !== void 0 && displayNameTag) {
1174
- annotations.push({
1175
- kind: "annotation",
1176
- annotationKind: "displayName",
1177
- value: displayName,
1178
- provenance: provenanceForJSDocTag(displayNameTag, file)
1179
- });
1180
- }
1181
- if (description !== void 0 && descriptionTag) {
1182
- annotations.push({
1183
- kind: "annotation",
1184
- annotationKind: "description",
1185
- value: description,
1186
- provenance: provenanceForJSDocTag(descriptionTag, file)
1187
- });
1188
- }
1189
1359
  return { constraints, annotations };
1190
1360
  }
1361
+ function extractPathTarget(text) {
1362
+ const trimmed = text.trimStart();
1363
+ const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1364
+ if (!match?.[1] || !match[2]) return null;
1365
+ return {
1366
+ path: { segments: [match[1]] },
1367
+ remainingText: match[2]
1368
+ };
1369
+ }
1191
1370
  function extractBlockText(block) {
1192
1371
  return extractPlainText(block.content);
1193
1372
  }
@@ -1207,12 +1386,15 @@ function extractPlainText(node) {
1207
1386
  return result;
1208
1387
  }
1209
1388
  function parseConstraintValue(tagName, text, provenance) {
1210
- if (!isBuiltinConstraintName(tagName)) {
1389
+ if (!(0, import_core3.isBuiltinConstraintName)(tagName)) {
1211
1390
  return null;
1212
1391
  }
1392
+ const pathResult = extractPathTarget(text);
1393
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1394
+ const path3 = pathResult?.path;
1213
1395
  const expectedType = import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1214
1396
  if (expectedType === "number") {
1215
- const value = Number(text);
1397
+ const value = Number(effectiveText);
1216
1398
  if (Number.isNaN(value)) {
1217
1399
  return null;
1218
1400
  }
@@ -1222,6 +1404,7 @@ function parseConstraintValue(tagName, text, provenance) {
1222
1404
  kind: "constraint",
1223
1405
  constraintKind: numericKind,
1224
1406
  value,
1407
+ ...path3 && { path: path3 },
1225
1408
  provenance
1226
1409
  };
1227
1410
  }
@@ -1231,42 +1414,41 @@ function parseConstraintValue(tagName, text, provenance) {
1231
1414
  kind: "constraint",
1232
1415
  constraintKind: lengthKind,
1233
1416
  value,
1417
+ ...path3 && { path: path3 },
1234
1418
  provenance
1235
1419
  };
1236
1420
  }
1237
1421
  return null;
1238
1422
  }
1239
1423
  if (expectedType === "json") {
1240
- try {
1241
- const parsed = JSON.parse(text);
1242
- if (!Array.isArray(parsed)) {
1243
- return null;
1244
- }
1245
- const members = [];
1246
- for (const item of parsed) {
1247
- if (typeof item === "string" || typeof item === "number") {
1248
- members.push(item);
1249
- } else if (typeof item === "object" && item !== null && "id" in item) {
1250
- const id = item["id"];
1251
- if (typeof id === "string" || typeof id === "number") {
1252
- members.push(id);
1253
- }
1424
+ const parsed = tryParseJson(effectiveText);
1425
+ if (!Array.isArray(parsed)) {
1426
+ return null;
1427
+ }
1428
+ const members = [];
1429
+ for (const item of parsed) {
1430
+ if (typeof item === "string" || typeof item === "number") {
1431
+ members.push(item);
1432
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1433
+ const id = item["id"];
1434
+ if (typeof id === "string" || typeof id === "number") {
1435
+ members.push(id);
1254
1436
  }
1255
1437
  }
1256
- return {
1257
- kind: "constraint",
1258
- constraintKind: "allowedMembers",
1259
- members,
1260
- provenance
1261
- };
1262
- } catch {
1263
- return null;
1264
1438
  }
1439
+ return {
1440
+ kind: "constraint",
1441
+ constraintKind: "allowedMembers",
1442
+ members,
1443
+ ...path3 && { path: path3 },
1444
+ provenance
1445
+ };
1265
1446
  }
1266
1447
  return {
1267
1448
  kind: "constraint",
1268
1449
  constraintKind: "pattern",
1269
- pattern: text,
1450
+ pattern: effectiveText,
1451
+ ...path3 && { path: path3 },
1270
1452
  provenance
1271
1453
  };
1272
1454
  }
@@ -1438,18 +1620,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1438
1620
  const tsType = checker.getTypeAtLocation(prop);
1439
1621
  const optional = prop.questionToken !== void 0;
1440
1622
  const provenance = provenanceForNode(prop, file);
1441
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1623
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1442
1624
  const constraints = [];
1443
1625
  if (prop.type) {
1444
1626
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1445
1627
  }
1446
1628
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1447
- const annotations = [];
1629
+ let annotations = [];
1448
1630
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1449
1631
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1450
1632
  if (defaultAnnotation) {
1451
1633
  annotations.push(defaultAnnotation);
1452
1634
  }
1635
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1453
1636
  return {
1454
1637
  kind: "field",
1455
1638
  name,
@@ -1468,14 +1651,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1468
1651
  const tsType = checker.getTypeAtLocation(prop);
1469
1652
  const optional = prop.questionToken !== void 0;
1470
1653
  const provenance = provenanceForNode(prop, file);
1471
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1654
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1472
1655
  const constraints = [];
1473
1656
  if (prop.type) {
1474
1657
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1475
1658
  }
1476
1659
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1477
- const annotations = [];
1660
+ let annotations = [];
1478
1661
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1662
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1479
1663
  return {
1480
1664
  kind: "field",
1481
1665
  name,
@@ -1486,6 +1670,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1486
1670
  provenance
1487
1671
  };
1488
1672
  }
1673
+ function applyEnumMemberDisplayNames(type, annotations) {
1674
+ if (!annotations.some(
1675
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1676
+ )) {
1677
+ return { type, annotations: [...annotations] };
1678
+ }
1679
+ const consumed = /* @__PURE__ */ new Set();
1680
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1681
+ if (consumed.size === 0) {
1682
+ return { type, annotations: [...annotations] };
1683
+ }
1684
+ return {
1685
+ type: nextType,
1686
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1687
+ };
1688
+ }
1689
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1690
+ switch (type.kind) {
1691
+ case "enum":
1692
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1693
+ case "union": {
1694
+ return {
1695
+ ...type,
1696
+ members: type.members.map(
1697
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1698
+ )
1699
+ };
1700
+ }
1701
+ default:
1702
+ return type;
1703
+ }
1704
+ }
1705
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1706
+ const displayNames = /* @__PURE__ */ new Map();
1707
+ for (const annotation of annotations) {
1708
+ if (annotation.annotationKind !== "displayName") continue;
1709
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1710
+ if (!parsed) continue;
1711
+ consumed.add(annotation);
1712
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1713
+ if (!member) continue;
1714
+ displayNames.set(String(member.value), parsed.label);
1715
+ }
1716
+ if (displayNames.size === 0) {
1717
+ return type;
1718
+ }
1719
+ return {
1720
+ ...type,
1721
+ members: type.members.map((member) => {
1722
+ const displayName = displayNames.get(String(member.value));
1723
+ return displayName !== void 0 ? { ...member, displayName } : member;
1724
+ })
1725
+ };
1726
+ }
1727
+ function parseEnumMemberDisplayName(value) {
1728
+ const trimmed = value.trim();
1729
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1730
+ if (!match?.[1] || !match[2]) return null;
1731
+ const label = match[2].trim();
1732
+ if (label === "") return null;
1733
+ return { value: match[1], label };
1734
+ }
1489
1735
  function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1490
1736
  if (type.flags & ts4.TypeFlags.String) {
1491
1737
  return { kind: "primitive", primitiveKind: "string" };
@@ -1596,7 +1842,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1596
1842
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1597
1843
  return { kind: "array", items };
1598
1844
  }
1845
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1846
+ if (type.getProperties().length > 0) {
1847
+ return null;
1848
+ }
1849
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
1850
+ if (!indexInfo) {
1851
+ return null;
1852
+ }
1853
+ if (visiting.has(type)) {
1854
+ return null;
1855
+ }
1856
+ visiting.add(type);
1857
+ try {
1858
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1859
+ return { kind: "record", valueType };
1860
+ } finally {
1861
+ visiting.delete(type);
1862
+ }
1863
+ }
1599
1864
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1865
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1866
+ if (recordNode) {
1867
+ return recordNode;
1868
+ }
1600
1869
  if (visiting.has(type)) {
1601
1870
  return { kind: "object", properties: [], additionalProperties: false };
1602
1871
  }
@@ -1628,7 +1897,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1628
1897
  const objectNode = {
1629
1898
  kind: "object",
1630
1899
  properties,
1631
- additionalProperties: false
1900
+ additionalProperties: true
1632
1901
  };
1633
1902
  if (typeName) {
1634
1903
  typeRegistry[typeName] = {
@@ -1697,14 +1966,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1697
1966
  }
1698
1967
  return map;
1699
1968
  }
1700
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
1969
+ var MAX_ALIAS_CHAIN_DEPTH = 8;
1970
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1701
1971
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
1972
+ if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1973
+ const aliasName = typeNode.typeName.getText();
1974
+ throw new Error(
1975
+ `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.`
1976
+ );
1977
+ }
1702
1978
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1703
1979
  if (!symbol?.declarations) return [];
1704
1980
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1705
1981
  if (!aliasDecl) return [];
1706
1982
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1707
- return extractJSDocConstraintNodes(aliasDecl, file);
1983
+ const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1984
+ constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1985
+ return constraints;
1708
1986
  }
1709
1987
  function provenanceForNode(node, file) {
1710
1988
  const sourceFile = node.getSourceFile();
@@ -1822,15 +2100,19 @@ function generateSchemas(options) {
1822
2100
  }
1823
2101
 
1824
2102
  // src/index.ts
1825
- function buildFormSchemas(form) {
2103
+ function buildFormSchemas(form, options) {
1826
2104
  return {
1827
- jsonSchema: generateJsonSchema(form),
2105
+ jsonSchema: generateJsonSchema(form, options),
1828
2106
  uiSchema: generateUiSchema(form)
1829
2107
  };
1830
2108
  }
1831
2109
  function writeSchemas(form, options) {
1832
- const { outDir, name = "schema", indent = 2 } = options;
1833
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2110
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2111
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2112
+ extensionRegistry,
2113
+ vendorPrefix
2114
+ };
2115
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
1834
2116
  if (!fs.existsSync(outDir)) {
1835
2117
  fs.mkdirSync(outDir, { recursive: true });
1836
2118
  }
@@ -1846,7 +2128,9 @@ function writeSchemas(form, options) {
1846
2128
  categorizationSchema,
1847
2129
  categorySchema,
1848
2130
  controlSchema,
2131
+ createExtensionRegistry,
1849
2132
  generateJsonSchema,
2133
+ generateJsonSchemaFromIR,
1850
2134
  generateSchemas,
1851
2135
  generateSchemasFromClass,
1852
2136
  generateUiSchema,