@json-schema-engine/compiler 0.0.1
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/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/emit.d.ts +52 -0
- package/dist/emit.d.ts.map +1 -0
- package/dist/emit.js +166 -0
- package/dist/emit.js.map +1 -0
- package/dist/explain.d.ts +28 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +42 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/dist/index.js.map +1 -0
- package/dist/plan.d.ts +83 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +385 -0
- package/dist/plan.js.map +1 -0
- package/dist/runtime-compile.d.ts +30 -0
- package/dist/runtime-compile.d.ts.map +1 -0
- package/dist/runtime-compile.js +29 -0
- package/dist/runtime-compile.js.map +1 -0
- package/dist/runtime.d.ts +157 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +336 -0
- package/dist/runtime.js.map +1 -0
- package/dist/serialize/apply.d.ts +28 -0
- package/dist/serialize/apply.d.ts.map +1 -0
- package/dist/serialize/apply.js +211 -0
- package/dist/serialize/apply.js.map +1 -0
- package/dist/serialize/context.d.ts +143 -0
- package/dist/serialize/context.d.ts.map +1 -0
- package/dist/serialize/context.js +93 -0
- package/dist/serialize/context.js.map +1 -0
- package/dist/serialize/expressions.d.ts +7 -0
- package/dist/serialize/expressions.d.ts.map +1 -0
- package/dist/serialize/expressions.js +177 -0
- package/dist/serialize/expressions.js.map +1 -0
- package/dist/serialize/guards.d.ts +7 -0
- package/dist/serialize/guards.d.ts.map +1 -0
- package/dist/serialize/guards.js +19 -0
- package/dist/serialize/guards.js.map +1 -0
- package/dist/serialize/index.d.ts +7 -0
- package/dist/serialize/index.d.ts.map +1 -0
- package/dist/serialize/index.js +266 -0
- package/dist/serialize/index.js.map +1 -0
- package/dist/serialize/keywords.d.ts +32 -0
- package/dist/serialize/keywords.d.ts.map +1 -0
- package/dist/serialize/keywords.js +131 -0
- package/dist/serialize/keywords.js.map +1 -0
- package/dist/serialize/messages.d.ts +36 -0
- package/dist/serialize/messages.d.ts.map +1 -0
- package/dist/serialize/messages.js +100 -0
- package/dist/serialize/messages.js.map +1 -0
- package/dist/serialize/names.d.ts +47 -0
- package/dist/serialize/names.d.ts.map +1 -0
- package/dist/serialize/names.js +115 -0
- package/dist/serialize/names.js.map +1 -0
- package/dist/serialize/spans.d.ts +34 -0
- package/dist/serialize/spans.d.ts.map +1 -0
- package/dist/serialize/spans.js +61 -0
- package/dist/serialize/spans.js.map +1 -0
- package/dist/serialize/statements.d.ts +16 -0
- package/dist/serialize/statements.d.ts.map +1 -0
- package/dist/serialize/statements.js +396 -0
- package/dist/serialize/statements.js.map +1 -0
- package/dist/serialize/unit.d.ts +8 -0
- package/dist/serialize/unit.d.ts.map +1 -0
- package/dist/serialize/unit.js +107 -0
- package/dist/serialize/unit.js.map +1 -0
- package/dist/standalone.d.ts +24 -0
- package/dist/standalone.d.ts.map +1 -0
- package/dist/standalone.js +166 -0
- package/dist/standalone.js.map +1 -0
- package/package.json +38 -0
- package/src/emit.ts +196 -0
- package/src/explain.ts +67 -0
- package/src/index.ts +366 -0
- package/src/plan.ts +496 -0
- package/src/runtime-compile.ts +90 -0
- package/src/runtime.ts +599 -0
- package/src/serialize/apply.ts +251 -0
- package/src/serialize/context.ts +161 -0
- package/src/serialize/expressions.ts +202 -0
- package/src/serialize/guards.ts +20 -0
- package/src/serialize/index.ts +432 -0
- package/src/serialize/keywords.ts +144 -0
- package/src/serialize/messages.ts +120 -0
- package/src/serialize/names.ts +136 -0
- package/src/serialize/spans.ts +88 -0
- package/src/serialize/statements.ts +531 -0
- package/src/serialize/unit.ts +118 -0
- package/src/standalone.ts +187 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// @json-schema-engine/compiler public API (M6.2 vertical slice): compile a registered
|
|
2
|
+
// schema into a specialized flag-mode validator. Static subschemas become
|
|
3
|
+
// emitted JS; dynamic islands and every fallback cause trampoline to the
|
|
4
|
+
// interpreter through core's evaluateFragment, so the compiled artifact is
|
|
5
|
+
// exactly as correct as the interpreter — never less complete.
|
|
6
|
+
import { DEFAULT_MAX_DEPTH, OutputOptionsError, assembleResult, renderBasic, resolveOutputDemand, } from "@json-schema-engine/core";
|
|
7
|
+
import { buildPlan } from "./plan.js";
|
|
8
|
+
import { serializePlan } from "./serialize/index.js";
|
|
9
|
+
import { finishTrace, makeRuntime } from "./runtime.js";
|
|
10
|
+
import { instantiate, instantiateList, instantiateListAnn, instantiateTrace, } from "./runtime-compile.js";
|
|
11
|
+
export { buildPlan } from "./plan.js";
|
|
12
|
+
export { explainCompilation } from "./explain.js";
|
|
13
|
+
export { serializePlan } from "./serialize/index.js";
|
|
14
|
+
export { emitStandalone, StandaloneUnsupportedError } from "./standalone.js";
|
|
15
|
+
/**
|
|
16
|
+
* Compile a registered schema into a flag-mode validator. The artifact
|
|
17
|
+
* binds to a snapshot of the engine's schema and dialect registries taken at
|
|
18
|
+
* compile time, plus the engine's pattern cache: schemas registered,
|
|
19
|
+
* re-registered, or given a new dialect later are invisible to it, and a
|
|
20
|
+
* reference unresolved at compile time stays unresolved for it (E1).
|
|
21
|
+
*/
|
|
22
|
+
export function compileValidator(engine, schemaUri, options = {}) {
|
|
23
|
+
const plan = buildPlan(engine, schemaUri);
|
|
24
|
+
// Compilation is synchronous, so the plan (built from the live registry)
|
|
25
|
+
// and this snapshot see one state; the artifact's islands never see a
|
|
26
|
+
// later registration.
|
|
27
|
+
const registry = engine.registry.snapshot();
|
|
28
|
+
const source = serializePlan(plan, registry, {
|
|
29
|
+
flags: options.conservative
|
|
30
|
+
? { inline: false, plainData: false }
|
|
31
|
+
: { inline: true, plainData: true },
|
|
32
|
+
});
|
|
33
|
+
const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, false, undefined, engine.formats, plan.formats);
|
|
34
|
+
const validate = instantiate(source, runtime, plan.targets.map((t) => t.ref));
|
|
35
|
+
return {
|
|
36
|
+
validate: (instance) => validate(instance),
|
|
37
|
+
plan,
|
|
38
|
+
source,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Compile a registered schema into a list-output validator (D9e): flat,
|
|
43
|
+
* interpreter-exact error units — the same elements
|
|
44
|
+
* `Engine.evaluate(uri, x, { output: "list" }).errors` yields, in the same
|
|
45
|
+
* order (list artifacts never short-circuit; every branch runs, DESIGN §7).
|
|
46
|
+
* Error-unit objects materialize only on failure paths. Binds to registry
|
|
47
|
+
* snapshots exactly like {@link compileValidator}.
|
|
48
|
+
*/
|
|
49
|
+
export function compileList(engine, schemaUri, options = {}) {
|
|
50
|
+
const selection = options.annotations ?? false;
|
|
51
|
+
const collect = selection !== false;
|
|
52
|
+
const errorParams = options.errorParams ?? false;
|
|
53
|
+
const plan = buildPlan(engine, schemaUri, { output: "list" });
|
|
54
|
+
const registry = engine.registry.snapshot();
|
|
55
|
+
const source = serializePlan(plan, registry, {
|
|
56
|
+
flags: options.conservative
|
|
57
|
+
? { inline: false, plainData: false }
|
|
58
|
+
: { inline: true, plainData: true },
|
|
59
|
+
output: "list",
|
|
60
|
+
listParams: errorParams,
|
|
61
|
+
annotate: collect ? { selection } : undefined,
|
|
62
|
+
});
|
|
63
|
+
const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, errorParams, collect ? { selection } : undefined, engine.formats, plan.formats);
|
|
64
|
+
const targets = plan.targets.map((t) => t.ref);
|
|
65
|
+
const root = registry.rootRef(schemaUri);
|
|
66
|
+
const rootLocation = `${root.baseUri}#${root.pointer}`;
|
|
67
|
+
const keep = typeof selection === "object" ? selection.keep : undefined;
|
|
68
|
+
if (!collect) {
|
|
69
|
+
const evaluateList = instantiateList(source, runtime, targets);
|
|
70
|
+
return {
|
|
71
|
+
evaluateList,
|
|
72
|
+
basic: (instance) => {
|
|
73
|
+
const { valid, errors } = evaluateList(instance);
|
|
74
|
+
return renderBasic(valid, rootLocation, errors, []);
|
|
75
|
+
},
|
|
76
|
+
plan,
|
|
77
|
+
source,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// The emitted evaluator returns the raw (static-list-filtered) annotation
|
|
81
|
+
// array; the wrapper applies `keep` (over the native unit, as the
|
|
82
|
+
// interpreter does) and the valid-only presence rule, matching
|
|
83
|
+
// `Engine.evaluate`'s Result.annotations; `basic()` renders that same
|
|
84
|
+
// flat surface through core's renderer.
|
|
85
|
+
const rawEval = instantiateListAnn(source, runtime, targets);
|
|
86
|
+
const evaluateList = (instance) => {
|
|
87
|
+
const r = rawEval(instance);
|
|
88
|
+
if (!r.valid)
|
|
89
|
+
return { valid: false, errors: r.errors };
|
|
90
|
+
const anns = keep ? r.annotations.filter(keep) : r.annotations;
|
|
91
|
+
return { valid: true, errors: r.errors, annotations: anns };
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
evaluateList,
|
|
95
|
+
basic: (instance) => {
|
|
96
|
+
const r = evaluateList(instance);
|
|
97
|
+
return renderBasic(r.valid, rootLocation, r.errors, r.annotations ?? []);
|
|
98
|
+
},
|
|
99
|
+
plan,
|
|
100
|
+
source,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Compile a registered schema into an evaluator that renders every output
|
|
105
|
+
* format: a list artifact that also records each application's node —
|
|
106
|
+
* locations, keyword verdicts, and the records it raised — into core's
|
|
107
|
+
* located tree, so the documents render through the same code as the
|
|
108
|
+
* interpreter's. Format, level, and `trace` are chosen per evaluation; the
|
|
109
|
+
* annotation selection, `errorParams`, and whether irrelevant records are
|
|
110
|
+
* retained (`verbose`) are fixed here (D5). Without retention the records
|
|
111
|
+
* are discarded at the cut and a verbose-level request throws
|
|
112
|
+
* {@link OutputOptionsError}. Binds to registry snapshots exactly like
|
|
113
|
+
* {@link compileValidator}.
|
|
114
|
+
*/
|
|
115
|
+
export function compileEvaluator(engine, schemaUri, options = {}) {
|
|
116
|
+
const selection = options.annotations ?? false;
|
|
117
|
+
const collect = selection !== false;
|
|
118
|
+
const errorParams = options.errorParams ?? false;
|
|
119
|
+
const retain = options.verbose ?? false;
|
|
120
|
+
const plan = buildPlan(engine, schemaUri, { output: "list" });
|
|
121
|
+
const registry = engine.registry.snapshot();
|
|
122
|
+
const source = serializePlan(plan, registry, {
|
|
123
|
+
flags: options.conservative
|
|
124
|
+
? { inline: false, plainData: false }
|
|
125
|
+
: { inline: true, plainData: true },
|
|
126
|
+
output: "list",
|
|
127
|
+
listParams: errorParams,
|
|
128
|
+
annotate: collect ? { selection } : undefined,
|
|
129
|
+
trace: true,
|
|
130
|
+
});
|
|
131
|
+
const runtime = makeRuntime(registry, engine.patternCache, plan.patterns, options.maxDepth ?? DEFAULT_MAX_DEPTH, errorParams, collect ? { selection } : undefined, engine.formats, plan.formats, true, retain);
|
|
132
|
+
const run = instantiateTrace(source, runtime, plan.targets.map((t) => t.ref));
|
|
133
|
+
const root = registry.rootRef(schemaUri);
|
|
134
|
+
const rootLocation = `${root.baseUri}#${root.pointer}`;
|
|
135
|
+
const keep = typeof selection === "object" ? selection.keep : undefined;
|
|
136
|
+
const evaluate = (instance, evaluateOptions = {}) => {
|
|
137
|
+
// The controls fixed at compile time are rejected, not ignored (ADR
|
|
138
|
+
// 0003: every combination is supported or refused).
|
|
139
|
+
for (const fixed of ["annotations", "errorParams", "positions"]) {
|
|
140
|
+
if (fixed in evaluateOptions) {
|
|
141
|
+
throw new OutputOptionsError(`'${fixed}' is fixed when the evaluator is compiled`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// The compiled selection stands in for the option; `flag` is admitted
|
|
145
|
+
// whatever the selection, since it renders no records.
|
|
146
|
+
const demand = {
|
|
147
|
+
...resolveOutputDemand({
|
|
148
|
+
output: evaluateOptions.output,
|
|
149
|
+
verbose: evaluateOptions.verbose,
|
|
150
|
+
trace: evaluateOptions.trace,
|
|
151
|
+
}),
|
|
152
|
+
annotations: selection,
|
|
153
|
+
};
|
|
154
|
+
if (demand.verbose && !retain) {
|
|
155
|
+
throw new OutputOptionsError("the verbose level needs an evaluator compiled with verbose: true");
|
|
156
|
+
}
|
|
157
|
+
const st = run(instance);
|
|
158
|
+
if (demand.format === "flag")
|
|
159
|
+
return { valid: st.valid };
|
|
160
|
+
const { units, root: node } = finishTrace(st, keep, demand.verbose);
|
|
161
|
+
return assembleResult(demand, st.valid, units, node, rootLocation, evaluateOptions.trace === true);
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
evaluate: evaluate,
|
|
165
|
+
plan,
|
|
166
|
+
source,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type Engine, type SchemaRef, type SubschemaApplication } from "@json-schema-engine/core";
|
|
2
|
+
/** Why a unit is interpreted rather than compiled. */
|
|
3
|
+
export type FallbackCause = "dynamic" | "unlowerable" | "cycle" | "nonSchema";
|
|
4
|
+
/** Static evaluated-name coverage for one schema object (slice: own-node trio only). */
|
|
5
|
+
export interface StaticNameCoverage {
|
|
6
|
+
names: readonly string[];
|
|
7
|
+
patterns: readonly string[];
|
|
8
|
+
coversAllNames: boolean;
|
|
9
|
+
prefixCount: number;
|
|
10
|
+
coversAllIndexes: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** One application edge out of a unit, resolved at plan time. */
|
|
13
|
+
export interface PlannedApplication {
|
|
14
|
+
keyword: string;
|
|
15
|
+
app: SubschemaApplication;
|
|
16
|
+
targetKey: string;
|
|
17
|
+
}
|
|
18
|
+
export interface PlannedUnit {
|
|
19
|
+
key: string;
|
|
20
|
+
ref: SchemaRef;
|
|
21
|
+
kind: "static" | "interpreted";
|
|
22
|
+
cause?: FallbackCause;
|
|
23
|
+
/** static units: resolved outgoing application edges (keyword order preserved) */
|
|
24
|
+
edges: PlannedApplication[];
|
|
25
|
+
/** static coverage for this schema object, when computable (slice rule) */
|
|
26
|
+
coverage: StaticNameCoverage | null;
|
|
27
|
+
/**
|
|
28
|
+
* Consumer compiled with RUNTIME evaluated-set tracking instead of the
|
|
29
|
+
* interpreter (COMPILED-CONSUMERS.md phase B): a flag-mode consumer whose
|
|
30
|
+
* evaluated coverage is dynamic (static licensing failed), or ANY list-mode
|
|
31
|
+
* consumer (list plans never static-license — see PlanOptions.output).
|
|
32
|
+
* `coverage` stays null; the unit's body threads a runtime coverage channel
|
|
33
|
+
* through its in-place closure (its {@link inRegion} members) and its
|
|
34
|
+
* consumer keywords read it. Never set together with {@link inRegion}
|
|
35
|
+
* (nested tracked consumers island).
|
|
36
|
+
*/
|
|
37
|
+
tracking?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A static unit in some tracked unit's in-place coverage region: reachable
|
|
40
|
+
* from a tracked (or region) unit via in-place edges. Emitted with a second
|
|
41
|
+
* calling convention (a trailing coverage channel) so its producers'
|
|
42
|
+
* post-success coverage flows to the consumer (COMPILED-CONSUMERS.md phase B).
|
|
43
|
+
*/
|
|
44
|
+
inRegion?: boolean;
|
|
45
|
+
/** true when any apply path from this unit can reach an interpreted unit */
|
|
46
|
+
reachesInterpreted: boolean;
|
|
47
|
+
/** number of planned edges targeting this unit (D9 inline licensing) */
|
|
48
|
+
useCount: number;
|
|
49
|
+
}
|
|
50
|
+
export interface CompilationPlan {
|
|
51
|
+
rootKey: string;
|
|
52
|
+
units: Map<string, PlannedUnit>;
|
|
53
|
+
/** every regex source any static unit tests (pattern + coverage patterns) */
|
|
54
|
+
patterns: string[];
|
|
55
|
+
/** every format name any static unit's asserting `format` tests */
|
|
56
|
+
formats: string[];
|
|
57
|
+
/** interpreted units in stable order; index = target-table slot */
|
|
58
|
+
targets: PlannedUnit[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build the compilation plan for one registered root schema. The walk mirrors
|
|
62
|
+
* the registration walk's position logic by construction: descent uses the
|
|
63
|
+
* same analyze() facts and the same registry.child pointer navigation.
|
|
64
|
+
*/
|
|
65
|
+
/** Options for {@link buildPlan}. */
|
|
66
|
+
export interface PlanOptions {
|
|
67
|
+
/**
|
|
68
|
+
* The artifact's output mode. Static-coverage licensing for unevaluated*
|
|
69
|
+
* consumers is sound for "flag" only: coverage models the parent-SUCCESS
|
|
70
|
+
* path, but when a contributor (e.g. prefixItems inside an allOf branch)
|
|
71
|
+
* FAILS, the interpreter drops its annotations and unevaluated* reports
|
|
72
|
+
* additional errors. That difference is verdict-invisible — the failing
|
|
73
|
+
* contributor already fails the parent — but list output must reproduce
|
|
74
|
+
* the interpreter's error units exactly. So "list" plans NEVER static-
|
|
75
|
+
* license a consumer: every consumer is compiled with runtime coverage
|
|
76
|
+
* tracking (`tracking`), which reproduces the drop-on-failure behavior by
|
|
77
|
+
* construction (a failed application's channel span truncates, so the sweep
|
|
78
|
+
* covers less). "flag" keeps the static-coverage fast path.
|
|
79
|
+
*/
|
|
80
|
+
output?: "flag" | "list";
|
|
81
|
+
}
|
|
82
|
+
export declare function buildPlan(engine: Engine, schemaUri: string, options?: PlanOptions): CompilationPlan;
|
|
83
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,MAAM,EAEX,KAAK,SAAS,EAEd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,sDAAsD;AACtD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,aAAa,GACb,OAAO,GACP,WAAW,CAAC;AAEhB,wFAAwF;AACxF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,oBAAoB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,EAAE,QAAQ,GAAG,aAAa,CAAC;IAC/B,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,kFAAkF;IAClF,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,2EAA2E;IAC3E,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,4EAA4E;IAC5E,kBAAkB,EAAE,OAAO,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mEAAmE;IACnE,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAOD;;;;GAIG;AACH,qCAAqC;AACrC,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,WAAgB,GACxB,eAAe,CA8QjB"}
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// Compilation planner (M6.2): classifies every reachable schema node as a
|
|
2
|
+
// static (compilable) or interpreted (trampoline) unit, using ONLY public
|
|
3
|
+
// core APIs and analyze() facts — never keyword names (D1). Conservative by
|
|
4
|
+
// design: anything uncertain falls back to the interpreter, which is always
|
|
5
|
+
// correct.
|
|
6
|
+
import { UnresolvableRefError, } from "@json-schema-engine/core";
|
|
7
|
+
const unitKey = (ref) => `${ref.baseUri}#${ref.pointer}`;
|
|
8
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
9
|
+
export function buildPlan(engine, schemaUri, options = {}) {
|
|
10
|
+
const registry = engine.registry;
|
|
11
|
+
const units = new Map();
|
|
12
|
+
const patterns = new Set();
|
|
13
|
+
const formats = new Set();
|
|
14
|
+
const rootRef = registry.rootRef(schemaUri);
|
|
15
|
+
// inPlaceChain: units connected to the current edge via consecutive
|
|
16
|
+
// in-place applications — a back-edge into this chain is an in-place
|
|
17
|
+
// cycle (same-cursor re-entry, InfiniteLoopError class) and its target
|
|
18
|
+
// must stay interpreted. A descending edge resets the chain.
|
|
19
|
+
const plan = (ref, inPlaceChain) => {
|
|
20
|
+
const key = unitKey(ref);
|
|
21
|
+
const existing = units.get(key);
|
|
22
|
+
if (existing)
|
|
23
|
+
return existing;
|
|
24
|
+
const unit = {
|
|
25
|
+
key,
|
|
26
|
+
ref,
|
|
27
|
+
kind: "static",
|
|
28
|
+
edges: [],
|
|
29
|
+
coverage: null,
|
|
30
|
+
reachesInterpreted: false,
|
|
31
|
+
useCount: 0,
|
|
32
|
+
};
|
|
33
|
+
units.set(key, unit);
|
|
34
|
+
const node = ref.node;
|
|
35
|
+
if (typeof node === "boolean")
|
|
36
|
+
return unit; // trivially static
|
|
37
|
+
if (!isObj(node)) {
|
|
38
|
+
unit.kind = "interpreted";
|
|
39
|
+
unit.cause = "nonSchema"; // interpreter D19 backstop reports it lazily
|
|
40
|
+
return unit;
|
|
41
|
+
}
|
|
42
|
+
// No dialect allowlist: keyword facts are the compiler's whole window
|
|
43
|
+
// into semantics (D1), so a dialect compiles exactly when every present
|
|
44
|
+
// keyword lowers — the per-keyword check below. That is what lets a
|
|
45
|
+
// dialect PACKAGE (draft-04, future OAS dialects) become compilable
|
|
46
|
+
// purely by shipping lower() on its behaviors; the only dialect-LEVEL
|
|
47
|
+
// semantic the planner must mirror is refIgnoresSiblings, handled
|
|
48
|
+
// generically here.
|
|
49
|
+
const dialect = registry.dialectFor(ref.baseUri);
|
|
50
|
+
// draft-07/06 (D18, engine.ts:397): a $ref makes every sibling keyword
|
|
51
|
+
// act as if absent — plan/lower ONLY $ref, exactly as applySchemaAtDepth
|
|
52
|
+
// skips every non-$ref entry when refOnly. Without this, a draft-07
|
|
53
|
+
// $ref-with-siblings unit would compile both the ref AND the siblings,
|
|
54
|
+
// a real divergence from the interpreter (unreachable before this
|
|
55
|
+
// milestone, since every legacy unit was interpreted regardless).
|
|
56
|
+
const refOnly = dialect.refIgnoresSiblings && Object.hasOwn(node, "$ref");
|
|
57
|
+
const present = [];
|
|
58
|
+
let consumerPresent = false;
|
|
59
|
+
for (const entry of dialect.ordered) {
|
|
60
|
+
if (refOnly && entry.name !== "$ref")
|
|
61
|
+
continue;
|
|
62
|
+
if (!Object.hasOwn(node, entry.name))
|
|
63
|
+
continue;
|
|
64
|
+
const behavior = entry.behavior;
|
|
65
|
+
const value = node[entry.name];
|
|
66
|
+
const facts = behavior.analyze?.(value, { schema: node }) ?? {};
|
|
67
|
+
if (facts.dynamicScopeSensitive) {
|
|
68
|
+
unit.kind = "interpreted";
|
|
69
|
+
unit.cause = "dynamic";
|
|
70
|
+
return unit;
|
|
71
|
+
}
|
|
72
|
+
if (typeof behavior.lower !== "function") {
|
|
73
|
+
unit.kind = "interpreted";
|
|
74
|
+
unit.cause = "unlowerable";
|
|
75
|
+
return unit;
|
|
76
|
+
}
|
|
77
|
+
// A coverage consumer (unevaluated*) declares `consumes` AND an
|
|
78
|
+
// evaluated-coverage fact; a keyword consuming other dependency data
|
|
79
|
+
// (then/else reading if's outcome) needs no channel.
|
|
80
|
+
if ((facts.consumes?.length ?? 0) > 0 &&
|
|
81
|
+
(facts.evaluatesNames !== undefined ||
|
|
82
|
+
facts.evaluatesIndexes !== undefined)) {
|
|
83
|
+
consumerPresent = true;
|
|
84
|
+
}
|
|
85
|
+
for (const rx of facts.regexes ?? [])
|
|
86
|
+
patterns.add(rx);
|
|
87
|
+
for (const fmt of facts.formats ?? [])
|
|
88
|
+
formats.add(fmt);
|
|
89
|
+
present.push({ name: entry.name, facts });
|
|
90
|
+
}
|
|
91
|
+
// Unknown keywords: annotations, elided in flag mode.
|
|
92
|
+
// Consumer licensing (D9a): a consumer lowers only when its coverage
|
|
93
|
+
// kind is statically known — from this object's own contributors plus,
|
|
94
|
+
// transitively, unconditional asserting in-place applications (allOf
|
|
95
|
+
// conjuncts, $ref chains). Anything runtime-conditional (anyOf/oneOf/
|
|
96
|
+
// if/dependentSchemas branches, if's condition, dynamic references,
|
|
97
|
+
// cycles) makes that coverage kind dynamic and the node interpreted.
|
|
98
|
+
if (consumerPresent) {
|
|
99
|
+
if (options.output === "list") {
|
|
100
|
+
// See PlanOptions.output: list mode NEVER static-licenses. Static
|
|
101
|
+
// coverage models only the parent-SUCCESS path (the M6.6 finding) —
|
|
102
|
+
// a FAILING contributor's dropped annotations make the interpreter
|
|
103
|
+
// emit additional unevaluated* errors, verdict-invisible but list-
|
|
104
|
+
// visible. Runtime tracking reproduces that by construction: the
|
|
105
|
+
// failed application's channel span truncates and the consumer sweep
|
|
106
|
+
// then covers less. So every list consumer is tracked; the unit stays
|
|
107
|
+
// static and CONTINUES to edge resolution (its tracked body needs
|
|
108
|
+
// planned in-place edges to thread the channel), and the region
|
|
109
|
+
// fixpoint below threads its in-place closure.
|
|
110
|
+
unit.tracking = true;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Flag mode: static-coverage licensing is sound (verdict-only), so a
|
|
114
|
+
// consumer whose coverage kind is statically known keeps the fast path.
|
|
115
|
+
const halves = coverageHalves(registry, ref, new Set(), true);
|
|
116
|
+
const needsNames = present.some((k) => (k.facts.consumes?.length ?? 0) > 0 && k.facts.evaluatesNames);
|
|
117
|
+
const needsIndexes = present.some((k) => (k.facts.consumes?.length ?? 0) > 0 && k.facts.evaluatesIndexes);
|
|
118
|
+
if ((needsNames && halves.name === null) ||
|
|
119
|
+
(needsIndexes && halves.index === null)) {
|
|
120
|
+
// Static licensing failed. FLAG mode compiles the consumer anyway,
|
|
121
|
+
// with runtime evaluated-set tracking (coverage stays null); the
|
|
122
|
+
// unit stays static and CONTINUES to edge resolution / child
|
|
123
|
+
// planning like any static unit — its tracked body needs planned
|
|
124
|
+
// in-place edges to thread the coverage channel through. The region
|
|
125
|
+
// membership of those edges is computed after the walk (below).
|
|
126
|
+
unit.tracking = true;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
unit.coverage = {
|
|
130
|
+
names: halves.name ? [...halves.name.names] : [],
|
|
131
|
+
patterns: halves.name ? halves.name.patterns : [],
|
|
132
|
+
coversAllNames: halves.name?.all ?? false,
|
|
133
|
+
prefixCount: halves.index?.prefix ?? 0,
|
|
134
|
+
coversAllIndexes: halves.index?.all ?? false,
|
|
135
|
+
};
|
|
136
|
+
for (const pat of unit.coverage.patterns)
|
|
137
|
+
patterns.add(pat);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Resolve application edges; plan children.
|
|
142
|
+
for (const { name, facts } of present) {
|
|
143
|
+
for (const app of facts.applications ?? []) {
|
|
144
|
+
let target;
|
|
145
|
+
try {
|
|
146
|
+
if (app.ref !== undefined) {
|
|
147
|
+
target = registry.resolveRef(app.ref, ref.baseUri);
|
|
148
|
+
}
|
|
149
|
+
else if (app.sibling !== undefined) {
|
|
150
|
+
target = registry.child(ref, [app.sibling, ...app.path]);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
target = registry.child(ref, [name, ...app.path]);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (err instanceof UnresolvableRefError) {
|
|
158
|
+
// Lazy-failure parity: the interpreter throws only when the
|
|
159
|
+
// reference is actually followed, so the whole node falls back.
|
|
160
|
+
unit.kind = "interpreted";
|
|
161
|
+
unit.cause = "unlowerable";
|
|
162
|
+
unit.edges = [];
|
|
163
|
+
return unit;
|
|
164
|
+
}
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
const targetKey = unitKey(target);
|
|
168
|
+
if (app.mode === "inPlace") {
|
|
169
|
+
if (inPlaceChain.includes(targetKey) || targetKey === key) {
|
|
170
|
+
// In-place cycle: same-cursor re-entry. The interpreter's
|
|
171
|
+
// seen-set gives exact InfiniteLoopError parity.
|
|
172
|
+
const t = units.get(targetKey) ?? plan(target, [...inPlaceChain, key]);
|
|
173
|
+
t.kind = "interpreted";
|
|
174
|
+
t.cause = "cycle";
|
|
175
|
+
t.edges = [];
|
|
176
|
+
unit.edges.push({ keyword: name, app, targetKey });
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
plan(target, [...inPlaceChain, key]);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
plan(target, []);
|
|
183
|
+
}
|
|
184
|
+
unit.edges.push({ keyword: name, app, targetKey });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return unit;
|
|
188
|
+
};
|
|
189
|
+
const root = plan(rootRef, []);
|
|
190
|
+
// Coverage-region fixpoint (COMPILED-CONSUMERS.md phase B). A tracked
|
|
191
|
+
// consumer's coverage comes from its own producers AND, recursively, its
|
|
192
|
+
// asserting in-place applications; each static unit in that in-place closure
|
|
193
|
+
// must report its post-success coverage through the runtime channel, so it
|
|
194
|
+
// gets the second (channel-threaded) calling convention (inRegion). Seeded
|
|
195
|
+
// by tracked units, propagated through in-place edges of static units.
|
|
196
|
+
//
|
|
197
|
+
// Exception — a nested tracked consumer reached inside the closure: threading
|
|
198
|
+
// one unit's channel through another's consumer is a v1 non-goal, so it is
|
|
199
|
+
// re-classified interpreted and becomes an ISLAND. The parent then trampolines
|
|
200
|
+
// it (fragCov) and folds its harvested root coverage — no nested channel.
|
|
201
|
+
// Islands and already-interpreted targets reached by region edges stay
|
|
202
|
+
// interpreted. A consumer-bearing STATIC-coverage unit stays a normal region
|
|
203
|
+
// member (its produces flow like any producer's).
|
|
204
|
+
const regionStack = [...units.values()]
|
|
205
|
+
.filter((u) => u.tracking)
|
|
206
|
+
.map((u) => u.key);
|
|
207
|
+
while (regionStack.length > 0) {
|
|
208
|
+
const u = units.get(regionStack.pop());
|
|
209
|
+
if (u.kind !== "static")
|
|
210
|
+
continue; // a seed islanded by another seed
|
|
211
|
+
for (const edge of u.edges) {
|
|
212
|
+
if (edge.app.mode !== "inPlace")
|
|
213
|
+
continue;
|
|
214
|
+
const m = units.get(edge.targetKey);
|
|
215
|
+
if (m.kind !== "static")
|
|
216
|
+
continue; // island target: stays interpreted
|
|
217
|
+
// Boolean subschemas contribute no coverage and fold to a literal at the
|
|
218
|
+
// call site — never a channel-threaded member.
|
|
219
|
+
if (typeof m.ref.node === "boolean")
|
|
220
|
+
continue;
|
|
221
|
+
if (m.tracking) {
|
|
222
|
+
// Nested tracked consumer: island it (v1 nested-channel simplification).
|
|
223
|
+
m.kind = "interpreted";
|
|
224
|
+
m.cause = "unlowerable";
|
|
225
|
+
m.edges = [];
|
|
226
|
+
delete m.tracking;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (!m.inRegion) {
|
|
230
|
+
m.inRegion = true;
|
|
231
|
+
regionStack.push(m.key);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// reachesInterpreted fixpoint over the edge graph.
|
|
236
|
+
let changed = true;
|
|
237
|
+
while (changed) {
|
|
238
|
+
changed = false;
|
|
239
|
+
for (const unit of units.values()) {
|
|
240
|
+
if (unit.kind === "interpreted" || unit.reachesInterpreted)
|
|
241
|
+
continue;
|
|
242
|
+
for (const edge of unit.edges) {
|
|
243
|
+
const t = units.get(edge.targetKey);
|
|
244
|
+
if (t.kind === "interpreted" || t.reachesInterpreted) {
|
|
245
|
+
unit.reachesInterpreted = true;
|
|
246
|
+
changed = true;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
for (const unit of units.values()) {
|
|
253
|
+
if (unit.kind !== "static")
|
|
254
|
+
continue;
|
|
255
|
+
for (const edge of unit.edges)
|
|
256
|
+
units.get(edge.targetKey).useCount++;
|
|
257
|
+
}
|
|
258
|
+
const targets = [...units.values()].filter((u) => u.kind === "interpreted");
|
|
259
|
+
return {
|
|
260
|
+
rootKey: root.key,
|
|
261
|
+
units,
|
|
262
|
+
patterns: [...patterns],
|
|
263
|
+
formats: [...formats],
|
|
264
|
+
targets,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The evaluated-coverage a schema node contributes at its own cursor (D9a),
|
|
269
|
+
* including — transitively — unconditional asserting in-place applications.
|
|
270
|
+
* `excludeConsumers` is true only for the licensing node itself: a
|
|
271
|
+
* consumer's own coverage fact describes the state AFTER it runs. Inside
|
|
272
|
+
* transitive targets, consumer facts count (post-success contribution).
|
|
273
|
+
* Facts come from analyze() and are valid regardless of which tier
|
|
274
|
+
* evaluates the target, so contribution is independent of compilability.
|
|
275
|
+
*/
|
|
276
|
+
function coverageHalves(registry, ref, visiting, excludeConsumers) {
|
|
277
|
+
const key = unitKey(ref);
|
|
278
|
+
if (visiting.has(key))
|
|
279
|
+
return { name: null, index: null }; // cycle
|
|
280
|
+
visiting.add(key);
|
|
281
|
+
try {
|
|
282
|
+
const node = ref.node;
|
|
283
|
+
if (typeof node === "boolean") {
|
|
284
|
+
// Contributes nothing; `false` fails the parent, making coverage moot.
|
|
285
|
+
return {
|
|
286
|
+
name: { names: new Set(), patterns: [], all: false },
|
|
287
|
+
index: { prefix: 0, all: false },
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (!isObj(node))
|
|
291
|
+
return { name: null, index: null };
|
|
292
|
+
// Coverage comes from analyze() facts alone, dialect-agnostic (D1) —
|
|
293
|
+
// the keyword-author contract is that evaluates* facts are complete.
|
|
294
|
+
const dialect = registry.dialectFor(ref.baseUri);
|
|
295
|
+
// Same $ref-only reading as buildPlan's refOnly (engine.ts:397): a
|
|
296
|
+
// draft-07/06 sibling contributes nothing when $ref is present.
|
|
297
|
+
const refOnly = dialect.refIgnoresSiblings && Object.hasOwn(node, "$ref");
|
|
298
|
+
const acc = {
|
|
299
|
+
name: { names: new Set(), patterns: [], all: false },
|
|
300
|
+
index: { prefix: 0, all: false },
|
|
301
|
+
};
|
|
302
|
+
const fold = (h) => {
|
|
303
|
+
if (acc.name && h.name) {
|
|
304
|
+
for (const n of h.name.names)
|
|
305
|
+
acc.name.names.add(n);
|
|
306
|
+
acc.name.patterns.push(...h.name.patterns);
|
|
307
|
+
acc.name.all ||= h.name.all;
|
|
308
|
+
}
|
|
309
|
+
else
|
|
310
|
+
acc.name = null;
|
|
311
|
+
if (acc.index && h.index) {
|
|
312
|
+
acc.index.prefix = Math.max(acc.index.prefix, h.index.prefix);
|
|
313
|
+
acc.index.all ||= h.index.all;
|
|
314
|
+
}
|
|
315
|
+
else
|
|
316
|
+
acc.index = null;
|
|
317
|
+
};
|
|
318
|
+
for (const entry of dialect.ordered) {
|
|
319
|
+
if (refOnly && entry.name !== "$ref")
|
|
320
|
+
continue;
|
|
321
|
+
if (!Object.hasOwn(node, entry.name))
|
|
322
|
+
continue;
|
|
323
|
+
const value = node[entry.name];
|
|
324
|
+
const facts = entry.behavior.analyze?.(value, { schema: node }) ?? {};
|
|
325
|
+
if (facts.dynamicScopeSensitive)
|
|
326
|
+
return { name: null, index: null };
|
|
327
|
+
const isConsumer = (facts.consumes?.length ?? 0) > 0;
|
|
328
|
+
if (!(excludeConsumers && isConsumer)) {
|
|
329
|
+
if (acc.name && facts.evaluatesNames) {
|
|
330
|
+
const c = facts.evaluatesNames;
|
|
331
|
+
if (c.kind === "dynamic")
|
|
332
|
+
acc.name = null;
|
|
333
|
+
else if (c.kind === "all")
|
|
334
|
+
acc.name.all = true;
|
|
335
|
+
else if (c.kind === "names")
|
|
336
|
+
for (const n of c.names)
|
|
337
|
+
acc.name.names.add(n);
|
|
338
|
+
else
|
|
339
|
+
acc.name.patterns.push(...c.patterns);
|
|
340
|
+
}
|
|
341
|
+
if (acc.index && facts.evaluatesIndexes) {
|
|
342
|
+
const c = facts.evaluatesIndexes;
|
|
343
|
+
if (c.kind === "dynamic")
|
|
344
|
+
acc.index = null;
|
|
345
|
+
else if (c.kind === "prefix")
|
|
346
|
+
acc.index.prefix = Math.max(acc.index.prefix, c.count);
|
|
347
|
+
// allFrom's start is bounded by this node's own prefix
|
|
348
|
+
// contribution (items starts after sibling prefixItems), so the
|
|
349
|
+
// per-node union covers everything.
|
|
350
|
+
else
|
|
351
|
+
acc.index.all = true;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
for (const app of facts.applications ?? []) {
|
|
355
|
+
if (app.mode !== "inPlace")
|
|
356
|
+
continue; // child cursors: no contribution here
|
|
357
|
+
if (app.inverted)
|
|
358
|
+
continue; // never survives the parent-success path
|
|
359
|
+
if (app.conditional || !app.asserts) {
|
|
360
|
+
// Runtime-conditional contribution (branching, or if's condition
|
|
361
|
+
// merging only on its own success): statically unknowable.
|
|
362
|
+
return { name: null, index: null };
|
|
363
|
+
}
|
|
364
|
+
let target;
|
|
365
|
+
try {
|
|
366
|
+
target =
|
|
367
|
+
app.ref !== undefined
|
|
368
|
+
? registry.resolveRef(app.ref, ref.baseUri)
|
|
369
|
+
: registry.child(ref, [app.sibling ?? entry.name, ...app.path]);
|
|
370
|
+
}
|
|
371
|
+
catch {
|
|
372
|
+
return { name: null, index: null };
|
|
373
|
+
}
|
|
374
|
+
fold(coverageHalves(registry, target, visiting, false));
|
|
375
|
+
}
|
|
376
|
+
if (!acc.name && !acc.index)
|
|
377
|
+
return acc; // both dynamic already
|
|
378
|
+
}
|
|
379
|
+
return acc;
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
visiting.delete(key);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=plan.js.map
|