@typed-sql/compiler 1.0.0 → 2.0.0-rc.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 (35) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +28 -5
  3. package/dist/packages/compiler/src/compatibility.d.ts +66 -0
  4. package/dist/packages/compiler/src/compatibility.d.ts.map +1 -0
  5. package/dist/packages/compiler/src/compatibility.js +786 -0
  6. package/dist/packages/compiler/src/compatibility.js.map +1 -0
  7. package/dist/packages/compiler/src/compiler.d.ts +16 -1
  8. package/dist/packages/compiler/src/compiler.d.ts.map +1 -1
  9. package/dist/packages/compiler/src/compiler.js +67 -4
  10. package/dist/packages/compiler/src/compiler.js.map +1 -1
  11. package/dist/packages/compiler/src/index.d.ts +13 -3
  12. package/dist/packages/compiler/src/index.d.ts.map +1 -1
  13. package/dist/packages/compiler/src/index.js +6 -1
  14. package/dist/packages/compiler/src/index.js.map +1 -1
  15. package/dist/packages/compiler/src/manifest.d.ts +505 -0
  16. package/dist/packages/compiler/src/manifest.d.ts.map +1 -0
  17. package/dist/packages/compiler/src/manifest.js +628 -0
  18. package/dist/packages/compiler/src/manifest.js.map +1 -0
  19. package/dist/packages/compiler/src/plans.d.ts +88 -0
  20. package/dist/packages/compiler/src/plans.d.ts.map +1 -0
  21. package/dist/packages/compiler/src/plans.js +459 -0
  22. package/dist/packages/compiler/src/plans.js.map +1 -0
  23. package/dist/packages/compiler/src/project.d.ts +8 -0
  24. package/dist/packages/compiler/src/project.d.ts.map +1 -0
  25. package/dist/packages/compiler/src/project.js +62 -0
  26. package/dist/packages/compiler/src/project.js.map +1 -0
  27. package/dist/packages/compiler/src/scanner.d.ts +6 -0
  28. package/dist/packages/compiler/src/scanner.d.ts.map +1 -1
  29. package/dist/packages/compiler/src/scanner.js +46 -0
  30. package/dist/packages/compiler/src/scanner.js.map +1 -1
  31. package/dist/packages/compiler/src/verification.d.ts +106 -0
  32. package/dist/packages/compiler/src/verification.d.ts.map +1 -0
  33. package/dist/packages/compiler/src/verification.js +516 -0
  34. package/dist/packages/compiler/src/verification.js.map +1 -0
  35. package/package.json +4 -3
