@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.js CHANGED
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
177
177
  const itemsType = {
178
178
  kind: "object",
179
179
  properties: itemProperties,
180
- additionalProperties: false
180
+ additionalProperties: true
181
181
  };
182
182
  const type = { kind: "array", items: itemsType };
183
183
  const constraints = [];
@@ -212,7 +212,7 @@ function canonicalizeObjectField(field) {
212
212
  const type = {
213
213
  kind: "object",
214
214
  properties,
215
- additionalProperties: false
215
+ additionalProperties: true
216
216
  };
217
217
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
218
218
  }
@@ -384,11 +384,21 @@ function wrapInConditional(field, layout, provenance) {
384
384
  }
385
385
 
386
386
  // src/json-schema/ir-generator.ts
387
- function makeContext() {
388
- return { defs: {} };
387
+ function makeContext(options) {
388
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
389
+ if (!vendorPrefix.startsWith("x-")) {
390
+ throw new Error(
391
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
392
+ );
393
+ }
394
+ return {
395
+ defs: {},
396
+ extensionRegistry: options?.extensionRegistry,
397
+ vendorPrefix
398
+ };
389
399
  }
390
- function generateJsonSchemaFromIR(ir) {
391
- const ctx = makeContext();
400
+ function generateJsonSchemaFromIR(ir, options) {
401
+ const ctx = makeContext(options);
392
402
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
393
403
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
394
404
  }
@@ -431,8 +441,70 @@ function collectFields(elements, properties, required, ctx) {
431
441
  }
432
442
  function generateFieldSchema(field, ctx) {
433
443
  const schema = generateTypeNode(field.type, ctx);
434
- applyConstraints(schema, field.constraints);
435
- applyAnnotations(schema, field.annotations);
444
+ const directConstraints = [];
445
+ const pathConstraints = [];
446
+ for (const c of field.constraints) {
447
+ if (c.path) {
448
+ pathConstraints.push(c);
449
+ } else {
450
+ directConstraints.push(c);
451
+ }
452
+ }
453
+ applyConstraints(schema, directConstraints, ctx);
454
+ applyAnnotations(schema, field.annotations, ctx);
455
+ if (pathConstraints.length === 0) {
456
+ return schema;
457
+ }
458
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
459
+ }
460
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
461
+ if (schema.type === "array" && schema.items) {
462
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
463
+ return schema;
464
+ }
465
+ const byTarget = /* @__PURE__ */ new Map();
466
+ for (const c of pathConstraints) {
467
+ const target = c.path?.segments[0];
468
+ if (!target) continue;
469
+ const group = byTarget.get(target) ?? [];
470
+ group.push(c);
471
+ byTarget.set(target, group);
472
+ }
473
+ const propertyOverrides = {};
474
+ for (const [target, constraints] of byTarget) {
475
+ const subSchema = {};
476
+ applyConstraints(subSchema, constraints, ctx);
477
+ propertyOverrides[target] = subSchema;
478
+ }
479
+ if (schema.$ref) {
480
+ const { $ref, ...rest } = schema;
481
+ const refPart = { $ref };
482
+ const overridePart = {
483
+ properties: propertyOverrides,
484
+ ...rest
485
+ };
486
+ return { allOf: [refPart, overridePart] };
487
+ }
488
+ if (schema.type === "object" && schema.properties) {
489
+ const missingOverrides = {};
490
+ for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
491
+ if (schema.properties[target]) {
492
+ Object.assign(schema.properties[target], overrideSchema);
493
+ } else {
494
+ missingOverrides[target] = overrideSchema;
495
+ }
496
+ }
497
+ if (Object.keys(missingOverrides).length === 0) {
498
+ return schema;
499
+ }
500
+ return {
501
+ allOf: [schema, { properties: missingOverrides }]
502
+ };
503
+ }
504
+ if (schema.allOf) {
505
+ schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
506
+ return schema;
507
+ }
436
508
  return schema;
437
509
  }
