@formspec/build 0.1.0-alpha.14 → 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 (60) hide show
  1. package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
  2. package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
  3. package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
  4. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  5. package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
  6. package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
  7. package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
  8. package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -5
  9. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
  10. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
  11. package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
  12. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
  13. package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
  14. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
  15. package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
  16. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
  17. package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
  18. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
  19. package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
  20. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
  21. package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
  22. package/dist/__tests__/parity/utils.d.ts +6 -1
  23. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  24. package/dist/analyzer/class-analyzer.d.ts +1 -1
  25. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  26. package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
  27. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  28. package/dist/analyzer/tsdoc-parser.d.ts +6 -8
  29. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  30. package/dist/browser.cjs +387 -98
  31. package/dist/browser.cjs.map +1 -1
  32. package/dist/browser.d.ts +15 -2
  33. package/dist/browser.d.ts.map +1 -1
  34. package/dist/browser.js +385 -98
  35. package/dist/browser.js.map +1 -1
  36. package/dist/build.d.ts +131 -5
  37. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
  38. package/dist/cli.cjs +272 -69
  39. package/dist/cli.cjs.map +1 -1
  40. package/dist/cli.js +271 -72
  41. package/dist/cli.js.map +1 -1
  42. package/dist/index.cjs +257 -67
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.ts +20 -3
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +255 -71
  47. package/dist/index.js.map +1 -1
  48. package/dist/internals.cjs +461 -137
  49. package/dist/internals.cjs.map +1 -1
  50. package/dist/internals.js +459 -139
  51. package/dist/internals.js.map +1 -1
  52. package/dist/json-schema/generator.d.ts +8 -2
  53. package/dist/json-schema/generator.d.ts.map +1 -1
  54. package/dist/json-schema/ir-generator.d.ts +24 -2
  55. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  56. package/dist/json-schema/types.d.ts +1 -1
  57. package/dist/json-schema/types.d.ts.map +1 -1
  58. package/dist/validate/constraint-validator.d.ts +3 -7
  59. package/dist/validate/constraint-validator.d.ts.map +1 -1
  60. package/package.json +1 -1
