@formspec/build 0.1.0-alpha.13 → 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/README.md +20 -20
- package/dist/__tests__/alias-chain-propagation.test.d.ts +9 -0
- package/dist/__tests__/alias-chain-propagation.test.d.ts.map +1 -0
- 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/alias-chains.d.ts +37 -0
- package/dist/__tests__/fixtures/alias-chains.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 +13 -13
- package/dist/__tests__/fixtures/example-interface-types.d.ts +33 -33
- 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__/json-utils.test.d.ts +5 -0
- package/dist/__tests__/json-utils.test.d.ts.map +1 -0
- 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/__tests__/path-target-parser.test.d.ts +9 -0
- package/dist/__tests__/path-target-parser.test.d.ts.map +1 -0
- 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 +8 -52
- package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
- package/dist/analyzer/json-utils.d.ts +22 -0
- package/dist/analyzer/json-utils.d.ts.map +1 -0
- package/dist/analyzer/tsdoc-parser.d.ts +24 -12
- package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
- package/dist/browser.cjs +452 -94
- 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 +450 -94
- package/dist/browser.js.map +1 -1
- package/dist/build.d.ts +132 -5
- package/dist/canonicalize/tsdoc-canonicalizer.d.ts +3 -3
- package/dist/cli.cjs +406 -104
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +407 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +386 -102
- 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 +386 -104
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +597 -172
- package/dist/internals.cjs.map +1 -1
- package/dist/internals.js +597 -172
- 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 +25 -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 +3 -3
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
|
}
|
|
@@ -408,8 +420,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
408
420
|
}
|
|
409
421
|
function generateFieldSchema(field, ctx) {
|
|
410
422
|
const schema = generateTypeNode(field.type, ctx);
|
|
411
|
-
|
|
412
|
-
|
|
423
|
+
const directConstraints = [];
|
|
424
|
+
const pathConstraints = [];
|
|
425
|
+
for (const c of field.constraints) {
|
|
426
|
+
if (c.path) {
|
|
427
|
+
pathConstraints.push(c);
|
|
428
|
+
} else {
|
|
429
|
+
directConstraints.push(c);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
433
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
434
|
+
if (pathConstraints.length === 0) {
|
|
435
|
+
return schema;
|
|
436
|
+
}
|
|
437
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
438
|
+
}
|
|
439
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
440
|
+
if (schema.type === "array" && schema.items) {
|
|
441
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
442
|
+
return schema;
|
|
443
|
+
}
|
|
444
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
445
|
+
for (const c of pathConstraints) {
|
|
446
|
+
const target = c.path?.segments[0];
|
|
447
|
+
if (!target) continue;
|
|
448
|
+
const group = byTarget.get(target) ?? [];
|
|
449
|
+
group.push(c);
|
|
450
|
+
byTarget.set(target, group);
|
|
451
|
+
}
|
|
452
|
+
const propertyOverrides = {};
|
|
453
|
+
for (const [target, constraints] of byTarget) {
|
|
454
|
+
const subSchema = {};
|
|
455
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
456
|
+
propertyOverrides[target] = subSchema;
|
|
457
|
+
}
|
|
458
|
+
if (schema.$ref) {
|
|
459
|
+
const { $ref, ...rest } = schema;
|
|
460
|
+
const refPart = { $ref };
|
|
461
|
+
const overridePart = {
|
|
462
|
+
properties: propertyOverrides,
|
|
463
|
+
...rest
|
|
464
|
+
};
|
|
465
|
+
return { allOf: [refPart, overridePart] };
|
|
466
|
+
}
|
|
467
|
+
if (schema.type === "object" && schema.properties) {
|
|
468
|
+
const missingOverrides = {};
|
|
469
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
470
|
+
if (schema.properties[target]) {
|
|
471
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
472
|
+
} else {
|
|
473
|
+
missingOverrides[target] = overrideSchema;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
477
|
+
return schema;
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
if (schema.allOf) {
|
|
484
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
485
|
+
return schema;
|
|
486
|
+
}
|
|
413
487
|
return schema;
|
|
414
488
|
}
|
|
415
489
|
function generateTypeNode(type, ctx) {
|
|
@@ -422,6 +496,8 @@ function generateTypeNode(type, ctx) {
|
|
|
422
496
|
return generateArrayType(type, ctx);
|
|
423
497
|
case "object":
|
|
424
498
|
return generateObjectType(type, ctx);
|
|
499
|
+
case "record":
|
|
500
|
+
return generateRecordType(type, ctx);
|
|
425
501
|
case "union":
|
|
426
502
|
return generateUnionType(type, ctx);
|
|
427
503
|
case "reference":
|
|
@@ -429,7 +505,7 @@ function generateTypeNode(type, ctx) {
|
|
|
429
505
|
case "dynamic":
|
|
430
506
|
return generateDynamicType(type);
|
|
431
507
|
case "custom":
|
|
432
|
-
return generateCustomType(type);
|
|
508
|
+
return generateCustomType(type, ctx);
|
|
433
509
|
default: {
|
|
434
510
|
const _exhaustive = type;
|
|
435
511
|
return _exhaustive;
|
|
@@ -478,16 +554,27 @@ function generateObjectType(type, ctx) {
|
|
|
478
554
|
}
|
|
479
555
|
return schema;
|
|
480
556
|
}
|
|
557
|
+
function generateRecordType(type, ctx) {
|
|
558
|
+
return {
|
|
559
|
+
type: "object",
|
|
560
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
561
|
+
};
|
|
562
|
+
}
|
|
481
563
|
function generatePropertySchema(prop, ctx) {
|
|
482
564
|
const schema = generateTypeNode(prop.type, ctx);
|
|
483
|
-
applyConstraints(schema, prop.constraints);
|
|
484
|
-
applyAnnotations(schema, prop.annotations);
|
|
565
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
566
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
485
567
|
return schema;
|
|
486
568
|
}
|
|
487
569
|
function generateUnionType(type, ctx) {
|
|
488
570
|
if (isBooleanUnion(type)) {
|
|
489
571
|
return { type: "boolean" };
|
|
490
572
|
}
|
|
573
|
+
if (isNullableUnion(type)) {
|
|
574
|
+
return {
|
|
575
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
576
|
+
};
|
|
577
|
+
}
|
|
491
578
|
return {
|
|
492
579
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
493
580
|
};
|
|
@@ -497,6 +584,13 @@ function isBooleanUnion(type) {
|
|
|
497
584
|
const kinds = type.members.map((m) => m.kind);
|
|
498
585
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
499
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
|
+
}
|
|
500
594
|
function generateReferenceType(type) {
|
|
501
595
|
return { $ref: `#/$defs/${type.name}` };
|
|
502
596
|
}
|
|
@@ -517,10 +611,7 @@ function generateDynamicType(type) {
|
|
|
517
611
|
"x-formspec-schemaSource": type.sourceKey
|
|
518
612
|
};
|
|
519
613
|
}
|
|
520
|
-
function
|
|
521
|
-
return { type: "object" };
|
|
522
|
-
}
|
|
523
|
-
function applyConstraints(schema, constraints) {
|
|
614
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
524
615
|
for (const constraint of constraints) {
|
|
525
616
|
switch (constraint.constraintKind) {
|
|
526
617
|
case "minimum":
|
|
@@ -565,6 +656,7 @@ function applyConstraints(schema, constraints) {
|
|
|
565
656
|
case "allowedMembers":
|
|
566
657
|
break;
|
|
567
658
|
case "custom":
|
|
659
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
568
660
|
break;
|
|
569
661
|
default: {
|
|
570
662
|
const _exhaustive = constraint;
|
|
@@ -573,7 +665,7 @@ function applyConstraints(schema, constraints) {
|
|
|
573
665
|
}
|
|
574
666
|
}
|
|
575
667
|
}
|
|
576
|
-
function applyAnnotations(schema, annotations) {
|
|
668
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
577
669
|
for (const annotation of annotations) {
|
|
578
670
|
switch (annotation.annotationKind) {
|
|
579
671
|
case "displayName":
|
|
@@ -593,6 +685,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
593
685
|
case "formatHint":
|
|
594
686
|
break;
|
|
595
687
|
case "custom":
|
|
688
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
596
689
|
break;
|
|
597
690
|
default: {
|
|
598
691
|
const _exhaustive = annotation;
|
|
@@ -601,11 +694,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
601
694
|
}
|
|
602
695
|
}
|
|
603
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
|
+
}
|
|
604
727
|
|
|
605
728
|
// src/json-schema/generator.ts
|
|
606
|
-
function generateJsonSchema(form) {
|
|
729
|
+
function generateJsonSchema(form, options) {
|
|
607
730
|
const ir = canonicalizeChainDSL(form);
|
|
608
|
-
return generateJsonSchemaFromIR(ir);
|
|
731
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
609
732
|
}
|
|
610
733
|
|
|
611
734
|
// src/ui-schema/schema.ts
|
|
@@ -835,6 +958,48 @@ function getSchemaExtension(schema, key) {
|
|
|
835
958
|
return schema[key];
|
|
836
959
|
}
|
|
837
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
|
+
|
|
838
1003
|
// src/json-schema/schema.ts
|
|
839
1004
|
var import_zod3 = require("zod");
|
|
840
1005
|
var jsonSchemaTypeSchema = import_zod3.z.enum([
|
|
@@ -898,12 +1063,9 @@ var jsonSchema7Schema = import_zod3.z.lazy(
|
|
|
898
1063
|
);
|
|
899
1064
|
|
|
900
1065
|
// src/validate/constraint-validator.ts
|
|
901
|
-
function makeCode(ctx, category, number) {
|
|
902
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
903
|
-
}
|
|
904
1066
|
function addContradiction(ctx, message, primary, related) {
|
|
905
1067
|
ctx.diagnostics.push({
|
|
906
|
-
code:
|
|
1068
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
907
1069
|
message,
|
|
908
1070
|
severity: "error",
|
|
909
1071
|
primaryLocation: primary,
|
|
@@ -912,7 +1074,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
912
1074
|
}
|
|
913
1075
|
function addTypeMismatch(ctx, message, primary) {
|
|
914
1076
|
ctx.diagnostics.push({
|
|
915
|
-
code:
|
|
1077
|
+
code: "TYPE_MISMATCH",
|
|
916
1078
|
message,
|
|
917
1079
|
severity: "error",
|
|
918
1080
|
primaryLocation: primary,
|
|
@@ -921,28 +1083,153 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
921
1083
|
}
|
|
922
1084
|
function addUnknownExtension(ctx, message, primary) {
|
|
923
1085
|
ctx.diagnostics.push({
|
|
924
|
-
code:
|
|
1086
|
+
code: "UNKNOWN_EXTENSION",
|
|
925
1087
|
message,
|
|
926
1088
|
severity: "warning",
|
|
927
1089
|
primaryLocation: primary,
|
|
928
1090
|
relatedLocations: []
|
|
929
1091
|
});
|
|
930
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
|
+
}
|
|
931
1102
|
function findNumeric(constraints, constraintKind) {
|
|
932
|
-
return constraints.find(
|
|
933
|
-
(c) => c.constraintKind === constraintKind
|
|
934
|
-
);
|
|
1103
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
935
1104
|
}
|
|
936
1105
|
function findLength(constraints, constraintKind) {
|
|
937
|
-
return constraints.find(
|
|
938
|
-
(c) => c.constraintKind === constraintKind
|
|
939
|
-
);
|
|
1106
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
940
1107
|
}
|
|
941
1108
|
function findAllowedMembers(constraints) {
|
|
942
1109
|
return constraints.filter(
|
|
943
1110
|
(c) => c.constraintKind === "allowedMembers"
|
|
944
1111
|
);
|
|
945
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
|
+
}
|
|
946
1233
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
947
1234
|
const min = findNumeric(constraints, "minimum");
|
|
948
1235
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -1039,6 +1326,8 @@ function typeLabel(type) {
|
|
|
1039
1326
|
return "array";
|
|
1040
1327
|
case "object":
|
|
1041
1328
|
return "object";
|
|
1329
|
+
case "record":
|
|
1330
|
+
return "record";
|
|
1042
1331
|
case "union":
|
|
1043
1332
|
return "union";
|
|
1044
1333
|
case "reference":
|
|
@@ -1053,74 +1342,140 @@ function typeLabel(type) {
|
|
|
1053
1342
|
}
|
|
1054
1343
|
}
|
|
1055
1344
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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) {
|
|
1400
|
+
addTypeMismatch(
|
|
1401
|
+
ctx,
|
|
1402
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1403
|
+
constraint.provenance
|
|
1404
|
+
);
|
|
1078
1405
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
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
|
+
);
|
|
1090
1417
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
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
|
+
);
|
|
1102
1429
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
+
);
|
|
1112
1439
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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;
|
|
1116
1466
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
`
|
|
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
|
|
1121
1472
|
);
|
|
1473
|
+
continue;
|
|
1122
1474
|
}
|
|
1475
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1476
|
+
continue;
|
|
1123
1477
|
}
|
|
1478
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1124
1479
|
}
|
|
1125
1480
|
}
|
|
1126
1481
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -1164,6 +1519,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1164
1519
|
checkNumericContradictions(ctx, name, constraints);
|
|
1165
1520
|
checkLengthContradictions(ctx, name, constraints);
|
|
1166
1521
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1522
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
1167
1523
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1168
1524
|
}
|
|
1169
1525
|
function validateElement(ctx, element) {
|
|
@@ -1190,8 +1546,8 @@ function validateElement(ctx, element) {
|
|
|
1190
1546
|
function validateIR(ir, options) {
|
|
1191
1547
|
const ctx = {
|
|
1192
1548
|
diagnostics: [],
|
|
1193
|
-
|
|
1194
|
-
|
|
1549
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1550
|
+
typeRegistry: ir.typeRegistry
|
|
1195
1551
|
};
|
|
1196
1552
|
for (const element of ir.elements) {
|
|
1197
1553
|
validateElement(ctx, element);
|
|
@@ -1203,9 +1559,9 @@ function validateIR(ir, options) {
|
|
|
1203
1559
|
}
|
|
1204
1560
|
|
|
1205
1561
|
// src/browser.ts
|
|
1206
|
-
function buildFormSchemas(form) {
|
|
1562
|
+
function buildFormSchemas(form, options) {
|
|
1207
1563
|
return {
|
|
1208
|
-
jsonSchema: generateJsonSchema(form),
|
|
1564
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1209
1565
|
uiSchema: generateUiSchema(form)
|
|
1210
1566
|
};
|
|
1211
1567
|
}
|
|
@@ -1216,7 +1572,9 @@ function buildFormSchemas(form) {
|
|
|
1216
1572
|
categorizationSchema,
|
|
1217
1573
|
categorySchema,
|
|
1218
1574
|
controlSchema,
|
|
1575
|
+
createExtensionRegistry,
|
|
1219
1576
|
generateJsonSchema,
|
|
1577
|
+
generateJsonSchemaFromIR,
|
|
1220
1578
|
generateUiSchema,
|
|
1221
1579
|
getSchemaExtension,
|
|
1222
1580
|
groupLayoutSchema,
|