@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
@@ -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
+ }