@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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/emit.d.ts +52 -0
  4. package/dist/emit.d.ts.map +1 -0
  5. package/dist/emit.js +166 -0
  6. package/dist/emit.js.map +1 -0
  7. package/dist/explain.d.ts +28 -0
  8. package/dist/explain.d.ts.map +1 -0
  9. package/dist/explain.js +42 -0
  10. package/dist/explain.js.map +1 -0
  11. package/dist/index.d.ts +132 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +169 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/plan.d.ts +83 -0
  16. package/dist/plan.d.ts.map +1 -0
  17. package/dist/plan.js +385 -0
  18. package/dist/plan.js.map +1 -0
  19. package/dist/runtime-compile.d.ts +30 -0
  20. package/dist/runtime-compile.d.ts.map +1 -0
  21. package/dist/runtime-compile.js +29 -0
  22. package/dist/runtime-compile.js.map +1 -0
  23. package/dist/runtime.d.ts +157 -0
  24. package/dist/runtime.d.ts.map +1 -0
  25. package/dist/runtime.js +336 -0
  26. package/dist/runtime.js.map +1 -0
  27. package/dist/serialize/apply.d.ts +28 -0
  28. package/dist/serialize/apply.d.ts.map +1 -0
  29. package/dist/serialize/apply.js +211 -0
  30. package/dist/serialize/apply.js.map +1 -0
  31. package/dist/serialize/context.d.ts +143 -0
  32. package/dist/serialize/context.d.ts.map +1 -0
  33. package/dist/serialize/context.js +93 -0
  34. package/dist/serialize/context.js.map +1 -0
  35. package/dist/serialize/expressions.d.ts +7 -0
  36. package/dist/serialize/expressions.d.ts.map +1 -0
  37. package/dist/serialize/expressions.js +177 -0
  38. package/dist/serialize/expressions.js.map +1 -0
  39. package/dist/serialize/guards.d.ts +7 -0
  40. package/dist/serialize/guards.d.ts.map +1 -0
  41. package/dist/serialize/guards.js +19 -0
  42. package/dist/serialize/guards.js.map +1 -0
  43. package/dist/serialize/index.d.ts +7 -0
  44. package/dist/serialize/index.d.ts.map +1 -0
  45. package/dist/serialize/index.js +266 -0
  46. package/dist/serialize/index.js.map +1 -0
  47. package/dist/serialize/keywords.d.ts +32 -0
  48. package/dist/serialize/keywords.d.ts.map +1 -0
  49. package/dist/serialize/keywords.js +131 -0
  50. package/dist/serialize/keywords.js.map +1 -0
  51. package/dist/serialize/messages.d.ts +36 -0
  52. package/dist/serialize/messages.d.ts.map +1 -0
  53. package/dist/serialize/messages.js +100 -0
  54. package/dist/serialize/messages.js.map +1 -0
  55. package/dist/serialize/names.d.ts +47 -0
  56. package/dist/serialize/names.d.ts.map +1 -0
  57. package/dist/serialize/names.js +115 -0
  58. package/dist/serialize/names.js.map +1 -0
  59. package/dist/serialize/spans.d.ts +34 -0
  60. package/dist/serialize/spans.d.ts.map +1 -0
  61. package/dist/serialize/spans.js +61 -0
  62. package/dist/serialize/spans.js.map +1 -0
  63. package/dist/serialize/statements.d.ts +16 -0
  64. package/dist/serialize/statements.d.ts.map +1 -0
  65. package/dist/serialize/statements.js +396 -0
  66. package/dist/serialize/statements.js.map +1 -0
  67. package/dist/serialize/unit.d.ts +8 -0
  68. package/dist/serialize/unit.d.ts.map +1 -0
  69. package/dist/serialize/unit.js +107 -0
  70. package/dist/serialize/unit.js.map +1 -0
  71. package/dist/standalone.d.ts +24 -0
  72. package/dist/standalone.d.ts.map +1 -0
  73. package/dist/standalone.js +166 -0
  74. package/dist/standalone.js.map +1 -0
  75. package/package.json +38 -0
  76. package/src/emit.ts +196 -0
  77. package/src/explain.ts +67 -0
  78. package/src/index.ts +366 -0
  79. package/src/plan.ts +496 -0
  80. package/src/runtime-compile.ts +90 -0
  81. package/src/runtime.ts +599 -0
  82. package/src/serialize/apply.ts +251 -0
  83. package/src/serialize/context.ts +161 -0
  84. package/src/serialize/expressions.ts +202 -0
  85. package/src/serialize/guards.ts +20 -0
  86. package/src/serialize/index.ts +432 -0
  87. package/src/serialize/keywords.ts +144 -0
  88. package/src/serialize/messages.ts +120 -0
  89. package/src/serialize/names.ts +136 -0
  90. package/src/serialize/spans.ts +88 -0
  91. package/src/serialize/statements.ts +531 -0
  92. package/src/serialize/unit.ts +118 -0
  93. package/src/standalone.ts +187 -0
