json-schema-compatibility-checker 1.1.0 → 1.1.2
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/cjs/json-schema-compatibility-checker.d.ts +14 -7
- package/dist/cjs/json-schema-compatibility-checker.js +1 -1
- package/dist/cjs/json-schema-compatibility-checker.js.map +1 -1
- package/dist/cjs/types.d.ts +29 -7
- package/dist/esm/json-schema-compatibility-checker.d.ts +14 -7
- package/dist/esm/json-schema-compatibility-checker.js +1 -1
- package/dist/esm/json-schema-compatibility-checker.js.map +1 -1
- package/dist/esm/types.d.ts +29 -7
- package/dist/esm/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,17 +24,21 @@ export declare class JsonSchemaCompatibilityChecker {
|
|
|
24
24
|
* Checks `sub ⊆ sup` and returns a detailed diagnostic
|
|
25
25
|
* with human-readable semantic errors.
|
|
26
26
|
*
|
|
27
|
-
* When `options` is provided
|
|
28
|
-
*
|
|
27
|
+
* When `options` is provided, both schemas go through runtime-aware
|
|
28
|
+
* processing before the static check:
|
|
29
29
|
* 1. Conditions (`if/then/else`) are resolved using `data`
|
|
30
|
+
* (if `data` is `undefined`, conditions are resolved with `{}`)
|
|
30
31
|
* 2. Schemas are narrowed using runtime values (enum materialization)
|
|
31
|
-
* 3.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* 3. The static subset check runs on the resolved/narrowed schemas
|
|
33
|
+
*
|
|
34
|
+
* When `validate: true` is set in options, two additional runtime steps
|
|
35
|
+
* run **after** the static check passes:
|
|
36
|
+
* 4. `data` is validated against both resolved schemas via AJV
|
|
37
|
+
* 5. Custom constraints are validated against `data`
|
|
34
38
|
*
|
|
35
39
|
* @param sub - The source schema (subset candidate)
|
|
36
40
|
* @param sup - The target schema (expected superset)
|
|
37
|
-
* @param options - Runtime options with `data`
|
|
41
|
+
* @param options - Runtime options with `data` and optional `validate` flag
|
|
38
42
|
* @returns SubsetResult if no options, ResolvedSubsetResult if options provided
|
|
39
43
|
*
|
|
40
44
|
* @example
|
|
@@ -42,8 +46,11 @@ export declare class JsonSchemaCompatibilityChecker {
|
|
|
42
46
|
* // Static check (no runtime data)
|
|
43
47
|
* checker.check(sub, sup);
|
|
44
48
|
*
|
|
45
|
-
* //
|
|
49
|
+
* // Resolve conditions + narrowing + static check (no runtime validation)
|
|
46
50
|
* checker.check(sub, sup, { data: { kind: "text", value: "hello" } });
|
|
51
|
+
*
|
|
52
|
+
* // Full pipeline including AJV + constraint runtime validation
|
|
53
|
+
* checker.check(sub, sup, { data: { kind: "text", value: "hello" }, validate: true });
|
|
47
54
|
* ```
|
|
48
55
|
*/
|
|
49
56
|
check(sub: JSONSchema7Definition, sup: JSONSchema7Definition, options: CheckRuntimeOptions): ResolvedSubsetResult;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get JsonSchemaCompatibilityChecker(){return JsonSchemaCompatibilityChecker},get MergeEngine(){return _mergeenginets.MergeEngine},get arePatternsEquivalent(){return _patternsubsetts.arePatternsEquivalent},get formatResult(){return _formatterts.formatResult},get isPatternSubset(){return _patternsubsetts.isPatternSubset},get isTrivialPattern(){return _patternsubsetts.isTrivialPattern},get normalize(){return _normalizerts.normalize},get resolveConditions(){return _conditionresolverts.resolveConditions}});const _conditionresolverts=require("./condition-resolver.js");const _constraintvalidatorts=require("./constraint-validator.js");const _datanarrowingts=require("./data-narrowing.js");const _formatterts=require("./formatter.js");const _mergeenginets=require("./merge-engine.js");const _normalizerts=require("./normalizer.js");const _patternsubsetts=require("./pattern-subset.js");const _runtimevalidatorts=require("./runtime-validator.js");const _subsetcheckerts=require("./subset-checker.js");const _utilsts=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}class JsonSchemaCompatibilityChecker{isSubset(sub,sup){if(sub===sup)return true;if((0,_utilsts.deepEqual)(sub,sup))return true;const nSub=(0,_normalizerts.normalize)(sub);const nSup=(0,_normalizerts.normalize)(sup);if(nSub!==sub&&nSup!==sup&&(0,_utilsts.deepEqual)(nSub,nSup))return true;if(nSub!==nSup&&(0,_utilsts.deepEqual)(nSub,nSup))return true;const{branches:subBranches}=(0,_subsetcheckerts.getBranchesTyped)(nSub);if(subBranches.length>1||subBranches[0]!==nSub){return subBranches.every(branch=>(0,_subsetcheckerts.isAtomicSubsetOf)(branch,nSup,this.engine))}return(0,_subsetcheckerts.isAtomicSubsetOf)(nSub,nSup,this.engine)}check(sub,sup,options){if(options){const data=options.data;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});function _export(target,all){for(var name in all)Object.defineProperty(target,name,{enumerable:true,get:Object.getOwnPropertyDescriptor(all,name).get})}_export(exports,{get JsonSchemaCompatibilityChecker(){return JsonSchemaCompatibilityChecker},get MergeEngine(){return _mergeenginets.MergeEngine},get arePatternsEquivalent(){return _patternsubsetts.arePatternsEquivalent},get formatResult(){return _formatterts.formatResult},get isPatternSubset(){return _patternsubsetts.isPatternSubset},get isTrivialPattern(){return _patternsubsetts.isTrivialPattern},get normalize(){return _normalizerts.normalize},get resolveConditions(){return _conditionresolverts.resolveConditions}});const _conditionresolverts=require("./condition-resolver.js");const _constraintvalidatorts=require("./constraint-validator.js");const _datanarrowingts=require("./data-narrowing.js");const _formatterts=require("./formatter.js");const _mergeenginets=require("./merge-engine.js");const _normalizerts=require("./normalizer.js");const _patternsubsetts=require("./pattern-subset.js");const _runtimevalidatorts=require("./runtime-validator.js");const _subsetcheckerts=require("./subset-checker.js");const _utilsts=require("./utils.js");function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}class JsonSchemaCompatibilityChecker{isSubset(sub,sup){if(sub===sup)return true;if((0,_utilsts.deepEqual)(sub,sup))return true;const nSub=(0,_normalizerts.normalize)(sub);const nSup=(0,_normalizerts.normalize)(sup);if(nSub!==sub&&nSup!==sup&&(0,_utilsts.deepEqual)(nSub,nSup))return true;if(nSub!==nSup&&(0,_utilsts.deepEqual)(nSub,nSup))return true;const{branches:subBranches}=(0,_subsetcheckerts.getBranchesTyped)(nSub);if(subBranches.length>1||subBranches[0]!==nSub){return subBranches.every(branch=>(0,_subsetcheckerts.isAtomicSubsetOf)(branch,nSup,this.engine))}return(0,_subsetcheckerts.isAtomicSubsetOf)(nSub,nSup,this.engine)}check(sub,sup,options){if(options){const data=options.data;const shouldValidate=options.validate===true;const dataForConditions=(0,_utilsts.isPlainObj)(data)?data:{};const resolvedSub=(0,_conditionresolverts.resolveConditions)(sub,dataForConditions,this.engine);const resolvedSup=(0,_conditionresolverts.resolveConditions)(sup,dataForConditions,this.engine);const canNarrow=data!==undefined;const canNarrowSub=canNarrow&&(0,_utilsts.isPlainObj)(resolvedSub.resolved);const canNarrowSup=canNarrow&&(0,_utilsts.isPlainObj)(resolvedSup.resolved);const narrowedSubResolved=canNarrowSub?(0,_datanarrowingts.narrowSchemaWithData)(resolvedSub.resolved,data,resolvedSup.resolved):resolvedSub.resolved;const narrowedSupResolved=canNarrowSup?(0,_datanarrowingts.narrowSchemaWithData)(resolvedSup.resolved,data,resolvedSub.resolved):resolvedSup.resolved;const staticResult=this.checkInternal(narrowedSubResolved,narrowedSupResolved);if(!staticResult.isSubset){return{...staticResult,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}if(shouldValidate&&data!==undefined){const runtimeErrors=[];runtimeErrors.push(...this.prefixRuntimeErrors((0,_runtimevalidatorts.getRuntimeValidationErrors)(narrowedSubResolved,data),"$sub"));runtimeErrors.push(...this.prefixRuntimeErrors((0,_runtimevalidatorts.getRuntimeValidationErrors)(narrowedSupResolved,data),"$sup"));if(Object.keys(this.constraintValidators).length>0){runtimeErrors.push(...this.prefixRuntimeErrors((0,_constraintvalidatorts.validateSchemaConstraints)(narrowedSubResolved,data,this.constraintValidators),"$sub"));runtimeErrors.push(...this.prefixRuntimeErrors((0,_constraintvalidatorts.validateSchemaConstraints)(narrowedSupResolved,data,this.constraintValidators),"$sup"))}if(runtimeErrors.length>0){return{isSubset:false,merged:null,errors:runtimeErrors,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}}return{...staticResult,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}return this.checkInternal(sub,sup)}isEqual(a,b){return this.engine.isEqual((0,_normalizerts.normalize)(a),(0,_normalizerts.normalize)(b))}intersect(a,b){if(a===b||(0,_utilsts.deepEqual)(a,b))return(0,_normalizerts.normalize)(a);const nA=(0,_normalizerts.normalize)(a);const nB=(0,_normalizerts.normalize)(b);if((0,_utilsts.deepEqual)(nA,nB))return nA;const merged=this.engine.merge(nA,nB);if(merged===null)return null;if((0,_utilsts.deepEqual)(merged,nA)||(0,_utilsts.deepEqual)(merged,nB))return merged;return(0,_normalizerts.normalize)(merged)}normalize(def){return(0,_normalizerts.normalize)(def)}formatResult(label,result){return(0,_formatterts.formatResult)(label,result)}resolveConditions(schema,data){return(0,_conditionresolverts.resolveConditions)(schema,data,this.engine)}prefixRuntimeErrors(errors,rootKey){return errors.map(error=>({...error,key:error.key==="$root"?rootKey:`${rootKey}.${error.key}`}))}checkInternal(sub,sup){if(sub===sup){return{isSubset:true,merged:sub,errors:[]}}if((0,_utilsts.deepEqual)(sub,sup)){return{isSubset:true,merged:sub,errors:[]}}const nSub=(0,_normalizerts.normalize)(sub);const nSup=(0,_normalizerts.normalize)(sup);if((0,_utilsts.deepEqual)(nSub,nSup)){return{isSubset:true,merged:nSub,errors:[]}}const{branches:subBranches,type:subBranchType}=(0,_subsetcheckerts.getBranchesTyped)(nSub);const{branches:supBranches,type:supBranchType}=(0,_subsetcheckerts.getBranchesTyped)(nSup);if(subBranches.length>1||subBranches[0]!==nSub){return(0,_subsetcheckerts.checkBranchedSub)(subBranches,nSup,this.engine,subBranchType)}if(supBranches.length>1||supBranches[0]!==nSup){return(0,_subsetcheckerts.checkBranchedSup)(nSub,supBranches,this.engine,supBranchType)}return(0,_subsetcheckerts.checkAtomic)(nSub,nSup,this.engine)}static clearCache(){(0,_runtimevalidatorts.clearAllValidatorCaches)()}constructor(options){_define_property(this,"constraintValidators",void 0);_define_property(this,"engine",void 0);this.engine=new _mergeenginets.MergeEngine;this.constraintValidators=options?.constraints??{}}}
|
|
2
2
|
//# sourceMappingURL=json-schema-compatibility-checker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/json-schema-compatibility-checker.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { resolveConditions } from \"./condition-resolver.ts\";\nimport { validateSchemaConstraints } from \"./constraint-validator.ts\";\nimport { narrowSchemaWithData } from \"./data-narrowing.ts\";\nimport { formatResult } from \"./formatter.ts\";\nimport { MergeEngine } from \"./merge-engine.ts\";\nimport { normalize } from \"./normalizer.ts\";\nimport {\n\tarePatternsEquivalent,\n\tisPatternSubset,\n\tisTrivialPattern,\n} from \"./pattern-subset.ts\";\nimport {\n\tclearAllValidatorCaches,\n\tgetRuntimeValidationErrors,\n} from \"./runtime-validator.ts\";\nimport type { BranchResult, BranchType } from \"./subset-checker.ts\";\nimport {\n\tcheckAtomic,\n\tcheckBranchedSub,\n\tcheckBranchedSup,\n\tgetBranchesTyped,\n\tisAtomicSubsetOf,\n} from \"./subset-checker.ts\";\nimport type {\n\tCheckerOptions,\n\tCheckRuntimeOptions,\n\tConstraintValidatorRegistry,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tSchemaError,\n\tSubsetResult,\n} from \"./types.ts\";\nimport { deepEqual, isPlainObj } from \"./utils.ts\";\n\n// ─── Re-exports ──────────────────────────────────────────────────────────────\n\nexport type {\n\tSchemaError,\n\tSubsetResult,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tCheckRuntimeOptions,\n\tBranchType,\n\tBranchResult,\n};\n\nexport {\n\tnormalize,\n\tresolveConditions,\n\tformatResult,\n\tMergeEngine,\n\tisPatternSubset,\n\tarePatternsEquivalent,\n\tisTrivialPattern,\n};\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n//\n// Lightweight facade that orchestrates sub-modules to verify compatibility\n// between JSON Schemas (Draft-07).\n//\n// Mathematical principle:\n// A ⊆ B ⟺ A ∩ B ≡ A\n//\n// In JSON Schema terms:\n// - A ∩ B = allOf([A, B]) resolved via merge\n// - ≡ = structural comparison\n//\n// @example\n// ```ts\n// const checker = new JsonSchemaCompatibilityChecker();\n//\n// checker.isSubset(strict, loose); // true\n// checker.check(loose, strict); // { isSubset: false, diffs: [...] }\n// checker.check(sub, sup, { data: {...} }); // resolves conditions then checks\n// ```\n\nexport class JsonSchemaCompatibilityChecker {\n\tprivate readonly constraintValidators: ConstraintValidatorRegistry;\n\tprivate readonly engine: MergeEngine;\n\n\tconstructor(options?: CheckerOptions) {\n\t\tthis.engine = new MergeEngine();\n\t\tthis.constraintValidators = options?.constraints ?? {};\n\t}\n\n\t// ── Subset check (boolean) ─────────────────────────────────────────────\n\n\t/**\n\t * Checks whether `sub ⊆ sup`.\n\t * Is every value valid for sub also valid for sup?\n\t *\n\t * Uses `getBranchesTyped` to distinguish `anyOf` from `oneOf`\n\t * internally, although the boolean result does not reflect the distinction.\n\t */\n\tisSubset(sub: JSONSchema7Definition, sup: JSONSchema7Definition): boolean {\n\t\t// ── Identity short-circuit ──\n\t\t// If sub and sup are the same reference, sub ⊆ sup is trivially true.\n\t\t// This avoids the entire normalize + merge + compare pipeline.\n\t\tif (sub === sup) return true;\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// If sub and sup are structurally identical before normalization,\n\t\t// they represent the same schema → sub ⊆ sup trivially.\n\t\t// This avoids the WeakMap overhead of normalize() for common cases\n\t\t// like {} ⊆ {} or identical schema objects with different references.\n\t\tif (deepEqual(sub, sup)) return true;\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// After normalization, schemas that were syntactically different\n\t\t// but semantically equivalent become structurally equal\n\t\t// (e.g. {const:1} vs {const:1, type:\"integer\"}).\n\t\tif (nSub !== sub && nSup !== sup && deepEqual(nSub, nSup)) return true;\n\t\tif (nSub !== nSup && deepEqual(nSub, nSup)) return true;\n\n\t\tconst { branches: subBranches } = getBranchesTyped(nSub);\n\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn subBranches.every((branch) =>\n\t\t\t\tisAtomicSubsetOf(branch, nSup, this.engine),\n\t\t\t);\n\t\t}\n\n\t\treturn isAtomicSubsetOf(nSub, nSup, this.engine);\n\t}\n\n\t// ── Subset check (detailed) ────────────────────────────────────────────\n\n\t/**\n\t * Checks `sub ⊆ sup` and returns a detailed diagnostic\n\t * with human-readable semantic errors.\n\t *\n\t * When `options` is provided with `data`, both schemas go through\n\t * runtime-aware processing before the static check:\n\t * 1. Conditions (`if/then/else`) are resolved using `data`\n\t * 2. Schemas are narrowed using runtime values (enum materialization)\n\t * 3. `data` is validated against both resolved schemas via AJV\n\t * 4. If runtime validation fails, `isSubset: false` is returned immediately\n\t * 5. Otherwise the static subset check runs on resolved/narrowed schemas\n\t *\n\t * @param sub - The source schema (subset candidate)\n\t * @param sup - The target schema (expected superset)\n\t * @param options - Runtime options with `data` (optional)\n\t * @returns SubsetResult if no options, ResolvedSubsetResult if options provided\n\t *\n\t * @example\n\t * ```ts\n\t * // Static check (no runtime data)\n\t * checker.check(sub, sup);\n\t *\n\t * // Runtime-aware check\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" } });\n\t * ```\n\t */\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions: CheckRuntimeOptions,\n\t): ResolvedSubsetResult;\n\tcheck(sub: JSONSchema7Definition, sup: JSONSchema7Definition): SubsetResult;\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions?: CheckRuntimeOptions,\n\t): SubsetResult | ResolvedSubsetResult {\n\t\t// ── Runtime-aware path ──\n\t\tif (options) {\n\t\t\tconst data = options.data;\n\n\t\t\t// If data is explicitly undefined, fall back to the static path.\n\t\t\t// The runtime path requires concrete data for condition resolution,\n\t\t\t// narrowing, and validation — undefined data would skip all of these,\n\t\t\t// producing results identical to the static path.\n\t\t\t// We still wrap the result in ResolvedSubsetResult to satisfy the\n\t\t\t// overload contract: callers passing options always get resolved info.\n\t\t\tif (data === undefined) {\n\t\t\t\tconst staticResult = this.checkInternal(sub, sup);\n\t\t\t\tconst noopResolution: ResolvedConditionResult = {\n\t\t\t\t\tresolved: sub as JSONSchema7,\n\t\t\t\t\tbranch: null,\n\t\t\t\t\tdiscriminant: {},\n\t\t\t\t};\n\t\t\t\tconst noopSupResolution: ResolvedConditionResult = {\n\t\t\t\t\tresolved: sup as JSONSchema7,\n\t\t\t\t\tbranch: null,\n\t\t\t\t\tdiscriminant: {},\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: noopResolution,\n\t\t\t\t\tresolvedSup: noopSupResolution,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// resolveConditions expects Record<string, unknown> for property access;\n\t\t\t// coerce non-object data to empty object (no conditions to resolve for primitives)\n\t\t\tconst dataForConditions: Record<string, unknown> = isPlainObj(data)\n\t\t\t\t? data\n\t\t\t\t: {};\n\n\t\t\tconst resolvedSub = resolveConditions(\n\t\t\t\tsub as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\t\t\tconst resolvedSup = resolveConditions(\n\t\t\t\tsup as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\n\t\t\t// ── Runtime-aware data narrowing ──\n\t\t\t// Apply narrowing before any equality short-circuit so runtime data\n\t\t\t// can invalidate or refine enum/const-constrained schemas even when\n\t\t\t// the resolved schemas are structurally identical.\n\t\t\t// Boolean schemas (true/false) cannot be narrowed — skip narrowing\n\t\t\t// to avoid passing a non-object to narrowSchemaWithData.\n\t\t\tconst canNarrowSub = isPlainObj(resolvedSub.resolved);\n\t\t\tconst canNarrowSup = isPlainObj(resolvedSup.resolved);\n\n\t\t\tconst narrowedSubResolved = canNarrowSub\n\t\t\t\t? narrowSchemaWithData(resolvedSub.resolved, data, resolvedSup.resolved)\n\t\t\t\t: resolvedSub.resolved;\n\n\t\t\tconst narrowedSupResolved = canNarrowSup\n\t\t\t\t? narrowSchemaWithData(resolvedSup.resolved, data, resolvedSub.resolved)\n\t\t\t\t: resolvedSup.resolved;\n\n\t\t\t// ── Static subset check (runs first) ──\n\t\t\t// Structural incompatibilities are schema-level problems — they are\n\t\t\t// permanent regardless of the concrete data. Run this before runtime\n\t\t\t// validation so that static errors always surface with higher priority.\n\t\t\tconst staticResult = this.checkInternal(\n\t\t\t\tnarrowedSubResolved,\n\t\t\t\tnarrowedSupResolved,\n\t\t\t);\n\n\t\t\tif (!staticResult.isSubset) {\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// ── Runtime validation ──\n\t\t\t// The schemas are structurally compatible. Now validate the concrete\n\t\t\t// data against both resolved/narrowed schemas to catch data-level\n\t\t\t// violations (e.g. value doesn't match format, out-of-range, etc.).\n\t\t\tconst runtimeErrors: SchemaError[] = [];\n\n\t\t\truntimeErrors.push(\n\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\tgetRuntimeValidationErrors(narrowedSubResolved, data),\n\t\t\t\t\t\"$sub\",\n\t\t\t\t),\n\t\t\t);\n\n\t\t\truntimeErrors.push(\n\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\tgetRuntimeValidationErrors(narrowedSupResolved, data),\n\t\t\t\t\t\"$sup\",\n\t\t\t\t),\n\t\t\t);\n\n\t\t\t// ── Constraint validation ──\n\t\t\t// Validate runtime data against custom constraints in both schemas.\n\t\t\t// Only runs if constraint validators were registered in the constructor.\n\t\t\tif (Object.keys(this.constraintValidators).length > 0) {\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\tnarrowedSubResolved,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\tnarrowedSupResolved,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (runtimeErrors.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tisSubset: false,\n\t\t\t\t\tmerged: null,\n\t\t\t\t\terrors: runtimeErrors,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...staticResult,\n\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t};\n\t\t}\n\n\t\t// ── Standard path (no condition resolution) ──\n\t\treturn this.checkInternal(sub, sup);\n\t}\n\n\t// ── Equality ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Checks structural equality between two schemas.\n\t */\n\tisEqual(a: JSONSchema7Definition, b: JSONSchema7Definition): boolean {\n\t\treturn this.engine.isEqual(normalize(a), normalize(b));\n\t}\n\n\t// ── Intersection ───────────────────────────────────────────────────────\n\n\t/**\n\t * Computes the intersection of two schemas (allOf merge).\n\t * Returns null if the schemas are incompatible.\n\t *\n\t * The result is normalized to eliminate structural artifacts\n\t * from the merge (e.g. redundant `enum` when `const` is present).\n\t */\n\tintersect(\n\t\ta: JSONSchema7Definition,\n\t\tb: JSONSchema7Definition,\n\t): JSONSchema7Definition | null {\n\t\t// ── Identity short-circuit ──\n\t\t// If a and b are the same reference or structurally equal,\n\t\t// intersection is just normalize(a) — skip the merge entirely.\n\t\tif (a === b || deepEqual(a, b)) return normalize(a);\n\n\t\tconst nA = normalize(a);\n\t\tconst nB = normalize(b);\n\n\t\t// ── Post-normalize identity ──\n\t\tif (deepEqual(nA, nB)) return nA;\n\n\t\tconst merged = this.engine.merge(nA, nB);\n\t\tif (merged === null) return null;\n\t\t// Fast path: if merge result equals one of the normalized inputs,\n\t\t// it's already normalized — skip redundant normalize call.\n\t\tif (deepEqual(merged, nA) || deepEqual(merged, nB)) return merged;\n\t\treturn normalize(merged);\n\t}\n\n\t// ── Normalization ──────────────────────────────────────────────────────\n\n\t/**\n\t * Normalizes a schema: infers `type` from `const`/`enum`,\n\t * and recursively normalizes all sub-schemas.\n\t */\n\tnormalize(def: JSONSchema7Definition): JSONSchema7Definition {\n\t\treturn normalize(def);\n\t}\n\n\t// ── Formatting ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Formats a SubsetResult into a readable string (useful for logs/debug).\n\t */\n\tformatResult(label: string, result: SubsetResult): string {\n\t\treturn formatResult(label, result);\n\t}\n\n\t// ── Condition Resolution ────────────────────────────────────────────────\n\n\t/**\n\t * Resolves `if/then/else` conditions in a schema by evaluating the `if`\n\t * against runtime data.\n\t *\n\t * @param schema - The schema containing conditions to resolve\n\t * @param data - The runtime data used to evaluate conditions\n\t * @returns The resolved schema with branch info and discriminants\n\t */\n\tresolveConditions(\n\t\tschema: JSONSchema7,\n\t\tdata: Record<string, unknown>,\n\t): ResolvedConditionResult {\n\t\treturn resolveConditions(schema, data, this.engine);\n\t}\n\n\t// ── Private ────────────────────────────────────────────────────────────\n\n\tprivate prefixRuntimeErrors(\n\t\terrors: SchemaError[],\n\t\trootKey: \"$sub\" | \"$sup\",\n\t): SchemaError[] {\n\t\treturn errors.map((error) => ({\n\t\t\t...error,\n\t\t\tkey: error.key === \"$root\" ? rootKey : `${rootKey}.${error.key}`,\n\t\t}));\n\t}\n\n\t/**\n\t * Internal check logic without condition resolution.\n\t * Factorizes the normalize → branch → atomic pipeline to avoid\n\t * duplication between the two paths of `check()`.\n\t */\n\tprivate checkInternal(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t): SubsetResult {\n\t\t// ── Identity short-circuit ──\n\t\t// Same reference → no errors, no merge needed.\n\t\tif (sub === sup) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// Avoids WeakMap overhead for identical schemas ({} ⊆ {}, etc.).\n\t\tif (deepEqual(sub, sup)) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// Catches semantically equivalent schemas after normalization.\n\t\tif (deepEqual(nSub, nSup)) {\n\t\t\treturn { isSubset: true, merged: nSub, errors: [] };\n\t\t}\n\n\t\tconst { branches: subBranches, type: subBranchType } =\n\t\t\tgetBranchesTyped(nSub);\n\t\tconst { branches: supBranches, type: supBranchType } =\n\t\t\tgetBranchesTyped(nSup);\n\n\t\t// anyOf/oneOf in sub\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn checkBranchedSub(subBranches, nSup, this.engine, subBranchType);\n\t\t}\n\n\t\t// anyOf/oneOf in sup only\n\t\tif (supBranches.length > 1 || supBranches[0] !== nSup) {\n\t\t\treturn checkBranchedSup(nSub, supBranches, this.engine, supBranchType);\n\t\t}\n\n\t\t// Standard case\n\t\treturn checkAtomic(nSub, nSup, this.engine);\n\t}\n\n\t// ── Cache management ───────────────────────────────────────────────────\n\n\t/**\n\t * Clears all compiled AJV validator caches (WeakMap, LRU, and AJV internal).\n\t *\n\t * Useful for:\n\t * - Long-running processes where schemas evolve over time\n\t * - Test isolation (ensuring no cross-test cache pollution)\n\t * - Memory pressure situations where cached validators are no longer needed\n\t *\n\t * After calling this, the next validation call will recompile validators\n\t * from scratch — there is a one-time performance cost per unique schema.\n\t *\n\t * This is a static method because the AJV instance is a module-level\n\t * singleton shared across all `JsonSchemaCompatibilityChecker` instances.\n\t *\n\t * @example\n\t * ```ts\n\t * JsonSchemaCompatibilityChecker.clearCache();\n\t * ```\n\t */\n\tstatic clearCache(): void {\n\t\tclearAllValidatorCaches();\n\t}\n}\n"],"names":["JsonSchemaCompatibilityChecker","MergeEngine","arePatternsEquivalent","formatResult","isPatternSubset","isTrivialPattern","normalize","resolveConditions","isSubset","sub","sup","deepEqual","nSub","nSup","branches","subBranches","getBranchesTyped","length","every","branch","isAtomicSubsetOf","engine","check","options","data","undefined","staticResult","checkInternal","noopResolution","resolved","discriminant","noopSupResolution","resolvedSub","resolvedSup","dataForConditions","isPlainObj","canNarrowSub","canNarrowSup","narrowedSubResolved","narrowSchemaWithData","narrowedSupResolved","runtimeErrors","push","prefixRuntimeErrors","getRuntimeValidationErrors","Object","keys","constraintValidators","validateSchemaConstraints","merged","errors","isEqual","a","b","intersect","nA","nB","merge","def","label","result","schema","rootKey","map","error","key","type","subBranchType","supBranches","supBranchType","checkBranchedSub","checkBranchedSup","checkAtomic","clearCache","clearAllValidatorCaches","constraints"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/json-schema-compatibility-checker.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { resolveConditions } from \"./condition-resolver.ts\";\nimport { validateSchemaConstraints } from \"./constraint-validator.ts\";\nimport { narrowSchemaWithData } from \"./data-narrowing.ts\";\nimport { formatResult } from \"./formatter.ts\";\nimport { MergeEngine } from \"./merge-engine.ts\";\nimport { normalize } from \"./normalizer.ts\";\nimport {\n\tarePatternsEquivalent,\n\tisPatternSubset,\n\tisTrivialPattern,\n} from \"./pattern-subset.ts\";\nimport {\n\tclearAllValidatorCaches,\n\tgetRuntimeValidationErrors,\n} from \"./runtime-validator.ts\";\nimport type { BranchResult, BranchType } from \"./subset-checker.ts\";\nimport {\n\tcheckAtomic,\n\tcheckBranchedSub,\n\tcheckBranchedSup,\n\tgetBranchesTyped,\n\tisAtomicSubsetOf,\n} from \"./subset-checker.ts\";\nimport type {\n\tCheckerOptions,\n\tCheckRuntimeOptions,\n\tConstraintValidatorRegistry,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tSchemaError,\n\tSubsetResult,\n} from \"./types.ts\";\nimport { deepEqual, isPlainObj } from \"./utils.ts\";\n\n// ─── Re-exports ──────────────────────────────────────────────────────────────\n\nexport type {\n\tSchemaError,\n\tSubsetResult,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tCheckRuntimeOptions,\n\tBranchType,\n\tBranchResult,\n};\n\nexport {\n\tnormalize,\n\tresolveConditions,\n\tformatResult,\n\tMergeEngine,\n\tisPatternSubset,\n\tarePatternsEquivalent,\n\tisTrivialPattern,\n};\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n//\n// Lightweight facade that orchestrates sub-modules to verify compatibility\n// between JSON Schemas (Draft-07).\n//\n// Mathematical principle:\n// A ⊆ B ⟺ A ∩ B ≡ A\n//\n// In JSON Schema terms:\n// - A ∩ B = allOf([A, B]) resolved via merge\n// - ≡ = structural comparison\n//\n// @example\n// ```ts\n// const checker = new JsonSchemaCompatibilityChecker();\n//\n// checker.isSubset(strict, loose); // true\n// checker.check(loose, strict); // { isSubset: false, diffs: [...] }\n// checker.check(sub, sup, { data: {...} }); // resolves conditions then checks\n// ```\n\nexport class JsonSchemaCompatibilityChecker {\n\tprivate readonly constraintValidators: ConstraintValidatorRegistry;\n\tprivate readonly engine: MergeEngine;\n\n\tconstructor(options?: CheckerOptions) {\n\t\tthis.engine = new MergeEngine();\n\t\tthis.constraintValidators = options?.constraints ?? {};\n\t}\n\n\t// ── Subset check (boolean) ─────────────────────────────────────────────\n\n\t/**\n\t * Checks whether `sub ⊆ sup`.\n\t * Is every value valid for sub also valid for sup?\n\t *\n\t * Uses `getBranchesTyped` to distinguish `anyOf` from `oneOf`\n\t * internally, although the boolean result does not reflect the distinction.\n\t */\n\tisSubset(sub: JSONSchema7Definition, sup: JSONSchema7Definition): boolean {\n\t\t// ── Identity short-circuit ──\n\t\t// If sub and sup are the same reference, sub ⊆ sup is trivially true.\n\t\t// This avoids the entire normalize + merge + compare pipeline.\n\t\tif (sub === sup) return true;\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// If sub and sup are structurally identical before normalization,\n\t\t// they represent the same schema → sub ⊆ sup trivially.\n\t\t// This avoids the WeakMap overhead of normalize() for common cases\n\t\t// like {} ⊆ {} or identical schema objects with different references.\n\t\tif (deepEqual(sub, sup)) return true;\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// After normalization, schemas that were syntactically different\n\t\t// but semantically equivalent become structurally equal\n\t\t// (e.g. {const:1} vs {const:1, type:\"integer\"}).\n\t\tif (nSub !== sub && nSup !== sup && deepEqual(nSub, nSup)) return true;\n\t\tif (nSub !== nSup && deepEqual(nSub, nSup)) return true;\n\n\t\tconst { branches: subBranches } = getBranchesTyped(nSub);\n\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn subBranches.every((branch) =>\n\t\t\t\tisAtomicSubsetOf(branch, nSup, this.engine),\n\t\t\t);\n\t\t}\n\n\t\treturn isAtomicSubsetOf(nSub, nSup, this.engine);\n\t}\n\n\t// ── Subset check (detailed) ────────────────────────────────────────────\n\n\t/**\n\t * Checks `sub ⊆ sup` and returns a detailed diagnostic\n\t * with human-readable semantic errors.\n\t *\n\t * When `options` is provided, both schemas go through runtime-aware\n\t * processing before the static check:\n\t * 1. Conditions (`if/then/else`) are resolved using `data`\n\t * (if `data` is `undefined`, conditions are resolved with `{}`)\n\t * 2. Schemas are narrowed using runtime values (enum materialization)\n\t * 3. The static subset check runs on the resolved/narrowed schemas\n\t *\n\t * When `validate: true` is set in options, two additional runtime steps\n\t * run **after** the static check passes:\n\t * 4. `data` is validated against both resolved schemas via AJV\n\t * 5. Custom constraints are validated against `data`\n\t *\n\t * @param sub - The source schema (subset candidate)\n\t * @param sup - The target schema (expected superset)\n\t * @param options - Runtime options with `data` and optional `validate` flag\n\t * @returns SubsetResult if no options, ResolvedSubsetResult if options provided\n\t *\n\t * @example\n\t * ```ts\n\t * // Static check (no runtime data)\n\t * checker.check(sub, sup);\n\t *\n\t * // Resolve conditions + narrowing + static check (no runtime validation)\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" } });\n\t *\n\t * // Full pipeline including AJV + constraint runtime validation\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" }, validate: true });\n\t * ```\n\t */\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions: CheckRuntimeOptions,\n\t): ResolvedSubsetResult;\n\tcheck(sub: JSONSchema7Definition, sup: JSONSchema7Definition): SubsetResult;\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions?: CheckRuntimeOptions,\n\t): SubsetResult | ResolvedSubsetResult {\n\t\t// ── Runtime-aware path ──\n\t\tif (options) {\n\t\t\tconst data = options.data;\n\t\t\tconst shouldValidate = options.validate === true;\n\n\t\t\t// resolveConditions expects Record<string, unknown> for property access;\n\t\t\t// coerce non-object / undefined data to empty object so conditions\n\t\t\t// are always resolved (v1.0.11 compat: subData: undefined → {})\n\t\t\tconst dataForConditions: Record<string, unknown> = isPlainObj(data)\n\t\t\t\t? data\n\t\t\t\t: {};\n\n\t\t\tconst resolvedSub = resolveConditions(\n\t\t\t\tsub as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\t\t\tconst resolvedSup = resolveConditions(\n\t\t\t\tsup as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\n\t\t\t// ── Runtime-aware data narrowing ──\n\t\t\t// Apply narrowing only when concrete data is available.\n\t\t\t// When data is undefined there is nothing to narrow with.\n\t\t\t// Boolean schemas (true/false) cannot be narrowed — skip narrowing\n\t\t\t// to avoid passing a non-object to narrowSchemaWithData.\n\t\t\tconst canNarrow = data !== undefined;\n\t\t\tconst canNarrowSub = canNarrow && isPlainObj(resolvedSub.resolved);\n\t\t\tconst canNarrowSup = canNarrow && isPlainObj(resolvedSup.resolved);\n\n\t\t\tconst narrowedSubResolved = canNarrowSub\n\t\t\t\t? narrowSchemaWithData(resolvedSub.resolved, data, resolvedSup.resolved)\n\t\t\t\t: resolvedSub.resolved;\n\n\t\t\tconst narrowedSupResolved = canNarrowSup\n\t\t\t\t? narrowSchemaWithData(resolvedSup.resolved, data, resolvedSub.resolved)\n\t\t\t\t: resolvedSup.resolved;\n\n\t\t\t// ── Static subset check ──\n\t\t\t// Structural incompatibilities are schema-level problems — they are\n\t\t\t// permanent regardless of the concrete data. Run this before runtime\n\t\t\t// validation so that static errors always surface with higher priority.\n\t\t\tconst staticResult = this.checkInternal(\n\t\t\t\tnarrowedSubResolved,\n\t\t\t\tnarrowedSupResolved,\n\t\t\t);\n\n\t\t\tif (!staticResult.isSubset) {\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// ── Runtime validation (opt-in) ──\n\t\t\t// Only runs when `validate: true` is explicitly set.\n\t\t\t// Validates the concrete data against both resolved/narrowed schemas\n\t\t\t// via AJV, then runs custom constraint validators if registered.\n\t\t\tif (shouldValidate && data !== undefined) {\n\t\t\t\tconst runtimeErrors: SchemaError[] = [];\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tgetRuntimeValidationErrors(narrowedSubResolved, data),\n\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tgetRuntimeValidationErrors(narrowedSupResolved, data),\n\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// ── Constraint validation ──\n\t\t\t\t// Validate runtime data against custom constraints in both schemas.\n\t\t\t\t// Only runs if constraint validators were registered in the constructor.\n\t\t\t\tif (Object.keys(this.constraintValidators).length > 0) {\n\t\t\t\t\truntimeErrors.push(\n\t\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\t\tnarrowedSubResolved,\n\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\truntimeErrors.push(\n\t\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\t\tnarrowedSupResolved,\n\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (runtimeErrors.length > 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tisSubset: false,\n\t\t\t\t\t\tmerged: null,\n\t\t\t\t\t\terrors: runtimeErrors,\n\t\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...staticResult,\n\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t};\n\t\t}\n\n\t\t// ── Standard path (no condition resolution) ──\n\t\treturn this.checkInternal(sub, sup);\n\t}\n\n\t// ── Equality ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Checks structural equality between two schemas.\n\t */\n\tisEqual(a: JSONSchema7Definition, b: JSONSchema7Definition): boolean {\n\t\treturn this.engine.isEqual(normalize(a), normalize(b));\n\t}\n\n\t// ── Intersection ───────────────────────────────────────────────────────\n\n\t/**\n\t * Computes the intersection of two schemas (allOf merge).\n\t * Returns null if the schemas are incompatible.\n\t *\n\t * The result is normalized to eliminate structural artifacts\n\t * from the merge (e.g. redundant `enum` when `const` is present).\n\t */\n\tintersect(\n\t\ta: JSONSchema7Definition,\n\t\tb: JSONSchema7Definition,\n\t): JSONSchema7Definition | null {\n\t\t// ── Identity short-circuit ──\n\t\t// If a and b are the same reference or structurally equal,\n\t\t// intersection is just normalize(a) — skip the merge entirely.\n\t\tif (a === b || deepEqual(a, b)) return normalize(a);\n\n\t\tconst nA = normalize(a);\n\t\tconst nB = normalize(b);\n\n\t\t// ── Post-normalize identity ──\n\t\tif (deepEqual(nA, nB)) return nA;\n\n\t\tconst merged = this.engine.merge(nA, nB);\n\t\tif (merged === null) return null;\n\t\t// Fast path: if merge result equals one of the normalized inputs,\n\t\t// it's already normalized — skip redundant normalize call.\n\t\tif (deepEqual(merged, nA) || deepEqual(merged, nB)) return merged;\n\t\treturn normalize(merged);\n\t}\n\n\t// ── Normalization ──────────────────────────────────────────────────────\n\n\t/**\n\t * Normalizes a schema: infers `type` from `const`/`enum`,\n\t * and recursively normalizes all sub-schemas.\n\t */\n\tnormalize(def: JSONSchema7Definition): JSONSchema7Definition {\n\t\treturn normalize(def);\n\t}\n\n\t// ── Formatting ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Formats a SubsetResult into a readable string (useful for logs/debug).\n\t */\n\tformatResult(label: string, result: SubsetResult): string {\n\t\treturn formatResult(label, result);\n\t}\n\n\t// ── Condition Resolution ────────────────────────────────────────────────\n\n\t/**\n\t * Resolves `if/then/else` conditions in a schema by evaluating the `if`\n\t * against runtime data.\n\t *\n\t * @param schema - The schema containing conditions to resolve\n\t * @param data - The runtime data used to evaluate conditions\n\t * @returns The resolved schema with branch info and discriminants\n\t */\n\tresolveConditions(\n\t\tschema: JSONSchema7,\n\t\tdata: Record<string, unknown>,\n\t): ResolvedConditionResult {\n\t\treturn resolveConditions(schema, data, this.engine);\n\t}\n\n\t// ── Private ────────────────────────────────────────────────────────────\n\n\tprivate prefixRuntimeErrors(\n\t\terrors: SchemaError[],\n\t\trootKey: \"$sub\" | \"$sup\",\n\t): SchemaError[] {\n\t\treturn errors.map((error) => ({\n\t\t\t...error,\n\t\t\tkey: error.key === \"$root\" ? rootKey : `${rootKey}.${error.key}`,\n\t\t}));\n\t}\n\n\t/**\n\t * Internal check logic without condition resolution.\n\t * Factorizes the normalize → branch → atomic pipeline to avoid\n\t * duplication between the two paths of `check()`.\n\t */\n\tprivate checkInternal(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t): SubsetResult {\n\t\t// ── Identity short-circuit ──\n\t\t// Same reference → no errors, no merge needed.\n\t\tif (sub === sup) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// Avoids WeakMap overhead for identical schemas ({} ⊆ {}, etc.).\n\t\tif (deepEqual(sub, sup)) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// Catches semantically equivalent schemas after normalization.\n\t\tif (deepEqual(nSub, nSup)) {\n\t\t\treturn { isSubset: true, merged: nSub, errors: [] };\n\t\t}\n\n\t\tconst { branches: subBranches, type: subBranchType } =\n\t\t\tgetBranchesTyped(nSub);\n\t\tconst { branches: supBranches, type: supBranchType } =\n\t\t\tgetBranchesTyped(nSup);\n\n\t\t// anyOf/oneOf in sub\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn checkBranchedSub(subBranches, nSup, this.engine, subBranchType);\n\t\t}\n\n\t\t// anyOf/oneOf in sup only\n\t\tif (supBranches.length > 1 || supBranches[0] !== nSup) {\n\t\t\treturn checkBranchedSup(nSub, supBranches, this.engine, supBranchType);\n\t\t}\n\n\t\t// Standard case\n\t\treturn checkAtomic(nSub, nSup, this.engine);\n\t}\n\n\t// ── Cache management ───────────────────────────────────────────────────\n\n\t/**\n\t * Clears all compiled AJV validator caches (WeakMap, LRU, and AJV internal).\n\t *\n\t * Useful for:\n\t * - Long-running processes where schemas evolve over time\n\t * - Test isolation (ensuring no cross-test cache pollution)\n\t * - Memory pressure situations where cached validators are no longer needed\n\t *\n\t * After calling this, the next validation call will recompile validators\n\t * from scratch — there is a one-time performance cost per unique schema.\n\t *\n\t * This is a static method because the AJV instance is a module-level\n\t * singleton shared across all `JsonSchemaCompatibilityChecker` instances.\n\t *\n\t * @example\n\t * ```ts\n\t * JsonSchemaCompatibilityChecker.clearCache();\n\t * ```\n\t */\n\tstatic clearCache(): void {\n\t\tclearAllValidatorCaches();\n\t}\n}\n"],"names":["JsonSchemaCompatibilityChecker","MergeEngine","arePatternsEquivalent","formatResult","isPatternSubset","isTrivialPattern","normalize","resolveConditions","isSubset","sub","sup","deepEqual","nSub","nSup","branches","subBranches","getBranchesTyped","length","every","branch","isAtomicSubsetOf","engine","check","options","data","shouldValidate","validate","dataForConditions","isPlainObj","resolvedSub","resolvedSup","canNarrow","undefined","canNarrowSub","resolved","canNarrowSup","narrowedSubResolved","narrowSchemaWithData","narrowedSupResolved","staticResult","checkInternal","runtimeErrors","push","prefixRuntimeErrors","getRuntimeValidationErrors","Object","keys","constraintValidators","validateSchemaConstraints","merged","errors","isEqual","a","b","intersect","nA","nB","merge","def","label","result","schema","rootKey","map","error","key","type","subBranchType","supBranches","supBranchType","checkBranchedSub","checkBranchedSup","checkAtomic","clearCache","clearAllValidatorCaches","constraints"],"mappings":"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"}
|
package/dist/cjs/types.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
2
|
+
declare module "json-schema" {
|
|
3
|
+
interface JSONSchema7 {
|
|
4
|
+
constraints?: Constraints;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
2
7
|
export interface SchemaError {
|
|
3
8
|
/** Normalized path to the concerned property (e.g. "user.name", "users[].name", "accountId") */
|
|
4
9
|
key: string;
|
|
@@ -20,17 +25,34 @@ export interface SubsetResult {
|
|
|
20
25
|
*
|
|
21
26
|
* When `data` is provided, the checker:
|
|
22
27
|
* 1. Resolves `if/then/else` conditions in both `sub` and `sup` using `data`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* (if `data` is `undefined`, conditions are resolved with `{}`)
|
|
29
|
+
* 2. Narrows schemas using runtime values (e.g. enum materialization)
|
|
30
|
+
* 3. Performs the static subset check on the resolved/narrowed schemas
|
|
31
|
+
*
|
|
32
|
+
* When `validate` is `true`, two additional runtime steps run **after** the
|
|
33
|
+
* static check passes:
|
|
34
|
+
* 4. `data` is validated against both resolved schemas via AJV
|
|
35
|
+
* 5. Custom constraints are validated against `data`
|
|
26
36
|
*
|
|
27
|
-
* `data`
|
|
28
|
-
*
|
|
29
|
-
* the
|
|
37
|
+
* `data` can be a partial discriminant (e.g. `{ kind: "text" }`) used solely
|
|
38
|
+
* for condition resolution and narrowing. It does **not** need to be a complete
|
|
39
|
+
* instance of the schemas unless `validate: true` is set.
|
|
30
40
|
*/
|
|
31
41
|
export interface CheckRuntimeOptions {
|
|
32
|
-
/** Runtime data used for condition resolution,
|
|
42
|
+
/** Runtime data used for condition resolution, narrowing, and optionally runtime validation */
|
|
33
43
|
data: unknown;
|
|
44
|
+
/**
|
|
45
|
+
* When `true`, enables runtime validation of `data` against both resolved
|
|
46
|
+
* schemas via AJV, and custom constraint validation if a registry was
|
|
47
|
+
* provided at construction time.
|
|
48
|
+
*
|
|
49
|
+
* When `false` or omitted (default), `data` is used only for condition
|
|
50
|
+
* resolution (`if/then/else`) and schema narrowing — no AJV validation
|
|
51
|
+
* or constraint validation is performed.
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
validate?: boolean;
|
|
34
56
|
}
|
|
35
57
|
/**
|
|
36
58
|
* Extended result from `check()` when runtime options are provided.
|
|
@@ -24,17 +24,21 @@ export declare class JsonSchemaCompatibilityChecker {
|
|
|
24
24
|
* Checks `sub ⊆ sup` and returns a detailed diagnostic
|
|
25
25
|
* with human-readable semantic errors.
|
|
26
26
|
*
|
|
27
|
-
* When `options` is provided
|
|
28
|
-
*
|
|
27
|
+
* When `options` is provided, both schemas go through runtime-aware
|
|
28
|
+
* processing before the static check:
|
|
29
29
|
* 1. Conditions (`if/then/else`) are resolved using `data`
|
|
30
|
+
* (if `data` is `undefined`, conditions are resolved with `{}`)
|
|
30
31
|
* 2. Schemas are narrowed using runtime values (enum materialization)
|
|
31
|
-
* 3.
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* 3. The static subset check runs on the resolved/narrowed schemas
|
|
33
|
+
*
|
|
34
|
+
* When `validate: true` is set in options, two additional runtime steps
|
|
35
|
+
* run **after** the static check passes:
|
|
36
|
+
* 4. `data` is validated against both resolved schemas via AJV
|
|
37
|
+
* 5. Custom constraints are validated against `data`
|
|
34
38
|
*
|
|
35
39
|
* @param sub - The source schema (subset candidate)
|
|
36
40
|
* @param sup - The target schema (expected superset)
|
|
37
|
-
* @param options - Runtime options with `data`
|
|
41
|
+
* @param options - Runtime options with `data` and optional `validate` flag
|
|
38
42
|
* @returns SubsetResult if no options, ResolvedSubsetResult if options provided
|
|
39
43
|
*
|
|
40
44
|
* @example
|
|
@@ -42,8 +46,11 @@ export declare class JsonSchemaCompatibilityChecker {
|
|
|
42
46
|
* // Static check (no runtime data)
|
|
43
47
|
* checker.check(sub, sup);
|
|
44
48
|
*
|
|
45
|
-
* //
|
|
49
|
+
* // Resolve conditions + narrowing + static check (no runtime validation)
|
|
46
50
|
* checker.check(sub, sup, { data: { kind: "text", value: "hello" } });
|
|
51
|
+
*
|
|
52
|
+
* // Full pipeline including AJV + constraint runtime validation
|
|
53
|
+
* checker.check(sub, sup, { data: { kind: "text", value: "hello" }, validate: true });
|
|
47
54
|
* ```
|
|
48
55
|
*/
|
|
49
56
|
check(sub: JSONSchema7Definition, sup: JSONSchema7Definition, options: CheckRuntimeOptions): ResolvedSubsetResult;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{resolveConditions}from"./condition-resolver.js";import{validateSchemaConstraints}from"./constraint-validator.js";import{narrowSchemaWithData}from"./data-narrowing.js";import{formatResult}from"./formatter.js";import{MergeEngine}from"./merge-engine.js";import{normalize}from"./normalizer.js";import{arePatternsEquivalent,isPatternSubset,isTrivialPattern}from"./pattern-subset.js";import{clearAllValidatorCaches,getRuntimeValidationErrors}from"./runtime-validator.js";import{checkAtomic,checkBranchedSub,checkBranchedSup,getBranchesTyped,isAtomicSubsetOf}from"./subset-checker.js";import{deepEqual,isPlainObj}from"./utils.js";export{normalize,resolveConditions,formatResult,MergeEngine,isPatternSubset,arePatternsEquivalent,isTrivialPattern};export class JsonSchemaCompatibilityChecker{isSubset(sub,sup){if(sub===sup)return true;if(deepEqual(sub,sup))return true;const nSub=normalize(sub);const nSup=normalize(sup);if(nSub!==sub&&nSup!==sup&&deepEqual(nSub,nSup))return true;if(nSub!==nSup&&deepEqual(nSub,nSup))return true;const{branches:subBranches}=getBranchesTyped(nSub);if(subBranches.length>1||subBranches[0]!==nSub){return subBranches.every(branch=>isAtomicSubsetOf(branch,nSup,this.engine))}return isAtomicSubsetOf(nSub,nSup,this.engine)}check(sub,sup,options){if(options){const data=options.data;
|
|
1
|
+
function _define_property(obj,key,value){if(key in obj){Object.defineProperty(obj,key,{value:value,enumerable:true,configurable:true,writable:true})}else{obj[key]=value}return obj}import{resolveConditions}from"./condition-resolver.js";import{validateSchemaConstraints}from"./constraint-validator.js";import{narrowSchemaWithData}from"./data-narrowing.js";import{formatResult}from"./formatter.js";import{MergeEngine}from"./merge-engine.js";import{normalize}from"./normalizer.js";import{arePatternsEquivalent,isPatternSubset,isTrivialPattern}from"./pattern-subset.js";import{clearAllValidatorCaches,getRuntimeValidationErrors}from"./runtime-validator.js";import{checkAtomic,checkBranchedSub,checkBranchedSup,getBranchesTyped,isAtomicSubsetOf}from"./subset-checker.js";import{deepEqual,isPlainObj}from"./utils.js";export{normalize,resolveConditions,formatResult,MergeEngine,isPatternSubset,arePatternsEquivalent,isTrivialPattern};export class JsonSchemaCompatibilityChecker{isSubset(sub,sup){if(sub===sup)return true;if(deepEqual(sub,sup))return true;const nSub=normalize(sub);const nSup=normalize(sup);if(nSub!==sub&&nSup!==sup&&deepEqual(nSub,nSup))return true;if(nSub!==nSup&&deepEqual(nSub,nSup))return true;const{branches:subBranches}=getBranchesTyped(nSub);if(subBranches.length>1||subBranches[0]!==nSub){return subBranches.every(branch=>isAtomicSubsetOf(branch,nSup,this.engine))}return isAtomicSubsetOf(nSub,nSup,this.engine)}check(sub,sup,options){if(options){const data=options.data;const shouldValidate=options.validate===true;const dataForConditions=isPlainObj(data)?data:{};const resolvedSub=resolveConditions(sub,dataForConditions,this.engine);const resolvedSup=resolveConditions(sup,dataForConditions,this.engine);const canNarrow=data!==undefined;const canNarrowSub=canNarrow&&isPlainObj(resolvedSub.resolved);const canNarrowSup=canNarrow&&isPlainObj(resolvedSup.resolved);const narrowedSubResolved=canNarrowSub?narrowSchemaWithData(resolvedSub.resolved,data,resolvedSup.resolved):resolvedSub.resolved;const narrowedSupResolved=canNarrowSup?narrowSchemaWithData(resolvedSup.resolved,data,resolvedSub.resolved):resolvedSup.resolved;const staticResult=this.checkInternal(narrowedSubResolved,narrowedSupResolved);if(!staticResult.isSubset){return{...staticResult,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}if(shouldValidate&&data!==undefined){const runtimeErrors=[];runtimeErrors.push(...this.prefixRuntimeErrors(getRuntimeValidationErrors(narrowedSubResolved,data),"$sub"));runtimeErrors.push(...this.prefixRuntimeErrors(getRuntimeValidationErrors(narrowedSupResolved,data),"$sup"));if(Object.keys(this.constraintValidators).length>0){runtimeErrors.push(...this.prefixRuntimeErrors(validateSchemaConstraints(narrowedSubResolved,data,this.constraintValidators),"$sub"));runtimeErrors.push(...this.prefixRuntimeErrors(validateSchemaConstraints(narrowedSupResolved,data,this.constraintValidators),"$sup"))}if(runtimeErrors.length>0){return{isSubset:false,merged:null,errors:runtimeErrors,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}}return{...staticResult,resolvedSub:{...resolvedSub,resolved:narrowedSubResolved},resolvedSup:{...resolvedSup,resolved:narrowedSupResolved}}}return this.checkInternal(sub,sup)}isEqual(a,b){return this.engine.isEqual(normalize(a),normalize(b))}intersect(a,b){if(a===b||deepEqual(a,b))return normalize(a);const nA=normalize(a);const nB=normalize(b);if(deepEqual(nA,nB))return nA;const merged=this.engine.merge(nA,nB);if(merged===null)return null;if(deepEqual(merged,nA)||deepEqual(merged,nB))return merged;return normalize(merged)}normalize(def){return normalize(def)}formatResult(label,result){return formatResult(label,result)}resolveConditions(schema,data){return resolveConditions(schema,data,this.engine)}prefixRuntimeErrors(errors,rootKey){return errors.map(error=>({...error,key:error.key==="$root"?rootKey:`${rootKey}.${error.key}`}))}checkInternal(sub,sup){if(sub===sup){return{isSubset:true,merged:sub,errors:[]}}if(deepEqual(sub,sup)){return{isSubset:true,merged:sub,errors:[]}}const nSub=normalize(sub);const nSup=normalize(sup);if(deepEqual(nSub,nSup)){return{isSubset:true,merged:nSub,errors:[]}}const{branches:subBranches,type:subBranchType}=getBranchesTyped(nSub);const{branches:supBranches,type:supBranchType}=getBranchesTyped(nSup);if(subBranches.length>1||subBranches[0]!==nSub){return checkBranchedSub(subBranches,nSup,this.engine,subBranchType)}if(supBranches.length>1||supBranches[0]!==nSup){return checkBranchedSup(nSub,supBranches,this.engine,supBranchType)}return checkAtomic(nSub,nSup,this.engine)}static clearCache(){clearAllValidatorCaches()}constructor(options){_define_property(this,"constraintValidators",void 0);_define_property(this,"engine",void 0);this.engine=new MergeEngine;this.constraintValidators=options?.constraints??{}}}
|
|
2
2
|
//# sourceMappingURL=json-schema-compatibility-checker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/json-schema-compatibility-checker.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { resolveConditions } from \"./condition-resolver.ts\";\nimport { validateSchemaConstraints } from \"./constraint-validator.ts\";\nimport { narrowSchemaWithData } from \"./data-narrowing.ts\";\nimport { formatResult } from \"./formatter.ts\";\nimport { MergeEngine } from \"./merge-engine.ts\";\nimport { normalize } from \"./normalizer.ts\";\nimport {\n\tarePatternsEquivalent,\n\tisPatternSubset,\n\tisTrivialPattern,\n} from \"./pattern-subset.ts\";\nimport {\n\tclearAllValidatorCaches,\n\tgetRuntimeValidationErrors,\n} from \"./runtime-validator.ts\";\nimport type { BranchResult, BranchType } from \"./subset-checker.ts\";\nimport {\n\tcheckAtomic,\n\tcheckBranchedSub,\n\tcheckBranchedSup,\n\tgetBranchesTyped,\n\tisAtomicSubsetOf,\n} from \"./subset-checker.ts\";\nimport type {\n\tCheckerOptions,\n\tCheckRuntimeOptions,\n\tConstraintValidatorRegistry,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tSchemaError,\n\tSubsetResult,\n} from \"./types.ts\";\nimport { deepEqual, isPlainObj } from \"./utils.ts\";\n\n// ─── Re-exports ──────────────────────────────────────────────────────────────\n\nexport type {\n\tSchemaError,\n\tSubsetResult,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tCheckRuntimeOptions,\n\tBranchType,\n\tBranchResult,\n};\n\nexport {\n\tnormalize,\n\tresolveConditions,\n\tformatResult,\n\tMergeEngine,\n\tisPatternSubset,\n\tarePatternsEquivalent,\n\tisTrivialPattern,\n};\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n//\n// Lightweight facade that orchestrates sub-modules to verify compatibility\n// between JSON Schemas (Draft-07).\n//\n// Mathematical principle:\n// A ⊆ B ⟺ A ∩ B ≡ A\n//\n// In JSON Schema terms:\n// - A ∩ B = allOf([A, B]) resolved via merge\n// - ≡ = structural comparison\n//\n// @example\n// ```ts\n// const checker = new JsonSchemaCompatibilityChecker();\n//\n// checker.isSubset(strict, loose); // true\n// checker.check(loose, strict); // { isSubset: false, diffs: [...] }\n// checker.check(sub, sup, { data: {...} }); // resolves conditions then checks\n// ```\n\nexport class JsonSchemaCompatibilityChecker {\n\tprivate readonly constraintValidators: ConstraintValidatorRegistry;\n\tprivate readonly engine: MergeEngine;\n\n\tconstructor(options?: CheckerOptions) {\n\t\tthis.engine = new MergeEngine();\n\t\tthis.constraintValidators = options?.constraints ?? {};\n\t}\n\n\t// ── Subset check (boolean) ─────────────────────────────────────────────\n\n\t/**\n\t * Checks whether `sub ⊆ sup`.\n\t * Is every value valid for sub also valid for sup?\n\t *\n\t * Uses `getBranchesTyped` to distinguish `anyOf` from `oneOf`\n\t * internally, although the boolean result does not reflect the distinction.\n\t */\n\tisSubset(sub: JSONSchema7Definition, sup: JSONSchema7Definition): boolean {\n\t\t// ── Identity short-circuit ──\n\t\t// If sub and sup are the same reference, sub ⊆ sup is trivially true.\n\t\t// This avoids the entire normalize + merge + compare pipeline.\n\t\tif (sub === sup) return true;\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// If sub and sup are structurally identical before normalization,\n\t\t// they represent the same schema → sub ⊆ sup trivially.\n\t\t// This avoids the WeakMap overhead of normalize() for common cases\n\t\t// like {} ⊆ {} or identical schema objects with different references.\n\t\tif (deepEqual(sub, sup)) return true;\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// After normalization, schemas that were syntactically different\n\t\t// but semantically equivalent become structurally equal\n\t\t// (e.g. {const:1} vs {const:1, type:\"integer\"}).\n\t\tif (nSub !== sub && nSup !== sup && deepEqual(nSub, nSup)) return true;\n\t\tif (nSub !== nSup && deepEqual(nSub, nSup)) return true;\n\n\t\tconst { branches: subBranches } = getBranchesTyped(nSub);\n\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn subBranches.every((branch) =>\n\t\t\t\tisAtomicSubsetOf(branch, nSup, this.engine),\n\t\t\t);\n\t\t}\n\n\t\treturn isAtomicSubsetOf(nSub, nSup, this.engine);\n\t}\n\n\t// ── Subset check (detailed) ────────────────────────────────────────────\n\n\t/**\n\t * Checks `sub ⊆ sup` and returns a detailed diagnostic\n\t * with human-readable semantic errors.\n\t *\n\t * When `options` is provided with `data`, both schemas go through\n\t * runtime-aware processing before the static check:\n\t * 1. Conditions (`if/then/else`) are resolved using `data`\n\t * 2. Schemas are narrowed using runtime values (enum materialization)\n\t * 3. `data` is validated against both resolved schemas via AJV\n\t * 4. If runtime validation fails, `isSubset: false` is returned immediately\n\t * 5. Otherwise the static subset check runs on resolved/narrowed schemas\n\t *\n\t * @param sub - The source schema (subset candidate)\n\t * @param sup - The target schema (expected superset)\n\t * @param options - Runtime options with `data` (optional)\n\t * @returns SubsetResult if no options, ResolvedSubsetResult if options provided\n\t *\n\t * @example\n\t * ```ts\n\t * // Static check (no runtime data)\n\t * checker.check(sub, sup);\n\t *\n\t * // Runtime-aware check\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" } });\n\t * ```\n\t */\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions: CheckRuntimeOptions,\n\t): ResolvedSubsetResult;\n\tcheck(sub: JSONSchema7Definition, sup: JSONSchema7Definition): SubsetResult;\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions?: CheckRuntimeOptions,\n\t): SubsetResult | ResolvedSubsetResult {\n\t\t// ── Runtime-aware path ──\n\t\tif (options) {\n\t\t\tconst data = options.data;\n\n\t\t\t// If data is explicitly undefined, fall back to the static path.\n\t\t\t// The runtime path requires concrete data for condition resolution,\n\t\t\t// narrowing, and validation — undefined data would skip all of these,\n\t\t\t// producing results identical to the static path.\n\t\t\t// We still wrap the result in ResolvedSubsetResult to satisfy the\n\t\t\t// overload contract: callers passing options always get resolved info.\n\t\t\tif (data === undefined) {\n\t\t\t\tconst staticResult = this.checkInternal(sub, sup);\n\t\t\t\tconst noopResolution: ResolvedConditionResult = {\n\t\t\t\t\tresolved: sub as JSONSchema7,\n\t\t\t\t\tbranch: null,\n\t\t\t\t\tdiscriminant: {},\n\t\t\t\t};\n\t\t\t\tconst noopSupResolution: ResolvedConditionResult = {\n\t\t\t\t\tresolved: sup as JSONSchema7,\n\t\t\t\t\tbranch: null,\n\t\t\t\t\tdiscriminant: {},\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: noopResolution,\n\t\t\t\t\tresolvedSup: noopSupResolution,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// resolveConditions expects Record<string, unknown> for property access;\n\t\t\t// coerce non-object data to empty object (no conditions to resolve for primitives)\n\t\t\tconst dataForConditions: Record<string, unknown> = isPlainObj(data)\n\t\t\t\t? data\n\t\t\t\t: {};\n\n\t\t\tconst resolvedSub = resolveConditions(\n\t\t\t\tsub as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\t\t\tconst resolvedSup = resolveConditions(\n\t\t\t\tsup as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\n\t\t\t// ── Runtime-aware data narrowing ──\n\t\t\t// Apply narrowing before any equality short-circuit so runtime data\n\t\t\t// can invalidate or refine enum/const-constrained schemas even when\n\t\t\t// the resolved schemas are structurally identical.\n\t\t\t// Boolean schemas (true/false) cannot be narrowed — skip narrowing\n\t\t\t// to avoid passing a non-object to narrowSchemaWithData.\n\t\t\tconst canNarrowSub = isPlainObj(resolvedSub.resolved);\n\t\t\tconst canNarrowSup = isPlainObj(resolvedSup.resolved);\n\n\t\t\tconst narrowedSubResolved = canNarrowSub\n\t\t\t\t? narrowSchemaWithData(resolvedSub.resolved, data, resolvedSup.resolved)\n\t\t\t\t: resolvedSub.resolved;\n\n\t\t\tconst narrowedSupResolved = canNarrowSup\n\t\t\t\t? narrowSchemaWithData(resolvedSup.resolved, data, resolvedSub.resolved)\n\t\t\t\t: resolvedSup.resolved;\n\n\t\t\t// ── Static subset check (runs first) ──\n\t\t\t// Structural incompatibilities are schema-level problems — they are\n\t\t\t// permanent regardless of the concrete data. Run this before runtime\n\t\t\t// validation so that static errors always surface with higher priority.\n\t\t\tconst staticResult = this.checkInternal(\n\t\t\t\tnarrowedSubResolved,\n\t\t\t\tnarrowedSupResolved,\n\t\t\t);\n\n\t\t\tif (!staticResult.isSubset) {\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// ── Runtime validation ──\n\t\t\t// The schemas are structurally compatible. Now validate the concrete\n\t\t\t// data against both resolved/narrowed schemas to catch data-level\n\t\t\t// violations (e.g. value doesn't match format, out-of-range, etc.).\n\t\t\tconst runtimeErrors: SchemaError[] = [];\n\n\t\t\truntimeErrors.push(\n\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\tgetRuntimeValidationErrors(narrowedSubResolved, data),\n\t\t\t\t\t\"$sub\",\n\t\t\t\t),\n\t\t\t);\n\n\t\t\truntimeErrors.push(\n\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\tgetRuntimeValidationErrors(narrowedSupResolved, data),\n\t\t\t\t\t\"$sup\",\n\t\t\t\t),\n\t\t\t);\n\n\t\t\t// ── Constraint validation ──\n\t\t\t// Validate runtime data against custom constraints in both schemas.\n\t\t\t// Only runs if constraint validators were registered in the constructor.\n\t\t\tif (Object.keys(this.constraintValidators).length > 0) {\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\tnarrowedSubResolved,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\tnarrowedSupResolved,\n\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t),\n\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (runtimeErrors.length > 0) {\n\t\t\t\treturn {\n\t\t\t\t\tisSubset: false,\n\t\t\t\t\tmerged: null,\n\t\t\t\t\terrors: runtimeErrors,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...staticResult,\n\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t};\n\t\t}\n\n\t\t// ── Standard path (no condition resolution) ──\n\t\treturn this.checkInternal(sub, sup);\n\t}\n\n\t// ── Equality ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Checks structural equality between two schemas.\n\t */\n\tisEqual(a: JSONSchema7Definition, b: JSONSchema7Definition): boolean {\n\t\treturn this.engine.isEqual(normalize(a), normalize(b));\n\t}\n\n\t// ── Intersection ───────────────────────────────────────────────────────\n\n\t/**\n\t * Computes the intersection of two schemas (allOf merge).\n\t * Returns null if the schemas are incompatible.\n\t *\n\t * The result is normalized to eliminate structural artifacts\n\t * from the merge (e.g. redundant `enum` when `const` is present).\n\t */\n\tintersect(\n\t\ta: JSONSchema7Definition,\n\t\tb: JSONSchema7Definition,\n\t): JSONSchema7Definition | null {\n\t\t// ── Identity short-circuit ──\n\t\t// If a and b are the same reference or structurally equal,\n\t\t// intersection is just normalize(a) — skip the merge entirely.\n\t\tif (a === b || deepEqual(a, b)) return normalize(a);\n\n\t\tconst nA = normalize(a);\n\t\tconst nB = normalize(b);\n\n\t\t// ── Post-normalize identity ──\n\t\tif (deepEqual(nA, nB)) return nA;\n\n\t\tconst merged = this.engine.merge(nA, nB);\n\t\tif (merged === null) return null;\n\t\t// Fast path: if merge result equals one of the normalized inputs,\n\t\t// it's already normalized — skip redundant normalize call.\n\t\tif (deepEqual(merged, nA) || deepEqual(merged, nB)) return merged;\n\t\treturn normalize(merged);\n\t}\n\n\t// ── Normalization ──────────────────────────────────────────────────────\n\n\t/**\n\t * Normalizes a schema: infers `type` from `const`/`enum`,\n\t * and recursively normalizes all sub-schemas.\n\t */\n\tnormalize(def: JSONSchema7Definition): JSONSchema7Definition {\n\t\treturn normalize(def);\n\t}\n\n\t// ── Formatting ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Formats a SubsetResult into a readable string (useful for logs/debug).\n\t */\n\tformatResult(label: string, result: SubsetResult): string {\n\t\treturn formatResult(label, result);\n\t}\n\n\t// ── Condition Resolution ────────────────────────────────────────────────\n\n\t/**\n\t * Resolves `if/then/else` conditions in a schema by evaluating the `if`\n\t * against runtime data.\n\t *\n\t * @param schema - The schema containing conditions to resolve\n\t * @param data - The runtime data used to evaluate conditions\n\t * @returns The resolved schema with branch info and discriminants\n\t */\n\tresolveConditions(\n\t\tschema: JSONSchema7,\n\t\tdata: Record<string, unknown>,\n\t): ResolvedConditionResult {\n\t\treturn resolveConditions(schema, data, this.engine);\n\t}\n\n\t// ── Private ────────────────────────────────────────────────────────────\n\n\tprivate prefixRuntimeErrors(\n\t\terrors: SchemaError[],\n\t\trootKey: \"$sub\" | \"$sup\",\n\t): SchemaError[] {\n\t\treturn errors.map((error) => ({\n\t\t\t...error,\n\t\t\tkey: error.key === \"$root\" ? rootKey : `${rootKey}.${error.key}`,\n\t\t}));\n\t}\n\n\t/**\n\t * Internal check logic without condition resolution.\n\t * Factorizes the normalize → branch → atomic pipeline to avoid\n\t * duplication between the two paths of `check()`.\n\t */\n\tprivate checkInternal(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t): SubsetResult {\n\t\t// ── Identity short-circuit ──\n\t\t// Same reference → no errors, no merge needed.\n\t\tif (sub === sup) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// Avoids WeakMap overhead for identical schemas ({} ⊆ {}, etc.).\n\t\tif (deepEqual(sub, sup)) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// Catches semantically equivalent schemas after normalization.\n\t\tif (deepEqual(nSub, nSup)) {\n\t\t\treturn { isSubset: true, merged: nSub, errors: [] };\n\t\t}\n\n\t\tconst { branches: subBranches, type: subBranchType } =\n\t\t\tgetBranchesTyped(nSub);\n\t\tconst { branches: supBranches, type: supBranchType } =\n\t\t\tgetBranchesTyped(nSup);\n\n\t\t// anyOf/oneOf in sub\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn checkBranchedSub(subBranches, nSup, this.engine, subBranchType);\n\t\t}\n\n\t\t// anyOf/oneOf in sup only\n\t\tif (supBranches.length > 1 || supBranches[0] !== nSup) {\n\t\t\treturn checkBranchedSup(nSub, supBranches, this.engine, supBranchType);\n\t\t}\n\n\t\t// Standard case\n\t\treturn checkAtomic(nSub, nSup, this.engine);\n\t}\n\n\t// ── Cache management ───────────────────────────────────────────────────\n\n\t/**\n\t * Clears all compiled AJV validator caches (WeakMap, LRU, and AJV internal).\n\t *\n\t * Useful for:\n\t * - Long-running processes where schemas evolve over time\n\t * - Test isolation (ensuring no cross-test cache pollution)\n\t * - Memory pressure situations where cached validators are no longer needed\n\t *\n\t * After calling this, the next validation call will recompile validators\n\t * from scratch — there is a one-time performance cost per unique schema.\n\t *\n\t * This is a static method because the AJV instance is a module-level\n\t * singleton shared across all `JsonSchemaCompatibilityChecker` instances.\n\t *\n\t * @example\n\t * ```ts\n\t * JsonSchemaCompatibilityChecker.clearCache();\n\t * ```\n\t */\n\tstatic clearCache(): void {\n\t\tclearAllValidatorCaches();\n\t}\n}\n"],"names":["resolveConditions","validateSchemaConstraints","narrowSchemaWithData","formatResult","MergeEngine","normalize","arePatternsEquivalent","isPatternSubset","isTrivialPattern","clearAllValidatorCaches","getRuntimeValidationErrors","checkAtomic","checkBranchedSub","checkBranchedSup","getBranchesTyped","isAtomicSubsetOf","deepEqual","isPlainObj","JsonSchemaCompatibilityChecker","isSubset","sub","sup","nSub","nSup","branches","subBranches","length","every","branch","engine","check","options","data","undefined","staticResult","checkInternal","noopResolution","resolved","discriminant","noopSupResolution","resolvedSub","resolvedSup","dataForConditions","canNarrowSub","canNarrowSup","narrowedSubResolved","narrowedSupResolved","runtimeErrors","push","prefixRuntimeErrors","Object","keys","constraintValidators","merged","errors","isEqual","a","b","intersect","nA","nB","merge","def","label","result","schema","rootKey","map","error","key","type","subBranchType","supBranches","supBranchType","clearCache","constraints"],"mappings":"oLACA,OAASA,iBAAiB,KAAQ,yBAA0B,AAC5D,QAASC,yBAAyB,KAAQ,2BAA4B,AACtE,QAASC,oBAAoB,KAAQ,qBAAsB,AAC3D,QAASC,YAAY,KAAQ,gBAAiB,AAC9C,QAASC,WAAW,KAAQ,mBAAoB,AAChD,QAASC,SAAS,KAAQ,iBAAkB,AAC5C,QACCC,qBAAqB,CACrBC,eAAe,CACfC,gBAAgB,KACV,qBAAsB,AAC7B,QACCC,uBAAuB,CACvBC,0BAA0B,KACpB,wBAAyB,AAEhC,QACCC,WAAW,CACXC,gBAAgB,CAChBC,gBAAgB,CAChBC,gBAAgB,CAChBC,gBAAgB,KACV,qBAAsB,AAU7B,QAASC,SAAS,CAAEC,UAAU,KAAQ,YAAa,AAcnD,QACCZ,SAAS,CACTL,iBAAiB,CACjBG,YAAY,CACZC,WAAW,CACXG,eAAe,CACfD,qBAAqB,CACrBE,gBAAgB,CACf,AAuBF,QAAO,MAAMU,+BAkBZC,SAASC,GAA0B,CAAEC,GAA0B,CAAW,CAIzE,GAAID,MAAQC,IAAK,OAAO,KAOxB,GAAIL,UAAUI,IAAKC,KAAM,OAAO,KAEhC,MAAMC,KAAOjB,UAAUe,KACvB,MAAMG,KAAOlB,UAAUgB,KAMvB,GAAIC,OAASF,KAAOG,OAASF,KAAOL,UAAUM,KAAMC,MAAO,OAAO,KAClE,GAAID,OAASC,MAAQP,UAAUM,KAAMC,MAAO,OAAO,KAEnD,KAAM,CAAEC,SAAUC,WAAW,CAAE,CAAGX,iBAAiBQ,MAEnD,GAAIG,YAAYC,MAAM,CAAG,GAAKD,WAAW,CAAC,EAAE,GAAKH,KAAM,CACtD,OAAOG,YAAYE,KAAK,CAAC,AAACC,QACzBb,iBAAiBa,OAAQL,KAAM,IAAI,CAACM,MAAM,EAE5C,CAEA,OAAOd,iBAAiBO,KAAMC,KAAM,IAAI,CAACM,MAAM,CAChD,CAoCAC,MACCV,GAA0B,CAC1BC,GAA0B,CAC1BU,OAA6B,CACS,CAEtC,GAAIA,QAAS,CACZ,MAAMC,KAAOD,QAAQC,IAAI,CAQzB,GAAIA,OAASC,UAAW,CACvB,MAAMC,aAAe,IAAI,CAACC,aAAa,CAACf,IAAKC,KAC7C,MAAMe,eAA0C,CAC/CC,SAAUjB,IACVQ,OAAQ,KACRU,aAAc,CAAC,CAChB,EACA,MAAMC,kBAA6C,CAClDF,SAAUhB,IACVO,OAAQ,KACRU,aAAc,CAAC,CAChB,EACA,MAAO,CACN,GAAGJ,YAAY,CACfM,YAAaJ,eACbK,YAAaF,iBACd,CACD,CAIA,MAAMG,kBAA6CzB,WAAWe,MAC3DA,KACA,CAAC,EAEJ,MAAMQ,YAAcxC,kBACnBoB,IACAsB,kBACA,IAAI,CAACb,MAAM,EAEZ,MAAMY,YAAczC,kBACnBqB,IACAqB,kBACA,IAAI,CAACb,MAAM,EASZ,MAAMc,aAAe1B,WAAWuB,YAAYH,QAAQ,EACpD,MAAMO,aAAe3B,WAAWwB,YAAYJ,QAAQ,EAEpD,MAAMQ,oBAAsBF,aACzBzC,qBAAqBsC,YAAYH,QAAQ,CAAEL,KAAMS,YAAYJ,QAAQ,EACrEG,YAAYH,QAAQ,CAEvB,MAAMS,oBAAsBF,aACzB1C,qBAAqBuC,YAAYJ,QAAQ,CAAEL,KAAMQ,YAAYH,QAAQ,EACrEI,YAAYJ,QAAQ,CAMvB,MAAMH,aAAe,IAAI,CAACC,aAAa,CACtCU,oBACAC,qBAGD,GAAI,CAACZ,aAAaf,QAAQ,CAAE,CAC3B,MAAO,CACN,GAAGe,YAAY,CACfM,YAAa,CAAE,GAAGA,WAAW,CAAEH,SAAUQ,mBAAoB,EAC7DJ,YAAa,CAAE,GAAGA,WAAW,CAAEJ,SAAUS,mBAAoB,CAC9D,CACD,CAMA,MAAMC,cAA+B,EAAE,CAEvCA,cAAcC,IAAI,IACd,IAAI,CAACC,mBAAmB,CAC1BvC,2BAA2BmC,oBAAqBb,MAChD,SAIFe,cAAcC,IAAI,IACd,IAAI,CAACC,mBAAmB,CAC1BvC,2BAA2BoC,oBAAqBd,MAChD,SAOF,GAAIkB,OAAOC,IAAI,CAAC,IAAI,CAACC,oBAAoB,EAAE1B,MAAM,CAAG,EAAG,CACtDqB,cAAcC,IAAI,IACd,IAAI,CAACC,mBAAmB,CAC1BhD,0BACC4C,oBACAb,KACA,IAAI,CAACoB,oBAAoB,EAE1B,SAIFL,cAAcC,IAAI,IACd,IAAI,CAACC,mBAAmB,CAC1BhD,0BACC6C,oBACAd,KACA,IAAI,CAACoB,oBAAoB,EAE1B,QAGH,CAEA,GAAIL,cAAcrB,MAAM,CAAG,EAAG,CAC7B,MAAO,CACNP,SAAU,MACVkC,OAAQ,KACRC,OAAQP,cACRP,YAAa,CAAE,GAAGA,WAAW,CAAEH,SAAUQ,mBAAoB,EAC7DJ,YAAa,CAAE,GAAGA,WAAW,CAAEJ,SAAUS,mBAAoB,CAC9D,CACD,CAEA,MAAO,CACN,GAAGZ,YAAY,CACfM,YAAa,CAAE,GAAGA,WAAW,CAAEH,SAAUQ,mBAAoB,EAC7DJ,YAAa,CAAE,GAAGA,WAAW,CAAEJ,SAAUS,mBAAoB,CAC9D,CACD,CAGA,OAAO,IAAI,CAACX,aAAa,CAACf,IAAKC,IAChC,CAOAkC,QAAQC,CAAwB,CAAEC,CAAwB,CAAW,CACpE,OAAO,IAAI,CAAC5B,MAAM,CAAC0B,OAAO,CAAClD,UAAUmD,GAAInD,UAAUoD,GACpD,CAWAC,UACCF,CAAwB,CACxBC,CAAwB,CACO,CAI/B,GAAID,IAAMC,GAAKzC,UAAUwC,EAAGC,GAAI,OAAOpD,UAAUmD,GAEjD,MAAMG,GAAKtD,UAAUmD,GACrB,MAAMI,GAAKvD,UAAUoD,GAGrB,GAAIzC,UAAU2C,GAAIC,IAAK,OAAOD,GAE9B,MAAMN,OAAS,IAAI,CAACxB,MAAM,CAACgC,KAAK,CAACF,GAAIC,IACrC,GAAIP,SAAW,KAAM,OAAO,KAG5B,GAAIrC,UAAUqC,OAAQM,KAAO3C,UAAUqC,OAAQO,IAAK,OAAOP,OAC3D,OAAOhD,UAAUgD,OAClB,CAQAhD,UAAUyD,GAA0B,CAAyB,CAC5D,OAAOzD,UAAUyD,IAClB,CAOA3D,aAAa4D,KAAa,CAAEC,MAAoB,CAAU,CACzD,OAAO7D,aAAa4D,MAAOC,OAC5B,CAYAhE,kBACCiE,MAAmB,CACnBjC,IAA6B,CACH,CAC1B,OAAOhC,kBAAkBiE,OAAQjC,KAAM,IAAI,CAACH,MAAM,CACnD,CAIA,AAAQoB,oBACPK,MAAqB,CACrBY,OAAwB,CACR,CAChB,OAAOZ,OAAOa,GAAG,CAAC,AAACC,OAAW,CAAA,CAC7B,GAAGA,KAAK,CACRC,IAAKD,MAAMC,GAAG,GAAK,QAAUH,QAAU,CAAC,EAAEA,QAAQ,CAAC,EAAEE,MAAMC,GAAG,CAAC,CAAC,AACjE,CAAA,EACD,CAOA,AAAQlC,cACPf,GAA0B,CAC1BC,GAA0B,CACX,CAGf,GAAID,MAAQC,IAAK,CAChB,MAAO,CAAEF,SAAU,KAAMkC,OAAQjC,IAAKkC,OAAQ,EAAE,AAAC,CAClD,CAIA,GAAItC,UAAUI,IAAKC,KAAM,CACxB,MAAO,CAAEF,SAAU,KAAMkC,OAAQjC,IAAKkC,OAAQ,EAAE,AAAC,CAClD,CAEA,MAAMhC,KAAOjB,UAAUe,KACvB,MAAMG,KAAOlB,UAAUgB,KAIvB,GAAIL,UAAUM,KAAMC,MAAO,CAC1B,MAAO,CAAEJ,SAAU,KAAMkC,OAAQ/B,KAAMgC,OAAQ,EAAE,AAAC,CACnD,CAEA,KAAM,CAAE9B,SAAUC,WAAW,CAAE6C,KAAMC,aAAa,CAAE,CACnDzD,iBAAiBQ,MAClB,KAAM,CAAEE,SAAUgD,WAAW,CAAEF,KAAMG,aAAa,CAAE,CACnD3D,iBAAiBS,MAGlB,GAAIE,YAAYC,MAAM,CAAG,GAAKD,WAAW,CAAC,EAAE,GAAKH,KAAM,CACtD,OAAOV,iBAAiBa,YAAaF,KAAM,IAAI,CAACM,MAAM,CAAE0C,cACzD,CAGA,GAAIC,YAAY9C,MAAM,CAAG,GAAK8C,WAAW,CAAC,EAAE,GAAKjD,KAAM,CACtD,OAAOV,iBAAiBS,KAAMkD,YAAa,IAAI,CAAC3C,MAAM,CAAE4C,cACzD,CAGA,OAAO9D,YAAYW,KAAMC,KAAM,IAAI,CAACM,MAAM,CAC3C,CAuBA,OAAO6C,YAAmB,CACzBjE,yBACD,CA5YA,YAAYsB,OAAwB,CAAE,CAHtC,sBAAiBqB,uBAAjB,KAAA,GACA,sBAAiBvB,SAAjB,KAAA,EAGC,CAAA,IAAI,CAACA,MAAM,CAAG,IAAIzB,WAClB,CAAA,IAAI,CAACgD,oBAAoB,CAAGrB,SAAS4C,aAAe,CAAC,CACtD,CA0YD"}
|
|
1
|
+
{"version":3,"sources":["../../src/json-schema-compatibility-checker.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\nimport { resolveConditions } from \"./condition-resolver.ts\";\nimport { validateSchemaConstraints } from \"./constraint-validator.ts\";\nimport { narrowSchemaWithData } from \"./data-narrowing.ts\";\nimport { formatResult } from \"./formatter.ts\";\nimport { MergeEngine } from \"./merge-engine.ts\";\nimport { normalize } from \"./normalizer.ts\";\nimport {\n\tarePatternsEquivalent,\n\tisPatternSubset,\n\tisTrivialPattern,\n} from \"./pattern-subset.ts\";\nimport {\n\tclearAllValidatorCaches,\n\tgetRuntimeValidationErrors,\n} from \"./runtime-validator.ts\";\nimport type { BranchResult, BranchType } from \"./subset-checker.ts\";\nimport {\n\tcheckAtomic,\n\tcheckBranchedSub,\n\tcheckBranchedSup,\n\tgetBranchesTyped,\n\tisAtomicSubsetOf,\n} from \"./subset-checker.ts\";\nimport type {\n\tCheckerOptions,\n\tCheckRuntimeOptions,\n\tConstraintValidatorRegistry,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tSchemaError,\n\tSubsetResult,\n} from \"./types.ts\";\nimport { deepEqual, isPlainObj } from \"./utils.ts\";\n\n// ─── Re-exports ──────────────────────────────────────────────────────────────\n\nexport type {\n\tSchemaError,\n\tSubsetResult,\n\tResolvedConditionResult,\n\tResolvedSubsetResult,\n\tCheckRuntimeOptions,\n\tBranchType,\n\tBranchResult,\n};\n\nexport {\n\tnormalize,\n\tresolveConditions,\n\tformatResult,\n\tMergeEngine,\n\tisPatternSubset,\n\tarePatternsEquivalent,\n\tisTrivialPattern,\n};\n\n// ─── Main Class ──────────────────────────────────────────────────────────────\n//\n// Lightweight facade that orchestrates sub-modules to verify compatibility\n// between JSON Schemas (Draft-07).\n//\n// Mathematical principle:\n// A ⊆ B ⟺ A ∩ B ≡ A\n//\n// In JSON Schema terms:\n// - A ∩ B = allOf([A, B]) resolved via merge\n// - ≡ = structural comparison\n//\n// @example\n// ```ts\n// const checker = new JsonSchemaCompatibilityChecker();\n//\n// checker.isSubset(strict, loose); // true\n// checker.check(loose, strict); // { isSubset: false, diffs: [...] }\n// checker.check(sub, sup, { data: {...} }); // resolves conditions then checks\n// ```\n\nexport class JsonSchemaCompatibilityChecker {\n\tprivate readonly constraintValidators: ConstraintValidatorRegistry;\n\tprivate readonly engine: MergeEngine;\n\n\tconstructor(options?: CheckerOptions) {\n\t\tthis.engine = new MergeEngine();\n\t\tthis.constraintValidators = options?.constraints ?? {};\n\t}\n\n\t// ── Subset check (boolean) ─────────────────────────────────────────────\n\n\t/**\n\t * Checks whether `sub ⊆ sup`.\n\t * Is every value valid for sub also valid for sup?\n\t *\n\t * Uses `getBranchesTyped` to distinguish `anyOf` from `oneOf`\n\t * internally, although the boolean result does not reflect the distinction.\n\t */\n\tisSubset(sub: JSONSchema7Definition, sup: JSONSchema7Definition): boolean {\n\t\t// ── Identity short-circuit ──\n\t\t// If sub and sup are the same reference, sub ⊆ sup is trivially true.\n\t\t// This avoids the entire normalize + merge + compare pipeline.\n\t\tif (sub === sup) return true;\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// If sub and sup are structurally identical before normalization,\n\t\t// they represent the same schema → sub ⊆ sup trivially.\n\t\t// This avoids the WeakMap overhead of normalize() for common cases\n\t\t// like {} ⊆ {} or identical schema objects with different references.\n\t\tif (deepEqual(sub, sup)) return true;\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// After normalization, schemas that were syntactically different\n\t\t// but semantically equivalent become structurally equal\n\t\t// (e.g. {const:1} vs {const:1, type:\"integer\"}).\n\t\tif (nSub !== sub && nSup !== sup && deepEqual(nSub, nSup)) return true;\n\t\tif (nSub !== nSup && deepEqual(nSub, nSup)) return true;\n\n\t\tconst { branches: subBranches } = getBranchesTyped(nSub);\n\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn subBranches.every((branch) =>\n\t\t\t\tisAtomicSubsetOf(branch, nSup, this.engine),\n\t\t\t);\n\t\t}\n\n\t\treturn isAtomicSubsetOf(nSub, nSup, this.engine);\n\t}\n\n\t// ── Subset check (detailed) ────────────────────────────────────────────\n\n\t/**\n\t * Checks `sub ⊆ sup` and returns a detailed diagnostic\n\t * with human-readable semantic errors.\n\t *\n\t * When `options` is provided, both schemas go through runtime-aware\n\t * processing before the static check:\n\t * 1. Conditions (`if/then/else`) are resolved using `data`\n\t * (if `data` is `undefined`, conditions are resolved with `{}`)\n\t * 2. Schemas are narrowed using runtime values (enum materialization)\n\t * 3. The static subset check runs on the resolved/narrowed schemas\n\t *\n\t * When `validate: true` is set in options, two additional runtime steps\n\t * run **after** the static check passes:\n\t * 4. `data` is validated against both resolved schemas via AJV\n\t * 5. Custom constraints are validated against `data`\n\t *\n\t * @param sub - The source schema (subset candidate)\n\t * @param sup - The target schema (expected superset)\n\t * @param options - Runtime options with `data` and optional `validate` flag\n\t * @returns SubsetResult if no options, ResolvedSubsetResult if options provided\n\t *\n\t * @example\n\t * ```ts\n\t * // Static check (no runtime data)\n\t * checker.check(sub, sup);\n\t *\n\t * // Resolve conditions + narrowing + static check (no runtime validation)\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" } });\n\t *\n\t * // Full pipeline including AJV + constraint runtime validation\n\t * checker.check(sub, sup, { data: { kind: \"text\", value: \"hello\" }, validate: true });\n\t * ```\n\t */\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions: CheckRuntimeOptions,\n\t): ResolvedSubsetResult;\n\tcheck(sub: JSONSchema7Definition, sup: JSONSchema7Definition): SubsetResult;\n\tcheck(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t\toptions?: CheckRuntimeOptions,\n\t): SubsetResult | ResolvedSubsetResult {\n\t\t// ── Runtime-aware path ──\n\t\tif (options) {\n\t\t\tconst data = options.data;\n\t\t\tconst shouldValidate = options.validate === true;\n\n\t\t\t// resolveConditions expects Record<string, unknown> for property access;\n\t\t\t// coerce non-object / undefined data to empty object so conditions\n\t\t\t// are always resolved (v1.0.11 compat: subData: undefined → {})\n\t\t\tconst dataForConditions: Record<string, unknown> = isPlainObj(data)\n\t\t\t\t? data\n\t\t\t\t: {};\n\n\t\t\tconst resolvedSub = resolveConditions(\n\t\t\t\tsub as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\t\t\tconst resolvedSup = resolveConditions(\n\t\t\t\tsup as JSONSchema7,\n\t\t\t\tdataForConditions,\n\t\t\t\tthis.engine,\n\t\t\t);\n\n\t\t\t// ── Runtime-aware data narrowing ──\n\t\t\t// Apply narrowing only when concrete data is available.\n\t\t\t// When data is undefined there is nothing to narrow with.\n\t\t\t// Boolean schemas (true/false) cannot be narrowed — skip narrowing\n\t\t\t// to avoid passing a non-object to narrowSchemaWithData.\n\t\t\tconst canNarrow = data !== undefined;\n\t\t\tconst canNarrowSub = canNarrow && isPlainObj(resolvedSub.resolved);\n\t\t\tconst canNarrowSup = canNarrow && isPlainObj(resolvedSup.resolved);\n\n\t\t\tconst narrowedSubResolved = canNarrowSub\n\t\t\t\t? narrowSchemaWithData(resolvedSub.resolved, data, resolvedSup.resolved)\n\t\t\t\t: resolvedSub.resolved;\n\n\t\t\tconst narrowedSupResolved = canNarrowSup\n\t\t\t\t? narrowSchemaWithData(resolvedSup.resolved, data, resolvedSub.resolved)\n\t\t\t\t: resolvedSup.resolved;\n\n\t\t\t// ── Static subset check ──\n\t\t\t// Structural incompatibilities are schema-level problems — they are\n\t\t\t// permanent regardless of the concrete data. Run this before runtime\n\t\t\t// validation so that static errors always surface with higher priority.\n\t\t\tconst staticResult = this.checkInternal(\n\t\t\t\tnarrowedSubResolved,\n\t\t\t\tnarrowedSupResolved,\n\t\t\t);\n\n\t\t\tif (!staticResult.isSubset) {\n\t\t\t\treturn {\n\t\t\t\t\t...staticResult,\n\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// ── Runtime validation (opt-in) ──\n\t\t\t// Only runs when `validate: true` is explicitly set.\n\t\t\t// Validates the concrete data against both resolved/narrowed schemas\n\t\t\t// via AJV, then runs custom constraint validators if registered.\n\t\t\tif (shouldValidate && data !== undefined) {\n\t\t\t\tconst runtimeErrors: SchemaError[] = [];\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tgetRuntimeValidationErrors(narrowedSubResolved, data),\n\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\truntimeErrors.push(\n\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\tgetRuntimeValidationErrors(narrowedSupResolved, data),\n\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\t// ── Constraint validation ──\n\t\t\t\t// Validate runtime data against custom constraints in both schemas.\n\t\t\t\t// Only runs if constraint validators were registered in the constructor.\n\t\t\t\tif (Object.keys(this.constraintValidators).length > 0) {\n\t\t\t\t\truntimeErrors.push(\n\t\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\t\tnarrowedSubResolved,\n\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\"$sub\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\n\t\t\t\t\truntimeErrors.push(\n\t\t\t\t\t\t...this.prefixRuntimeErrors(\n\t\t\t\t\t\t\tvalidateSchemaConstraints(\n\t\t\t\t\t\t\t\tnarrowedSupResolved,\n\t\t\t\t\t\t\t\tdata,\n\t\t\t\t\t\t\t\tthis.constraintValidators,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\"$sup\",\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tif (runtimeErrors.length > 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tisSubset: false,\n\t\t\t\t\t\tmerged: null,\n\t\t\t\t\t\terrors: runtimeErrors,\n\t\t\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\t...staticResult,\n\t\t\t\tresolvedSub: { ...resolvedSub, resolved: narrowedSubResolved },\n\t\t\t\tresolvedSup: { ...resolvedSup, resolved: narrowedSupResolved },\n\t\t\t};\n\t\t}\n\n\t\t// ── Standard path (no condition resolution) ──\n\t\treturn this.checkInternal(sub, sup);\n\t}\n\n\t// ── Equality ───────────────────────────────────────────────────────────\n\n\t/**\n\t * Checks structural equality between two schemas.\n\t */\n\tisEqual(a: JSONSchema7Definition, b: JSONSchema7Definition): boolean {\n\t\treturn this.engine.isEqual(normalize(a), normalize(b));\n\t}\n\n\t// ── Intersection ───────────────────────────────────────────────────────\n\n\t/**\n\t * Computes the intersection of two schemas (allOf merge).\n\t * Returns null if the schemas are incompatible.\n\t *\n\t * The result is normalized to eliminate structural artifacts\n\t * from the merge (e.g. redundant `enum` when `const` is present).\n\t */\n\tintersect(\n\t\ta: JSONSchema7Definition,\n\t\tb: JSONSchema7Definition,\n\t): JSONSchema7Definition | null {\n\t\t// ── Identity short-circuit ──\n\t\t// If a and b are the same reference or structurally equal,\n\t\t// intersection is just normalize(a) — skip the merge entirely.\n\t\tif (a === b || deepEqual(a, b)) return normalize(a);\n\n\t\tconst nA = normalize(a);\n\t\tconst nB = normalize(b);\n\n\t\t// ── Post-normalize identity ──\n\t\tif (deepEqual(nA, nB)) return nA;\n\n\t\tconst merged = this.engine.merge(nA, nB);\n\t\tif (merged === null) return null;\n\t\t// Fast path: if merge result equals one of the normalized inputs,\n\t\t// it's already normalized — skip redundant normalize call.\n\t\tif (deepEqual(merged, nA) || deepEqual(merged, nB)) return merged;\n\t\treturn normalize(merged);\n\t}\n\n\t// ── Normalization ──────────────────────────────────────────────────────\n\n\t/**\n\t * Normalizes a schema: infers `type` from `const`/`enum`,\n\t * and recursively normalizes all sub-schemas.\n\t */\n\tnormalize(def: JSONSchema7Definition): JSONSchema7Definition {\n\t\treturn normalize(def);\n\t}\n\n\t// ── Formatting ─────────────────────────────────────────────────────────\n\n\t/**\n\t * Formats a SubsetResult into a readable string (useful for logs/debug).\n\t */\n\tformatResult(label: string, result: SubsetResult): string {\n\t\treturn formatResult(label, result);\n\t}\n\n\t// ── Condition Resolution ────────────────────────────────────────────────\n\n\t/**\n\t * Resolves `if/then/else` conditions in a schema by evaluating the `if`\n\t * against runtime data.\n\t *\n\t * @param schema - The schema containing conditions to resolve\n\t * @param data - The runtime data used to evaluate conditions\n\t * @returns The resolved schema with branch info and discriminants\n\t */\n\tresolveConditions(\n\t\tschema: JSONSchema7,\n\t\tdata: Record<string, unknown>,\n\t): ResolvedConditionResult {\n\t\treturn resolveConditions(schema, data, this.engine);\n\t}\n\n\t// ── Private ────────────────────────────────────────────────────────────\n\n\tprivate prefixRuntimeErrors(\n\t\terrors: SchemaError[],\n\t\trootKey: \"$sub\" | \"$sup\",\n\t): SchemaError[] {\n\t\treturn errors.map((error) => ({\n\t\t\t...error,\n\t\t\tkey: error.key === \"$root\" ? rootKey : `${rootKey}.${error.key}`,\n\t\t}));\n\t}\n\n\t/**\n\t * Internal check logic without condition resolution.\n\t * Factorizes the normalize → branch → atomic pipeline to avoid\n\t * duplication between the two paths of `check()`.\n\t */\n\tprivate checkInternal(\n\t\tsub: JSONSchema7Definition,\n\t\tsup: JSONSchema7Definition,\n\t): SubsetResult {\n\t\t// ── Identity short-circuit ──\n\t\t// Same reference → no errors, no merge needed.\n\t\tif (sub === sup) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\t// ── Pre-normalize structural equality ──\n\t\t// Avoids WeakMap overhead for identical schemas ({} ⊆ {}, etc.).\n\t\tif (deepEqual(sub, sup)) {\n\t\t\treturn { isSubset: true, merged: sub, errors: [] };\n\t\t}\n\n\t\tconst nSub = normalize(sub);\n\t\tconst nSup = normalize(sup);\n\n\t\t// ── Post-normalize structural identity ──\n\t\t// Catches semantically equivalent schemas after normalization.\n\t\tif (deepEqual(nSub, nSup)) {\n\t\t\treturn { isSubset: true, merged: nSub, errors: [] };\n\t\t}\n\n\t\tconst { branches: subBranches, type: subBranchType } =\n\t\t\tgetBranchesTyped(nSub);\n\t\tconst { branches: supBranches, type: supBranchType } =\n\t\t\tgetBranchesTyped(nSup);\n\n\t\t// anyOf/oneOf in sub\n\t\tif (subBranches.length > 1 || subBranches[0] !== nSub) {\n\t\t\treturn checkBranchedSub(subBranches, nSup, this.engine, subBranchType);\n\t\t}\n\n\t\t// anyOf/oneOf in sup only\n\t\tif (supBranches.length > 1 || supBranches[0] !== nSup) {\n\t\t\treturn checkBranchedSup(nSub, supBranches, this.engine, supBranchType);\n\t\t}\n\n\t\t// Standard case\n\t\treturn checkAtomic(nSub, nSup, this.engine);\n\t}\n\n\t// ── Cache management ───────────────────────────────────────────────────\n\n\t/**\n\t * Clears all compiled AJV validator caches (WeakMap, LRU, and AJV internal).\n\t *\n\t * Useful for:\n\t * - Long-running processes where schemas evolve over time\n\t * - Test isolation (ensuring no cross-test cache pollution)\n\t * - Memory pressure situations where cached validators are no longer needed\n\t *\n\t * After calling this, the next validation call will recompile validators\n\t * from scratch — there is a one-time performance cost per unique schema.\n\t *\n\t * This is a static method because the AJV instance is a module-level\n\t * singleton shared across all `JsonSchemaCompatibilityChecker` instances.\n\t *\n\t * @example\n\t * ```ts\n\t * JsonSchemaCompatibilityChecker.clearCache();\n\t * ```\n\t */\n\tstatic clearCache(): void {\n\t\tclearAllValidatorCaches();\n\t}\n}\n"],"names":["resolveConditions","validateSchemaConstraints","narrowSchemaWithData","formatResult","MergeEngine","normalize","arePatternsEquivalent","isPatternSubset","isTrivialPattern","clearAllValidatorCaches","getRuntimeValidationErrors","checkAtomic","checkBranchedSub","checkBranchedSup","getBranchesTyped","isAtomicSubsetOf","deepEqual","isPlainObj","JsonSchemaCompatibilityChecker","isSubset","sub","sup","nSub","nSup","branches","subBranches","length","every","branch","engine","check","options","data","shouldValidate","validate","dataForConditions","resolvedSub","resolvedSup","canNarrow","undefined","canNarrowSub","resolved","canNarrowSup","narrowedSubResolved","narrowedSupResolved","staticResult","checkInternal","runtimeErrors","push","prefixRuntimeErrors","Object","keys","constraintValidators","merged","errors","isEqual","a","b","intersect","nA","nB","merge","def","label","result","schema","rootKey","map","error","key","type","subBranchType","supBranches","supBranchType","clearCache","constraints"],"mappings":"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"}
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
|
|
2
|
+
declare module "json-schema" {
|
|
3
|
+
interface JSONSchema7 {
|
|
4
|
+
constraints?: Constraints;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
2
7
|
export interface SchemaError {
|
|
3
8
|
/** Normalized path to the concerned property (e.g. "user.name", "users[].name", "accountId") */
|
|
4
9
|
key: string;
|
|
@@ -20,17 +25,34 @@ export interface SubsetResult {
|
|
|
20
25
|
*
|
|
21
26
|
* When `data` is provided, the checker:
|
|
22
27
|
* 1. Resolves `if/then/else` conditions in both `sub` and `sup` using `data`
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
28
|
+
* (if `data` is `undefined`, conditions are resolved with `{}`)
|
|
29
|
+
* 2. Narrows schemas using runtime values (e.g. enum materialization)
|
|
30
|
+
* 3. Performs the static subset check on the resolved/narrowed schemas
|
|
31
|
+
*
|
|
32
|
+
* When `validate` is `true`, two additional runtime steps run **after** the
|
|
33
|
+
* static check passes:
|
|
34
|
+
* 4. `data` is validated against both resolved schemas via AJV
|
|
35
|
+
* 5. Custom constraints are validated against `data`
|
|
26
36
|
*
|
|
27
|
-
* `data`
|
|
28
|
-
*
|
|
29
|
-
* the
|
|
37
|
+
* `data` can be a partial discriminant (e.g. `{ kind: "text" }`) used solely
|
|
38
|
+
* for condition resolution and narrowing. It does **not** need to be a complete
|
|
39
|
+
* instance of the schemas unless `validate: true` is set.
|
|
30
40
|
*/
|
|
31
41
|
export interface CheckRuntimeOptions {
|
|
32
|
-
/** Runtime data used for condition resolution,
|
|
42
|
+
/** Runtime data used for condition resolution, narrowing, and optionally runtime validation */
|
|
33
43
|
data: unknown;
|
|
44
|
+
/**
|
|
45
|
+
* When `true`, enables runtime validation of `data` against both resolved
|
|
46
|
+
* schemas via AJV, and custom constraint validation if a registry was
|
|
47
|
+
* provided at construction time.
|
|
48
|
+
*
|
|
49
|
+
* When `false` or omitted (default), `data` is used only for condition
|
|
50
|
+
* resolution (`if/then/else`) and schema narrowing — no AJV validation
|
|
51
|
+
* or constraint validation is performed.
|
|
52
|
+
*
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
validate?: boolean;
|
|
34
56
|
}
|
|
35
57
|
/**
|
|
36
58
|
* Extended result from `check()` when runtime options are provided.
|
package/dist/esm/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\n\n// ─── Public types ────────────────────────────────────────────────────────────\n\nexport interface SchemaError {\n\t/** Normalized path to the concerned property (e.g. \"user.name\", \"users[].name\", \"accountId\") */\n\tkey: string;\n\t/** Type or value expected by the target schema (sup) */\n\texpected: string;\n\t/** Type or value received from the source schema (sub) */\n\treceived: string;\n}\n\nexport interface SubsetResult {\n\t/** true if sub ⊆ sup (every value valid for sub is also valid for sup) */\n\tisSubset: boolean;\n\t/** The schema resulting from the intersection allOf(sub, sup), or null if incompatible */\n\tmerged: JSONSchema7Definition | null;\n\t/** Semantic errors describing incompatibilities between the two schemas */\n\terrors: SchemaError[];\n}\n\n/**\n * Options for runtime-aware subset checking.\n *\n * When `data` is provided, the checker:\n * 1. Resolves `if/then/else` conditions in both `sub` and `sup` using `data`\n *
|
|
1
|
+
{"version":3,"sources":["../../src/types.ts"],"sourcesContent":["import type { JSONSchema7, JSONSchema7Definition } from \"json-schema\";\n\n// ─── Module augmentation ─────────────────────────────────────────────────────\n// Extends JSONSchema7 with the custom `constraints` keyword so that consumers\n// of this package see the property on every JSONSchema7 without needing a\n// separate ambient file or `/// <reference>` directive.\n\ndeclare module \"json-schema\" {\n\tinterface JSONSchema7 {\n\t\tconstraints?: Constraints;\n\t}\n}\n\n// ─── Public types ────────────────────────────────────────────────────────────\n\nexport interface SchemaError {\n\t/** Normalized path to the concerned property (e.g. \"user.name\", \"users[].name\", \"accountId\") */\n\tkey: string;\n\t/** Type or value expected by the target schema (sup) */\n\texpected: string;\n\t/** Type or value received from the source schema (sub) */\n\treceived: string;\n}\n\nexport interface SubsetResult {\n\t/** true if sub ⊆ sup (every value valid for sub is also valid for sup) */\n\tisSubset: boolean;\n\t/** The schema resulting from the intersection allOf(sub, sup), or null if incompatible */\n\tmerged: JSONSchema7Definition | null;\n\t/** Semantic errors describing incompatibilities between the two schemas */\n\terrors: SchemaError[];\n}\n\n/**\n * Options for runtime-aware subset checking.\n *\n * When `data` is provided, the checker:\n * 1. Resolves `if/then/else` conditions in both `sub` and `sup` using `data`\n * (if `data` is `undefined`, conditions are resolved with `{}`)\n * 2. Narrows schemas using runtime values (e.g. enum materialization)\n * 3. Performs the static subset check on the resolved/narrowed schemas\n *\n * When `validate` is `true`, two additional runtime steps run **after** the\n * static check passes:\n * 4. `data` is validated against both resolved schemas via AJV\n * 5. Custom constraints are validated against `data`\n *\n * `data` can be a partial discriminant (e.g. `{ kind: \"text\" }`) used solely\n * for condition resolution and narrowing. It does **not** need to be a complete\n * instance of the schemas unless `validate: true` is set.\n */\nexport interface CheckRuntimeOptions {\n\t/** Runtime data used for condition resolution, narrowing, and optionally runtime validation */\n\tdata: unknown;\n\n\t/**\n\t * When `true`, enables runtime validation of `data` against both resolved\n\t * schemas via AJV, and custom constraint validation if a registry was\n\t * provided at construction time.\n\t *\n\t * When `false` or omitted (default), `data` is used only for condition\n\t * resolution (`if/then/else`) and schema narrowing — no AJV validation\n\t * or constraint validation is performed.\n\t *\n\t * @default false\n\t */\n\tvalidate?: boolean;\n}\n\n/**\n * Extended result from `check()` when runtime options are provided.\n * Includes resolution results for sub and sup in addition to the SubsetResult.\n */\nexport interface ResolvedSubsetResult extends SubsetResult {\n\tresolvedSub: ResolvedConditionResult;\n\tresolvedSup: ResolvedConditionResult;\n}\n\nexport interface ResolvedConditionResult {\n\t/** The schema with if/then/else resolved (flattened) */\n\tresolved: JSONSchema7;\n\t/** The branch that was applied (\"then\" | \"else\" | null if no condition) */\n\tbranch: \"then\" | \"else\" | null;\n\t/** The discriminant used for resolution */\n\tdiscriminant: Record<string, unknown>;\n}\n\nexport type Constraint =\n\t| string\n\t| {\n\t\t\tname: string;\n\t\t\tparams?: Record<string, unknown>;\n\t };\n\nexport type Constraints = Constraint | Constraint[];\n\n// ─── Constraint Validator types ──────────────────────────────────────────────\n\n/**\n * Result of a constraint validation.\n */\nexport interface ConstraintValidationResult {\n\t/** Whether the value satisfies the constraint */\n\tvalid: boolean;\n\t/** Human-readable message when `valid` is `false` */\n\tmessage?: string;\n}\n\n/**\n * A constraint validator function.\n *\n * Receives the value to validate and optional params defined\n * in the schema's constraint definition.\n *\n * Must be synchronous — async validation is out of scope\n * for this library. Wrap async checks in your application layer.\n *\n * @param value - The runtime value to validate\n * @param params - The `params` object from the constraint definition, if any\n * @returns The validation result\n *\n * @example\n * ```ts\n * const isUuid: ConstraintValidator = (value) => ({\n * valid: typeof value === \"string\" && /^[0-9a-f]{8}-/.test(value),\n * message: \"Value must be a valid UUID\",\n * });\n *\n * const minAge: ConstraintValidator = (value, params) => ({\n * valid: typeof value === \"number\" && value >= (params?.min ?? 0),\n * message: `Value must be at least ${params?.min}`,\n * });\n * ```\n */\nexport type ConstraintValidator = (\n\tvalue: unknown,\n\tparams?: Record<string, unknown>,\n) => ConstraintValidationResult;\n\n/**\n * Registry mapping constraint names to their validator functions.\n *\n * Keys are constraint names as they appear in schema definitions\n * (e.g. `\"IsUuid\"`, `\"MinAge\"`).\n */\nexport type ConstraintValidatorRegistry = Record<string, ConstraintValidator>;\n\n/**\n * Options for the JsonSchemaCompatibilityChecker constructor.\n */\nexport interface CheckerOptions {\n\t/**\n\t * Registry of custom constraint validators.\n\t *\n\t * When provided, the checker can validate runtime data against\n\t * custom constraints defined in schemas via the `constraints` keyword.\n\t *\n\t * Constraint names must match those used in schema definitions.\n\t * Unknown constraints (present in a schema but absent from the registry)\n\t * will be reported as errors during runtime validation.\n\t */\n\tconstraints?: ConstraintValidatorRegistry;\n}\n"],"names":[],"mappings":"AAsJA,QAYC"}
|