@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.js
CHANGED
|
@@ -177,7 +177,7 @@ function canonicalizeArrayField(field) {
|
|
|
177
177
|
const itemsType = {
|
|
178
178
|
kind: "object",
|
|
179
179
|
properties: itemProperties,
|
|
180
|
-
additionalProperties:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
1067
|
-
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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}":
|
|
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
|
-
|
|
1356
|
+
break;
|
|
1083
1357
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
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
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
`
|
|
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
|
-
|
|
1215
|
-
|
|
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,
|