@@ -0,0 +1,628 @@
1
+ import { createHash } from "node:crypto";
2
+ import { isAbsolute, relative, resolve, sep } from "node:path";
3
+ import { calculateSchemaHash, calculateTypePolicyHash } from "@typed-sql/schema";
4
+ import { compileSource } from "./compiler.js";
5
+ import { extractDynamicQueries, extractStaticQueries } from "./scanner.js";
6
+ export const QUERY_MANIFEST_FORMAT_VERSION = 1;
7
+ export const QUERY_FINGERPRINT_ALGORITHM = "typed-sql-v1";
8
+ const sha256 = (value) => createHash("sha256").update(value).digest("hex");
9
+ const compareText = (left, right) => (left < right ? -1 : left > right ? 1 : 0);
10
+ function portableRelative(rootDir, file) {
11
+ const root = resolve(rootDir);
12
+ const path = relative(root, isAbsolute(file) ? resolve(file) : resolve(root, file))
13
+ .split(sep)
14
+ .join("/");
15
+ if (path.length === 0 || isAbsolute(path))
16
+ throw new TypeError(`Cannot make ${file} relative to ${rootDir}`);
17
+ return path;
18
+ }
19
+ function isPortableRelativePath(path) {
20
+ return (path.length > 0 &&
21
+ !path.startsWith("/") &&
22
+ !path.startsWith("\\") &&
23
+ !/^[A-Za-z]:[\\/]/u.test(path) &&
24
+ !path.includes("\0"));
25
+ }
26
+ function columnDescription(column) {
27
+ return {
28
+ name: column.name,
29
+ tsType: column.tsType,
30
+ nullable: column.nullable,
31
+ ...(column.databaseType === undefined ? {} : { databaseType: column.databaseType }),
32
+ };
33
+ }
34
+ function parameterDescription(parameter) {
35
+ return {
36
+ index: parameter.index,
37
+ tsType: parameter.tsType,
38
+ nullable: parameter.nullable,
39
+ ...(parameter.databaseType === undefined ? {} : { databaseType: parameter.databaseType }),
40
+ };
41
+ }
42
+ function choiceDescriptions(choices) {
43
+ return Object.fromEntries(Object.entries(choices)
44
+ .map(([condition, enabled]) => [`sha256:${sha256(condition)}`, enabled])
45
+ .sort(([left], [right]) => compareText(left, right)));
46
+ }
47
+ function manifestDiagnostic(diagnostic) {
48
+ return { code: diagnostic.code, severity: diagnostic.severity, range: diagnostic.range };
49
+ }
50
+ function manifestSemantics(semantics) {
51
+ const evidence = (items) => items.map((item) => ({ kind: item.kind, range: item.range }));
52
+ const fact = (value) => ({
53
+ value: value.value,
54
+ evidence: evidence(value.evidence),
55
+ });
56
+ return {
57
+ version: semantics.version,
58
+ operation: fact(semantics.operation),
59
+ dependencies: semantics.dependencies,
60
+ cardinality: { ...semantics.cardinality, evidence: evidence(semantics.cardinality.evidence) },
61
+ volatility: fact(semantics.volatility),
62
+ locking: fact(semantics.locking),
63
+ connectionAffinity: fact(semantics.connectionAffinity),
64
+ capabilities: semantics.capabilities,
65
+ };
66
+ }
67
+ function entryId(dialect, location, identity) {
68
+ return `sha256:${sha256(`${dialect}\0${location.file}\0${location.range.start}\0${identity}`)}`;
69
+ }
70
+ function inRange(diagnostic, range) {
71
+ return diagnostic.range.start >= range.start && diagnostic.range.start < range.end;
72
+ }
73
+ function sourceEntries(file, source, options) {
74
+ const compilation = compileSource({
75
+ source,
76
+ dialect: options.dialect,
77
+ schema: options.schema,
78
+ ...(options.typePolicy === undefined ? {} : { typePolicy: options.typePolicy }),
79
+ ...(options.maxStructuralVariants === undefined ? {} : { maxStructuralVariants: options.maxStructuralVariants }),
80
+ });
81
+ const extracted = extractStaticQueries(source, (index) => options.dialect.placeholder(index), [
82
+ options.dialect.sqlModule,
83
+ ]);
84
+ const compiledByStart = new Map(compilation.queries.map((query) => [query.query.range.start, query]));
85
+ const entries = extracted.map((query) => {
86
+ const location = { file, range: query.range };
87
+ const compiled = compiledByStart.get(query.range.start);
88
+ if (compiled === undefined) {
89
+ const diagnostics = compilation.diagnostics.filter((diagnostic) => inRange(diagnostic, query.range));
90
+ return {
91
+ id: entryId(options.dialect.id, location, `unresolved\0${query.sql}`),
92
+ source: location,
93
+ status: "unresolved",
94
+ reason: "diagnostic",
95
+ diagnostics: diagnostics.map(manifestDiagnostic),
96
+ };
97
+ }
98
+ return {
99
+ id: entryId(options.dialect.id, location, compiled.fingerprint),
100
+ source: location,
101
+ status: "resolved",
102
+ structural: compiled.structural === true,
103
+ fingerprint: compiled.fingerprint,
104
+ rowType: compiled.rowType,
105
+ parameterType: compiled.parameterType,
106
+ diagnostics: compilation.diagnostics
107
+ .filter((diagnostic) => inRange(diagnostic, query.range))
108
+ .map(manifestDiagnostic),
109
+ variants: compiled.variants.map((variant) => ({
110
+ fingerprint: variant.fingerprint,
111
+ choices: choiceDescriptions(variant.choices),
112
+ rowType: variant.rowType,
113
+ parameterType: variant.parameterType,
114
+ columns: variant.columns.map(columnDescription),
115
+ parameters: variant.parameters.map(parameterDescription),
116
+ semantics: manifestSemantics(variant.semantics),
117
+ })),
118
+ semantics: manifestSemantics(compiled.semantics),
119
+ };
120
+ });
121
+ for (const dynamic of extractDynamicQueries(source, [options.dialect.sqlModule])) {
122
+ const location = { file, range: dynamic.range };
123
+ entries.push({
124
+ id: entryId(options.dialect.id, location, "dynamic"),
125
+ source: location,
126
+ status: "unresolved",
127
+ reason: "dynamic",
128
+ diagnostics: [
129
+ {
130
+ code: "TSQ005",
131
+ severity: "info",
132
+ range: dynamic.range,
133
+ },
134
+ ],
135
+ });
136
+ }
137
+ return entries.sort((left, right) => left.source.range.start - right.source.range.start);
138
+ }
139
+ function compatiblePrevious(previous, compilerVersion, dialect, schemaHash, typePolicyHash) {
140
+ return (previous?.formatVersion === QUERY_MANIFEST_FORMAT_VERSION &&
141
+ previous.compilerVersion === compilerVersion &&
142
+ previous.fingerprintAlgorithm === QUERY_FINGERPRINT_ALGORITHM &&
143
+ previous.dialect.id === dialect.id &&
144
+ previous.dialect.grammarVersion === dialect.grammarVersion &&
145
+ previous.schemaHash === schemaHash &&
146
+ previous.typePolicyHash === typePolicyHash);
147
+ }
148
+ export function buildQueryManifest(options) {
149
+ if (options.compilerVersion.length === 0)
150
+ throw new TypeError("compilerVersion must be a non-empty string");
151
+ if (options.schema.dialect !== options.dialect.id) {
152
+ throw new TypeError(`Dialect ${options.dialect.id} cannot build a manifest for ${options.schema.dialect}`);
153
+ }
154
+ const schemaHash = calculateSchemaHash(options.schema);
155
+ const typePolicyHash = calculateTypePolicyHash(options.typePolicy ?? {});
156
+ const inputs = options.sources
157
+ .map((input) => ({ input, file: portableRelative(options.rootDir, input.file), hash: sha256(input.source) }))
158
+ .sort((left, right) => compareText(left.file, right.file));
159
+ const duplicate = inputs.find((input, index) => input.file === inputs[index - 1]?.file);
160
+ if (duplicate !== undefined)
161
+ throw new TypeError(`Duplicate manifest source ${duplicate.file}`);
162
+ const mayReuse = compatiblePrevious(options.previous, options.compilerVersion, options.dialect, schemaHash, typePolicyHash);
163
+ const previousSources = new Map(options.previous?.sources.map((source) => [source.file, source.hash]) ?? []);
164
+ const previousEntries = new Map();
165
+ for (const entry of options.previous?.queries ?? []) {
166
+ const entries = previousEntries.get(entry.source.file);
167
+ previousEntries.set(entry.source.file, entries === undefined ? [entry] : [...entries, entry]);
168
+ }
169
+ let analyzedFiles = 0;
170
+ let reusedFiles = 0;
171
+ const queries = inputs.flatMap(({ input, file, hash }) => {
172
+ if (mayReuse && previousSources.get(file) === hash) {
173
+ reusedFiles += 1;
174
+ return previousEntries.get(file) ?? [];
175
+ }
176
+ analyzedFiles += 1;
177
+ return sourceEntries(file, input.source, options);
178
+ });
179
+ queries.sort((left, right) => compareText(left.source.file, right.source.file) || left.source.range.start - right.source.range.start);
180
+ const projects = [
181
+ ...new Set((options.projects ?? []).map((project) => portableRelative(options.rootDir, project))),
182
+ ].sort();
183
+ const manifest = {
184
+ formatVersion: QUERY_MANIFEST_FORMAT_VERSION,
185
+ compilerVersion: options.compilerVersion,
186
+ fingerprintAlgorithm: QUERY_FINGERPRINT_ALGORITHM,
187
+ dialect: { id: options.dialect.id, grammarVersion: options.dialect.grammarVersion },
188
+ schemaHash,
189
+ typePolicyHash,
190
+ projects,
191
+ sources: inputs.map(({ file, hash }) => ({ file, hash })),
192
+ queries,
193
+ };
194
+ return {
195
+ manifest,
196
+ stats: {
197
+ analyzedFiles,
198
+ reusedFiles,
199
+ resolvedQueries: queries.filter((query) => query.status === "resolved").length,
200
+ unresolvedQueries: queries.filter((query) => query.status === "unresolved").length,
201
+ },
202
+ };
203
+ }
204
+ function canonicalize(value) {
205
+ if (Array.isArray(value))
206
+ return value.map(canonicalize);
207
+ if (typeof value !== "object" || value === null)
208
+ return value;
209
+ return Object.fromEntries(Object.entries(value)
210
+ .sort(([left], [right]) => compareText(left, right))
211
+ .map(([key, item]) => [key, canonicalize(item)]));
212
+ }
213
+ export function serializeQueryManifest(manifest) {
214
+ return `${JSON.stringify(canonicalize(manifest), null, 2)}\n`;
215
+ }
216
+ function record(value) {
217
+ return typeof value === "object" && value !== null && !Array.isArray(value);
218
+ }
219
+ function assertString(value, description) {
220
+ if (typeof value !== "string")
221
+ throw new TypeError(`${description} must be a string`);
222
+ }
223
+ function assertRange(value, description) {
224
+ if (!record(value))
225
+ throw new TypeError(`${description} must be a source range`);
226
+ for (const property of ["start", "end", "line", "column"]) {
227
+ const minimum = property === "line" || property === "column" ? 1 : 0;
228
+ if (!Number.isSafeInteger(value[property]) || value[property] < minimum) {
229
+ throw new TypeError(`${description}.${property} must be a safe integer greater than or equal to ${minimum}`);
230
+ }
231
+ }
232
+ if (value.end < value.start)
233
+ throw new TypeError(`${description}.end must not precede start`);
234
+ }
235
+ function assertDiagnostics(value, description) {
236
+ if (!Array.isArray(value))
237
+ throw new TypeError(`${description} must be an array`);
238
+ for (const diagnostic of value) {
239
+ if (!record(diagnostic))
240
+ throw new TypeError(`${description} entries must be objects`);
241
+ assertString(diagnostic.code, `${description} code`);
242
+ if (!["error", "warning", "info"].includes(diagnostic.severity)) {
243
+ throw new TypeError(`${description} severity is unsupported`);
244
+ }
245
+ assertRange(diagnostic.range, `${description} range`);
246
+ }
247
+ }
248
+ function assertEvidence(value, description) {
249
+ if (!Array.isArray(value))
250
+ throw new TypeError(`${description} must be an array`);
251
+ for (const evidence of value) {
252
+ if (!record(evidence) || !["syntax", "schema", "conservative"].includes(evidence.kind)) {
253
+ throw new TypeError(`${description} contains unsupported evidence`);
254
+ }
255
+ assertRange(evidence.range, `${description} range`);
256
+ }
257
+ }
258
+ function assertFact(value, allowed, description) {
259
+ if (!record(value) || typeof value.value !== "string" || !allowed.includes(value.value)) {
260
+ throw new TypeError(`${description} contains an unsupported value`);
261
+ }
262
+ assertEvidence(value.evidence, `${description} evidence`);
263
+ }
264
+ function assertSemantics(value, description) {
265
+ if (!record(value) || value.version !== 1)
266
+ throw new TypeError(`${description} must use semantics version 1`);
267
+ assertFact(value.operation, ["read", "write", "ddl", "transaction-control", "unknown"], `${description} operation`);
268
+ assertFact(value.volatility, ["immutable", "stable", "volatile", "unknown"], `${description} volatility`);
269
+ assertFact(value.locking, ["none", "row", "table", "unknown"], `${description} locking`);
270
+ assertFact(value.connectionAffinity, ["none", "transaction", "session", "unknown"], `${description} affinity`);
271
+ if (!record(value.cardinality) || (value.cardinality.minimum !== 0 && value.cardinality.minimum !== 1)) {
272
+ throw new TypeError(`${description} cardinality is invalid`);
273
+ }
274
+ if (![0, 1, "many", "unknown"].includes(value.cardinality.maximum)) {
275
+ throw new TypeError(`${description} cardinality maximum is invalid`);
276
+ }
277
+ assertEvidence(value.cardinality.evidence, `${description} cardinality evidence`);
278
+ if (!Array.isArray(value.dependencies))
279
+ throw new TypeError(`${description} dependencies must be an array`);
280
+ for (const dependency of value.dependencies) {
281
+ if (!record(dependency))
282
+ throw new TypeError(`${description} dependencies must contain objects`);
283
+ for (const property of ["kind", "access", "name", "certainty"]) {
284
+ assertString(dependency[property], `${description} dependency ${property}`);
285
+ }
286
+ if (!["relation", "column", "function", "type", "sequence", "unknown"].includes(dependency.kind) ||
287
+ !["read", "write", "execute", "reference", "unknown"].includes(dependency.access) ||
288
+ !["resolved", "syntactic"].includes(dependency.certainty)) {
289
+ throw new TypeError(`${description} dependency classification is unsupported`);
290
+ }
291
+ if (dependency.schema !== undefined)
292
+ assertString(dependency.schema, `${description} dependency schema`);
293
+ if (dependency.parent !== undefined)
294
+ assertString(dependency.parent, `${description} dependency parent`);
295
+ assertRange(dependency.range, `${description} dependency range`);
296
+ }
297
+ if (!Array.isArray(value.capabilities) || value.capabilities.some((capability) => typeof capability !== "string")) {
298
+ throw new TypeError(`${description} capabilities must contain strings`);
299
+ }
300
+ }
301
+ function assertVariant(value, description) {
302
+ if (!record(value))
303
+ throw new TypeError(`${description} must be an object`);
304
+ assertString(value.fingerprint, `${description}.fingerprint`);
305
+ assertString(value.rowType, `${description}.rowType`);
306
+ assertString(value.parameterType, `${description}.parameterType`);
307
+ if (!/^sha256:[a-f\d]{64}$/u.test(value.fingerprint)) {
308
+ throw new TypeError(`${description}.fingerprint must use SHA-256`);
309
+ }
310
+ if (!record(value.choices) ||
311
+ Object.entries(value.choices).some(([choice, enabled]) => !/^sha256:[a-f\d]{64}$/u.test(choice) || typeof enabled !== "boolean")) {
312
+ throw new TypeError(`${description}.choices must contain booleans`);
313
+ }
314
+ if (!Array.isArray(value.columns) || !Array.isArray(value.parameters)) {
315
+ throw new TypeError(`${description} columns and parameters must be arrays`);
316
+ }
317
+ for (const column of value.columns) {
318
+ if (!record(column) || typeof column.nullable !== "boolean") {
319
+ throw new TypeError(`${description} columns are invalid`);
320
+ }
321
+ assertString(column.name, `${description} column name`);
322
+ assertString(column.tsType, `${description} column tsType`);
323
+ if (column.databaseType !== undefined)
324
+ assertString(column.databaseType, `${description} column databaseType`);
325
+ }
326
+ for (const parameter of value.parameters) {
327
+ if (!record(parameter) ||
328
+ !Number.isSafeInteger(parameter.index) ||
329
+ parameter.index < 1 ||
330
+ typeof parameter.nullable !== "boolean") {
331
+ throw new TypeError(`${description} parameters are invalid`);
332
+ }
333
+ assertString(parameter.tsType, `${description} parameter tsType`);
334
+ if (parameter.databaseType !== undefined) {
335
+ assertString(parameter.databaseType, `${description} parameter databaseType`);
336
+ }
337
+ }
338
+ assertSemantics(value.semantics, `${description} semantics`);
339
+ }
340
+ export function parseQueryManifest(value) {
341
+ if (!record(value))
342
+ throw new TypeError("Query manifest must be an object");
343
+ if (value.formatVersion !== QUERY_MANIFEST_FORMAT_VERSION) {
344
+ throw new TypeError(`Unsupported query manifest format ${String(value.formatVersion)}; this release supports format ${QUERY_MANIFEST_FORMAT_VERSION}`);
345
+ }
346
+ if (typeof value.compilerVersion !== "string" || value.compilerVersion.length === 0) {
347
+ throw new TypeError("Query manifest compilerVersion must be a non-empty string");
348
+ }
349
+ if (value.fingerprintAlgorithm !== QUERY_FINGERPRINT_ALGORITHM) {
350
+ throw new TypeError(`Unsupported query fingerprint algorithm ${String(value.fingerprintAlgorithm)}`);
351
+ }
352
+ if (!record(value.dialect) ||
353
+ typeof value.dialect.id !== "string" ||
354
+ value.dialect.id.length === 0 ||
355
+ typeof value.dialect.grammarVersion !== "string" ||
356
+ value.dialect.grammarVersion.length === 0) {
357
+ throw new TypeError("Query manifest dialect must contain id and grammarVersion");
358
+ }
359
+ if (typeof value.schemaHash !== "string" ||
360
+ !/^[a-f\d]{64}$/u.test(value.schemaHash) ||
361
+ typeof value.typePolicyHash !== "string" ||
362
+ !/^[a-f\d]{64}$/u.test(value.typePolicyHash)) {
363
+ throw new TypeError("Query manifest schemaHash and typePolicyHash must be strings");
364
+ }
365
+ if (!Array.isArray(value.projects) || !Array.isArray(value.sources) || !Array.isArray(value.queries)) {
366
+ throw new TypeError("Query manifest projects, sources, and queries must be arrays");
367
+ }
368
+ if (value.projects.some((project) => typeof project !== "string" || !isPortableRelativePath(project))) {
369
+ throw new TypeError("Query manifest projects must contain relative paths");
370
+ }
371
+ for (const source of value.sources) {
372
+ if (!record(source) ||
373
+ typeof source.file !== "string" ||
374
+ !isPortableRelativePath(source.file) ||
375
+ typeof source.hash !== "string" ||
376
+ !/^[a-f\d]{64}$/u.test(source.hash)) {
377
+ throw new TypeError("Query manifest sources must contain relative file paths and hashes");
378
+ }
379
+ }
380
+ for (const query of value.queries) {
381
+ if (!record(query) ||
382
+ typeof query.id !== "string" ||
383
+ !/^sha256:[a-f\d]{64}$/u.test(query.id) ||
384
+ !record(query.source)) {
385
+ throw new TypeError("Query manifest entries must contain id and source");
386
+ }
387
+ const location = query.source;
388
+ if (typeof location.file !== "string" || !isPortableRelativePath(location.file) || !record(location.range)) {
389
+ throw new TypeError("Query manifest entry sources must contain a relative file and range");
390
+ }
391
+ assertRange(location.range, "Query manifest source range");
392
+ if (query.status === "resolved") {
393
+ if (typeof query.fingerprint !== "string" ||
394
+ !/^sha256:[a-f\d]{64}$/u.test(query.fingerprint) ||
395
+ typeof query.rowType !== "string" ||
396
+ typeof query.parameterType !== "string" ||
397
+ typeof query.structural !== "boolean" ||
398
+ !Array.isArray(query.variants) ||
399
+ query.variants.length === 0) {
400
+ throw new TypeError("Resolved query manifest entries are incomplete");
401
+ }
402
+ assertDiagnostics(query.diagnostics, "Resolved query diagnostics");
403
+ for (const [index, variant] of query.variants.entries()) {
404
+ assertVariant(variant, `Query manifest variant ${index}`);
405
+ }
406
+ assertSemantics(query.semantics, "Resolved query semantics");
407
+ }
408
+ else if (query.status === "unresolved") {
409
+ if (query.reason !== "diagnostic" && query.reason !== "dynamic") {
410
+ throw new TypeError("Unresolved query manifest entries are incomplete");
411
+ }
412
+ assertDiagnostics(query.diagnostics, "Unresolved query diagnostics");
413
+ if (query.diagnostics.length === 0)
414
+ throw new TypeError("Unresolved query diagnostics cannot be empty");
415
+ }
416
+ else
417
+ throw new TypeError(`Unsupported query manifest entry status ${String(query.status)}`);
418
+ }
419
+ return value;
420
+ }
421
+ /** Machine-readable public contract. Readers accept unknown object properties within format v1. */
422
+ export const QUERY_MANIFEST_JSON_SCHEMA = Object.freeze({
423
+ $schema: "https://json-schema.org/draft/2020-12/schema",
424
+ $id: "urn:typed-sql:query-manifest:1",
425
+ title: "typed-sql query manifest",
426
+ type: "object",
427
+ $defs: {
428
+ relativePath: { type: "string", minLength: 1, pattern: "^(?!/|\\\\|[A-Za-z]:[\\\\/])(?!.*\\u0000).+$" },
429
+ hash: { type: "string", pattern: "^[a-f0-9]{64}$" },
430
+ fingerprint: { type: "string", pattern: "^sha256:[a-f0-9]{64}$" },
431
+ range: {
432
+ type: "object",
433
+ required: ["start", "end", "line", "column"],
434
+ properties: {
435
+ start: { type: "integer", minimum: 0 },
436
+ end: { type: "integer", minimum: 0 },
437
+ line: { type: "integer", minimum: 1 },
438
+ column: { type: "integer", minimum: 1 },
439
+ },
440
+ },
441
+ diagnostic: {
442
+ type: "object",
443
+ required: ["code", "range", "severity"],
444
+ properties: {
445
+ code: { type: "string" },
446
+ range: { $ref: "#/$defs/range" },
447
+ severity: { enum: ["error", "warning", "info"] },
448
+ },
449
+ },
450
+ evidence: {
451
+ type: "object",
452
+ required: ["kind", "range"],
453
+ properties: {
454
+ kind: { enum: ["syntax", "schema", "conservative"] },
455
+ range: { $ref: "#/$defs/range" },
456
+ },
457
+ },
458
+ fact: {
459
+ type: "object",
460
+ required: ["value", "evidence"],
461
+ properties: {
462
+ value: { type: "string" },
463
+ evidence: { type: "array", items: { $ref: "#/$defs/evidence" } },
464
+ },
465
+ },
466
+ dependency: {
467
+ type: "object",
468
+ required: ["kind", "access", "name", "certainty", "range"],
469
+ properties: {
470
+ kind: { enum: ["relation", "column", "function", "type", "sequence", "unknown"] },
471
+ access: { enum: ["read", "write", "execute", "reference", "unknown"] },
472
+ name: { type: "string" },
473
+ schema: { type: "string" },
474
+ parent: { type: "string" },
475
+ certainty: { enum: ["resolved", "syntactic"] },
476
+ range: { $ref: "#/$defs/range" },
477
+ },
478
+ },
479
+ semantics: {
480
+ type: "object",
481
+ required: [
482
+ "version",
483
+ "operation",
484
+ "dependencies",
485
+ "cardinality",
486
+ "volatility",
487
+ "locking",
488
+ "connectionAffinity",
489
+ "capabilities",
490
+ ],
491
+ properties: {
492
+ version: { const: 1 },
493
+ operation: { $ref: "#/$defs/fact" },
494
+ dependencies: { type: "array", items: { $ref: "#/$defs/dependency" } },
495
+ cardinality: {
496
+ type: "object",
497
+ required: ["minimum", "maximum", "evidence"],
498
+ properties: {
499
+ minimum: { enum: [0, 1] },
500
+ maximum: { enum: [0, 1, "many", "unknown"] },
501
+ evidence: { type: "array", items: { $ref: "#/$defs/evidence" } },
502
+ },
503
+ },
504
+ volatility: { $ref: "#/$defs/fact" },
505
+ locking: { $ref: "#/$defs/fact" },
506
+ connectionAffinity: { $ref: "#/$defs/fact" },
507
+ capabilities: { type: "array", items: { type: "string" } },
508
+ },
509
+ },
510
+ column: {
511
+ type: "object",
512
+ required: ["name", "tsType", "nullable"],
513
+ properties: {
514
+ name: { type: "string" },
515
+ tsType: { type: "string" },
516
+ nullable: { type: "boolean" },
517
+ databaseType: { type: "string" },
518
+ },
519
+ },
520
+ parameter: {
521
+ type: "object",
522
+ required: ["index", "tsType", "nullable"],
523
+ properties: {
524
+ index: { type: "integer", minimum: 1 },
525
+ tsType: { type: "string" },
526
+ nullable: { type: "boolean" },
527
+ databaseType: { type: "string" },
528
+ },
529
+ },
530
+ variant: {
531
+ type: "object",
532
+ required: ["fingerprint", "choices", "rowType", "parameterType", "columns", "parameters", "semantics"],
533
+ properties: {
534
+ fingerprint: { $ref: "#/$defs/fingerprint" },
535
+ choices: {
536
+ type: "object",
537
+ propertyNames: { $ref: "#/$defs/fingerprint" },
538
+ additionalProperties: { type: "boolean" },
539
+ },
540
+ rowType: { type: "string" },
541
+ parameterType: { type: "string" },
542
+ columns: { type: "array", items: { $ref: "#/$defs/column" } },
543
+ parameters: { type: "array", items: { $ref: "#/$defs/parameter" } },
544
+ semantics: { $ref: "#/$defs/semantics" },
545
+ },
546
+ },
547
+ location: {
548
+ type: "object",
549
+ required: ["file", "range"],
550
+ properties: { file: { $ref: "#/$defs/relativePath" }, range: { $ref: "#/$defs/range" } },
551
+ },
552
+ resolvedQuery: {
553
+ type: "object",
554
+ required: [
555
+ "id",
556
+ "source",
557
+ "status",
558
+ "structural",
559
+ "fingerprint",
560
+ "rowType",
561
+ "parameterType",
562
+ "diagnostics",
563
+ "variants",
564
+ "semantics",
565
+ ],
566
+ properties: {
567
+ id: { $ref: "#/$defs/fingerprint" },
568
+ source: { $ref: "#/$defs/location" },
569
+ status: { const: "resolved" },
570
+ structural: { type: "boolean" },
571
+ fingerprint: { $ref: "#/$defs/fingerprint" },
572
+ rowType: { type: "string" },
573
+ parameterType: { type: "string" },
574
+ diagnostics: { type: "array", items: { $ref: "#/$defs/diagnostic" } },
575
+ variants: { type: "array", minItems: 1, items: { $ref: "#/$defs/variant" } },
576
+ semantics: { $ref: "#/$defs/semantics" },
577
+ },
578
+ },
579
+ unresolvedQuery: {
580
+ type: "object",
581
+ required: ["id", "source", "status", "reason", "diagnostics"],
582
+ properties: {
583
+ id: { $ref: "#/$defs/fingerprint" },
584
+ source: { $ref: "#/$defs/location" },
585
+ status: { const: "unresolved" },
586
+ reason: { enum: ["diagnostic", "dynamic"] },
587
+ diagnostics: { type: "array", minItems: 1, items: { $ref: "#/$defs/diagnostic" } },
588
+ },
589
+ },
590
+ },
591
+ required: [
592
+ "formatVersion",
593
+ "compilerVersion",
594
+ "fingerprintAlgorithm",
595
+ "dialect",
596
+ "schemaHash",
597
+ "typePolicyHash",
598
+ "projects",
599
+ "sources",
600
+ "queries",
601
+ ],
602
+ properties: {
603
+ formatVersion: { const: QUERY_MANIFEST_FORMAT_VERSION },
604
+ compilerVersion: { type: "string", minLength: 1 },
605
+ fingerprintAlgorithm: { const: QUERY_FINGERPRINT_ALGORITHM },
606
+ dialect: {
607
+ type: "object",
608
+ required: ["id", "grammarVersion"],
609
+ properties: { id: { type: "string" }, grammarVersion: { type: "string" } },
610
+ },
611
+ schemaHash: { type: "string" },
612
+ typePolicyHash: { type: "string" },
613
+ projects: { type: "array", items: { $ref: "#/$defs/relativePath" } },
614
+ sources: {
615
+ type: "array",
616
+ items: {
617
+ type: "object",
618
+ required: ["file", "hash"],
619
+ properties: { file: { $ref: "#/$defs/relativePath" }, hash: { $ref: "#/$defs/hash" } },
620
+ },
621
+ },
622
+ queries: {
623
+ type: "array",
624
+ items: { oneOf: [{ $ref: "#/$defs/resolvedQuery" }, { $ref: "#/$defs/unresolvedQuery" }] },
625
+ },
626
+ },
627
+ });
628
+ //# sourceMappingURL=manifest.js.map