@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
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// Standalone source emission (D10 build-time mode): a self-contained ES
|
|
2
|
+
// module with zero imports and zero code generation — deployable under a
|
|
3
|
+
// strict CSP (`node --disallow-code-generation-from-strings`, workers,
|
|
4
|
+
// serverless cold start).
|
|
5
|
+
//
|
|
6
|
+
// Scope (M6.5 design call, DESIGN §7): fully static plans only. A schema
|
|
7
|
+
// with dynamic islands or fallback units needs the interpreter at runtime,
|
|
8
|
+
// and the interpreter IS the CSP-safe tier (D10) — under CSP, evaluate such
|
|
9
|
+
// schemas with @json-schema-engine/core directly; where code generation is allowed, use
|
|
10
|
+
// the runtime mode. Emitting a core-importing hybrid module buys nothing
|
|
11
|
+
// over those two paths.
|
|
12
|
+
//
|
|
13
|
+
// The preamble duplicates core's helper semantics by necessity (a
|
|
14
|
+
// self-contained module cannot import them). Drift is guarded twice:
|
|
15
|
+
// standalone.test.ts compares every helper against its core original over a
|
|
16
|
+
// vector corpus, and the CSP check re-runs the official suite through
|
|
17
|
+
// emitted modules.
|
|
18
|
+
|
|
19
|
+
import { DEFAULT_MAX_DEPTH, type Engine } from "@json-schema-engine/core";
|
|
20
|
+
import { buildPlan } from "./plan.js";
|
|
21
|
+
import { serializePlan } from "./serialize/index.js";
|
|
22
|
+
|
|
23
|
+
/** Thrown when a plan needs the interpreter at runtime (islands/fallbacks). */
|
|
24
|
+
export class StandaloneUnsupportedError extends Error {}
|
|
25
|
+
|
|
26
|
+
// Helper preamble: compiler-controlled structural code (never
|
|
27
|
+
// schema-derived), semantics mirrored 1:1 from @json-schema-engine/core — see the drift
|
|
28
|
+
// guards above. h_rx mirrors core's schemaRegExp (u-flag with fallback).
|
|
29
|
+
/**
|
|
30
|
+
* The standalone helper preamble, exported so standalone.test.ts can compare
|
|
31
|
+
* each helper against its core original over a vector corpus (the drift
|
|
32
|
+
* guard the header describes). Not part of the public API surface.
|
|
33
|
+
*/
|
|
34
|
+
export const STANDALONE_PREAMBLE = `// Generated by @json-schema-engine/compiler (standalone mode). Self-contained; no imports.
|
|
35
|
+
const h_obj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
36
|
+
const h_int = (v) => typeof v === "number" && Number.isInteger(v);
|
|
37
|
+
const h_hop = Object.prototype.hasOwnProperty;
|
|
38
|
+
const h_s0 = [];
|
|
39
|
+
// Mirrors core's MaxDepthExceededError, which the interpreter and every
|
|
40
|
+
// runtime-compiled artifact throw for this bound. Self-contained emission
|
|
41
|
+
// cannot share core's class object, so \`instanceof\` against it is false
|
|
42
|
+
// here by construction; \`constructor.name\` is the discriminator a
|
|
43
|
+
// standalone consumer has. Throwing RangeError instead would be worse than
|
|
44
|
+
// imprecise: core converts a native stack RangeError INTO this class so a
|
|
45
|
+
// depth failure is never uncatchable by type, and reusing RangeError for
|
|
46
|
+
// the bound makes "schema too deep" and "blew the native stack"
|
|
47
|
+
// indistinguishable.
|
|
48
|
+
class MaxDepthExceededError extends Error {}
|
|
49
|
+
const h_rx = (source) => {
|
|
50
|
+
try {
|
|
51
|
+
return new RegExp(source, "u");
|
|
52
|
+
} catch {
|
|
53
|
+
return new RegExp(source);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const h_eq = (a, b) => {
|
|
57
|
+
if (a === b) return true;
|
|
58
|
+
if (Array.isArray(a)) {
|
|
59
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
60
|
+
for (let i = 0; i < a.length; i++) if (!h_eq(a[i], b[i])) return false;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (h_obj(a)) {
|
|
64
|
+
if (!h_obj(b)) return false;
|
|
65
|
+
const keys = Object.keys(a);
|
|
66
|
+
if (keys.length !== Object.keys(b).length) return false;
|
|
67
|
+
for (const k of keys) {
|
|
68
|
+
if (!h_hop.call(b, k) || !h_eq(a[k], b[k])) return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
};
|
|
74
|
+
const h_ck = (v) => {
|
|
75
|
+
if (v === null || typeof v !== "object") return JSON.stringify(v);
|
|
76
|
+
if (Array.isArray(v)) return "[" + v.map(h_ck).join(",") + "]";
|
|
77
|
+
const keys = Object.keys(v).sort();
|
|
78
|
+
return (
|
|
79
|
+
"{" +
|
|
80
|
+
keys.map((k) => JSON.stringify(k) + ":" + h_ck(v[k])).join(",") +
|
|
81
|
+
"}"
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
const h_dup = (items) => {
|
|
85
|
+
const seen = new Map();
|
|
86
|
+
for (let i = 0; i < items.length; i++) {
|
|
87
|
+
const key = h_ck(items[i]);
|
|
88
|
+
const bucket = seen.get(key);
|
|
89
|
+
if (bucket === undefined) {
|
|
90
|
+
seen.set(key, [i]);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
for (const j of bucket) if (h_eq(items[j], items[i])) return true;
|
|
94
|
+
bucket.push(i);
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
};
|
|
98
|
+
const h_cpl = (s) => {
|
|
99
|
+
let n = 0;
|
|
100
|
+
for (let i = 0; i < s.length; i++) {
|
|
101
|
+
n++;
|
|
102
|
+
const c = s.charCodeAt(i);
|
|
103
|
+
if (c >= 0xd800 && c <= 0xdbff) i++;
|
|
104
|
+
}
|
|
105
|
+
return n;
|
|
106
|
+
};
|
|
107
|
+
const h_esc = (s) => s.replace(/~/g, "~0").replace(/\\//g, "~1");
|
|
108
|
+
const h_digits = (n) => {
|
|
109
|
+
if (!Number.isFinite(n)) return 0;
|
|
110
|
+
const s = Math.abs(n).toString();
|
|
111
|
+
const eIndex = s.indexOf("e");
|
|
112
|
+
if (eIndex !== -1) {
|
|
113
|
+
const mantissa = s.slice(0, eIndex);
|
|
114
|
+
const exponent = Number(s.slice(eIndex + 1));
|
|
115
|
+
const dot = mantissa.indexOf(".");
|
|
116
|
+
const mantissaDigits = dot === -1 ? 0 : mantissa.length - dot - 1;
|
|
117
|
+
return Math.max(0, mantissaDigits - exponent);
|
|
118
|
+
}
|
|
119
|
+
const dot = s.indexOf(".");
|
|
120
|
+
return dot === -1 ? 0 : s.length - dot - 1;
|
|
121
|
+
};
|
|
122
|
+
const h_mof = (instance, divisor) => {
|
|
123
|
+
const scale = 10 ** Math.max(h_digits(instance), h_digits(divisor));
|
|
124
|
+
const scaledInstance = instance * scale;
|
|
125
|
+
const scaledDivisor = divisor * scale;
|
|
126
|
+
if (Number.isFinite(scaledInstance) && Number.isFinite(scaledDivisor)) {
|
|
127
|
+
return Math.round(scaledInstance) % Math.round(scaledDivisor) === 0;
|
|
128
|
+
}
|
|
129
|
+
const quotient = instance / divisor;
|
|
130
|
+
return Number.isFinite(quotient) && Number.isInteger(quotient);
|
|
131
|
+
};
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
/** Options for {@link emitStandalone}. */
|
|
135
|
+
export interface StandaloneOptions {
|
|
136
|
+
/** depth bound baked into the module; defaults to core's DEFAULT_MAX_DEPTH */
|
|
137
|
+
maxDepth?: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Emit a self-contained ES module validating against one registered,
|
|
142
|
+
* fully-static schema. The module's default export is
|
|
143
|
+
* `validate(instance): boolean`.
|
|
144
|
+
* @throws StandaloneUnsupportedError when the plan contains interpreted
|
|
145
|
+
* units — use the interpreter (CSP) or runtime compilation (no CSP).
|
|
146
|
+
*/
|
|
147
|
+
export function emitStandalone(
|
|
148
|
+
engine: Engine,
|
|
149
|
+
schemaUri: string,
|
|
150
|
+
options: StandaloneOptions = {},
|
|
151
|
+
): string {
|
|
152
|
+
const plan = buildPlan(engine, schemaUri);
|
|
153
|
+
if (plan.targets.length > 0) {
|
|
154
|
+
const causes = [...new Set(plan.targets.map((t) => t.cause))].join(", ");
|
|
155
|
+
throw new StandaloneUnsupportedError(
|
|
156
|
+
`schema '${schemaUri}' needs the interpreter at runtime (${causes}); ` +
|
|
157
|
+
"standalone emission covers fully static schemas only",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
// Format assertions resolve through the engine's format table (predicates
|
|
161
|
+
// like IDNA that a zero-import module cannot duplicate); a format-bearing
|
|
162
|
+
// schema stays on the interpreter (CSP) or runtime compilation (no CSP).
|
|
163
|
+
if (plan.formats.length > 0) {
|
|
164
|
+
throw new StandaloneUnsupportedError(
|
|
165
|
+
`schema '${schemaUri}' asserts formats (${plan.formats.join(", ")}); ` +
|
|
166
|
+
"standalone emission cannot carry the engine's format predicates",
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
// A tracked consumer's runtime coverage channel binds the harvest/fold
|
|
170
|
+
// helpers the standalone preamble deliberately omits (phase B is runtime
|
|
171
|
+
// mode only); the interpreter is the CSP-safe tier for these schemas.
|
|
172
|
+
if ([...plan.units.values()].some((u) => u.tracking)) {
|
|
173
|
+
throw new StandaloneUnsupportedError(
|
|
174
|
+
`schema '${schemaUri}' has a dynamic-coverage consumer needing runtime ` +
|
|
175
|
+
"tracking; standalone emission covers static-coverage schemas only",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
179
|
+
const depthPreamble =
|
|
180
|
+
`const h_maxd = ${String(maxDepth)};\n` +
|
|
181
|
+
`const h_deep = () => { throw new MaxDepthExceededError("compiled evaluation exceeds maxDepth (${String(maxDepth)})"); };\n`;
|
|
182
|
+
return (
|
|
183
|
+
STANDALONE_PREAMBLE +
|
|
184
|
+
depthPreamble +
|
|
185
|
+
serializePlan(plan, engine.registry, { mode: "standalone" })
|
|
186
|
+
);
|
|
187
|
+
}
|