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

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 (52) hide show
  1. package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
  2. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  3. package/dist/__tests__/fixtures/example-numeric-extension.d.ts +20 -0
  4. package/dist/__tests__/fixtures/example-numeric-extension.d.ts.map +1 -0
  5. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +31 -0
  6. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
  7. package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
  8. package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
  9. package/dist/__tests__/numeric-extension.integration.test.d.ts +2 -0
  10. package/dist/__tests__/numeric-extension.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/parity/utils.d.ts +5 -3
  12. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  13. package/dist/analyzer/class-analyzer.d.ts +8 -5
  14. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  15. package/dist/analyzer/jsdoc-constraints.d.ts +3 -2
  16. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  17. package/dist/analyzer/tsdoc-parser.d.ts +38 -4
  18. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  19. package/dist/browser.cjs +371 -21
  20. package/dist/browser.cjs.map +1 -1
  21. package/dist/browser.d.ts.map +1 -1
  22. package/dist/browser.js +371 -21
  23. package/dist/browser.js.map +1 -1
  24. package/dist/build.d.ts +67 -3
  25. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  26. package/dist/cli.cjs +1159 -150
  27. package/dist/cli.cjs.map +1 -1
  28. package/dist/cli.js +1159 -150
  29. package/dist/cli.js.map +1 -1
  30. package/dist/extensions/registry.d.ts +25 -1
  31. package/dist/extensions/registry.d.ts.map +1 -1
  32. package/dist/generators/class-schema.d.ts +4 -4
  33. package/dist/generators/class-schema.d.ts.map +1 -1
  34. package/dist/generators/mixed-authoring.d.ts +45 -0
  35. package/dist/generators/mixed-authoring.d.ts.map +1 -0
  36. package/dist/index.cjs +1146 -149
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +1145 -149
  41. package/dist/index.js.map +1 -1
  42. package/dist/internals.cjs +1156 -149
  43. package/dist/internals.cjs.map +1 -1
  44. package/dist/internals.js +1154 -147
  45. package/dist/internals.js.map +1 -1
  46. package/dist/json-schema/ir-generator.d.ts +3 -2
  47. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  48. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  49. package/dist/validate/constraint-validator.d.ts.map +1 -1
  50. package/package.json +3 -3
  51. package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
  52. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,KAAK,cAAc,EACpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGrD,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,YAAY,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AACrF,YAAY,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E,YAAY,EACV,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,YAAY,EACV,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,IAAI,cAAc,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGlF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,WAAW,EAAE,EAC/D,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,uBAAuB,GAChC,WAAW,CAKb"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAsB,KAAK,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAEhG,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGrD,YAAY,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,YAAY,EAAE,+BAA+B,EAAE,MAAM,+BAA+B,CAAC;AACrF,YAAY,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E,YAAY,EACV,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,YAAY,EACV,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,gBAAgB,EAChB,yBAAyB,EACzB,mBAAmB,EACnB,0BAA0B,EAC1B,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,sBAAsB,EACtB,iBAAiB,EACjB,oBAAoB,EACpB,cAAc,EACd,kBAAkB,EAClB,qBAAqB,EACrB,QAAQ,IAAI,cAAc,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAGlF,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAGjF,OAAO,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AAChE,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,yCAAyC;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;CAC7B;AAED;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAEhE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,WAAW,EAAE,EAC/D,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,CAAC,EAAE,uBAAuB,GAChC,WAAW,CAKb"}
package/dist/browser.js CHANGED
@@ -330,6 +330,9 @@ function generateJsonSchemaFromIR(ir, options) {
330
330
  const ctx = makeContext(options);
331
331
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
332
332
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
333
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
334
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
335
+ }
333
336
  }
334
337
  const properties = {};
335
338
  const required = [];
@@ -341,6 +344,9 @@ function generateJsonSchemaFromIR(ir, options) {
341
344
  properties,
342
345
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
343
346
  };
347
+ if (ir.annotations && ir.annotations.length > 0) {
348
+ applyAnnotations(result, ir.annotations, ctx);
349
+ }
344
350
  if (Object.keys(ctx.defs).length > 0) {
345
351
  result.$defs = ctx.defs;
346
352
  }
@@ -370,22 +376,51 @@ function collectFields(elements, properties, required, ctx) {
370
376
  }