438
510
  function generateTypeNode(type, ctx) {
@@ -445,6 +517,8 @@ function generateTypeNode(type, ctx) {
445
517
  return generateArrayType(type, ctx);
446
518
  case "object":
447
519
  return generateObjectType(type, ctx);
520
+ case "record":
521
+ return generateRecordType(type, ctx);
448
522
  case "union":
449
523
  return generateUnionType(type, ctx);
450
524
  case "reference":
@@ -452,7 +526,7 @@ function generateTypeNode(type, ctx) {
452
526
  case "dynamic":
453
527
  return generateDynamicType(type);
454
528
  case "custom":
455
- return generateCustomType(type);
529
+ return generateCustomType(type, ctx);
456
530
  default: {
457
531
  const _exhaustive = type;
458
532
  return _exhaustive;
@@ -501,16 +575,27 @@ function generateObjectType(type, ctx) {
501
575
  }
502
576
  return schema;
503
577
  }
578
+ function generateRecordType(type, ctx) {
579
+ return {
580
+ type: "object",
581
+ additionalProperties: generateTypeNode(type.valueType, ctx)
582
+ };
583
+ }
504
584
  function generatePropertySchema(prop, ctx) {
505
585
  const schema = generateTypeNode(prop.type, ctx);
506
- applyConstraints(schema, prop.constraints);
507
- applyAnnotations(schema, prop.annotations);
586
+ applyConstraints(schema, prop.constraints, ctx);
587
+ applyAnnotations(schema, prop.annotations, ctx);
508
588
  return schema;
509
589
  }
510
590
  function generateUnionType(type, ctx) {
511
591
  if (isBooleanUnion(type)) {
512
592
  return { type: "boolean" };
513
593
  }
594
+ if (isNullableUnion(type)) {
595
+ return {
596
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
597
+ };
598
+ }
514
599
  return {
515
600
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
516
601
  };
@@ -520,6 +605,13 @@ function isBooleanUnion(type) {
520
605
  const kinds = type.members.map((m) => m.kind);
521
606
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
522
607
  }
608
+ function isNullableUnion(type) {
609
+ if (type.members.length !== 2) return false;
610
+ const nullCount = type.members.filter(
611
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
612
+ ).length;
613
+ return nullCount === 1;
614
+ }
523
615
  function generateReferenceType(type) {
524
616
  return { $ref: `#/$defs/${type.name}` };
525
617
  }
@@ -540,10 +632,7 @@ function generateDynamicType(type) {
540
632
  "x-formspec-schemaSource": type.sourceKey
541
633
  };
542
634
  }
543
- function generateCustomType(_type) {
544
- return { type: "object" };
545
- }
546
- function applyConstraints(schema, constraints) {
635
+ function applyConstraints(schema, constraints, ctx) {
547
636
  for (const constraint of constraints) {
548
637
  switch (constraint.constraintKind) {
549
638
  case "minimum":
@@ -588,6 +677,7 @@ function applyConstraints(schema, constraints) {
588
677
  case "allowedMembers":
589
678
  break;
590
679
  case "custom":
680
+ applyCustomConstraint(schema, constraint, ctx);
591
681
  break;
592
682
  default: {
593
683
  const _exhaustive = constraint;
@@ -596,7 +686,7 @@ function applyConstraints(schema, constraints) {
596
686
  }
597
687
  }
598
688
  }
599
- function applyAnnotations(schema, annotations) {
689
+ function applyAnnotations(schema, annotations, ctx) {
600
690
  for (const annotation of annotations) {
601
691
  switch (annotation.annotationKind) {
602
692
  case "displayName":
@@ -616,6 +706,7 @@ function applyAnnotations(schema, annotations) {
616
706
  case "formatHint":
617
707
  break;
618
708
  case "custom":
709
+ applyCustomAnnotation(schema, annotation, ctx);
619
710
  break;
620
711
  default: {
621
712
  const _exhaustive = annotation;
@@ -624,11 +715,41 @@ function applyAnnotations(schema, annotations) {
624
715
  }
625
716
  }
626
717
  }
718
+ function generateCustomType(type, ctx) {
719
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
720
+ if (registration === void 0) {
721
+ throw new Error(
722
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
723
+ );
724
+ }
725
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
726
+ }
727
+ function applyCustomConstraint(schema, constraint, ctx) {
728
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
729
+ if (registration === void 0) {
730
+ throw new Error(
731
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
732
+ );
733
+ }
734
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
735
+ }
736
+ function applyCustomAnnotation(schema, annotation, ctx) {
737
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
738
+ if (registration === void 0) {
739
+ throw new Error(
740
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
741
+ );
742
+ }
743
+ if (registration.toJsonSchema === void 0) {
744
+ return;
745
+ }
746
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
747
+ }
627
748
 
628
749
  // src/json-schema/generator.ts
629
- function generateJsonSchema(form) {
750
+ function generateJsonSchema(form, options) {
630
751
  const ir = canonicalizeChainDSL(form);
631
- return generateJsonSchemaFromIR(ir);
752
+ return generateJsonSchemaFromIR(ir, options);
632
753
  }
633
754
 
634
755
  // src/ui-schema/schema.ts
@@ -862,6 +983,48 @@ function getSchemaExtension(schema, key) {
862
983
  return schema[key];
863
984
  }
864
985
 
986
+ // src/extensions/registry.ts
987
+ function createExtensionRegistry(extensions) {
988
+ const typeMap = /* @__PURE__ */ new Map();
989
+ const constraintMap = /* @__PURE__ */ new Map();
990
+ const annotationMap = /* @__PURE__ */ new Map();
991
+ for (const ext of extensions) {
992
+ if (ext.types !== void 0) {
993
+ for (const type of ext.types) {
994
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
995
+ if (typeMap.has(qualifiedId)) {
996
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
997
+ }
998
+ typeMap.set(qualifiedId, type);
999
+ }
1000
+ }
1001
+ if (ext.constraints !== void 0) {
1002
+ for (const constraint of ext.constraints) {
1003
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
1004
+ if (constraintMap.has(qualifiedId)) {
1005
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
1006
+ }
1007
+ constraintMap.set(qualifiedId, constraint);
1008
+ }
1009
+ }
1010
+ if (ext.annotations !== void 0) {
1011
+ for (const annotation of ext.annotations) {
1012
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
1013
+ if (annotationMap.has(qualifiedId)) {
1014
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
1015
+ }
1016
+ annotationMap.set(qualifiedId, annotation);
1017
+ }
1018
+ }
1019
+ }
1020
+ return {
1021
+ extensions,
1022
+ findType: (typeId) => typeMap.get(typeId),
1023
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
1024
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1025
+ };
1026
+ }
1027
+
865
1028
  // src/json-schema/schema.ts
866
1029
  import { z as z3 } from "zod";
867
1030
  var jsonSchemaTypeSchema = z3.enum([
@@ -1001,9 +1164,6 @@ import * as ts4 from "typescript";
1001
1164
 
1002
1165
  // src/analyzer/jsdoc-constraints.ts
1003
1166
  import * as ts3 from "typescript";
1004
- import {
1005
- BUILTIN_CONSTRAINT_DEFINITIONS as BUILTIN_CONSTRAINT_DEFINITIONS2
1006
- } from "@formspec/core";
1007
1167
 
1008
1168
  // src/analyzer/tsdoc-parser.ts
1009
1169
  import * as ts2 from "typescript";
@@ -1017,22 +1177,35 @@ import {
1017
1177
  TextRange
1018
1178
  } from "@microsoft/tsdoc";
1019
1179
  import {
1020
- BUILTIN_CONSTRAINT_DEFINITIONS
1180
+ BUILTIN_CONSTRAINT_DEFINITIONS,
1181
+ normalizeConstraintTagName,
1182
+ isBuiltinConstraintName
1021
1183
  } from "@formspec/core";
1184
+
1185
+ // src/analyzer/json-utils.ts
1186
+ function tryParseJson(text) {
1187
+ try {
1188
+ return JSON.parse(text);
1189
+ } catch {
1190
+ return null;
1191
+ }
1192
+ }
1193
+
1194
+ // src/analyzer/tsdoc-parser.ts
1022
1195
  var NUMERIC_CONSTRAINT_MAP = {
1023
- Minimum: "minimum",
1024
- Maximum: "maximum",
1025
- ExclusiveMinimum: "exclusiveMinimum",
1026
- ExclusiveMaximum: "exclusiveMaximum"
1196
+ minimum: "minimum",
1197
+ maximum: "maximum",
1198
+ exclusiveMinimum: "exclusiveMinimum",
1199
+ exclusiveMaximum: "exclusiveMaximum",
1200
+ multipleOf: "multipleOf"
1027
1201
  };
1028
1202
  var LENGTH_CONSTRAINT_MAP = {
1029
- MinLength: "minLength",
1030
- MaxLength: "maxLength"
1203
+ minLength: "minLength",
1204
+ maxLength: "maxLength",
1205
+ minItems: "minItems",
1206
+ maxItems: "maxItems"
1031
1207
  };
1032
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["Pattern", "EnumOptions"]);
1033
- function isBuiltinConstraintName(tagName) {
1034
- return tagName in BUILTIN_CONSTRAINT_DEFINITIONS;
1035
- }
1208
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1036
1209
  function createFormSpecTSDocConfig() {
1037
1210
  const config = new TSDocConfiguration();
1038
1211
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1044,6 +1217,15 @@ function createFormSpecTSDocConfig() {
1044
1217
  })
1045
1218
  );
1046
1219
  }
1220
+ for (const tagName of ["displayName", "description"]) {
1221
+ config.addTagDefinition(
1222
+ new TSDocTagDefinition({
1223
+ tagName: "@" + tagName,
1224
+ syntaxKind: TSDocTagSyntaxKind.BlockTag,
1225
+ allowMultiple: true
1226
+ })
1227
+ );
1228
+ }
1047
1229
  return config;
1048
1230
  }
1049
1231
  var sharedParser;
@@ -1072,7 +1254,28 @@ function parseTSDocTags(node, file = "") {
1072
1254
  );
1073
1255
  const docComment = parserContext.docComment;
1074
1256
  for (const block of docComment.customBlocks) {
1075
- const tagName = block.blockTag.tagName.substring(1);
1257
+ const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1258
+ if (tagName === "displayName" || tagName === "description") {
1259
+ const text2 = extractBlockText(block).trim();
1260
+ if (text2 === "") continue;
1261
+ const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1262
+ if (tagName === "displayName") {
1263
+ annotations.push({
1264
+ kind: "annotation",
1265
+ annotationKind: "displayName",
1266
+ value: text2,
1267
+ provenance: provenance2
1268
+ });
1269
+ } else {
1270
+ annotations.push({
1271
+ kind: "annotation",
1272
+ annotationKind: "description",
1273
+ value: text2,
1274
+ provenance: provenance2
1275
+ });
1276
+ }
1277
+ continue;
1278
+ }
1076
1279
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1077
1280
  const text = extractBlockText(block).trim();
1078
1281
  if (text === "") continue;
@@ -1093,7 +1296,7 @@ function parseTSDocTags(node, file = "") {
1093
1296
  }
1094
1297
  const jsDocTagsAll = ts2.getJSDocTags(node);
1095
1298
  for (const tag of jsDocTagsAll) {
1096
- const tagName = tag.tagName.text;
1299
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1097
1300
  if (!TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1098
1301
  const commentText = getTagCommentText(tag);
1099
1302
  if (commentText === void 0 || commentText.trim() === "") continue;
@@ -1104,43 +1307,17 @@ function parseTSDocTags(node, file = "") {
1104
1307
  constraints.push(constraintNode);
1105
1308
  }
1106
1309
  }
1107
- let displayName;
1108
- let description;
1109
- let displayNameTag;
1110
- let descriptionTag;
1111
- for (const tag of jsDocTagsAll) {
1112
- const tagName = tag.tagName.text;
1113
- const commentText = getTagCommentText(tag);
1114
- if (commentText === void 0 || commentText.trim() === "") {
1115
- continue;
1116
- }
1117
- const trimmed = commentText.trim();
1118
- if (tagName === "Field_displayName") {
1119
- displayName = trimmed;
1120
- displayNameTag = tag;
1121
- } else if (tagName === "Field_description") {
1122
- description = trimmed;
1123
- descriptionTag = tag;
1124
- }
1125
- }
1126
- if (displayName !== void 0 && displayNameTag) {
1127
- annotations.push({
1128
- kind: "annotation",
1129
- annotationKind: "displayName",
1130
- value: displayName,
1131
- provenance: provenanceForJSDocTag(displayNameTag, file)
1132
- });
1133
- }
1134
- if (description !== void 0 && descriptionTag) {
1135
- annotations.push({
1136
- kind: "annotation",
1137
- annotationKind: "description",
1138
- value: description,
1139
- provenance: provenanceForJSDocTag(descriptionTag, file)
1140
- });
1141
- }
1142
1310
  return { constraints, annotations };
1143
1311
  }
1312
+ function extractPathTarget(text) {
1313
+ const trimmed = text.trimStart();
1314
+ const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
1315
+ if (!match?.[1] || !match[2]) return null;
1316
+ return {
1317
+ path: { segments: [match[1]] },
1318
+ remainingText: match[2]
1319
+ };
1320
+ }
1144
1321
  function extractBlockText(block) {
1145
1322
  return extractPlainText(block.content);
1146
1323
  }
@@ -1163,9 +1340,12 @@ function parseConstraintValue(tagName, text, provenance) {
1163
1340
  if (!isBuiltinConstraintName(tagName)) {
1164
1341
  return null;
1165
1342
  }
1343
+ const pathResult = extractPathTarget(text);
1344
+ const effectiveText = pathResult ? pathResult.remainingText : text;
1345
+ const path3 = pathResult?.path;
1166
1346
  const expectedType = BUILTIN_CONSTRAINT_DEFINITIONS[tagName];
1167
1347
  if (expectedType === "number") {
1168
- const value = Number(text);
1348
+ const value = Number(effectiveText);
1169
1349
  if (Number.isNaN(value)) {
1170
1350
  return null;
1171
1351
  }
@@ -1175,6 +1355,7 @@ function parseConstraintValue(tagName, text, provenance) {
1175
1355
  kind: "constraint",
1176
1356
  constraintKind: numericKind,
1177
1357
  value,
1358
+ ...path3 && { path: path3 },
1178
1359
  provenance
1179
1360
  };
1180
1361
  }
@@ -1184,42 +1365,41 @@ function parseConstraintValue(tagName, text, provenance) {
1184
1365
  kind: "constraint",
1185
1366
  constraintKind: lengthKind,
1186
1367
  value,
1368
+ ...path3 && { path: path3 },
1187
1369
  provenance
1188
1370
  };
1189
1371
  }
1190
1372
  return null;
1191
1373
  }
1192
1374
  if (expectedType === "json") {
1193
- try {
1194
- const parsed = JSON.parse(text);
1195
- if (!Array.isArray(parsed)) {
1196
- return null;
1197
- }
1198
- const members = [];
1199
- for (const item of parsed) {
1200
- if (typeof item === "string" || typeof item === "number") {
1201
- members.push(item);
1202
- } else if (typeof item === "object" && item !== null && "id" in item) {
1203
- const id = item["id"];
1204
- if (typeof id === "string" || typeof id === "number") {
1205
- members.push(id);
1206
- }
1375
+ const parsed = tryParseJson(effectiveText);
1376
+ if (!Array.isArray(parsed)) {
1377
+ return null;
1378
+ }
1379
+ const members = [];
1380
+ for (const item of parsed) {
1381
+ if (typeof item === "string" || typeof item === "number") {
1382
+ members.push(item);
1383
+ } else if (typeof item === "object" && item !== null && "id" in item) {
1384
+ const id = item["id"];
1385
+ if (typeof id === "string" || typeof id === "number") {
1386
+ members.push(id);
1207
1387
  }
1208
1388
  }
1209
- return {
1210
- kind: "constraint",
1211
- constraintKind: "allowedMembers",
1212
- members,
1213
- provenance
1214
- };
1215
- } catch {
1216
- return null;
1217
1389
  }
1390
+ return {
1391
+ kind: "constraint",
1392
+ constraintKind: "allowedMembers",
1393
+ members,
1394
+ ...path3 && { path: path3 },
1395
+ provenance
1396
+ };
1218
1397
  }
1219
1398
  return {
1220
1399
  kind: "constraint",
1221
1400
  constraintKind: "pattern",
1222
- pattern: text,
1401
+ pattern: effectiveText,
1402
+ ...path3 && { path: path3 },
1223
1403
  provenance
1224
1404
  };
1225
1405
  }
@@ -1391,18 +1571,19 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1391
1571
  const tsType = checker.getTypeAtLocation(prop);
1392
1572
  const optional = prop.questionToken !== void 0;
1393
1573
  const provenance = provenanceForNode(prop, file);
1394
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1574
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1395
1575
  const constraints = [];
1396
1576
  if (prop.type) {
1397
1577
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1398
1578
  }
1399
1579
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1400
- const annotations = [];
1580
+ let annotations = [];
1401
1581
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1402
1582
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1403
1583
  if (defaultAnnotation) {
1404
1584
  annotations.push(defaultAnnotation);
1405
1585
  }
1586
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1406
1587
  return {
1407
1588
  kind: "field",
1408
1589
  name,
@@ -1421,14 +1602,15 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1421
1602
  const tsType = checker.getTypeAtLocation(prop);
1422
1603
  const optional = prop.questionToken !== void 0;
1423
1604
  const provenance = provenanceForNode(prop, file);
1424
- const type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1605
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1425
1606
  const constraints = [];
1426
1607
  if (prop.type) {
1427
1608
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
1428
1609
  }
1429
1610
  constraints.push(...extractJSDocConstraintNodes(prop, file));
1430
- const annotations = [];
1611
+ let annotations = [];
1431
1612
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1613
+ ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
1432
1614
  return {
1433
1615
  kind: "field",
1434
1616
  name,
@@ -1439,6 +1621,68 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1439
1621
  provenance
1440
1622
  };
1441
1623
  }
1624
+ function applyEnumMemberDisplayNames(type, annotations) {
1625
+ if (!annotations.some(
1626
+ (annotation) => annotation.annotationKind === "displayName" && annotation.value.trim().startsWith(":")
1627
+ )) {
1628
+ return { type, annotations: [...annotations] };
1629
+ }
1630
+ const consumed = /* @__PURE__ */ new Set();
1631
+ const nextType = rewriteEnumDisplayNames(type, annotations, consumed);
1632
+ if (consumed.size === 0) {
1633
+ return { type, annotations: [...annotations] };
1634
+ }
1635
+ return {
1636
+ type: nextType,
1637
+ annotations: annotations.filter((annotation) => !consumed.has(annotation))
1638
+ };
1639
+ }
1640
+ function rewriteEnumDisplayNames(type, annotations, consumed) {
1641
+ switch (type.kind) {
1642
+ case "enum":
1643
+ return applyEnumMemberDisplayNamesToEnum(type, annotations, consumed);
1644
+ case "union": {
1645
+ return {
1646
+ ...type,
1647
+ members: type.members.map(
1648
+ (member) => rewriteEnumDisplayNames(member, annotations, consumed)
1649
+ )
1650
+ };
1651
+ }
1652
+ default:
1653
+ return type;
1654
+ }
1655
+ }
1656
+ function applyEnumMemberDisplayNamesToEnum(type, annotations, consumed) {
1657
+ const displayNames = /* @__PURE__ */ new Map();
1658
+ for (const annotation of annotations) {
1659
+ if (annotation.annotationKind !== "displayName") continue;
1660
+ const parsed = parseEnumMemberDisplayName(annotation.value);
1661
+ if (!parsed) continue;
1662
+ consumed.add(annotation);
1663
+ const member = type.members.find((m) => String(m.value) === parsed.value);
1664
+ if (!member) continue;
1665
+ displayNames.set(String(member.value), parsed.label);
1666
+ }
1667
+ if (displayNames.size === 0) {
1668
+ return type;
1669
+ }
1670
+ return {
1671
+ ...type,
1672
+ members: type.members.map((member) => {
1673
+ const displayName = displayNames.get(String(member.value));
1674
+ return displayName !== void 0 ? { ...member, displayName } : member;
1675
+ })
1676
+ };
1677
+ }
1678
+ function parseEnumMemberDisplayName(value) {
1679
+ const trimmed = value.trim();
1680
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(trimmed);
1681
+ if (!match?.[1] || !match[2]) return null;
1682
+ const label = match[2].trim();
1683
+ if (label === "") return null;
1684
+ return { value: match[1], label };
1685
+ }
1442
1686
  function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1443
1687
  if (type.flags & ts4.TypeFlags.String) {
1444
1688
  return { kind: "primitive", primitiveKind: "string" };
@@ -1549,7 +1793,30 @@ function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1549
1793
  const items = elementType ? resolveTypeNode(elementType, checker, file, typeRegistry, visiting) : { kind: "primitive", primitiveKind: "string" };
1550
1794
  return { kind: "array", items };
1551
1795
  }
1796
+ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1797
+ if (type.getProperties().length > 0) {
1798
+ return null;
1799
+ }
1800
+ const indexInfo = checker.getIndexInfoOfType(type, ts4.IndexKind.String);
1801
+ if (!indexInfo) {
1802
+ return null;
1803
+ }
1804
+ if (visiting.has(type)) {
1805
+ return null;
1806
+ }
1807
+ visiting.add(type);
1808
+ try {
1809
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1810
+ return { kind: "record", valueType };
1811
+ } finally {
1812
+ visiting.delete(type);
1813
+ }
1814
+ }
1552
1815
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1816
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1817
+ if (recordNode) {
1818
+ return recordNode;
1819
+ }
1553
1820
  if (visiting.has(type)) {
1554
1821
  return { kind: "object", properties: [], additionalProperties: false };
1555
1822
  }
@@ -1581,7 +1848,7 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1581
1848
  const objectNode = {
1582
1849
  kind: "object",
1583
1850
  properties,
1584
- additionalProperties: false
1851
+ additionalProperties: true
1585
1852
  };
1586
1853
  if (typeName) {
1587
1854
  typeRegistry[typeName] = {
@@ -1650,14 +1917,23 @@ function buildFieldNodeInfoMap(members, checker, file, typeRegistry, visiting) {
1650
1917
  }
1651
1918
  return map;
1652
1919
  }
1653
- function extractTypeAliasConstraintNodes(typeNode, checker, file) {
1920
+ var MAX_ALIAS_CHAIN_DEPTH = 8;
1921
+ function extractTypeAliasConstraintNodes(typeNode, checker, file, depth = 0) {
1654
1922
  if (!ts4.isTypeReferenceNode(typeNode)) return [];
1923
+ if (depth >= MAX_ALIAS_CHAIN_DEPTH) {
1924
+ const aliasName = typeNode.typeName.getText();
1925
+ throw new Error(
1926
+ `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.`
1927
+ );
1928
+ }
1655
1929
  const symbol = checker.getSymbolAtLocation(typeNode.typeName);
1656
1930
  if (!symbol?.declarations) return [];
1657
1931
  const aliasDecl = symbol.declarations.find(ts4.isTypeAliasDeclaration);
1658
1932
  if (!aliasDecl) return [];
1659
1933
  if (ts4.isTypeLiteralNode(aliasDecl.type)) return [];
1660
- return extractJSDocConstraintNodes(aliasDecl, file);
1934
+ const constraints = extractJSDocConstraintNodes(aliasDecl, file);
1935
+ constraints.push(...extractTypeAliasConstraintNodes(aliasDecl.type, checker, file, depth + 1));
1936
+ return constraints;
1661
1937
  }
1662
1938
  function provenanceForNode(node, file) {
1663
1939
  const sourceFile = node.getSourceFile();
@@ -1775,15 +2051,19 @@ function generateSchemas(options) {
1775
2051
  }
1776
2052
 
1777
2053
  // src/index.ts
1778
- function buildFormSchemas(form) {
2054
+ function buildFormSchemas(form, options) {
1779
2055
  return {
1780
- jsonSchema: generateJsonSchema(form),
2056
+ jsonSchema: generateJsonSchema(form, options),
1781
2057
  uiSchema: generateUiSchema(form)
1782
2058
  };
1783
2059
  }
1784
2060
  function writeSchemas(form, options) {
1785
- const { outDir, name = "schema", indent = 2 } = options;
1786
- const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form);
2061
+ const { outDir, name = "schema", indent = 2, extensionRegistry, vendorPrefix } = options;
2062
+ const buildOptions = extensionRegistry === void 0 && vendorPrefix === void 0 ? void 0 : {
2063
+ extensionRegistry,
2064
+ vendorPrefix
2065
+ };
2066
+ const { jsonSchema, uiSchema: uiSchema2 } = buildFormSchemas(form, buildOptions);
1787
2067
  if (!fs.existsSync(outDir)) {
1788
2068
  fs.mkdirSync(outDir, { recursive: true });
1789
2069
  }
@@ -1798,7 +2078,9 @@ export {
1798
2078
  categorizationSchema,
1799
2079
  categorySchema,
1800
2080
  controlSchema,
2081
+ createExtensionRegistry,
1801
2082
  generateJsonSchema,
2083
+ generateJsonSchemaFromIR,
1802
2084
  generateSchemas,
1803
2085
  generateSchemasFromClass,
1804
2086
  generateUiSchema,