package/src/index.ts ADDED
@@ -0,0 +1,366 @@
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
+
7
+ import {
8
+ DEFAULT_MAX_DEPTH,
9
+ OutputOptionsError,
10
+ assembleResult,
11
+ renderBasic,
12
+ resolveOutputDemand,
13
+ type AnnotationSelection,
14
+ type AnnotationUnit,
15
+ type BasicOutputDocument,
16
+ type Engine,
17
+ type ErrorUnit,
18
+ type EvaluateOptions,
19
+ type JsonValue,
20
+ type Result,
21
+ type ResultFor,
22
+ } from "@json-schema-engine/core";
23
+ import { buildPlan, type CompilationPlan } from "./plan.js";
24
+ import { serializePlan } from "./serialize/index.js";
25
+ import { finishTrace, makeRuntime } from "./runtime.js";
26
+ import {
27
+ instantiate,
28
+ instantiateList,
29
+ instantiateListAnn,
30
+ instantiateTrace,
31
+ type CompiledValidate,
32
+ } from "./runtime-compile.js";
33
+
34
+ export type { CompilationPlan, PlannedUnit, FallbackCause } from "./plan.js";
35
+ export { buildPlan } from "./plan.js";
36
+ export { explainCompilation } from "./explain.js";
37
+ export type { CompilationExplanation } from "./explain.js";
38
+ export { serializePlan } from "./serialize/index.js";
39
+ export { emitStandalone, StandaloneUnsupportedError } from "./standalone.js";
40
+ export type { StandaloneOptions } from "./standalone.js";
41
+
42
+ /** Options for {@link compileValidator}. */
43
+ export interface CompileOptions {
44
+ /** depth bound shared between compiled nesting and fragments; default core's */
45
+ maxDepth?: number;
46
+ /**
47
+ * Disable the D9 optimizations (inlining, plain-data fast paths). Exists
48
+ * so the differential fuzzer referees both configurations; not a
49
+ * user-facing tuning knob.
50
+ */
51
+ conservative?: boolean;
52
+ }
53
+
54
+ /** Options for {@link compileList}. */
55
+ export interface ListCompileOptions extends CompileOptions {
56
+ /**
57
+ * Include `keyword`, `vocabulary`, and structured `params` on each error
58
+ * unit, matching the interpreter's `errorParams` option on `list` output
59
+ * (D13).
60
+ */
61
+ errorParams?: boolean;
62
+ /**
63
+ * Which annotations to collect, matching the interpreter's `annotations`
64
+ * option: `true` or a selection makes `evaluateList` return an
65
+ * `annotations` array on valid instances and `basic()` render the Basic
66
+ * document's annotation side. A selection's allow/deny lists are
67
+ * specialized into the artifact at compile time; its `keep` predicate runs
68
+ * at evaluation.
69
+ */
70
+ annotations?: boolean | AnnotationSelection;
71
+ }
72
+
73
+ /** A compiled list-mode result: interpreter-exact flat error units, plus annotations when collected. */
74
+ export interface CompiledListResult {
75
+ valid: boolean;
76
+ errors: ErrorUnit[];
77
+ /**
78
+ * Present on valid instances when compiled with `annotations` (absent on
79
+ * invalid ones, matching `Engine.evaluate`'s valid-only annotation
80
+ * contract); always absent otherwise.
81
+ */
82
+ annotations?: AnnotationUnit[];
83
+ }
84
+
85
+ /** A compiled list-mode artifact (see {@link compileList}). */
86
+ export interface CompiledListArtifact {
87
+ /** evaluate with full error collection ({@link CompiledListResult}) */
88
+ evaluateList(instance: JsonValue): CompiledListResult;
89
+ /**
90
+ * The same result as the Basic document (IETF draft-03 §13.4.2), matching
91
+ * `Engine.evaluate(uri, x, { output: "basic" }).outputDocument`: the flat
92
+ * `errors` array on invalid instances; on valid instances compiled with
93
+ * `annotations`, the selected annotations, present only when non-empty.
94
+ */
95
+ basic(instance: JsonValue): BasicOutputDocument;
96
+ plan: CompilationPlan;
97
+ source: string;
98
+ }
99
+
100
+ /** A compiled artifact: the validator plus its plan and source (inspection/tests). */
101
+ export interface CompiledArtifact {
102
+ validate: CompiledValidate;
103
+ plan: CompilationPlan;
104
+ source: string;
105
+ }
106
+
107
+ /**
108
+ * Options for {@link compileEvaluator}: the flat surface's controls
109
+ * (`errorParams`, the annotation selection) and the level, fixed when the
110
+ * evaluator is compiled.
111
+ */
112
+ export interface EvaluatorCompileOptions extends ListCompileOptions {
113
+ /**
114
+ * Retain irrelevant records (draft-03 §12.2) so the artifact can serve
115
+ * the verbose level: `output: "verbose"`, and `list`/`hierarchical` with
116
+ * `verbose: true`. A retaining artifact serves the relevant level too;
117
+ * the retention costs a copy per dropped record and nothing on the
118
+ * emitted source, which is identical at both levels.
119
+ */
120
+ verbose?: boolean;
121
+ }
122
+
123
+ /**
124
+ * The render-time choices of {@link CompiledEvaluator.evaluate}: format,
125
+ * level, and trace. The controls fixed at compile time are rejected here
126
+ * with {@link OutputOptionsError} rather than ignored.
127
+ */
128
+ export type EvaluatorOptions = Pick<
129
+ EvaluateOptions,
130
+ "output" | "verbose" | "trace"
131
+ >;
132
+
133
+ /**
134
+ * A compiled evaluator (see {@link compileEvaluator}): every output format
135
+ * from one recorded application tree, with `Engine.evaluate`'s result for
136
+ * the same instance and options.
137
+ */
138
+ export interface CompiledEvaluator {
139
+ evaluate<O extends EvaluatorOptions>(
140
+ instance: JsonValue,
141
+ options?: O,
142
+ ): ResultFor<O>;
143
+ plan: CompilationPlan;
144
+ source: string;
145
+ }
146
+
147
+ /**
148
+ * Compile a registered schema into a flag-mode validator. The artifact
149
+ * binds to a snapshot of the engine's schema and dialect registries taken at
150
+ * compile time, plus the engine's pattern cache: schemas registered,
151
+ * re-registered, or given a new dialect later are invisible to it, and a
152
+ * reference unresolved at compile time stays unresolved for it (E1).
153
+ */
154
+ export function compileValidator(
155
+ engine: Engine,
156
+ schemaUri: string,
157
+ options: CompileOptions = {},
158
+ ): CompiledArtifact {
159
+ const plan = buildPlan(engine, schemaUri);
160
+ // Compilation is synchronous, so the plan (built from the live registry)
161
+ // and this snapshot see one state; the artifact's islands never see a
162
+ // later registration.
163
+ const registry = engine.registry.snapshot();
164
+ const source = serializePlan(plan, registry, {
165
+ flags: options.conservative
166
+ ? { inline: false, plainData: false }
167
+ : { inline: true, plainData: true },
168
+ });
169
+ const runtime = makeRuntime(
170
+ registry,
171
+ engine.patternCache,
172
+ plan.patterns,
173
+ options.maxDepth ?? DEFAULT_MAX_DEPTH,
174
+ false,
175
+ undefined,
176
+ engine.formats,
177
+ plan.formats,
178
+ );
179
+ const validate = instantiate(
180
+ source,
181
+ runtime,
182
+ plan.targets.map((t) => t.ref),
183
+ );
184
+ return {
185
+ validate: (instance: JsonValue) => validate(instance),
186
+ plan,
187
+ source,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Compile a registered schema into a list-output validator (D9e): flat,
193
+ * interpreter-exact error units — the same elements
194
+ * `Engine.evaluate(uri, x, { output: "list" }).errors` yields, in the same
195
+ * order (list artifacts never short-circuit; every branch runs, DESIGN §7).
196
+ * Error-unit objects materialize only on failure paths. Binds to registry
197
+ * snapshots exactly like {@link compileValidator}.
198
+ */
199
+ export function compileList(
200
+ engine: Engine,
201
+ schemaUri: string,
202
+ options: ListCompileOptions = {},
203
+ ): CompiledListArtifact {
204
+ const selection = options.annotations ?? false;
205
+ const collect = selection !== false;
206
+ const errorParams = options.errorParams ?? false;
207
+ const plan = buildPlan(engine, schemaUri, { output: "list" });
208
+ const registry = engine.registry.snapshot();
209
+ const source = serializePlan(plan, registry, {
210
+ flags: options.conservative
211
+ ? { inline: false, plainData: false }
212
+ : { inline: true, plainData: true },
213
+ output: "list",
214
+ listParams: errorParams,
215
+ annotate: collect ? { selection } : undefined,
216
+ });
217
+ const runtime = makeRuntime(
218
+ registry,
219
+ engine.patternCache,
220
+ plan.patterns,
221
+ options.maxDepth ?? DEFAULT_MAX_DEPTH,
222
+ errorParams,
223
+ collect ? { selection } : undefined,
224
+ engine.formats,
225
+ plan.formats,
226
+ );
227
+ const targets = plan.targets.map((t) => t.ref);
228
+ const root = registry.rootRef(schemaUri);
229
+ const rootLocation = `${root.baseUri}#${root.pointer}`;
230
+ const keep = typeof selection === "object" ? selection.keep : undefined;
231
+
232
+ if (!collect) {
233
+ const evaluateList = instantiateList<ErrorUnit>(source, runtime, targets);
234
+ return {
235
+ evaluateList,
236
+ basic: (instance) => {
237
+ const { valid, errors } = evaluateList(instance);
238
+ return renderBasic(valid, rootLocation, errors, []);
239
+ },
240
+ plan,
241
+ source,
242
+ };
243
+ }
244
+
245
+ // The emitted evaluator returns the raw (static-list-filtered) annotation
246
+ // array; the wrapper applies `keep` (over the native unit, as the
247
+ // interpreter does) and the valid-only presence rule, matching
248
+ // `Engine.evaluate`'s Result.annotations; `basic()` renders that same
249
+ // flat surface through core's renderer.
250
+ const rawEval = instantiateListAnn<ErrorUnit>(source, runtime, targets);
251
+ const evaluateList = (instance: JsonValue): CompiledListResult => {
252
+ const r = rawEval(instance);
253
+ if (!r.valid) return { valid: false, errors: r.errors };
254
+ const anns = keep ? r.annotations.filter(keep) : r.annotations;
255
+ return { valid: true, errors: r.errors, annotations: anns };
256
+ };
257
+ return {
258
+ evaluateList,
259
+ basic: (instance) => {
260
+ const r = evaluateList(instance);
261
+ return renderBasic(r.valid, rootLocation, r.errors, r.annotations ?? []);
262
+ },
263
+ plan,
264
+ source,
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Compile a registered schema into an evaluator that renders every output
270
+ * format: a list artifact that also records each application's node —
271
+ * locations, keyword verdicts, and the records it raised — into core's
272
+ * located tree, so the documents render through the same code as the
273
+ * interpreter's. Format, level, and `trace` are chosen per evaluation; the
274
+ * annotation selection, `errorParams`, and whether irrelevant records are
275
+ * retained (`verbose`) are fixed here (D5). Without retention the records
276
+ * are discarded at the cut and a verbose-level request throws
277
+ * {@link OutputOptionsError}. Binds to registry snapshots exactly like
278
+ * {@link compileValidator}.
279
+ */
280
+ export function compileEvaluator(
281
+ engine: Engine,
282
+ schemaUri: string,
283
+ options: EvaluatorCompileOptions = {},
284
+ ): CompiledEvaluator {
285
+ const selection = options.annotations ?? false;
286
+ const collect = selection !== false;
287
+ const errorParams = options.errorParams ?? false;
288
+ const retain = options.verbose ?? false;
289
+ const plan = buildPlan(engine, schemaUri, { output: "list" });
290
+ const registry = engine.registry.snapshot();
291
+ const source = serializePlan(plan, registry, {
292
+ flags: options.conservative
293
+ ? { inline: false, plainData: false }
294
+ : { inline: true, plainData: true },
295
+ output: "list",
296
+ listParams: errorParams,
297
+ annotate: collect ? { selection } : undefined,
298
+ trace: true,
299
+ });
300
+ const runtime = makeRuntime(
301
+ registry,
302
+ engine.patternCache,
303
+ plan.patterns,
304
+ options.maxDepth ?? DEFAULT_MAX_DEPTH,
305
+ errorParams,
306
+ collect ? { selection } : undefined,
307
+ engine.formats,
308
+ plan.formats,
309
+ true,
310
+ retain,
311
+ );
312
+ const run = instantiateTrace(
313
+ source,
314
+ runtime,
315
+ plan.targets.map((t) => t.ref),
316
+ );
317
+ const root = registry.rootRef(schemaUri);
318
+ const rootLocation = `${root.baseUri}#${root.pointer}`;
319
+ const keep = typeof selection === "object" ? selection.keep : undefined;
320
+
321
+ const evaluate = (
322
+ instance: JsonValue,
323
+ evaluateOptions: EvaluatorOptions = {},
324
+ ): Result => {
325
+ // The controls fixed at compile time are rejected, not ignored (ADR
326
+ // 0003: every combination is supported or refused).
327
+ for (const fixed of ["annotations", "errorParams", "positions"]) {
328
+ if (fixed in evaluateOptions) {
329
+ throw new OutputOptionsError(
330
+ `'${fixed}' is fixed when the evaluator is compiled`,
331
+ );
332
+ }
333
+ }
334
+ // The compiled selection stands in for the option; `flag` is admitted
335
+ // whatever the selection, since it renders no records.
336
+ const demand = {
337
+ ...resolveOutputDemand({
338
+ output: evaluateOptions.output,
339
+ verbose: evaluateOptions.verbose,
340
+ trace: evaluateOptions.trace,
341
+ }),
342
+ annotations: selection,
343
+ };
344
+ if (demand.verbose && !retain) {
345
+ throw new OutputOptionsError(
346
+ "the verbose level needs an evaluator compiled with verbose: true",
347
+ );
348
+ }
349
+ const st = run(instance);
350
+ if (demand.format === "flag") return { valid: st.valid };
351
+ const { units, root: node } = finishTrace(st, keep, demand.verbose);
352
+ return assembleResult(
353
+ demand,
354
+ st.valid,
355
+ units,
356
+ node,
357
+ rootLocation,
358
+ evaluateOptions.trace === true,
359
+ );
360
+ };
361
+ return {
362
+ evaluate: evaluate as CompiledEvaluator["evaluate"],
363
+ plan,
364
+ source,
365
+ };
366
+ }