371
377
  function generateFieldSchema(field, ctx) {
372
378
  const schema = generateTypeNode(field.type, ctx);
379
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
373
380
  const directConstraints = [];
381
+ const itemConstraints = [];
374
382
  const pathConstraints = [];
375
383
  for (const c of field.constraints) {
376
384
  if (c.path) {
377
385
  pathConstraints.push(c);
386
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
387
+ itemConstraints.push(c);
378
388
  } else {
379
389
  directConstraints.push(c);
380
390
  }
381
391
  }
382
392
  applyConstraints(schema, directConstraints, ctx);
383
- applyAnnotations(schema, field.annotations, ctx);
393
+ if (itemStringSchema !== void 0) {
394
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
395
+ }
396
+ const rootAnnotations = [];
397
+ const itemAnnotations = [];
398
+ for (const annotation of field.annotations) {
399
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
400
+ itemAnnotations.push(annotation);
401
+ } else {
402
+ rootAnnotations.push(annotation);
403
+ }
404
+ }
405
+ applyAnnotations(schema, rootAnnotations, ctx);
406
+ if (itemStringSchema !== void 0) {
407
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
408
+ }
384
409
  if (pathConstraints.length === 0) {
385
410
  return schema;
386
411
  }
387
412
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
388
413
  }
414
+ function isStringItemConstraint(constraint) {
415
+ switch (constraint.constraintKind) {
416
+ case "minLength":
417
+ case "maxLength":
418
+ case "pattern":
419
+ return true;
420
+ default:
421
+ return false;
422
+ }
423
+ }
389
424
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
390
425
  if (schema.type === "array" && schema.items) {
391
426
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -603,6 +638,9 @@ function applyConstraints(schema, constraints, ctx) {
603
638
  case "uniqueItems":
604
639
  schema.uniqueItems = constraint.value;
605
640
  break;
641
+ case "const":
642
+ schema.const = constraint.value;
643
+ break;
606
644
  case "allowedMembers":
607
645
  break;
608
646
  case "custom":
@@ -627,8 +665,14 @@ function applyAnnotations(schema, annotations, ctx) {
627
665
  case "defaultValue":
628
666
  schema.default = annotation.value;
629
667
  break;
668
+ case "format":
669
+ schema.format = annotation.value;
670
+ break;
630
671
  case "deprecated":
631
672
  schema.deprecated = true;
673
+ if (annotation.message !== void 0 && annotation.message !== "") {
674
+ schema["x-formspec-deprecation-description"] = annotation.message;
675
+ }
632
676
  break;
633
677
  case "placeholder":
634
678
  break;
@@ -660,7 +704,12 @@ function applyCustomConstraint(schema, constraint, ctx) {
660
704
  `Cannot generate JSON Schema for custom constraint "${constraint.constraintId}" without a matching extension registration`
661
705
  );
662
706
  }
663
- Object.assign(schema, registration.toJsonSchema(constraint.payload, ctx.vendorPrefix));
707
+ assignVendorPrefixedExtensionKeywords(
708
+ schema,
709
+ registration.toJsonSchema(constraint.payload, ctx.vendorPrefix),
710
+ ctx.vendorPrefix,
711
+ `custom constraint "${constraint.constraintId}"`
712
+ );
664
713
  }
665
714
  function applyCustomAnnotation(schema, annotation, ctx) {
666
715
  const registration = ctx.extensionRegistry?.findAnnotation(annotation.annotationId);
@@ -672,7 +721,22 @@ function applyCustomAnnotation(schema, annotation, ctx) {
672
721
  if (registration.toJsonSchema === void 0) {
673
722
  return;
674
723
  }
675
- Object.assign(schema, registration.toJsonSchema(annotation.value, ctx.vendorPrefix));
724
+ assignVendorPrefixedExtensionKeywords(
725
+ schema,
726
+ registration.toJsonSchema(annotation.value, ctx.vendorPrefix),
727
+ ctx.vendorPrefix,
728
+ `custom annotation "${annotation.annotationId}"`
729
+ );
730
+ }
731
+ function assignVendorPrefixedExtensionKeywords(schema, extensionSchema, vendorPrefix, source) {
732
+ for (const [key, value] of Object.entries(extensionSchema)) {
733
+ if (!key.startsWith(`${vendorPrefix}-`)) {
734
+ throw new Error(
735
+ `Cannot apply ${source}: extension hooks may only emit "${vendorPrefix}-*" JSON Schema keywords`
736
+ );
737
+ }
738
+ schema[key] = value;
739
+ }
676
740
  }
677
741
 
678
742
  // src/json-schema/generator.ts
@@ -816,25 +880,31 @@ function createShowRule(fieldName, value) {
816
880
  }
817
881
  };
818
882
  }
