@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.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
|
}
|
|
@@ -360,8 +370,70 @@ function collectFields(elements, properties, required, ctx) {
|
|
|
360
370
|
}
|
|
361
371
|
function generateFieldSchema(field, ctx) {
|
|
362
372
|
const schema = generateTypeNode(field.type, ctx);
|
|
363
|
-
|
|
364
|
-
|
|
373
|
+
const directConstraints = [];
|
|
374
|
+
const pathConstraints = [];
|
|
375
|
+
for (const c of field.constraints) {
|
|
376
|
+
if (c.path) {
|
|
377
|
+
pathConstraints.push(c);
|
|
378
|
+
} else {
|
|
379
|
+
directConstraints.push(c);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
applyConstraints(schema, directConstraints, ctx);
|
|
383
|
+
applyAnnotations(schema, field.annotations, ctx);
|
|
384
|
+
if (pathConstraints.length === 0) {
|
|
385
|
+
return schema;
|
|
386
|
+
}
|
|
387
|
+
return applyPathTargetedConstraints(schema, pathConstraints, ctx);
|
|
388
|
+
}
|
|
389
|
+
function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
|
|
390
|
+
if (schema.type === "array" && schema.items) {
|
|
391
|
+
schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
|
|
392
|
+
return schema;
|
|
393
|
+
}
|
|
394
|
+
const byTarget = /* @__PURE__ */ new Map();
|
|
395
|
+
for (const c of pathConstraints) {
|
|
396
|
+
const target = c.path?.segments[0];
|
|
397
|
+
if (!target) continue;
|
|
398
|
+
const group = byTarget.get(target) ?? [];
|
|
399
|
+
group.push(c);
|
|
400
|
+
byTarget.set(target, group);
|
|
401
|
+
}
|
|
402
|
+
const propertyOverrides = {};
|
|
403
|
+
for (const [target, constraints] of byTarget) {
|
|
404
|
+
const subSchema = {};
|
|
405
|
+
applyConstraints(subSchema, constraints, ctx);
|
|
406
|
+
propertyOverrides[target] = subSchema;
|
|
407
|
+
}
|
|
408
|
+
if (schema.$ref) {
|
|
409
|
+
const { $ref, ...rest } = schema;
|
|
410
|
+
const refPart = { $ref };
|
|
411
|
+
const overridePart = {
|
|
412
|
+
properties: propertyOverrides,
|
|
413
|
+
...rest
|
|
414
|
+
};
|
|
415
|
+
return { allOf: [refPart, overridePart] };
|
|
416
|
+
}
|
|
417
|
+
if (schema.type === "object" && schema.properties) {
|
|
418
|
+
const missingOverrides = {};
|
|
419
|
+
for (const [target, overrideSchema] of Object.entries(propertyOverrides)) {
|
|
420
|
+
if (schema.properties[target]) {
|
|
421
|
+
Object.assign(schema.properties[target], overrideSchema);
|
|
422
|
+
} else {
|
|
423
|
+
missingOverrides[target] = overrideSchema;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (Object.keys(missingOverrides).length === 0) {
|
|
427
|
+
return schema;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
allOf: [schema, { properties: missingOverrides }]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (schema.allOf) {
|
|
434
|
+
schema.allOf = [...schema.allOf, { properties: propertyOverrides }];
|
|
435
|
+
return schema;
|
|
436
|
+
}
|
|
365
437
|
return schema;
|
|
366
438
|
}
|
|
367
439
|
function generateTypeNode(type, ctx) {
|
|
@@ -374,6 +446,8 @@ function generateTypeNode(type, ctx) {
|
|
|
374
446
|
return generateArrayType(type, ctx);
|
|
375
447
|
case "object":
|
|
376
448
|
return generateObjectType(type, ctx);
|
|
449
|
+
case "record":
|
|
450
|
+
return generateRecordType(type, ctx);
|
|
377
451
|
case "union":
|
|
378
452
|
return generateUnionType(type, ctx);
|
|
379
453
|
case "reference":
|
|
@@ -381,7 +455,7 @@ function generateTypeNode(type, ctx) {
|
|
|
381
455
|
case "dynamic":
|
|
382
456
|
return generateDynamicType(type);
|
|
383
457
|
case "custom":
|
|
384
|
-
return generateCustomType(type);
|
|
458
|
+
return generateCustomType(type, ctx);
|
|
385
459
|
default: {
|
|
386
460
|
const _exhaustive = type;
|
|
387
461
|
return _exhaustive;
|
|
@@ -430,16 +504,27 @@ function generateObjectType(type, ctx) {
|
|
|
430
504
|
}
|
|
431
505
|
return schema;
|
|
432
506
|
}
|
|
507
|
+
function generateRecordType(type, ctx) {
|
|
508
|
+
return {
|
|
509
|
+
type: "object",
|
|
510
|
+
additionalProperties: generateTypeNode(type.valueType, ctx)
|
|
511
|
+
};
|
|
512
|
+
}
|
|
433
513
|
function generatePropertySchema(prop, ctx) {
|
|
434
514
|
const schema = generateTypeNode(prop.type, ctx);
|
|
435
|
-
applyConstraints(schema, prop.constraints);
|
|
436
|
-
applyAnnotations(schema, prop.annotations);
|
|
515
|
+
applyConstraints(schema, prop.constraints, ctx);
|
|
516
|
+
applyAnnotations(schema, prop.annotations, ctx);
|
|
437
517
|
return schema;
|
|
438
518
|
}
|
|
439
519
|
function generateUnionType(type, ctx) {
|
|
440
520
|
if (isBooleanUnion(type)) {
|
|
441
521
|
return { type: "boolean" };
|
|
442
522
|
}
|
|
523
|
+
if (isNullableUnion(type)) {
|
|
524
|
+
return {
|
|
525
|
+
oneOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
526
|
+
};
|
|
527
|
+
}
|
|
443
528
|
return {
|
|
444
529
|
anyOf: type.members.map((m) => generateTypeNode(m, ctx))
|
|
445
530
|
};
|
|
@@ -449,6 +534,13 @@ function isBooleanUnion(type) {
|
|
|
449
534
|
const kinds = type.members.map((m) => m.kind);
|
|
450
535
|
return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
|
|
451
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
|
+
}
|
|
452
544
|
function generateReferenceType(type) {
|
|
453
545
|
return { $ref: `#/$defs/${type.name}` };
|
|
454
546
|
}
|
|
@@ -469,10 +561,7 @@ function generateDynamicType(type) {
|
|
|
469
561
|
"x-formspec-schemaSource": type.sourceKey
|
|
470
562
|
};
|
|
471
563
|
}
|
|
472
|
-
function
|
|
473
|
-
return { type: "object" };
|
|
474
|
-
}
|
|
475
|
-
function applyConstraints(schema, constraints) {
|
|
564
|
+
function applyConstraints(schema, constraints, ctx) {
|
|
476
565
|
for (const constraint of constraints) {
|
|
477
566
|
switch (constraint.constraintKind) {
|
|
478
567
|
case "minimum":
|
|
@@ -517,6 +606,7 @@ function applyConstraints(schema, constraints) {
|
|
|
517
606
|
case "allowedMembers":
|
|
518
607
|
break;
|
|
519
608
|
case "custom":
|
|
609
|
+
applyCustomConstraint(schema, constraint, ctx);
|
|
520
610
|
break;
|
|
521
611
|
default: {
|
|
522
612
|
const _exhaustive = constraint;
|
|
@@ -525,7 +615,7 @@ function applyConstraints(schema, constraints) {
|
|
|
525
615
|
}
|
|
526
616
|
}
|
|
527
617
|
}
|
|
528
|
-
function applyAnnotations(schema, annotations) {
|
|
618
|
+
function applyAnnotations(schema, annotations, ctx) {
|
|
529
619
|
for (const annotation of annotations) {
|
|
530
620
|
switch (annotation.annotationKind) {
|
|
531
621
|
case "displayName":
|
|
@@ -545,6 +635,7 @@ function applyAnnotations(schema, annotations) {
|
|
|
545
635
|
case "formatHint":
|
|
546
636
|
break;
|
|
547
637
|
case "custom":
|
|
638
|
+
applyCustomAnnotation(schema, annotation, ctx);
|
|
548
639
|
break;
|
|
549
640
|
default: {
|
|
550
641
|
const _exhaustive = annotation;
|
|
@@ -553,11 +644,41 @@ function applyAnnotations(schema, annotations) {
|
|
|
553
644
|
}
|
|
554
645
|
}
|
|
555
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
|
+
}
|
|
556
677
|
|
|
557
678
|
// src/json-schema/generator.ts
|
|
558
|
-
function generateJsonSchema(form) {
|
|
679
|
+
function generateJsonSchema(form, options) {
|
|
559
680
|
const ir = canonicalizeChainDSL(form);
|
|
560
|
-
return generateJsonSchemaFromIR(ir);
|
|
681
|
+
return generateJsonSchemaFromIR(ir, options);
|
|
561
682
|
}
|
|
562
683
|
|
|
563
684
|
// src/ui-schema/schema.ts
|
|
@@ -787,6 +908,48 @@ function getSchemaExtension(schema, key) {
|
|
|
787
908
|
return schema[key];
|
|
788
909
|
}
|
|
789
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
|
+
|
|
790
953
|
// src/json-schema/schema.ts
|
|
791
954
|
import { z as z3 } from "zod";
|
|
792
955
|
var jsonSchemaTypeSchema = z3.enum([
|
|
@@ -850,12 +1013,9 @@ var jsonSchema7Schema = z3.lazy(
|
|
|
850
1013
|
);
|
|
851
1014
|
|
|
852
1015
|
// src/validate/constraint-validator.ts
|
|
853
|
-
function makeCode(ctx, category, number) {
|
|
854
|
-
return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
|
|
855
|
-
}
|
|
856
1016
|
function addContradiction(ctx, message, primary, related) {
|
|
857
1017
|
ctx.diagnostics.push({
|
|
858
|
-
code:
|
|
1018
|
+
code: "CONTRADICTING_CONSTRAINTS",
|
|
859
1019
|
message,
|
|
860
1020
|
severity: "error",
|
|
861
1021
|
primaryLocation: primary,
|
|
@@ -864,7 +1024,7 @@ function addContradiction(ctx, message, primary, related) {
|
|
|
864
1024
|
}
|
|
865
1025
|
function addTypeMismatch(ctx, message, primary) {
|
|
866
1026
|
ctx.diagnostics.push({
|
|
867
|
-
code:
|
|
1027
|
+
code: "TYPE_MISMATCH",
|
|
868
1028
|
message,
|
|
869
1029
|
severity: "error",
|
|
870
1030
|
primaryLocation: primary,
|
|
@@ -873,28 +1033,153 @@ function addTypeMismatch(ctx, message, primary) {
|
|
|
873
1033
|
}
|
|
874
1034
|
function addUnknownExtension(ctx, message, primary) {
|
|
875
1035
|
ctx.diagnostics.push({
|
|
876
|
-
code:
|
|
1036
|
+
code: "UNKNOWN_EXTENSION",
|
|
877
1037
|
message,
|
|
878
1038
|
severity: "warning",
|
|
879
1039
|
primaryLocation: primary,
|
|
880
1040
|
relatedLocations: []
|
|
881
1041
|
});
|
|
882
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
|
+
}
|
|
883
1052
|
function findNumeric(constraints, constraintKind) {
|
|
884
|
-
return constraints.find(
|
|
885
|
-
(c) => c.constraintKind === constraintKind
|
|
886
|
-
);
|
|
1053
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
887
1054
|
}
|
|
888
1055
|
function findLength(constraints, constraintKind) {
|
|
889
|
-
return constraints.find(
|
|
890
|
-
(c) => c.constraintKind === constraintKind
|
|
891
|
-
);
|
|
1056
|
+
return constraints.find((c) => c.constraintKind === constraintKind);
|
|
892
1057
|
}
|
|
893
1058
|
function findAllowedMembers(constraints) {
|
|
894
1059
|
return constraints.filter(
|
|
895
1060
|
(c) => c.constraintKind === "allowedMembers"
|
|
896
1061
|
);
|
|
897
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
|
+
}
|
|
898
1183
|
function checkNumericContradictions(ctx, fieldName, constraints) {
|
|
899
1184
|
const min = findNumeric(constraints, "minimum");
|
|
900
1185
|
const max = findNumeric(constraints, "maximum");
|
|
@@ -991,6 +1276,8 @@ function typeLabel(type) {
|
|
|
991
1276
|
return "array";
|
|
992
1277
|
case "object":
|
|
993
1278
|
return "object";
|
|
1279
|
+
case "record":
|
|
1280
|
+
return "record";
|
|
994
1281
|
case "union":
|
|
995
1282
|
return "union";
|
|
996
1283
|
case "reference":
|
|
@@ -1005,74 +1292,140 @@ function typeLabel(type) {
|
|
|
1005
1292
|
}
|
|
1006
1293
|
}
|
|
1007
1294
|
}
|
|
1008
|
-
function
|
|
1009
|
-
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
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) {
|
|
1350
|
+
addTypeMismatch(
|
|
1351
|
+
ctx,
|
|
1352
|
+
`Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
|
|
1353
|
+
constraint.provenance
|
|
1354
|
+
);
|
|
1030
1355
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
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
|
+
);
|
|
1042
1367
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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
|
+
);
|
|
1054
1379
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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
|
+
);
|
|
1064
1389
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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;
|
|
1068
1416
|
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
`
|
|
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
|
|
1073
1422
|
);
|
|
1423
|
+
continue;
|
|
1074
1424
|
}
|
|
1425
|
+
checkConstraintOnType(ctx, targetFieldName, resolution.type, constraint);
|
|
1426
|
+
continue;
|
|
1075
1427
|
}
|
|
1428
|
+
checkConstraintOnType(ctx, fieldName, type, constraint);
|
|
1076
1429
|
}
|
|
1077
1430
|
}
|
|
1078
1431
|
function checkCustomConstraint(ctx, fieldName, type, constraint) {
|
|
@@ -1116,6 +1469,7 @@ function validateConstraints(ctx, name, type, constraints) {
|
|
|
1116
1469
|
checkNumericContradictions(ctx, name, constraints);
|
|
1117
1470
|
checkLengthContradictions(ctx, name, constraints);
|
|
1118
1471
|
checkAllowedMembersContradiction(ctx, name, constraints);
|
|
1472
|
+
checkConstraintBroadening(ctx, name, constraints);
|
|
1119
1473
|
checkTypeApplicability(ctx, name, type, constraints);
|
|
1120
1474
|
}
|
|
1121
1475
|
function validateElement(ctx, element) {
|
|
@@ -1142,8 +1496,8 @@ function validateElement(ctx, element) {
|
|
|
1142
1496
|
function validateIR(ir, options) {
|
|
1143
1497
|
const ctx = {
|
|
1144
1498
|
diagnostics: [],
|
|
1145
|
-
|
|
1146
|
-
|
|
1499
|
+
extensionRegistry: options?.extensionRegistry,
|
|
1500
|
+
typeRegistry: ir.typeRegistry
|
|
1147
1501
|
};
|
|
1148
1502
|
for (const element of ir.elements) {
|
|
1149
1503
|
validateElement(ctx, element);
|
|
@@ -1155,9 +1509,9 @@ function validateIR(ir, options) {
|
|
|
1155
1509
|
}
|
|
1156
1510
|
|
|
1157
1511
|
// src/browser.ts
|
|
1158
|
-
function buildFormSchemas(form) {
|
|
1512
|
+
function buildFormSchemas(form, options) {
|
|
1159
1513
|
return {
|
|
1160
|
-
jsonSchema: generateJsonSchema(form),
|
|
1514
|
+
jsonSchema: generateJsonSchema(form, options),
|
|
1161
1515
|
uiSchema: generateUiSchema(form)
|
|
1162
1516
|
};
|
|
1163
1517
|
}
|
|
@@ -1167,7 +1521,9 @@ export {
|
|
|
1167
1521
|
categorizationSchema,
|
|
1168
1522
|
categorySchema,
|
|
1169
1523
|
controlSchema,
|
|
1524
|
+
createExtensionRegistry,
|
|
1170
1525
|
generateJsonSchema,
|
|
1526
|
+
generateJsonSchemaFromIR,
|
|
1171
1527
|
generateUiSchema,
|
|
1172
1528
|
getSchemaExtension,
|
|
1173
1529
|
groupLayoutSchema,
|