package/dist/browser.cjs CHANGED
@@ -25,7 +25,9 @@ __export(browser_exports, {
25
25
  categorizationSchema: () => categorizationSchema,
26
26
  categorySchema: () => categorySchema,
27
27
  controlSchema: () => controlSchema,
28
+ createExtensionRegistry: () => createExtensionRegistry,
28
29
  generateJsonSchema: () => generateJsonSchema,
30
+ generateJsonSchemaFromIR: () => generateJsonSchemaFromIR,
29
31
  generateUiSchema: () => generateUiSchema,
30
32
  getSchemaExtension: () => getSchemaExtension,
31
33
  groupLayoutSchema: () => groupLayoutSchema,
@@ -225,7 +227,7 @@ function canonicalizeArrayField(field) {
225
227
  const itemsType = {
226
228
  kind: "object",
227
229
  properties: itemProperties,
228
- additionalProperties: false
230
+ additionalProperties: true
229
231
  };
230
232
  const type = { kind: "array", items: itemsType };
231
233
  const constraints = [];
@@ -260,7 +262,7 @@ function canonicalizeObjectField(field) {
260
262
  const type = {
261
263
  kind: "object",
262
264
  properties,
263
- additionalProperties: false
265
+ additionalProperties: true
264
266
  };
265
267
  return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
266
268
  }
@@ -361,11 +363,21 @@ function buildObjectProperties(elements, insideConditional = false) {
361
363
  var import_core2 = require("@formspec/core");
362
364
 
363
365
  // src/json-schema/ir-generator.ts
364
- function makeContext() {
365
- return { defs: {} };
366
+ function makeContext(options) {
367
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
368
+ if (!vendorPrefix.startsWith("x-")) {
369
+ throw new Error(
370
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
371
+ );
372
+ }
373
+ return {
374
+ defs: {},
375
+ extensionRegistry: options?.extensionRegistry,
376
+ vendorPrefix
377
+ };
366
378
  }
367
- function generateJsonSchemaFromIR(ir) {
368
- const ctx = makeContext();
379
+ function generateJsonSchemaFromIR(ir, options) {
380
+ const ctx = makeContext(options);
369
381
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
370
382
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
371
383
  }
@@ -417,16 +429,16 @@ function generateFieldSchema(field, ctx) {
417
429
  directConstraints.push(c);
418
430
  }
419
431
  }
420
- applyConstraints(schema, directConstraints);
421
- applyAnnotations(schema, field.annotations);
432
+ applyConstraints(schema, directConstraints, ctx);
433
+ applyAnnotations(schema, field.annotations, ctx);
422
434
  if (pathConstraints.length === 0) {
423
435
  return schema;
424
436
  }
425
- return applyPathTargetedConstraints(schema, pathConstraints);
437
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
426
438
  }
427
- function applyPathTargetedConstraints(schema, pathConstraints) {
439
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
428
440
  if (schema.type === "array" && schema.items) {
429
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
441
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
430
442
  return schema;
431
443
  }
432
444
  const byTarget = /* @__PURE__ */ new Map();
@@ -440,7 +452,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
440
452
  const propertyOverrides = {};
441
453
  for (const [target, constraints] of byTarget) {
442
454
  const subSchema = {};
443
- applyConstraints(subSchema, constraints);
455
+ applyConstraints(subSchema, constraints, ctx);
444
456
  propertyOverrides[target] = subSchema;
445
457
  }
446
458
  if (schema.$ref) {
@@ -484,6 +496,8 @@ function generateTypeNode(type, ctx) {
484
496
  return generateArrayType(type, ctx);
485
497
  case "object":
486
498
  return generateObjectType(type, ctx);
499
+ case "record":
500
+ return generateRecordType(type, ctx);
487
501
  case "union":
488
502
  return generateUnionType(type, ctx);
489
503
  case "reference":
@@ -491,7 +505,7 @@ function generateTypeNode(type, ctx) {
491
505
  case "dynamic":
492
506
  return generateDynamicType(type);
493
507
  case "custom":
494
- return generateCustomType(type);
508
+ return generateCustomType(type, ctx);
495
509
  default: {
496
510
  const _exhaustive = type;
497
511
  return _exhaustive;
@@ -540,16 +554,27 @@ function generateObjectType(type, ctx) {
540
554
  }
541
555
  return schema;
542
556
  }
557
+ function generateRecordType(type, ctx) {
558
+ return {
559
+ type: "object",
560
+ additionalProperties: generateTypeNode(type.valueType, ctx)
561
+ };
562
+ }
543
563
  function generatePropertySchema(prop, ctx) {
544
564
  const schema = generateTypeNode(prop.type, ctx);
545
- applyConstraints(schema, prop.constraints);
546
- applyAnnotations(schema, prop.annotations);
565
+ applyConstraints(schema, prop.constraints, ctx);
566
+ applyAnnotations(schema, prop.annotations, ctx);
547
567
  return schema;
548
568
  }
549
569
  function generateUnionType(type, ctx) {
550
570
  if (isBooleanUnion(type)) {
551
571
  return { type: "boolean" };
552
572
  }
573
+ if (isNullableUnion(type)) {
574
+ return {
575
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
576
+ };
577
+ }
553
578
  return {
554
579
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
555
580
  };
@@ -559,6 +584,13 @@ function isBooleanUnion(type) {
559
584
  const kinds = type.members.map((m) => m.kind);
560
585
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
561
586
  }
587
+ function isNullableUnion(type) {
588
+ if (type.members.length !== 2) return false;
589
+ const nullCount = type.members.filter(
590
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
591
+ ).length;
592
+ return nullCount === 1;
593
+ }
562
594
  function generateReferenceType(type) {
563
595
  return { $ref: `#/$defs/${type.name}` };
564
596
  }
@@ -579,10 +611,7 @@ function generateDynamicType(type) {
579
611
  "x-formspec-schemaSource": type.sourceKey
580
612
  };
581
613
  }
582
- function generateCustomType(_type) {
583
- return { type: "object" };
584
- }
585
- function applyConstraints(schema, constraints) {
614
+ function applyConstraints(schema, constraints, ctx) {
586
615
  for (const constraint of constraints) {
587
616
  switch (constraint.constraintKind) {
588
617
  case "minimum":
@@ -627,6 +656,7 @@ function applyConstraints(schema, constraints) {
627
656
  case "allowedMembers":
628
657
  break;
629
658
  case "custom":
659
+ applyCustomConstraint(schema, constraint, ctx);
630
660
  break;
631
661
  default: {
632
662
  const _exhaustive = constraint;
@@ -635,7 +665,7 @@ function applyConstraints(schema, constraints) {
635
665
  }
636
666
  }
637
667
  }
638
- function applyAnnotations(schema, annotations) {
668
+ function applyAnnotations(schema, annotations, ctx) {
639
669
  for (const annotation of annotations) {
640
670
  switch (annotation.annotationKind) {
641
671
  case "displayName":
@@ -655,6 +685,7 @@ function applyAnnotations(schema, annotations) {
655
685
  case "formatHint":
656
686
  break;
657
687
  case "custom":
688
+ applyCustomAnnotation(schema, annotation, ctx);
658
689
  break;
659
690
  default: {
660
691
  const _exhaustive = annotation;
@@ -663,11 +694,41 @@ function applyAnnotations(schema, annotations) {
663
694
  }
664
695
  }
665
696
  }
697
+ function generateCustomType(type, ctx) {
698
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
699
+ if (registration === void 0) {
700
+ throw new Error(
701
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
702
+ );
703
+ }
704
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
705
+ }
706
+ function applyCustomConstraint(schema, constraint, ctx) {
707
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
708
+ if (registration === void 0) {
709
+ throw new Error(
710
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
711
+ );
712
+ }
713
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
714
+ }
715
+ function applyCustomAnnotation(schema, annotation, ctx) {
716
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
717
+ if (registration === void 0) {
718
+ throw new Error(
719
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
720
+ );
721
+ }
722
+ if (registration.toJsonSchema === void 0) {
723
+ return;
724
+ }
725
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
726
+ }
666
727
 
667
728
  // src/json-schema/generator.ts
668
- function generateJsonSchema(form) {
729
+ function generateJsonSchema(form, options) {
669
730
  const ir = canonicalizeChainDSL(form);
670
- return generateJsonSchemaFromIR(ir);
731
+ return generateJsonSchemaFromIR(ir, options);
671
732
  }
672
733
 
673
734
  // src/ui-schema/schema.ts
@@ -897,6 +958,48 @@ function getSchemaExtension(schema, key) {
897
958
  return schema[key];
898
959
  }
899
960
 
961
+ // src/extensions/registry.ts
962
+ function createExtensionRegistry(extensions) {
963
+ const typeMap = /* @__PURE__ */ new Map();
964
+ const constraintMap = /* @__PURE__ */ new Map();
965
+ const annotationMap = /* @__PURE__ */ new Map();
966
+ for (const ext of extensions) {
967
+ if (ext.types !== void 0) {
968
+ for (const type of ext.types) {
969
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
970
+ if (typeMap.has(qualifiedId)) {
971
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
972
+ }
973
+ typeMap.set(qualifiedId, type);
974
+ }
975
+ }
976
+ if (ext.constraints !== void 0) {
977
+ for (const constraint of ext.constraints) {
978
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
979
+ if (constraintMap.has(qualifiedId)) {
980
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
981
+ }
982
+ constraintMap.set(qualifiedId, constraint);
983
+ }
984
+ }
985
+ if (ext.annotations !== void 0) {
986
+ for (const annotation of ext.annotations) {
987
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
988
+ if (annotationMap.has(qualifiedId)) {
989
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
990
+ }
991
+ annotationMap.set(qualifiedId, annotation);
992
+ }
993
+ }
994
+ }
995
+ return {
996
+ extensions,
997
+ findType: (typeId) => typeMap.get(typeId),
998
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
999
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
1000
+ };
1001
+ }
1002
+
900
1003
  // src/json-schema/schema.ts
901
1004
  var import_zod3 = require("zod");
902
1005
  var jsonSchemaTypeSchema = import_zod3.z.enum([
@@ -960,12 +1063,9 @@ var jsonSchema7Schema = import_zod3.z.lazy(
960
1063
  );
961
1064
 
962
1065
  // src/validate/constraint-validator.ts
963
- function makeCode(ctx, category, number) {
964
- return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
965
- }
966
1066
  function addContradiction(ctx, message, primary, related) {
967
1067
  ctx.diagnostics.push({
968
- code: makeCode(ctx, "CONTRADICTION", 1),
1068
+ code: "CONTRADICTING_CONSTRAINTS",
969
1069
  message,
970
1070
  severity: "error",
971
1071
  primaryLocation: primary,
@@ -974,7 +1074,7 @@ function addContradiction(ctx, message, primary, related) {
974
1074
  }
975
1075
  function addTypeMismatch(ctx, message, primary) {
976
1076
  ctx.diagnostics.push({
977
- code: makeCode(ctx, "TYPE_MISMATCH", 1),
1077
+ code: "TYPE_MISMATCH",
978
1078
  message,
979
1079
  severity: "error",
980
1080
  primaryLocation: primary,
@@ -983,13 +1083,22 @@ function addTypeMismatch(ctx, message, primary) {
983
1083
  }
984
1084
  function addUnknownExtension(ctx, message, primary) {
985
1085
  ctx.diagnostics.push({
986
- code: makeCode(ctx, "UNKNOWN_EXTENSION", 1),
1086
+ code: "UNKNOWN_EXTENSION",
987
1087
  message,
988
1088
  severity: "warning",
989
1089
  primaryLocation: primary,
990
1090
  relatedLocations: []
991
1091
  });
992
1092
  }
1093
+ function addConstraintBroadening(ctx, message, primary, related) {
1094
+ ctx.diagnostics.push({
1095
+ code: "CONSTRAINT_BROADENING",
1096
+ message,
1097
+ severity: "error",
1098
+ primaryLocation: primary,
1099
+ relatedLocations: [related]
1100
+ });
1101
+ }
993
1102
  function findNumeric(constraints, constraintKind) {
994
1103
  return constraints.find((c) => c.constraintKind === constraintKind);
995
1104
  }
@@ -1001,6 +1110,126 @@ function findAllowedMembers(constraints) {
1001
1110
  (c) => c.constraintKind === "allowedMembers"
1002
1111
  );
1003
1112
  }
1113
+ function isOrderedBoundConstraint(constraint) {
1114
+ return constraint.constraintKind === "minimum" || constraint.constraintKind === "exclusiveMinimum" || constraint.constraintKind === "minLength" || constraint.constraintKind === "minItems" || constraint.constraintKind === "maximum" || constraint.constraintKind === "exclusiveMaximum" || constraint.constraintKind === "maxLength" || constraint.constraintKind === "maxItems";
1115
+ }
1116
+ function pathKey(constraint) {
1117
+ return constraint.path?.segments.join(".") ?? "";
1118
+ }
1119
+ function orderedBoundFamily(kind) {
1120
+ switch (kind) {
1121
+ case "minimum":
1122
+ case "exclusiveMinimum":
1123
+ return "numeric-lower";
1124
+ case "maximum":
1125
+ case "exclusiveMaximum":
1126
+ return "numeric-upper";
1127
+ case "minLength":
1128
+ return "minLength";
1129
+ case "minItems":
1130
+ return "minItems";
1131
+ case "maxLength":
1132
+ return "maxLength";
1133
+ case "maxItems":
1134
+ return "maxItems";
1135
+ default: {
1136
+ const _exhaustive = kind;
1137
+ return _exhaustive;
1138
+ }
1139
+ }
1140
+ }
1141
+ function isNumericLowerKind(kind) {
1142
+ return kind === "minimum" || kind === "exclusiveMinimum";
1143
+ }
1144
+ function isNumericUpperKind(kind) {
1145
+ return kind === "maximum" || kind === "exclusiveMaximum";
1146
+ }
1147
+ function describeConstraintTag(constraint) {
1148
+ return `@${constraint.constraintKind}`;
1149
+ }
1150
+ function compareConstraintStrength(current, previous) {
1151
+ const family = orderedBoundFamily(current.constraintKind);
1152
+ if (family === "numeric-lower") {
1153
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
1154
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
1155
+ }
1156
+ if (current.value !== previous.value) {
1157
+ return current.value > previous.value ? 1 : -1;
1158
+ }
1159
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
1160
+ return 1;
1161
+ }
1162
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
1163
+ return -1;
1164
+ }
1165
+ return 0;
1166
+ }
1167
+ if (family === "numeric-upper") {
1168
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
1169
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
1170
+ }
1171
+ if (current.value !== previous.value) {
1172
+ return current.value < previous.value ? 1 : -1;
1173
+ }
1174
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
1175
+ return 1;
1176
+ }
1177
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
1178
+ return -1;
1179
+ }
1180
+ return 0;
1181
+ }
1182
+ switch (family) {
1183
+ case "minLength":
1184
+ case "minItems":
1185
+ if (current.value === previous.value) {
1186
+ return 0;
1187
+ }
1188
+ return current.value > previous.value ? 1 : -1;
1189
+ case "maxLength":
1190
+ case "maxItems":
1191
+ if (current.value === previous.value) {
1192
+ return 0;
1193
+ }
1194
+ return current.value < previous.value ? 1 : -1;
1195
+ default: {
1196
+ const _exhaustive = family;
1197
+ return _exhaustive;
1198
+ }
1199
+ }
1200
+ }
1201
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
1202
+ const strongestByKey = /* @__PURE__ */ new Map();
1203
+ for (const constraint of constraints) {
1204
+ if (!isOrderedBoundConstraint(constraint)) {
1205
+ continue;
1206
+ }
1207
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
1208
+ const previous = strongestByKey.get(key);
1209
+ if (previous === void 0) {
1210
+ strongestByKey.set(key, constraint);
1211
+ continue;
1212
+ }
1213
+ const strength = compareConstraintStrength(constraint, previous);
1214
+ if (strength < 0) {
1215
+ const displayFieldName = formatPathTargetFieldName(
1216
+ fieldName,
1217
+ constraint.path?.segments ?? []
1218
+ );
1219
+ addConstraintBroadening(
1220
+ ctx,
1221
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
1222
+ constraint.provenance,
1223
+ previous.provenance
1224
+ );
1225
+ continue;
1226
+ }
1227
+ if (strength <= 0) {
1228
+ continue;
1229
+ }
1230
+ strongestByKey.set(key, constraint);
1231
+ }
1232
+ }
1004
1233
  function checkNumericContradictions(ctx, fieldName, constraints) {
1005
1234
  const min = findNumeric(constraints, "minimum");
1006
1235
  const max = findNumeric(constraints, "maximum");
@@ -1097,6 +1326,8 @@ function typeLabel(type) {
1097
1326
  return "array";
1098
1327
  case "object":
1099
1328
  return "object";
1329
+ case "record":
1330
+ return "record";
1100
1331
  case "union":
1101
1332
  return "union";
1102
1333
  case "reference":
@@ -1111,85 +1342,140 @@ function typeLabel(type) {
1111
1342
  }
1112
1343
  }
1113
1344
  }
1114
- function checkTypeApplicability(ctx, fieldName, type, constraints) {
1115
- const isNumber = type.kind === "primitive" && type.primitiveKind === "number";
1116
- const isString = type.kind === "primitive" && type.primitiveKind === "string";
1117
- const isArray = type.kind === "array";
1118
- const isEnum = type.kind === "enum";
1119
- const label = typeLabel(type);
1120
- for (const constraint of constraints) {
1121
- if (constraint.path) {
1122
- const isTraversable = type.kind === "object" || type.kind === "array" || type.kind === "reference";
1123
- if (!isTraversable) {
1345
+ function dereferenceType(ctx, type) {
1346
+ let current = type;
1347
+ const seen = /* @__PURE__ */ new Set();
1348
+ while (current.kind === "reference") {
1349
+ if (seen.has(current.name)) {
1350
+ return current;
1351
+ }
1352
+ seen.add(current.name);
1353
+ const definition = ctx.typeRegistry[current.name];
1354
+ if (definition === void 0) {
1355
+ return current;
1356
+ }
1357
+ current = definition.type;
1358
+ }
1359
+ return current;
1360
+ }
1361
+ function resolvePathTargetType(ctx, type, segments) {
1362
+ const effectiveType = dereferenceType(ctx, type);
1363
+ if (segments.length === 0) {
1364
+ return { kind: "resolved", type: effectiveType };
1365
+ }
1366
+ if (effectiveType.kind === "array") {
1367
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
1368
+ }
1369
+ if (effectiveType.kind === "object") {
1370
+ const [segment, ...rest] = segments;
1371
+ if (segment === void 0) {
1372
+ throw new Error("Invariant violation: object path traversal requires a segment");
1373
+ }
1374
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
1375
+ if (property === void 0) {
1376
+ return { kind: "missing-property", segment };
1377
+ }
1378
+ return resolvePathTargetType(ctx, property.type, rest);
1379
+ }
1380
+ return { kind: "unresolvable", type: effectiveType };
1381
+ }
1382
+ function formatPathTargetFieldName(fieldName, path) {
1383
+ return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
1384
+ }
1385
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
1386
+ const effectiveType = dereferenceType(ctx, type);
1387
+ const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
1388
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
1389
+ const isArray = effectiveType.kind === "array";
1390
+ const isEnum = effectiveType.kind === "enum";
1391
+ const label = typeLabel(effectiveType);
1392
+ const ck = constraint.constraintKind;
1393
+ switch (ck) {
1394
+ case "minimum":
1395
+ case "maximum":
1396
+ case "exclusiveMinimum":
1397
+ case "exclusiveMaximum":
1398
+ case "multipleOf": {
1399
+ if (!isNumber) {
1124
1400
  addTypeMismatch(
1125
1401
  ctx,
1126
- `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${label}" cannot be traversed`,
1402
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
1127
1403
  constraint.provenance
1128
1404
  );
1129
1405
  }
1130
- continue;
1406
+ break;
1131
1407
  }
1132
- const ck = constraint.constraintKind;
1133
- switch (ck) {
1134
- case "minimum":
1135
- case "maximum":
1136
- case "exclusiveMinimum":
1137
- case "exclusiveMaximum":
1138
- case "multipleOf": {
1139
- if (!isNumber) {
1140
- addTypeMismatch(
1141
- ctx,
1142
- `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
1143
- constraint.provenance
1144
- );
1145
- }
1146
- break;
1147
- }
1148
- case "minLength":
1149
- case "maxLength":
1150
- case "pattern": {
1151
- if (!isString) {
1152
- addTypeMismatch(
1153
- ctx,
1154
- `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1155
- constraint.provenance
1156
- );
1157
- }
1158
- break;
1408
+ case "minLength":
1409
+ case "maxLength":
1410
+ case "pattern": {
1411
+ if (!isString) {
1412
+ addTypeMismatch(
1413
+ ctx,
1414
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1415
+ constraint.provenance
1416
+ );
1159
1417
  }
1160
- case "minItems":
1161
- case "maxItems":
1162
- case "uniqueItems": {
1163
- if (!isArray) {
1164
- addTypeMismatch(
1165
- ctx,
1166
- `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
1167
- constraint.provenance
1168
- );
1169
- }
1170
- break;
1418
+ break;
1419
+ }
1420
+ case "minItems":
1421
+ case "maxItems":
1422
+ case "uniqueItems": {
1423
+ if (!isArray) {
1424
+ addTypeMismatch(
1425
+ ctx,
1426
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
1427
+ constraint.provenance
1428
+ );
1171
1429
  }
1172
- case "allowedMembers": {
1173
- if (!isEnum) {
1174
- addTypeMismatch(
1175
- ctx,
1176
- `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
1177
- constraint.provenance
1178
- );
1179
- }
1180
- break;
1430
+ break;
1431
+ }
1432
+ case "allowedMembers": {
1433
+ if (!isEnum) {
1434
+ addTypeMismatch(
1435
+ ctx,
1436
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
1437
+ constraint.provenance
1438
+ );
1181
1439
  }
1182
- case "custom": {
1183
- checkCustomConstraint(ctx, fieldName, type, constraint);
1184
- break;
1440
+ break;
1441
+ }
1442
+ case "custom": {
1443
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
1444
+ break;
1445
+ }
1446
+ default: {
1447
+ const _exhaustive = constraint;
1448
+ throw new Error(
1449
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
1450
+ );
1451
+ }
1452
+ }
1453
+ }
1454
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
1455
+ for (const constraint of constraints) {
1456
+ if (constraint.path) {
1457
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
1458
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
1459
+ if (resolution.kind === "missing-property") {
1460
+ addTypeMismatch(
1461
+ ctx,
1462
+ `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
1463
+ constraint.provenance
1464
+ );
1465
+ continue;
1185
1466
  }
1186
- default: {
1187
- const _exhaustive = constraint;
1188
- throw new Error(
1189
- `Unhandled constraint kind: ${_exhaustive.constraintKind}`
1467
+ if (resolution.kind === "unresolvable") {
1468
+ addTypeMismatch(
1469
+ ctx,
1470
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
1471
+ constraint.provenance
1190
1472
  );
1473
+ continue;
1191
1474
  }
1475
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
1476
+ continue;
1192
1477
  }
1478
+ checkConstraintOnType(ctx, fieldName, type, constraint);
1193
1479
  }
1194
1480
  }
1195
1481
  function checkCustomConstraint(ctx, fieldName, type, constraint) {
@@ -1233,6 +1519,7 @@ function validateConstraints(ctx, name, type, constraints) {
1233
1519
  checkNumericContradictions(ctx, name, constraints);
1234
1520
  checkLengthContradictions(ctx, name, constraints);
1235
1521
  checkAllowedMembersContradiction(ctx, name, constraints);
1522
+ checkConstraintBroadening(ctx, name, constraints);
1236
1523
  checkTypeApplicability(ctx, name, type, constraints);
1237
1524
  }
1238
1525
  function validateElement(ctx, element) {
@@ -1259,8 +1546,8 @@ function validateElement(ctx, element) {
1259
1546
  function validateIR(ir, options) {
1260
1547
  const ctx = {
1261
1548
  diagnostics: [],
1262
- vendorPrefix: options?.vendorPrefix ?? "FORMSPEC",
1263
- extensionRegistry: options?.extensionRegistry
1549
+ extensionRegistry: options?.extensionRegistry,
1550
+ typeRegistry: ir.typeRegistry
1264
1551
  };
1265
1552
  for (const element of ir.elements) {
1266
1553
  validateElement(ctx, element);
@@ -1272,9 +1559,9 @@ function validateIR(ir, options) {
1272
1559
  }
1273
1560
 
1274
1561
  // src/browser.ts
1275
- function buildFormSchemas(form) {
1562
+ function buildFormSchemas(form, options) {
1276
1563
  return {
1277
- jsonSchema: generateJsonSchema(form),
1564
+ jsonSchema: generateJsonSchema(form, options),
1278
1565
  uiSchema: generateUiSchema(form)
1279
1566
  };
1280
1567
  }
@@ -1285,7 +1572,9 @@ function buildFormSchemas(form) {
1285
1572
  categorizationSchema,
1286
1573
  categorySchema,
1287
1574
  controlSchema,
1575
+ createExtensionRegistry,
1288
1576
  generateJsonSchema,
1577
+ generateJsonSchemaFromIR,
1289
1578
  generateUiSchema,
1290
1579
  getSchemaExtension,
1291
1580
  groupLayoutSchema,