@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.
- package/dist/__tests__/extension-runtime.integration.test.d.ts +2 -0
- package/dist/__tests__/extension-runtime.integration.test.d.ts.map +1 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
- package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
- package/dist/__tests__/fixtures/example-a-builtins.d.ts +6 -6
- package/dist/__tests__/fixtures/example-interface-types.d.ts +26 -26
- package/dist/__tests__/fixtures/example-interface-types.d.ts.map +1 -1
- package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -5
- package/dist/__tests__/jsdoc-constraints.test.d.ts.map +1 -1
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/plan-status/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/plan-status/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts +17 -0
- package/dist/__tests__/parity/fixtures/plan-status/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts +9 -0
- package/dist/__tests__/parity/fixtures/usd-cents/chain-dsl.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts +6 -0
- package/dist/__tests__/parity/fixtures/usd-cents/expected-ir.d.ts.map +1 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts +19 -0
- package/dist/__tests__/parity/fixtures/usd-cents/tsdoc.d.ts.map +1 -0
- package/dist/__tests__/parity/utils.d.ts +6 -1
- package/dist/__tests__/parity/utils.d.ts.map +1 -1
- package/dist/analyzer/class-analyzer.d.ts +1 -1
- package/dist/analyzer/class-analyzer.d.ts.map +1 -1
- package/dist/analyzer/jsdoc-constraints.d.ts +7 -51
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/tsdoc-parser.d.ts +6 -8
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +387 -98
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +15 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +385 -98
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +131 -5
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/cli.cjs +272 -69
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +271 -72
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +257 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +255 -71
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +461 -137
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +459 -139
- package/dist/internals.js.map +1 -1
- package/dist/json-schema/generator.d.ts +8 -2
- package/dist/json-schema/generator.d.ts.map +1 -1
- package/dist/json-schema/ir-generator.d.ts +24 -2
- package/dist/json-schema/ir-generator.d.ts.map +1 -1
- package/dist/json-schema/types.d.ts +1 -1
- package/dist/json-schema/types.d.ts.map +1 -1
- package/dist/validate/constraint-validator.d.ts +3 -7
- package/dist/validate/constraint-validator.d.ts.map +1 -1
- 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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1115
|
-
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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}":
|
|
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
|
-
|
|
1406
|
+
break;
|
|
1131
1407
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
`
|
|
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
|
-
|
|
1263
|
-
|
|
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,
|