@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.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
  }
@@ -313,11 +313,21 @@ function buildObjectProperties(elements, insideConditional = false) {
313
313
  import { IR_VERSION as IR_VERSION2 } from "@formspec/core";
314
314
 
315
315
  // src/json-schema/ir-generator.ts
316
- function makeContext() {
317
- return { defs: {} };
316
+ function makeContext(options) {
317
+ const vendorPrefix = options?.vendorPrefix ?? "x-formspec";
318
+ if (!vendorPrefix.startsWith("x-")) {
319
+ throw new Error(
320
+ `Invalid vendorPrefix "${vendorPrefix}". Extension JSON Schema keywords must start with "x-".`
321
+ );
322
+ }
323
+ return {
324
+ defs: {},
325
+ extensionRegistry: options?.extensionRegistry,
326
+ vendorPrefix
327
+ };
318
328
  }
319
- function generateJsonSchemaFromIR(ir) {
320
- const ctx = makeContext();
329
+ function generateJsonSchemaFromIR(ir, options) {
330
+ const ctx = makeContext(options);
321
331
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
322
332
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
323
333
  }
@@ -369,16 +379,16 @@ function generateFieldSchema(field, ctx) {
369
379
  directConstraints.push(c);
370
380
  }
371
381
  }
372
- applyConstraints(schema, directConstraints);
373
- applyAnnotations(schema, field.annotations);
382
+ applyConstraints(schema, directConstraints, ctx);
383
+ applyAnnotations(schema, field.annotations, ctx);
374
384
  if (pathConstraints.length === 0) {
375
385
  return schema;
376
386
  }
377
- return applyPathTargetedConstraints(schema, pathConstraints);
387
+ return applyPathTargetedConstraints(schema, pathConstraints, ctx);
378
388
  }
379
- function applyPathTargetedConstraints(schema, pathConstraints) {
389
+ function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
380
390
  if (schema.type === "array" && schema.items) {
381
- schema.items = applyPathTargetedConstraints(schema.items, pathConstraints);
391
+ schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
382
392
  return schema;
383
393
  }
384
394
  const byTarget = /* @__PURE__ */ new Map();
@@ -392,7 +402,7 @@ function applyPathTargetedConstraints(schema, pathConstraints) {
392
402
  const propertyOverrides = {};
393
403
  for (const [target, constraints] of byTarget) {
394
404
  const subSchema = {};
395
- applyConstraints(subSchema, constraints);
405
+ applyConstraints(subSchema, constraints, ctx);
396
406
  propertyOverrides[target] = subSchema;
397
407
  }
398
408
  if (schema.$ref) {
@@ -436,6 +446,8 @@ function generateTypeNode(type, ctx) {
436
446
  return generateArrayType(type, ctx);
437
447
  case "object":
438
448
  return generateObjectType(type, ctx);
449
+ case "record":
450
+ return generateRecordType(type, ctx);
439
451
  case "union":
440
452
  return generateUnionType(type, ctx);
441
453
  case "reference":
@@ -443,7 +455,7 @@ function generateTypeNode(type, ctx) {
443
455
  case "dynamic":
444
456
  return generateDynamicType(type);
445
457
  case "custom":
446
- return generateCustomType(type);
458
+ return generateCustomType(type, ctx);
447
459
  default: {
448
460
  const _exhaustive = type;
449
461
  return _exhaustive;
@@ -492,16 +504,27 @@ function generateObjectType(type, ctx) {
492
504
  }
493
505
  return schema;
494
506
  }
507
+ function generateRecordType(type, ctx) {
508
+ return {
509
+ type: "object",
510
+ additionalProperties: generateTypeNode(type.valueType, ctx)
511
+ };
512
+ }
495
513
  function generatePropertySchema(prop, ctx) {
496
514
  const schema = generateTypeNode(prop.type, ctx);
497
- applyConstraints(schema, prop.constraints);
498
- applyAnnotations(schema, prop.annotations);
515
+ applyConstraints(schema, prop.constraints, ctx);
516
+ applyAnnotations(schema, prop.annotations, ctx);
499
517
  return schema;
500
518
  }
501
519
  function generateUnionType(type, ctx) {
502
520
  if (isBooleanUnion(type)) {
503
521
  return { type: "boolean" };
504
522
  }
523
+ if (isNullableUnion(type)) {
524
+ return {
525
+ oneOf: type.members.map((m) => generateTypeNode(m, ctx))
526
+ };
527
+ }
505
528
  return {
506
529
  anyOf: type.members.map((m) => generateTypeNode(m, ctx))
507
530
  };
@@ -511,6 +534,13 @@ function isBooleanUnion(type) {
511
534
  const kinds = type.members.map((m) => m.kind);
512
535
  return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
513
536
  }
537
+ function isNullableUnion(type) {
538
+ if (type.members.length !== 2) return false;
539
+ const nullCount = type.members.filter(
540
+ (m) => m.kind === "primitive" && m.primitiveKind === "null"
541
+ ).length;
542
+ return nullCount === 1;
543
+ }
514
544
  function generateReferenceType(type) {
515
545
  return { $ref: `#/$defs/${type.name}` };
516
546
  }
@@ -531,10 +561,7 @@ function generateDynamicType(type) {
531
561
  "x-formspec-schemaSource": type.sourceKey
532
562
  };
533
563
  }
534
- function generateCustomType(_type) {
535
- return { type: "object" };
536
- }
537
- function applyConstraints(schema, constraints) {
564
+ function applyConstraints(schema, constraints, ctx) {
538
565
  for (const constraint of constraints) {
539
566
  switch (constraint.constraintKind) {
540
567
  case "minimum":
@@ -579,6 +606,7 @@ function applyConstraints(schema, constraints) {
579
606
  case "allowedMembers":
580
607
  break;
581
608
  case "custom":
609
+ applyCustomConstraint(schema, constraint, ctx);
582
610
  break;
583
611
  default: {
584
612
  const _exhaustive = constraint;
@@ -587,7 +615,7 @@ function applyConstraints(schema, constraints) {
587
615
  }
588
616
  }
589
617
  }
590
- function applyAnnotations(schema, annotations) {
618
+ function applyAnnotations(schema, annotations, ctx) {
591
619
  for (const annotation of annotations) {
592
620
  switch (annotation.annotationKind) {
593
621
  case "displayName":
@@ -607,6 +635,7 @@ function applyAnnotations(schema, annotations) {
607
635
  case "formatHint":
608
636
  break;
609
637
  case "custom":
638
+ applyCustomAnnotation(schema, annotation, ctx);
610
639
  break;
611
640
  default: {
612
641
  const _exhaustive = annotation;
@@ -615,11 +644,41 @@ function applyAnnotations(schema, annotations) {
615
644
  }
616
645
  }
617
646
  }
647
+ function generateCustomType(type, ctx) {
648
+ const registration = ctx.extensionRegistry?.findType(type.typeId);
649
+ if (registration === void 0) {
650
+ throw new Error(
651
+ `Cannot generate JSON Schema for custom type "${type.typeId}" without a matching extension registration`
652
+ );
653
+ }
654
+ return registration.toJsonSchema(type.payload, ctx.vendorPrefix);
655
+ }
656
+ function applyCustomConstraint(schema, constraint, ctx) {
657
+ const registration = ctx.extensionRegistry?.findConstraint(constraint.constraintId);
658
+ if (registration === void 0) {
659
+ throw new Error(
660
+ `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
661
+ );
662
+ }
663
+ Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
664
+ }
665
+ function applyCustomAnnotation(schema, annotation, ctx) {
666
+ const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
667
+ if (registration === void 0) {
668
+ throw new Error(
669
+ `Cannot generate JSON Schema for custom annotation "${annotation.annotationId}" without a matching extension registration`
670
+ );
671
+ }
672
+ if (registration.toJsonSchema === void 0) {
673
+ return;
674
+ }
675
+ Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
676
+ }
618
677
 
619
678
  // src/json-schema/generator.ts
620
- function generateJsonSchema(form) {
679
+ function generateJsonSchema(form, options) {
621
680
  const ir = canonicalizeChainDSL(form);
622
- return generateJsonSchemaFromIR(ir);
681
+ return generateJsonSchemaFromIR(ir, options);
623
682
  }
624
683
 
625
684
  // src/ui-schema/schema.ts
@@ -849,6 +908,48 @@ function getSchemaExtension(schema, key) {
849
908
  return schema[key];
850
909
  }
851
910
 
911
+ // src/extensions/registry.ts
912
+ function createExtensionRegistry(extensions) {
913
+ const typeMap = /* @__PURE__ */ new Map();
914
+ const constraintMap = /* @__PURE__ */ new Map();
915
+ const annotationMap = /* @__PURE__ */ new Map();
916
+ for (const ext of extensions) {
917
+ if (ext.types !== void 0) {
918
+ for (const type of ext.types) {
919
+ const qualifiedId = `${ext.extensionId}/${type.typeName}`;
920
+ if (typeMap.has(qualifiedId)) {
921
+ throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
922
+ }
923
+ typeMap.set(qualifiedId, type);
924
+ }
925
+ }
926
+ if (ext.constraints !== void 0) {
927
+ for (const constraint of ext.constraints) {
928
+ const qualifiedId = `${ext.extensionId}/${constraint.constraintName}`;
929
+ if (constraintMap.has(qualifiedId)) {
930
+ throw new Error(`Duplicate custom constraint ID: "${qualifiedId}"`);
931
+ }
932
+ constraintMap.set(qualifiedId, constraint);
933
+ }
934
+ }
935
+ if (ext.annotations !== void 0) {
936
+ for (const annotation of ext.annotations) {
937
+ const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
938
+ if (annotationMap.has(qualifiedId)) {
939
+ throw new Error(`Duplicate custom annotation ID: "${qualifiedId}"`);
940
+ }
941
+ annotationMap.set(qualifiedId, annotation);
942
+ }
943
+ }
944
+ }
945
+ return {
946
+ extensions,
947
+ findType: (typeId) => typeMap.get(typeId),
948
+ findConstraint: (constraintId) => constraintMap.get(constraintId),
949
+ findAnnotation: (annotationId) => annotationMap.get(annotationId)
950
+ };
951
+ }
952
+
852
953
  // src/json-schema/schema.ts
853
954
  import { z as z3 } from "zod";
854
955
  var jsonSchemaTypeSchema = z3.enum([
@@ -912,12 +1013,9 @@ var jsonSchema7Schema = z3.lazy(
912
1013
  );
913
1014
 
914
1015
  // src/validate/constraint-validator.ts
915
- function makeCode(ctx, category, number) {
916
- return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
917
- }
918
1016
  function addContradiction(ctx, message, primary, related) {
919
1017
  ctx.diagnostics.push({
920
- code: makeCode(ctx, "CONTRADICTION", 1),
1018
+ code: "CONTRADICTING_CONSTRAINTS",
921
1019
  message,
922
1020
  severity: "error",
923
1021
  primaryLocation: primary,
@@ -926,7 +1024,7 @@ function addContradiction(ctx, message, primary, related) {
926
1024
  }
927
1025
  function addTypeMismatch(ctx, message, primary) {
928
1026
  ctx.diagnostics.push({
929
- code: makeCode(ctx, "TYPE_MISMATCH", 1),
1027
+ code: "TYPE_MISMATCH",
930
1028
  message,
931
1029
  severity: "error",
932
1030
  primaryLocation: primary,
@@ -935,13 +1033,22 @@ function addTypeMismatch(ctx, message, primary) {
935
1033
  }
936
1034
  function addUnknownExtension(ctx, message, primary) {
937
1035
  ctx.diagnostics.push({
938
- code: makeCode(ctx, "UNKNOWN_EXTENSION", 1),
1036
+ code: "UNKNOWN_EXTENSION",
939
1037
  message,
940
1038
  severity: "warning",
941
1039
  primaryLocation: primary,
942
1040
  relatedLocations: []
943
1041
  });
944
1042
  }
1043
+ function addConstraintBroadening(ctx, message, primary, related) {
1044
+ ctx.diagnostics.push({
1045
+ code: "CONSTRAINT_BROADENING",
1046
+ message,
1047
+ severity: "error",
1048
+ primaryLocation: primary,
1049
+ relatedLocations: [related]
1050
+ });
1051
+ }
945
1052
  function findNumeric(constraints, constraintKind) {
946
1053
  return constraints.find((c) => c.constraintKind === constraintKind);
947
1054
  }
@@ -953,6 +1060,126 @@ function findAllowedMembers(constraints) {
953
1060
  (c) => c.constraintKind === "allowedMembers"
954
1061
  );
955
1062
  }
1063
+ function isOrderedBoundConstraint(constraint) {
1064
+ 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";
1065
+ }
1066
+ function pathKey(constraint) {
1067
+ return constraint.path?.segments.join(".") ?? "";
1068
+ }
1069
+ function orderedBoundFamily(kind) {
1070
+ switch (kind) {
1071
+ case "minimum":
1072
+ case "exclusiveMinimum":
1073
+ return "numeric-lower";
1074
+ case "maximum":
1075
+ case "exclusiveMaximum":
1076
+ return "numeric-upper";
1077
+ case "minLength":
1078
+ return "minLength";
1079
+ case "minItems":
1080
+ return "minItems";
1081
+ case "maxLength":
1082
+ return "maxLength";
1083
+ case "maxItems":
1084
+ return "maxItems";
1085
+ default: {
1086
+ const _exhaustive = kind;
1087
+ return _exhaustive;
1088
+ }
1089
+ }
1090
+ }
1091
+ function isNumericLowerKind(kind) {
1092
+ return kind === "minimum" || kind === "exclusiveMinimum";
1093
+ }
1094
+ function isNumericUpperKind(kind) {
1095
+ return kind === "maximum" || kind === "exclusiveMaximum";
1096
+ }
1097
+ function describeConstraintTag(constraint) {
1098
+ return `@${constraint.constraintKind}`;
1099
+ }
1100
+ function compareConstraintStrength(current, previous) {
1101
+ const family = orderedBoundFamily(current.constraintKind);
1102
+ if (family === "numeric-lower") {
1103
+ if (!isNumericLowerKind(current.constraintKind) || !isNumericLowerKind(previous.constraintKind)) {
1104
+ throw new Error("numeric-lower family received non-numeric lower-bound constraint");
1105
+ }
1106
+ if (current.value !== previous.value) {
1107
+ return current.value > previous.value ? 1 : -1;
1108
+ }
1109
+ if (current.constraintKind === "exclusiveMinimum" && previous.constraintKind === "minimum") {
1110
+ return 1;
1111
+ }
1112
+ if (current.constraintKind === "minimum" && previous.constraintKind === "exclusiveMinimum") {
1113
+ return -1;
1114
+ }
1115
+ return 0;
1116
+ }
1117
+ if (family === "numeric-upper") {
1118
+ if (!isNumericUpperKind(current.constraintKind) || !isNumericUpperKind(previous.constraintKind)) {
1119
+ throw new Error("numeric-upper family received non-numeric upper-bound constraint");
1120
+ }
1121
+ if (current.value !== previous.value) {
1122
+ return current.value < previous.value ? 1 : -1;
1123
+ }
1124
+ if (current.constraintKind === "exclusiveMaximum" && previous.constraintKind === "maximum") {
1125
+ return 1;
1126
+ }
1127
+ if (current.constraintKind === "maximum" && previous.constraintKind === "exclusiveMaximum") {
1128
+ return -1;
1129
+ }
1130
+ return 0;
1131
+ }
1132
+ switch (family) {
1133
+ case "minLength":
1134
+ case "minItems":
1135
+ if (current.value === previous.value) {
1136
+ return 0;
1137
+ }
1138
+ return current.value > previous.value ? 1 : -1;
1139
+ case "maxLength":
1140
+ case "maxItems":
1141
+ if (current.value === previous.value) {
1142
+ return 0;
1143
+ }
1144
+ return current.value < previous.value ? 1 : -1;
1145
+ default: {
1146
+ const _exhaustive = family;
1147
+ return _exhaustive;
1148
+ }
1149
+ }
1150
+ }
1151
+ function checkConstraintBroadening(ctx, fieldName, constraints) {
1152
+ const strongestByKey = /* @__PURE__ */ new Map();
1153
+ for (const constraint of constraints) {
1154
+ if (!isOrderedBoundConstraint(constraint)) {
1155
+ continue;
1156
+ }
1157
+ const key = `${orderedBoundFamily(constraint.constraintKind)}:${pathKey(constraint)}`;
1158
+ const previous = strongestByKey.get(key);
1159
+ if (previous === void 0) {
1160
+ strongestByKey.set(key, constraint);
1161
+ continue;
1162
+ }
1163
+ const strength = compareConstraintStrength(constraint, previous);
1164
+ if (strength < 0) {
1165
+ const displayFieldName = formatPathTargetFieldName(
1166
+ fieldName,
1167
+ constraint.path?.segments ?? []
1168
+ );
1169
+ addConstraintBroadening(
1170
+ ctx,
1171
+ `Field "${displayFieldName}": ${describeConstraintTag(constraint)} (${String(constraint.value)}) is broader than earlier ${describeConstraintTag(previous)} (${String(previous.value)}). Constraints can only narrow.`,
1172
+ constraint.provenance,
1173
+ previous.provenance
1174
+ );
1175
+ continue;
1176
+ }
1177
+ if (strength <= 0) {
1178
+ continue;
1179
+ }
1180
+ strongestByKey.set(key, constraint);
1181
+ }
1182
+ }
956
1183
  function checkNumericContradictions(ctx, fieldName, constraints) {
957
1184
  const min = findNumeric(constraints, "minimum");
958
1185
  const max = findNumeric(constraints, "maximum");
@@ -1049,6 +1276,8 @@ function typeLabel(type) {
1049
1276
  return "array";
1050
1277
  case "object":
1051
1278
  return "object";
1279
+ case "record":
1280
+ return "record";
1052
1281
  case "union":
1053
1282
  return "union";
1054
1283
  case "reference":
@@ -1063,85 +1292,140 @@ function typeLabel(type) {
1063
1292
  }
1064
1293
  }
1065
1294
  }
1066
- function checkTypeApplicability(ctx, fieldName, type, constraints) {
1067
- const isNumber = type.kind === "primitive" && type.primitiveKind === "number";
1068
- const isString = type.kind === "primitive" && type.primitiveKind === "string";
1069
- const isArray = type.kind === "array";
1070
- const isEnum = type.kind === "enum";
1071
- const label = typeLabel(type);
1072
- for (const constraint of constraints) {
1073
- if (constraint.path) {
1074
- const isTraversable = type.kind === "object" || type.kind === "array" || type.kind === "reference";
1075
- if (!isTraversable) {
1295
+ function dereferenceType(ctx, type) {
1296
+ let current = type;
1297
+ const seen = /* @__PURE__ */ new Set();
1298
+ while (current.kind === "reference") {
1299
+ if (seen.has(current.name)) {
1300
+ return current;
1301
+ }
1302
+ seen.add(current.name);
1303
+ const definition = ctx.typeRegistry[current.name];
1304
+ if (definition === void 0) {
1305
+ return current;
1306
+ }
1307
+ current = definition.type;
1308
+ }
1309
+ return current;
1310
+ }
1311
+ function resolvePathTargetType(ctx, type, segments) {
1312
+ const effectiveType = dereferenceType(ctx, type);
1313
+ if (segments.length === 0) {
1314
+ return { kind: "resolved", type: effectiveType };
1315
+ }
1316
+ if (effectiveType.kind === "array") {
1317
+ return resolvePathTargetType(ctx, effectiveType.items, segments);
1318
+ }
1319
+ if (effectiveType.kind === "object") {
1320
+ const [segment, ...rest] = segments;
1321
+ if (segment === void 0) {
1322
+ throw new Error("Invariant violation: object path traversal requires a segment");
1323
+ }
1324
+ const property = effectiveType.properties.find((prop) => prop.name === segment);
1325
+ if (property === void 0) {
1326
+ return { kind: "missing-property", segment };
1327
+ }
1328
+ return resolvePathTargetType(ctx, property.type, rest);
1329
+ }
1330
+ return { kind: "unresolvable", type: effectiveType };
1331
+ }
1332
+ function formatPathTargetFieldName(fieldName, path) {
1333
+ return path.length === 0 ? fieldName : `${fieldName}.${path.join(".")}`;
1334
+ }
1335
+ function checkConstraintOnType(ctx, fieldName, type, constraint) {
1336
+ const effectiveType = dereferenceType(ctx, type);
1337
+ const isNumber = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "number";
1338
+ const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
1339
+ const isArray = effectiveType.kind === "array";
1340
+ const isEnum = effectiveType.kind === "enum";
1341
+ const label = typeLabel(effectiveType);
1342
+ const ck = constraint.constraintKind;
1343
+ switch (ck) {
1344
+ case "minimum":
1345
+ case "maximum":
1346
+ case "exclusiveMinimum":
1347
+ case "exclusiveMaximum":
1348
+ case "multipleOf": {
1349
+ if (!isNumber) {
1076
1350
  addTypeMismatch(
1077
1351
  ctx,
1078
- `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${label}" cannot be traversed`,
1352
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
1079
1353
  constraint.provenance
1080
1354
  );
1081
1355
  }
1082
- continue;
1356
+ break;
1083
1357
  }
1084
- const ck = constraint.constraintKind;
1085
- switch (ck) {
1086
- case "minimum":
1087
- case "maximum":
1088
- case "exclusiveMinimum":
1089
- case "exclusiveMaximum":
1090
- case "multipleOf": {
1091
- if (!isNumber) {
1092
- addTypeMismatch(
1093
- ctx,
1094
- `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
1095
- constraint.provenance
1096
- );
1097
- }
1098
- break;
1099
- }
1100
- case "minLength":
1101
- case "maxLength":
1102
- case "pattern": {
1103
- if (!isString) {
1104
- addTypeMismatch(
1105
- ctx,
1106
- `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1107
- constraint.provenance
1108
- );
1109
- }
1110
- break;
1358
+ case "minLength":
1359
+ case "maxLength":
1360
+ case "pattern": {
1361
+ if (!isString) {
1362
+ addTypeMismatch(
1363
+ ctx,
1364
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1365
+ constraint.provenance
1366
+ );
1111
1367
  }
1112
- case "minItems":
1113
- case "maxItems":
1114
- case "uniqueItems": {
1115
- if (!isArray) {
1116
- addTypeMismatch(
1117
- ctx,
1118
- `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
1119
- constraint.provenance
1120
- );
1121
- }
1122
- break;
1368
+ break;
1369
+ }
1370
+ case "minItems":
1371
+ case "maxItems":
1372
+ case "uniqueItems": {
1373
+ if (!isArray) {
1374
+ addTypeMismatch(
1375
+ ctx,
1376
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
1377
+ constraint.provenance
1378
+ );
1123
1379
  }
1124
- case "allowedMembers": {
1125
- if (!isEnum) {
1126
- addTypeMismatch(
1127
- ctx,
1128
- `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
1129
- constraint.provenance
1130
- );
1131
- }
1132
- break;
1380
+ break;
1381
+ }
1382
+ case "allowedMembers": {
1383
+ if (!isEnum) {
1384
+ addTypeMismatch(
1385
+ ctx,
1386
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
1387
+ constraint.provenance
1388
+ );
1133
1389
  }
1134
- case "custom": {
1135
- checkCustomConstraint(ctx, fieldName, type, constraint);
1136
- break;
1390
+ break;
1391
+ }
1392
+ case "custom": {
1393
+ checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
1394
+ break;
1395
+ }
1396
+ default: {
1397
+ const _exhaustive = constraint;
1398
+ throw new Error(
1399
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
1400
+ );
1401
+ }
1402
+ }
1403
+ }
1404
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
1405
+ for (const constraint of constraints) {
1406
+ if (constraint.path) {
1407
+ const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
1408
+ const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
1409
+ if (resolution.kind === "missing-property") {
1410
+ addTypeMismatch(
1411
+ ctx,
1412
+ `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
1413
+ constraint.provenance
1414
+ );
1415
+ continue;
1137
1416
  }
1138
- default: {
1139
- const _exhaustive = constraint;
1140
- throw new Error(
1141
- `Unhandled constraint kind: ${_exhaustive.constraintKind}`
1417
+ if (resolution.kind === "unresolvable") {
1418
+ addTypeMismatch(
1419
+ ctx,
1420
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" is invalid because type "${typeLabel(resolution.type)}" cannot be traversed`,
1421
+ constraint.provenance
1142
1422
  );
1423
+ continue;
1143
1424
  }
1425
+ checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
1426
+ continue;
1144
1427
  }
1428
+ checkConstraintOnType(ctx, fieldName, type, constraint);
1145
1429
  }
1146
1430
  }
1147
1431
  function checkCustomConstraint(ctx, fieldName, type, constraint) {
@@ -1185,6 +1469,7 @@ function validateConstraints(ctx, name, type, constraints) {
1185
1469
  checkNumericContradictions(ctx, name, constraints);
1186
1470
  checkLengthContradictions(ctx, name, constraints);
1187
1471
  checkAllowedMembersContradiction(ctx, name, constraints);
1472
+ checkConstraintBroadening(ctx, name, constraints);
1188
1473
  checkTypeApplicability(ctx, name, type, constraints);
1189
1474
  }
1190
1475
  function validateElement(ctx, element) {
@@ -1211,8 +1496,8 @@ function validateElement(ctx, element) {
1211
1496
  function validateIR(ir, options) {
1212
1497
  const ctx = {
1213
1498
  diagnostics: [],
1214
- vendorPrefix: options?.vendorPrefix ?? "FORMSPEC",
1215
- extensionRegistry: options?.extensionRegistry
1499
+ extensionRegistry: options?.extensionRegistry,
1500
+ typeRegistry: ir.typeRegistry
1216
1501
  };
1217
1502
  for (const element of ir.elements) {
1218
1503
  validateElement(ctx, element);
@@ -1224,9 +1509,9 @@ function validateIR(ir, options) {
1224
1509
  }
1225
1510
 
1226
1511
  // src/browser.ts
1227
- function buildFormSchemas(form) {
1512
+ function buildFormSchemas(form, options) {
1228
1513
  return {
1229
- jsonSchema: generateJsonSchema(form),
1514
+ jsonSchema: generateJsonSchema(form, options),
1230
1515
  uiSchema: generateUiSchema(form)
1231
1516
  };
1232
1517
  }
@@ -1236,7 +1521,9 @@ export {
1236
1521
  categorizationSchema,
1237
1522
  categorySchema,
1238
1523
  controlSchema,
1524
+ createExtensionRegistry,
1239
1525
  generateJsonSchema,
1526
+ generateJsonSchemaFromIR,
1240
1527
  generateUiSchema,
1241
1528
  getSchemaExtension,
1242
1529
  groupLayoutSchema,