883
+ function flattenConditionSchema(scope, schema) {
884
+ if (schema.allOf === void 0) {
885
+ if (scope === "#") {
886
+ return [schema];
887
+ }
888
+ const fieldName = scope.replace("#/properties/", "");
889
+ return [
890
+ {
891
+ properties: {
892
+ [fieldName]: schema
893
+ }
894
+ }
895
+ ];
896
+ }
897
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
898
+ }
819
899
  function combineRules(parentRule, childRule) {
820
- const parentCondition = parentRule.condition;
821
- const childCondition = childRule.condition;
822
900
  return {
823
901
  effect: "SHOW",
824
902
  condition: {
825
903
  scope: "#",
826
904
  schema: {
827
905
  allOf: [
828
- {
829
- properties: {
830
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
831
- }
832
- },
833
- {
834
- properties: {
835
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
836
- }
837
- }
906
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
907
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
838
908
  ]
839
909
  }
840
910
  }
@@ -842,10 +912,14 @@ function combineRules(parentRule, childRule) {
842
912
  }
843
913
  function fieldNodeToControl(field, parentRule) {
844
914
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
915
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
845
916
  const control = {
846
917
  type: "Control",
847
918
  scope: fieldToScope(field.name),
848
919
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
920
+ ...placeholderAnnotation !== void 0 && {
921
+ options: { placeholder: placeholderAnnotation.value }
922
+ },
849
923
  ...parentRule !== void 0 && { rule: parentRule }
850
924
  };
851
925
  return control;
@@ -911,7 +985,10 @@ function getSchemaExtension(schema, key) {
911
985
  // src/extensions/registry.ts
912
986
  function createExtensionRegistry(extensions) {
913
987
  const typeMap = /* @__PURE__ */ new Map();
988
+ const typeNameMap = /* @__PURE__ */ new Map();
914
989
  const constraintMap = /* @__PURE__ */ new Map();
990
+ const constraintTagMap = /* @__PURE__ */ new Map();
991
+ const builtinBroadeningMap = /* @__PURE__ */ new Map();
915
992
  const annotationMap = /* @__PURE__ */ new Map();
916
993
  for (const ext of extensions) {
917
994
  if (ext.types !== void 0) {
@@ -921,6 +998,27 @@ function createExtensionRegistry(extensions) {
921
998
  throw new Error(`Duplicate custom type ID: "${qualifiedId}"`);
922
999
  }
923
1000
  typeMap.set(qualifiedId, type);
1001
+ for (const sourceTypeName of type.tsTypeNames ?? [type.typeName]) {
1002
+ if (typeNameMap.has(sourceTypeName)) {
1003
+ throw new Error(`Duplicate custom type source name: "${sourceTypeName}"`);
1004
+ }
1005
+ typeNameMap.set(sourceTypeName, {
1006
+ extensionId: ext.extensionId,
1007
+ registration: type
1008
+ });
1009
+ }
1010
+ if (type.builtinConstraintBroadenings !== void 0) {
1011
+ for (const broadening of type.builtinConstraintBroadenings) {
1012
+ const key = `${qualifiedId}:${broadening.tagName}`;
1013
+ if (builtinBroadeningMap.has(key)) {
1014
+ throw new Error(`Duplicate built-in constraint broadening: "${key}"`);
1015
+ }
1016
+ builtinBroadeningMap.set(key, {
1017
+ extensionId: ext.extensionId,
1018
+ registration: broadening
1019
+ });
1020
+ }
1021
+ }
924
1022
  }
925
1023
  }
926
1024
  if (ext.constraints !== void 0) {
@@ -932,6 +1030,17 @@ function createExtensionRegistry(extensions) {
932
1030
  constraintMap.set(qualifiedId, constraint);
933
1031
  }
934
1032
  }
1033
+ if (ext.constraintTags !== void 0) {
1034
+ for (const tag of ext.constraintTags) {
1035
+ if (constraintTagMap.has(tag.tagName)) {
1036
+ throw new Error(`Duplicate custom constraint tag: "@${tag.tagName}"`);
1037
+ }
1038
+ constraintTagMap.set(tag.tagName, {
1039
+ extensionId: ext.extensionId,
1040
+ registration: tag
1041
+ });
1042
+ }
1043
+ }
935
1044
  if (ext.annotations !== void 0) {
936
1045
  for (const annotation of ext.annotations) {
937
1046
  const qualifiedId = `${ext.extensionId}/${annotation.annotationName}`;
@@ -945,7 +1054,10 @@ function createExtensionRegistry(extensions) {
945
1054
  return {
946
1055
  extensions,
947
1056
  findType: (typeId) => typeMap.get(typeId),
1057
+ findTypeByName: (typeName) => typeNameMap.get(typeName),
948
1058
  findConstraint: (constraintId) => constraintMap.get(constraintId),
1059
+ findConstraintTag: (tagName) => constraintTagMap.get(tagName),
1060
+ findBuiltinConstraintBroadening: (typeId, tagName) => builtinBroadeningMap.get(`${typeId}:${tagName}`),
949
1061
  findAnnotation: (annotationId) => annotationMap.get(annotationId)
950
1062
  };
951
1063
  }
@@ -1013,6 +1125,7 @@ var jsonSchema7Schema = z3.lazy(
1013
1125
  );
1014
1126
 
1015
1127
  // src/validate/constraint-validator.ts
1128
+ import { normalizeConstraintTagName } from "@formspec/core";
1016
1129
  function addContradiction(ctx, message, primary, related) {
1017
1130
  ctx.diagnostics.push({
1018
1131
  code: "CONTRADICTING_CONSTRAINTS",
@@ -1040,6 +1153,15 @@ function addUnknownExtension(ctx, message, primary) {
1040
1153
  relatedLocations: []
1041
1154
  });
1042
1155
  }
1156
+ function addUnknownPathTarget(ctx, message, primary) {
1157
+ ctx.diagnostics.push({
1158
+ code: "UNKNOWN_PATH_TARGET",
1159
+ message,
1160
+ severity: "error",
1161
+ primaryLocation: primary,
1162
+ relatedLocations: []
1163
+ });
1164
+ }
1043
1165
  function addConstraintBroadening(ctx, message, primary, related) {
1044
1166
  ctx.diagnostics.push({
1045
1167
  code: "CONSTRAINT_BROADENING",
@@ -1049,6 +1171,13 @@ function addConstraintBroadening(ctx, message, primary, related) {
1049
1171
  relatedLocations: [related]
1050
1172
  });
1051
1173
  }
1174
+ function getExtensionIdFromConstraintId(constraintId) {
1175
+ const separator = constraintId.lastIndexOf("/");
1176
+ if (separator <= 0) {
1177
+ return null;
1178
+ }
1179
+ return constraintId.slice(0, separator);
1180
+ }
1052
1181
  function findNumeric(constraints, constraintKind) {
1053
1182
  return constraints.find((c) => c.constraintKind === constraintKind);
1054
1183
  }
@@ -1060,6 +1189,45 @@ function findAllowedMembers(constraints) {
1060
1189
  (c) => c.constraintKind === "allowedMembers"
1061
1190
  );
1062
1191
  }
1192
+ function findConstConstraints(constraints) {
1193
+ return constraints.filter(
1194
+ (c) => c.constraintKind === "const"
1195
+ );
1196
+ }
1197
+ function jsonValueEquals(left, right) {
1198
+ if (left === right) {
1199
+ return true;
1200
+ }
1201
+ if (Array.isArray(left) || Array.isArray(right)) {
1202
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
1203
+ return false;
1204
+ }
1205
+ return left.every((item, index) => jsonValueEquals(item, right[index]));
1206
+ }
1207
+ if (isJsonObject(left) || isJsonObject(right)) {
1208
+ if (!isJsonObject(left) || !isJsonObject(right)) {
1209
+ return false;
1210
+ }
1211
+ const leftKeys = Object.keys(left).sort();
1212
+ const rightKeys = Object.keys(right).sort();
1213
+ if (leftKeys.length !== rightKeys.length) {
1214
+ return false;
1215
+ }
1216
+ return leftKeys.every((key, index) => {
1217
+ const rightKey = rightKeys[index];
1218
+ if (rightKey !== key) {
1219
+ return false;
1220
+ }
1221
+ const leftValue = left[key];
1222
+ const rightValue = right[rightKey];
1223
+ return leftValue !== void 0 && rightValue !== void 0 && jsonValueEquals(leftValue, rightValue);
1224
+ });
1225
+ }
1226
+ return false;
1227
+ }
1228
+ function isJsonObject(value) {
1229
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1230
+ }
1063
1231
  function isOrderedBoundConstraint(constraint) {
1064
1232
  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
1233
  }
@@ -1180,6 +1348,112 @@ function checkConstraintBroadening(ctx, fieldName, constraints) {
1180
1348
  strongestByKey.set(key, constraint);
1181
1349
  }
1182
1350
  }
1351
+ function compareCustomConstraintStrength(current, previous) {
1352
+ const order = current.comparePayloads(current.constraint.payload, previous.constraint.payload);
1353
+ const equalPayloadTiebreaker = order === 0 ? compareSemanticInclusivity(current.role.inclusive, previous.role.inclusive) : order;
1354
+ switch (current.role.bound) {
1355
+ case "lower":
1356
+ return equalPayloadTiebreaker;
1357
+ case "upper":
1358
+ return equalPayloadTiebreaker === 0 ? 0 : -equalPayloadTiebreaker;
1359
+ case "exact":
1360
+ return order === 0 ? 0 : Number.NaN;
1361
+ default: {
1362
+ const _exhaustive = current.role.bound;
1363
+ return _exhaustive;
1364
+ }
1365
+ }
1366
+ }
1367
+ function compareSemanticInclusivity(currentInclusive, previousInclusive) {
1368
+ if (currentInclusive === previousInclusive) {
1369
+ return 0;
1370
+ }
1371
+ return currentInclusive ? -1 : 1;
1372
+ }
1373
+ function customConstraintsContradict(lower, upper) {
1374
+ const order = lower.comparePayloads(lower.constraint.payload, upper.constraint.payload);
1375
+ if (order > 0) {
1376
+ return true;
1377
+ }
1378
+ if (order < 0) {
1379
+ return false;
1380
+ }
1381
+ return !lower.role.inclusive || !upper.role.inclusive;
1382
+ }
1383
+ function describeCustomConstraintTag(constraint) {
1384
+ return constraint.provenance.tagName ?? constraint.constraintId;
1385
+ }
1386
+ function checkCustomConstraintSemantics(ctx, fieldName, constraints) {
1387
+ if (ctx.extensionRegistry === void 0) {
1388
+ return;
1389
+ }
1390
+ const strongestByKey = /* @__PURE__ */ new Map();
1391
+ const lowerByFamily = /* @__PURE__ */ new Map();
1392
+ const upperByFamily = /* @__PURE__ */ new Map();
1393
+ for (const constraint of constraints) {
1394
+ if (constraint.constraintKind !== "custom") {
1395
+ continue;
1396
+ }
1397
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
1398
+ if (registration?.comparePayloads === void 0 || registration.semanticRole === void 0) {
1399
+ continue;
1400
+ }
1401
+ const entry = {
1402
+ constraint,
1403
+ comparePayloads: registration.comparePayloads,
1404
+ role: registration.semanticRole
1405
+ };
1406
+ const familyKey = `${registration.semanticRole.family}:${pathKey(constraint)}`;
1407
+ const boundKey = `${familyKey}:${registration.semanticRole.bound}`;
1408
+ const previous = strongestByKey.get(boundKey);
1409
+ if (previous !== void 0) {
1410
+ const strength = compareCustomConstraintStrength(entry, previous);
1411
+ if (Number.isNaN(strength)) {
1412
+ addContradiction(
1413
+ ctx,
1414
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} conflicts with ${describeCustomConstraintTag(previous.constraint)}`,
1415
+ constraint.provenance,
1416
+ previous.constraint.provenance
1417
+ );
1418
+ continue;
1419
+ }
1420
+ if (strength < 0) {
1421
+ addConstraintBroadening(
1422
+ ctx,
1423
+ `Field "${formatPathTargetFieldName(fieldName, constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(constraint)} is broader than earlier ${describeCustomConstraintTag(previous.constraint)}. Constraints can only narrow.`,
1424
+ constraint.provenance,
1425
+ previous.constraint.provenance
1426
+ );
1427
+ continue;
1428
+ }
1429
+ if (strength > 0) {
1430
+ strongestByKey.set(boundKey, entry);
1431
+ }
1432
+ } else {
1433
+ strongestByKey.set(boundKey, entry);
1434
+ }
1435
+ if (registration.semanticRole.bound === "lower") {
1436
+ lowerByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
1437
+ } else if (registration.semanticRole.bound === "upper") {
1438
+ upperByFamily.set(familyKey, strongestByKey.get(boundKey) ?? entry);
1439
+ }
1440
+ }
1441
+ for (const [familyKey, lower] of lowerByFamily) {
1442
+ const upper = upperByFamily.get(familyKey);
1443
+ if (upper === void 0) {
1444
+ continue;
1445
+ }
1446
+ if (!customConstraintsContradict(lower, upper)) {
1447
+ continue;
1448
+ }
1449
+ addContradiction(
1450
+ ctx,
1451
+ `Field "${formatPathTargetFieldName(fieldName, lower.constraint.path?.segments ?? [])}": ${describeCustomConstraintTag(lower.constraint)} contradicts ${describeCustomConstraintTag(upper.constraint)}`,
1452
+ lower.constraint.provenance,
1453
+ upper.constraint.provenance
1454
+ );
1455
+ }
1456
+ }
1183
1457
  function checkNumericContradictions(ctx, fieldName, constraints) {
1184
1458
  const min = findNumeric(constraints, "minimum");
1185
1459
  const max = findNumeric(constraints, "maximum");
@@ -1266,6 +1540,25 @@ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
1266
1540
  }
1267
1541
  }
1268
1542
  }
1543
+ function checkConstContradictions(ctx, fieldName, constraints) {
1544
+ const constConstraints = findConstConstraints(constraints);
1545
+ if (constConstraints.length < 2) return;
1546
+ const first = constConstraints[0];
1547
+ if (first === void 0) return;
1548
+ for (let i = 1; i < constConstraints.length; i++) {
1549
+ const current = constConstraints[i];
1550
+ if (current === void 0) continue;
1551
+ if (jsonValueEquals(first.value, current.value)) {
1552
+ continue;
1553
+ }
1554
+ addContradiction(
1555
+ ctx,
1556
+ `Field "${fieldName}": conflicting @const constraints require both ${JSON.stringify(first.value)} and ${JSON.stringify(current.value)}`,
1557
+ first.provenance,
1558
+ current.provenance
1559
+ );
1560
+ }
1561
+ }
1269
1562
  function typeLabel(type) {
1270
1563
  switch (type.kind) {
1271
1564
  case "primitive":
@@ -1338,6 +1631,8 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
1338
1631
  const isString = effectiveType.kind === "primitive" && effectiveType.primitiveKind === "string";
1339
1632
  const isArray = effectiveType.kind === "array";
1340
1633
  const isEnum = effectiveType.kind === "enum";
1634
+ const arrayItemType = effectiveType.kind === "array" ? dereferenceType(ctx, effectiveType.items) : void 0;
1635
+ const isStringArray = arrayItemType?.kind === "primitive" && arrayItemType.primitiveKind === "string";
1341
1636
  const label = typeLabel(effectiveType);
1342
1637
  const ck = constraint.constraintKind;
1343
1638
  switch (ck) {
@@ -1358,10 +1653,10 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
1358
1653
  case "minLength":
1359
1654
  case "maxLength":
1360
1655
  case "pattern": {
1361
- if (!isString) {
1656
+ if (!isString && !isStringArray) {
1362
1657
  addTypeMismatch(
1363
1658
  ctx,
1364
- `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1659
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields or string array items, but field type is "${label}"`,
1365
1660
  constraint.provenance
1366
1661
  );
1367
1662
  }
@@ -1389,6 +1684,37 @@ function checkConstraintOnType(ctx, fieldName, type, constraint) {
1389
1684
  }
1390
1685
  break;
1391
1686
  }
1687
+ case "const": {
1688
+ const isPrimitiveConstType = effectiveType.kind === "primitive" && ["string", "number", "boolean", "null"].includes(effectiveType.primitiveKind) || effectiveType.kind === "enum";
1689
+ if (!isPrimitiveConstType) {
1690
+ addTypeMismatch(
1691
+ ctx,
1692
+ `Field "${fieldName}": constraint "const" is only valid on primitive or enum fields, but field type is "${label}"`,
1693
+ constraint.provenance
1694
+ );
1695
+ break;
1696
+ }
1697
+ if (effectiveType.kind === "primitive") {
1698
+ const valueType = constraint.value === null ? "null" : Array.isArray(constraint.value) ? "array" : typeof constraint.value;
1699
+ if (valueType !== effectiveType.primitiveKind) {
1700
+ addTypeMismatch(
1701
+ ctx,
1702
+ `Field "${fieldName}": @const value type "${valueType}" is incompatible with field type "${effectiveType.primitiveKind}"`,
1703
+ constraint.provenance
1704
+ );
1705
+ }
1706
+ break;
1707
+ }
1708
+ const memberValues = effectiveType.members.map((member) => member.value);
1709
+ if (!memberValues.some((member) => jsonValueEquals(member, constraint.value))) {
1710
+ addTypeMismatch(
1711
+ ctx,
1712
+ `Field "${fieldName}": @const value ${JSON.stringify(constraint.value)} is not one of the enum members`,
1713
+ constraint.provenance
1714
+ );
1715
+ }
1716
+ break;
1717
+ }
1392
1718
  case "custom": {
1393
1719
  checkCustomConstraint(ctx, fieldName, effectiveType, constraint);
1394
1720
  break;
@@ -1407,9 +1733,9 @@ function checkTypeApplicability(ctx, fieldName, type, constraints) {
1407
1733
  const resolution = resolvePathTargetType(ctx, type, constraint.path.segments);
1408
1734
  const targetFieldName = formatPathTargetFieldName(fieldName, constraint.path.segments);
1409
1735
  if (resolution.kind === "missing-property") {
1410
- addTypeMismatch(
1736
+ addUnknownPathTarget(
1411
1737
  ctx,
1412
- `Field "${fieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
1738
+ `Field "${targetFieldName}": path-targeted constraint "${constraint.constraintKind}" references unknown path segment "${resolution.segment}"`,
1413
1739
  constraint.provenance
1414
1740
  );
1415
1741
  continue;
@@ -1439,8 +1765,30 @@ function checkCustomConstraint(ctx, fieldName, type, constraint) {
1439
1765
  );
1440
1766
  return;
1441
1767
  }
1442
- if (registration.applicableTypes === null) return;
1443
- if (!registration.applicableTypes.includes(type.kind)) {
1768
+ const normalizedTagName = constraint.provenance.tagName === void 0 ? void 0 : normalizeConstraintTagName(constraint.provenance.tagName.replace(/^@/, ""));
1769
+ if (normalizedTagName !== void 0) {
1770
+ const tagRegistration = ctx.extensionRegistry.findConstraintTag(normalizedTagName);
1771
+ const extensionId = getExtensionIdFromConstraintId(constraint.constraintId);
1772
+ if (extensionId !== null && tagRegistration?.extensionId === extensionId && tagRegistration.registration.constraintName === registration.constraintName && tagRegistration.registration.isApplicableToType?.(type) === false) {
1773
+ addTypeMismatch(
1774
+ ctx,
1775
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
1776
+ constraint.provenance
1777
+ );
1778
+ return;
1779
+ }
1780
+ }
1781
+ if (registration.applicableTypes === null) {
1782
+ if (registration.isApplicableToType?.(type) === false) {
1783
+ addTypeMismatch(
1784
+ ctx,
1785
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
1786
+ constraint.provenance
1787
+ );
1788
+ }
1789
+ return;
1790
+ }
1791
+ if (!registration.applicableTypes.includes(type.kind) || registration.isApplicableToType?.(type) === false) {
1444
1792
  addTypeMismatch(
1445
1793
  ctx,
1446
1794
  `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
@@ -1469,7 +1817,9 @@ function validateConstraints(ctx, name, type, constraints) {
1469
1817
  checkNumericContradictions(ctx, name, constraints);
1470
1818
  checkLengthContradictions(ctx, name, constraints);
1471
1819
  checkAllowedMembersContradiction(ctx, name, constraints);
1820
+ checkConstContradictions(ctx, name, constraints);
1472
1821
  checkConstraintBroadening(ctx, name, constraints);
1822
+ checkCustomConstraintSemantics(ctx, name, constraints);
1473
1823
  checkTypeApplicability(ctx, name, type, constraints);
1474
1824
  }
1475
1825
  function validateElement(ctx, element) {