@sequenceholdings/orm 2.10.0 → 2.11.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/README.md +74 -4
- package/dist/authoring.cjs +796 -7
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +10 -1
- package/dist/authoring.d.ts +10 -1
- package/dist/authoring.js +796 -7
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +777 -6
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.js +777 -6
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +7697 -7686
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +7698 -7687
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +5 -10
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -13
- package/dist/client.d.ts +2 -13
- package/dist/client.js +6 -10
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +815 -100
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +5 -16
- package/dist/compiler.d.ts +5 -16
- package/dist/compiler.js +817 -99
- package/dist/compiler.js.map +1 -1
- package/dist/index.cjs +3611 -3287
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +577 -7
- package/dist/index.d.ts +577 -7
- package/dist/index.js +3600 -3285
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +784 -18
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +3 -6
- package/dist/ir.d.ts +3 -6
- package/dist/ir.js +784 -18
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +796 -31
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.js +796 -31
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +790 -28
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +1 -5
- package/dist/sdl.d.ts +1 -5
- package/dist/sdl.js +790 -28
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen-gql/server-fns.cjs +1 -1
package/dist/compiler.cjs
CHANGED
|
@@ -9,16 +9,21 @@ var pgParser = require('@supabase/pg-parser');
|
|
|
9
9
|
|
|
10
10
|
// src/constants.ts
|
|
11
11
|
var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
|
|
12
|
+
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
12
13
|
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
13
14
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
14
15
|
var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
|
|
15
16
|
var MAX_PERSISTED_READ_ROWS = 50001;
|
|
17
|
+
var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
16
18
|
var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
|
|
19
|
+
var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
|
|
17
20
|
var ORM_SCHEMA_PREFIX = "orm_";
|
|
18
21
|
var ormSchemaName = (namespace) => {
|
|
19
22
|
if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
|
|
20
23
|
return `${ORM_SCHEMA_PREFIX}${namespace}`;
|
|
21
24
|
};
|
|
25
|
+
var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
|
|
26
|
+
var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
|
|
22
27
|
var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
|
|
23
28
|
var CELL_PART_SUFFIX = {
|
|
24
29
|
at: "__at",
|
|
@@ -250,6 +255,748 @@ var Parser = class {
|
|
|
250
255
|
function compileCheckExpression(source, columns) {
|
|
251
256
|
return new Parser(tokenize(source), columns).parse();
|
|
252
257
|
}
|
|
258
|
+
|
|
259
|
+
// src/predicate/ast.ts
|
|
260
|
+
var COMPARE_OPS = ["=", "!=", "<", "<=", ">", ">="];
|
|
261
|
+
function walkPredicate(predicate, visit2) {
|
|
262
|
+
visit2(predicate);
|
|
263
|
+
if (predicate.kind === "and" || predicate.kind === "or") {
|
|
264
|
+
for (const operand of predicate.operands) walkPredicate(operand, visit2);
|
|
265
|
+
}
|
|
266
|
+
if (predicate.kind === "not") walkPredicate(predicate.operand, visit2);
|
|
267
|
+
}
|
|
268
|
+
function predicateOperands(predicate) {
|
|
269
|
+
const operands = [];
|
|
270
|
+
walkPredicate(predicate, (node) => {
|
|
271
|
+
if (node.kind === "compare") operands.push(node.left, node.right);
|
|
272
|
+
});
|
|
273
|
+
return operands;
|
|
274
|
+
}
|
|
275
|
+
function referencedColumns(predicate) {
|
|
276
|
+
const names = predicateOperands(predicate).flatMap(
|
|
277
|
+
(operand) => operand.kind === "column" ? [operand.name] : []
|
|
278
|
+
);
|
|
279
|
+
return [...new Set(names)];
|
|
280
|
+
}
|
|
281
|
+
var identifierSchema = zod.z.string().max(MAX_IDENTIFIER_LENGTH).regex(IDENTIFIER_PATTERN, "must be a lowercase identifier (a-z, 0-9, _)");
|
|
282
|
+
var namespaceSchema = zod.z.string().regex(NAMESPACE_PATTERN, "must be lowercase, start with a letter, and be at most 41 characters");
|
|
283
|
+
var tableReferenceSchema = zod.z.string().refine(
|
|
284
|
+
(value) => IDENTIFIER_PATTERN.test(value) || QUALIFIED_TABLE_PATTERN.test(value),
|
|
285
|
+
"must be a table name or a schema-qualified table reference"
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// src/schema/columns.ts
|
|
289
|
+
var COLUMN_TYPES = [
|
|
290
|
+
"uuid",
|
|
291
|
+
"text",
|
|
292
|
+
"integer",
|
|
293
|
+
"bigint",
|
|
294
|
+
"numeric",
|
|
295
|
+
"double",
|
|
296
|
+
"boolean",
|
|
297
|
+
"timestamptz",
|
|
298
|
+
"date",
|
|
299
|
+
"jsonb"
|
|
300
|
+
];
|
|
301
|
+
var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
|
|
302
|
+
var FUNCTION_DEFAULTS = {
|
|
303
|
+
"gen_random_uuid()": ["uuid"],
|
|
304
|
+
"now()": ["timestamptz", "date"]
|
|
305
|
+
};
|
|
306
|
+
var CAST_PATTERN = /^([a-z][a-z0-9_]*)::([a-z]+)$/;
|
|
307
|
+
function defaultIssue(value, type) {
|
|
308
|
+
if (typeof value === "boolean") return type === "boolean" ? null : `boolean default is invalid for type '${type}'`;
|
|
309
|
+
if (typeof value === "number") {
|
|
310
|
+
if (!["integer", "bigint", "numeric", "double"].includes(type)) {
|
|
311
|
+
return `numeric default is invalid for type '${type}'`;
|
|
312
|
+
}
|
|
313
|
+
if (["integer", "bigint"].includes(type)) {
|
|
314
|
+
if (!Number.isInteger(value)) return `default for type '${type}' must be an integer`;
|
|
315
|
+
if (!Number.isSafeInteger(value)) {
|
|
316
|
+
return `default for type '${type}' exceeds the safe integer range \u2014 YAML numbers lose precision past 2^53`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (type === "numeric" && Math.abs(value) > Number.MAX_SAFE_INTEGER) {
|
|
320
|
+
return "numeric default exceeds the safe float range \u2014 write it as a string (e.g. '9007199254740993') to keep exact precision";
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
const allowedTypes = FUNCTION_DEFAULTS[value];
|
|
325
|
+
if (allowedTypes) return allowedTypes.includes(type) ? null : `default '${value}' is invalid for type '${type}'`;
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
var columnSchema = zod.z.strictObject({
|
|
329
|
+
type: columnTypeSchema,
|
|
330
|
+
primary_key: zod.z.boolean().optional(),
|
|
331
|
+
nullable: zod.z.boolean().optional(),
|
|
332
|
+
unique: zod.z.boolean().optional(),
|
|
333
|
+
default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean()]).optional(),
|
|
334
|
+
description: zod.z.string().optional(),
|
|
335
|
+
renamed_from: identifierSchema.optional(),
|
|
336
|
+
cast: zod.z.string().optional(),
|
|
337
|
+
deprecated: zod.z.boolean().optional()
|
|
338
|
+
}).superRefine((column, ctx) => {
|
|
339
|
+
if (column.default !== void 0) {
|
|
340
|
+
const issue = defaultIssue(column.default, column.type);
|
|
341
|
+
if (issue) ctx.addIssue({ code: "custom", message: issue, path: ["default"] });
|
|
342
|
+
}
|
|
343
|
+
if (column.primary_key && column.nullable) {
|
|
344
|
+
ctx.addIssue({ code: "custom", message: "a primary key column cannot be nullable", path: ["nullable"] });
|
|
345
|
+
}
|
|
346
|
+
if (column.primary_key && column.deprecated) {
|
|
347
|
+
ctx.addIssue({ code: "custom", message: "a primary key column cannot be deprecated", path: ["deprecated"] });
|
|
348
|
+
}
|
|
349
|
+
if (column.cast && !CAST_PATTERN.test(column.cast)) {
|
|
350
|
+
ctx.addIssue({ code: "custom", message: "cast must have the shape '<column>::<type>'", path: ["cast"] });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
function castParts(cast) {
|
|
354
|
+
const match = CAST_PATTERN.exec(cast);
|
|
355
|
+
if (!match) return null;
|
|
356
|
+
return { column: match[1], type: match[2] };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/errors.ts
|
|
360
|
+
var OrmDefinitionError = class extends Error {
|
|
361
|
+
constructor(message, file) {
|
|
362
|
+
super(file ? `${file}: ${message}` : message);
|
|
363
|
+
this.file = file;
|
|
364
|
+
}
|
|
365
|
+
name = "OrmDefinitionError";
|
|
366
|
+
};
|
|
367
|
+
var PredicateParseError = class extends Error {
|
|
368
|
+
constructor(message, position) {
|
|
369
|
+
super(`${message} (at position ${position})`);
|
|
370
|
+
this.position = position;
|
|
371
|
+
}
|
|
372
|
+
name = "PredicateParseError";
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// src/predicate/parse.ts
|
|
376
|
+
var KEYWORDS = /* @__PURE__ */ new Set(["and", "or", "not", "true", "false", "null", "claim", "capability"]);
|
|
377
|
+
var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
|
|
378
|
+
var IDENTIFIER_START = /[a-z_]/;
|
|
379
|
+
var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
|
|
380
|
+
var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
|
|
381
|
+
function tokenize2(source) {
|
|
382
|
+
const tokens = [];
|
|
383
|
+
let position = 0;
|
|
384
|
+
while (position < source.length) {
|
|
385
|
+
const char = source[position];
|
|
386
|
+
if (/\s/.test(char)) {
|
|
387
|
+
position += 1;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (char === "(") {
|
|
391
|
+
tokens.push({ kind: "lparen", value: char, position });
|
|
392
|
+
position += 1;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (char === ")") {
|
|
396
|
+
tokens.push({ kind: "rparen", value: char, position });
|
|
397
|
+
position += 1;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
if (char === ",") {
|
|
401
|
+
tokens.push({ kind: "comma", value: char, position });
|
|
402
|
+
position += 1;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
if (char === "'") {
|
|
406
|
+
const { value, end } = readString(source, position);
|
|
407
|
+
tokens.push({ kind: "string", value, position });
|
|
408
|
+
position = end;
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const operator = OPERATOR_PATTERN.exec(source.slice(position));
|
|
412
|
+
if (operator) {
|
|
413
|
+
const value = operator[0] === "<>" ? "!=" : operator[0];
|
|
414
|
+
tokens.push({ kind: "operator", value, position });
|
|
415
|
+
position += operator[0].length;
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const number = NUMBER_PATTERN.exec(source.slice(position));
|
|
419
|
+
if (number && !IDENTIFIER_START.test(char)) {
|
|
420
|
+
tokens.push({ kind: "number", value: number[0], position });
|
|
421
|
+
position += number[0].length;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
if (IDENTIFIER_START.test(char)) {
|
|
425
|
+
let end = position;
|
|
426
|
+
while (end < source.length && IDENTIFIER_CHAR.test(source[end])) end += 1;
|
|
427
|
+
tokens.push({ kind: "identifier", value: source.slice(position, end), position });
|
|
428
|
+
position = end;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
throw new PredicateParseError(`unexpected character '${char}'`, position);
|
|
432
|
+
}
|
|
433
|
+
tokens.push({ kind: "end", value: "", position: source.length });
|
|
434
|
+
return tokens;
|
|
435
|
+
}
|
|
436
|
+
function readString(source, start) {
|
|
437
|
+
let value = "";
|
|
438
|
+
let position = start + 1;
|
|
439
|
+
while (position < source.length) {
|
|
440
|
+
const char = source[position];
|
|
441
|
+
if (char !== "'") {
|
|
442
|
+
value += char;
|
|
443
|
+
position += 1;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (source[position + 1] === "'") {
|
|
447
|
+
value += "'";
|
|
448
|
+
position += 2;
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
return { value, end: position + 1 };
|
|
452
|
+
}
|
|
453
|
+
throw new PredicateParseError("unterminated string literal", start);
|
|
454
|
+
}
|
|
455
|
+
var Parser2 = class {
|
|
456
|
+
constructor(tokens) {
|
|
457
|
+
this.tokens = tokens;
|
|
458
|
+
}
|
|
459
|
+
index = 0;
|
|
460
|
+
parse() {
|
|
461
|
+
const predicate = this.parseOr();
|
|
462
|
+
this.expect("end", "end of expression");
|
|
463
|
+
return predicate;
|
|
464
|
+
}
|
|
465
|
+
parseOr() {
|
|
466
|
+
const operands = [this.parseAnd()];
|
|
467
|
+
while (this.matchKeyword("or")) operands.push(this.parseAnd());
|
|
468
|
+
return operands.length === 1 ? operands[0] : { kind: "or", operands };
|
|
469
|
+
}
|
|
470
|
+
parseAnd() {
|
|
471
|
+
const operands = [this.parseNot()];
|
|
472
|
+
while (this.matchKeyword("and")) operands.push(this.parseNot());
|
|
473
|
+
return operands.length === 1 ? operands[0] : { kind: "and", operands };
|
|
474
|
+
}
|
|
475
|
+
parseNot() {
|
|
476
|
+
if (this.matchKeyword("not")) return { kind: "not", operand: this.parseNot() };
|
|
477
|
+
return this.parsePrimary();
|
|
478
|
+
}
|
|
479
|
+
parsePrimary() {
|
|
480
|
+
if (this.peek().kind === "lparen") {
|
|
481
|
+
this.advance();
|
|
482
|
+
const inner = this.parseOr();
|
|
483
|
+
this.expect("rparen", "')'");
|
|
484
|
+
return inner;
|
|
485
|
+
}
|
|
486
|
+
if (this.peekKeyword("capability")) {
|
|
487
|
+
const capability = this.parseCapability();
|
|
488
|
+
const next = this.peek();
|
|
489
|
+
if (next.kind === "operator") {
|
|
490
|
+
throw new PredicateParseError("capability() is boolean-valued and cannot appear in a comparison", next.position);
|
|
491
|
+
}
|
|
492
|
+
return capability;
|
|
493
|
+
}
|
|
494
|
+
return this.parseComparison();
|
|
495
|
+
}
|
|
496
|
+
parseCapability() {
|
|
497
|
+
this.advance();
|
|
498
|
+
const key = this.parseCallArgument("capability");
|
|
499
|
+
if (!CAPABILITY_KEY_PATTERN.test(key)) {
|
|
500
|
+
throw new PredicateParseError(`invalid capability key '${key}'`, this.peek().position);
|
|
501
|
+
}
|
|
502
|
+
return { kind: "capability", key };
|
|
503
|
+
}
|
|
504
|
+
parseComparison() {
|
|
505
|
+
const left = this.parseOperand();
|
|
506
|
+
const operator = this.expect("operator", "a comparison operator");
|
|
507
|
+
if (!COMPARE_OPS.includes(operator.value)) {
|
|
508
|
+
throw new PredicateParseError(`unsupported operator '${operator.value}'`, operator.position);
|
|
509
|
+
}
|
|
510
|
+
const right = this.parseOperand();
|
|
511
|
+
return { kind: "compare", op: operator.value, left, right };
|
|
512
|
+
}
|
|
513
|
+
parseOperand() {
|
|
514
|
+
const token = this.peek();
|
|
515
|
+
if (token.kind === "string") {
|
|
516
|
+
this.advance();
|
|
517
|
+
return { kind: "literal", value: token.value };
|
|
518
|
+
}
|
|
519
|
+
if (token.kind === "number") {
|
|
520
|
+
this.advance();
|
|
521
|
+
const value = Number(token.value);
|
|
522
|
+
if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
523
|
+
throw new PredicateParseError(
|
|
524
|
+
`numeric literal '${token.value}' exceeds safe integer precision \u2014 quote it as a string`,
|
|
525
|
+
token.position
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return { kind: "literal", value };
|
|
529
|
+
}
|
|
530
|
+
if (token.kind !== "identifier") {
|
|
531
|
+
throw new PredicateParseError(`expected an operand, got '${token.value || "end"}'`, token.position);
|
|
532
|
+
}
|
|
533
|
+
this.advance();
|
|
534
|
+
if (token.value === "true" || token.value === "false") {
|
|
535
|
+
return { kind: "literal", value: token.value === "true" };
|
|
536
|
+
}
|
|
537
|
+
if (token.value === "null") return { kind: "literal", value: null };
|
|
538
|
+
if (token.value === "claim") {
|
|
539
|
+
const key = this.parseCallArgument("claim");
|
|
540
|
+
if (!CLAIM_KEY_PATTERN.test(key)) {
|
|
541
|
+
throw new PredicateParseError(`invalid claim key '${key}'`, token.position);
|
|
542
|
+
}
|
|
543
|
+
return { kind: "claim", key };
|
|
544
|
+
}
|
|
545
|
+
if (token.value === "capability") {
|
|
546
|
+
throw new PredicateParseError("capability() is boolean-valued and cannot appear in a comparison", token.position);
|
|
547
|
+
}
|
|
548
|
+
if (KEYWORDS.has(token.value) || !IDENTIFIER_PATTERN.test(token.value)) {
|
|
549
|
+
throw new PredicateParseError(`invalid column reference '${token.value}'`, token.position);
|
|
550
|
+
}
|
|
551
|
+
return { kind: "column", name: token.value };
|
|
552
|
+
}
|
|
553
|
+
parseCallArgument(fn) {
|
|
554
|
+
this.expect("lparen", `'(' after ${fn}`);
|
|
555
|
+
const argument = this.expect("string", `a quoted key in ${fn}(...)`);
|
|
556
|
+
this.expect("rparen", `')' after ${fn} key`);
|
|
557
|
+
return argument.value;
|
|
558
|
+
}
|
|
559
|
+
peek() {
|
|
560
|
+
return this.tokens[this.index];
|
|
561
|
+
}
|
|
562
|
+
peekKeyword(keyword) {
|
|
563
|
+
const token = this.peek();
|
|
564
|
+
return token.kind === "identifier" && token.value === keyword;
|
|
565
|
+
}
|
|
566
|
+
matchKeyword(keyword) {
|
|
567
|
+
if (!this.peekKeyword(keyword)) return false;
|
|
568
|
+
this.advance();
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
advance() {
|
|
572
|
+
const token = this.peek();
|
|
573
|
+
if (token.kind !== "end") this.index += 1;
|
|
574
|
+
return token;
|
|
575
|
+
}
|
|
576
|
+
expect(kind, description) {
|
|
577
|
+
const token = this.peek();
|
|
578
|
+
if (token.kind !== kind) {
|
|
579
|
+
throw new PredicateParseError(`expected ${description}, got '${token.value || "end"}'`, token.position);
|
|
580
|
+
}
|
|
581
|
+
return this.advance();
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
function parsePredicate(source) {
|
|
585
|
+
const trimmed = source.trim();
|
|
586
|
+
if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
|
|
587
|
+
return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/schema/predicates.ts
|
|
591
|
+
var parsedPredicateSchema = zod.z.string().transform((source, ctx) => {
|
|
592
|
+
try {
|
|
593
|
+
return parsePredicate(source);
|
|
594
|
+
} catch (error) {
|
|
595
|
+
ctx.addIssue({ code: "custom", message: error instanceof Error ? error.message : String(error) });
|
|
596
|
+
return zod.z.NEVER;
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
// src/schema/actions.ts
|
|
601
|
+
var EXECUTES_VERB_PATTERN = /^\s*(insert|update|delete)\b/i;
|
|
602
|
+
var DOLLAR_QUOTE_PATTERN = /\$[a-zA-Z0-9_]*\$/;
|
|
603
|
+
var RETURNING_PATTERN = /\breturning\b/i;
|
|
604
|
+
var ESCAPE_STRING_PATTERN = /(?<![a-zA-Z0-9_])[Ee]'/;
|
|
605
|
+
var MASKED_CLAIM_CALL_PATTERN = /\b(?:claim|capability)\(\s*''\s*\)/g;
|
|
606
|
+
var BARE_CLAIM_CALL_PATTERN = /\b(?:claim|capability)\s*\(/;
|
|
607
|
+
var EXISTS_ENTRY_PATTERN = /^exists\(\s*'([^']+)'\s*,\s*([a-z][a-z0-9_]*)\s*\)$/;
|
|
608
|
+
var NAMED_PARAM_PATTERN = /(?<!:):([a-z][a-z0-9_]*)/g;
|
|
609
|
+
var QUOTED_STRING_PATTERN = /'(?:[^']|'')*'/g;
|
|
610
|
+
var SQL_COMMENT_PATTERN = /--|\/\*/;
|
|
611
|
+
var actionParamSchema = zod.z.strictObject({
|
|
612
|
+
type: columnTypeSchema,
|
|
613
|
+
optional: zod.z.boolean().optional(),
|
|
614
|
+
description: zod.z.string().optional()
|
|
615
|
+
});
|
|
616
|
+
var validatesEntrySchema = zod.z.string().transform((source, ctx) => {
|
|
617
|
+
const match = EXISTS_ENTRY_PATTERN.exec(source.trim());
|
|
618
|
+
if (!match) {
|
|
619
|
+
ctx.addIssue({ code: "custom", message: "validates entries must have the shape exists('<table>', <param>)" });
|
|
620
|
+
return zod.z.NEVER;
|
|
621
|
+
}
|
|
622
|
+
const table = tableReferenceSchema.safeParse(match[1]);
|
|
623
|
+
if (!table.success) {
|
|
624
|
+
ctx.addIssue({ code: "custom", message: `invalid table reference '${match[1]}'` });
|
|
625
|
+
return zod.z.NEVER;
|
|
626
|
+
}
|
|
627
|
+
return { source: source.trim(), table: table.data, param: match[2] };
|
|
628
|
+
});
|
|
629
|
+
var AUDIT_ACTIONS = ["create", "update", "delete"];
|
|
630
|
+
var auditSchema = zod.z.strictObject({
|
|
631
|
+
action: zod.z.enum(AUDIT_ACTIONS),
|
|
632
|
+
resource: identifierSchema.optional()
|
|
633
|
+
});
|
|
634
|
+
var actionSchema = zod.z.strictObject({
|
|
635
|
+
description: zod.z.string().optional(),
|
|
636
|
+
requires: parsedPredicateSchema.optional(),
|
|
637
|
+
params: zod.z.record(identifierSchema, actionParamSchema).optional(),
|
|
638
|
+
validates: zod.z.array(validatesEntrySchema).optional(),
|
|
639
|
+
// One statement, or an ordered list run atomically in the action function.
|
|
640
|
+
executes: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]),
|
|
641
|
+
// Declares the columns the action's RETURNING yields. Present → the
|
|
642
|
+
// function returns those rows (typed in the client); absent → the action
|
|
643
|
+
// reports rows_affected only. The column names/types must match the
|
|
644
|
+
// RETURNING list, which Postgres enforces at CREATE FUNCTION.
|
|
645
|
+
returns: zod.z.record(identifierSchema, columnTypeSchema).optional(),
|
|
646
|
+
audit: auditSchema.optional()
|
|
647
|
+
}).superRefine((action, ctx) => {
|
|
648
|
+
const params = Object.keys(action.params ?? {});
|
|
649
|
+
if (action.requires && referencedColumns(action.requires.ast).length > 0) {
|
|
650
|
+
ctx.addIssue({
|
|
651
|
+
code: "custom",
|
|
652
|
+
message: "requires may reference claims and capabilities only, not columns",
|
|
653
|
+
path: ["requires"]
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
for (const issue of executesIssues(action.executes, new Set(params), action.returns !== void 0)) {
|
|
657
|
+
ctx.addIssue({ code: "custom", message: issue, path: ["executes"] });
|
|
658
|
+
}
|
|
659
|
+
if (action.returns && Object.keys(action.returns).length === 0) {
|
|
660
|
+
ctx.addIssue({ code: "custom", message: "returns must declare at least one column", path: ["returns"] });
|
|
661
|
+
}
|
|
662
|
+
for (const [index, entry] of (action.validates ?? []).entries()) {
|
|
663
|
+
if (!params.includes(entry.param)) {
|
|
664
|
+
ctx.addIssue({
|
|
665
|
+
code: "custom",
|
|
666
|
+
message: `validates references undeclared param '${entry.param}'`,
|
|
667
|
+
path: ["validates", index]
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
function extractNamedParams(sql) {
|
|
673
|
+
const withoutStrings = sql.replace(QUOTED_STRING_PATTERN, "''");
|
|
674
|
+
const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
|
|
675
|
+
return [...new Set(names)];
|
|
676
|
+
}
|
|
677
|
+
function actionStatementSources(executes) {
|
|
678
|
+
return (Array.isArray(executes) ? executes : [executes]).map((statement) => statement.trim());
|
|
679
|
+
}
|
|
680
|
+
function executesIssues(executes, declaredParams, hasReturns) {
|
|
681
|
+
const issues = [];
|
|
682
|
+
const statements = actionStatementSources(executes);
|
|
683
|
+
statements.forEach((statement, index) => {
|
|
684
|
+
const withoutStrings = statement.replace(QUOTED_STRING_PATTERN, "''");
|
|
685
|
+
const at = statements.length > 1 ? ` (statement ${index + 1})` : "";
|
|
686
|
+
if (!EXECUTES_VERB_PATTERN.test(statement)) issues.push(`executes must be INSERT, UPDATE, or DELETE${at}`);
|
|
687
|
+
if (withoutStrings.replace(/;\s*$/, "").includes(";")) issues.push(`each executes entry must be a single statement${at}`);
|
|
688
|
+
if (SQL_COMMENT_PATTERN.test(withoutStrings)) issues.push(`executes must not contain SQL comments${at}`);
|
|
689
|
+
if (DOLLAR_QUOTE_PATTERN.test(withoutStrings)) issues.push(`executes must not use dollar-quoted strings${at}`);
|
|
690
|
+
if (ESCAPE_STRING_PATTERN.test(withoutStrings)) issues.push(`executes must not use escape strings (E'\u2026')${at}`);
|
|
691
|
+
if (withoutStrings.includes('"')) issues.push(`executes must not use double-quoted identifiers${at}`);
|
|
692
|
+
if (BARE_CLAIM_CALL_PATTERN.test(withoutStrings.replace(MASKED_CLAIM_CALL_PATTERN, ""))) {
|
|
693
|
+
issues.push(`executes has a malformed claim()/capability() call \u2014 use e.g. claim('sub')${at}`);
|
|
694
|
+
}
|
|
695
|
+
if (RETURNING_PATTERN.test(withoutStrings) && !(hasReturns && index === statements.length - 1)) {
|
|
696
|
+
issues.push(
|
|
697
|
+
hasReturns ? `only the last executes statement may use RETURNING${at}` : `executes must not use RETURNING unless the action declares returns${at}`
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
const last = statements.at(-1);
|
|
702
|
+
if (hasReturns && last && !RETURNING_PATTERN.test(last.replace(QUOTED_STRING_PATTERN, "''"))) {
|
|
703
|
+
issues.push("the last executes statement must use RETURNING to populate the declared returns columns");
|
|
704
|
+
}
|
|
705
|
+
const undeclared = extractNamedParams(statements.join("\n")).filter((name2) => !declaredParams.has(name2));
|
|
706
|
+
if (undeclared.length > 0) issues.push(`executes references undeclared params: ${undeclared.join(", ")}`);
|
|
707
|
+
return issues;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/schema/table.ts
|
|
711
|
+
var linkSchema = zod.z.strictObject({
|
|
712
|
+
to: tableReferenceSchema,
|
|
713
|
+
on: identifierSchema,
|
|
714
|
+
enforce: zod.z.boolean().optional(),
|
|
715
|
+
description: zod.z.string().optional()
|
|
716
|
+
});
|
|
717
|
+
var indexSchema = zod.z.union([
|
|
718
|
+
zod.z.array(identifierSchema).min(1),
|
|
719
|
+
zod.z.strictObject({
|
|
720
|
+
columns: zod.z.array(identifierSchema).min(1),
|
|
721
|
+
unique: zod.z.boolean().optional(),
|
|
722
|
+
name: identifierSchema.optional()
|
|
723
|
+
})
|
|
724
|
+
]).transform((index) => Array.isArray(index) ? { columns: index } : index);
|
|
725
|
+
var policiesSchema = zod.z.strictObject({
|
|
726
|
+
read: parsedPredicateSchema.optional(),
|
|
727
|
+
write: parsedPredicateSchema.optional(),
|
|
728
|
+
insert: parsedPredicateSchema.optional(),
|
|
729
|
+
update: parsedPredicateSchema.optional()
|
|
730
|
+
});
|
|
731
|
+
zod.z.strictObject({
|
|
732
|
+
namespace: namespaceSchema,
|
|
733
|
+
table: identifierSchema,
|
|
734
|
+
description: zod.z.string().optional(),
|
|
735
|
+
columns: zod.z.record(identifierSchema, columnSchema).optional(),
|
|
736
|
+
source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must be a qualified lowercase schema.table identifier").optional(),
|
|
737
|
+
links: zod.z.record(identifierSchema, linkSchema).optional(),
|
|
738
|
+
indexes: zod.z.array(indexSchema).optional(),
|
|
739
|
+
policies: policiesSchema.optional(),
|
|
740
|
+
actions: zod.z.record(identifierSchema, actionSchema).optional()
|
|
741
|
+
}).superRefine((table, ctx) => {
|
|
742
|
+
if (RESERVED_TABLE_NAMES.has(table.table)) {
|
|
743
|
+
ctx.addIssue({ code: "custom", message: `'${table.table}' is a reserved table name`, path: ["table"] });
|
|
744
|
+
}
|
|
745
|
+
for (const name2 of Object.keys(table.columns ?? {})) {
|
|
746
|
+
if (RESERVED_COLUMN_NAMES.has(name2)) {
|
|
747
|
+
ctx.addIssue({
|
|
748
|
+
code: "custom",
|
|
749
|
+
message: `column '${name2}' collides with the Data API's reserved query parameters (select, order, limit, offset) \u2014 it could never be filtered`,
|
|
750
|
+
path: ["columns", name2]
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (table.source) validateSyncedTable(table, ctx);
|
|
755
|
+
else validateColumnTable(table, ctx);
|
|
756
|
+
validateLinks(table, ctx);
|
|
757
|
+
validatePolicyColumns(table, ctx);
|
|
758
|
+
validateDerivedNameLengths(table, ctx);
|
|
759
|
+
});
|
|
760
|
+
var indexName = (table, definition) => definition.name ?? `ix_${table}_${definition.columns.join("_")}`;
|
|
761
|
+
var policyName = (table, suffix) => `${table}_${suffix}`;
|
|
762
|
+
var foreignKeyName = (table, link) => `fk_${table}_${link}`;
|
|
763
|
+
function validateSyncedTable(table, ctx) {
|
|
764
|
+
const forbidden = [
|
|
765
|
+
["actions", "synced tables are read-only; actions are not allowed"],
|
|
766
|
+
["indexes", "v1 synced tables cannot declare indexes; declare them with defineSyncedTable({ indexes }) in a v2 definition"]
|
|
767
|
+
];
|
|
768
|
+
for (const [field, message] of forbidden) {
|
|
769
|
+
if (table[field] !== void 0) ctx.addIssue({ code: "custom", message, path: [field] });
|
|
770
|
+
}
|
|
771
|
+
if (table.policies?.write) {
|
|
772
|
+
ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "write"] });
|
|
773
|
+
}
|
|
774
|
+
if (table.policies?.insert) {
|
|
775
|
+
ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "insert"] });
|
|
776
|
+
}
|
|
777
|
+
if (table.policies?.update) {
|
|
778
|
+
ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "update"] });
|
|
779
|
+
}
|
|
780
|
+
const columns = table.columns ?? {};
|
|
781
|
+
const names = Object.keys(columns);
|
|
782
|
+
if (names.length === 0) return;
|
|
783
|
+
const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
|
|
784
|
+
if (primaryKeys.length !== 1) {
|
|
785
|
+
ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
|
|
786
|
+
}
|
|
787
|
+
validateRenames(columns, names, ctx);
|
|
788
|
+
for (const name2 of names) {
|
|
789
|
+
const column = columns[name2];
|
|
790
|
+
if (!column) continue;
|
|
791
|
+
const cast = column.cast ? castParts(column.cast) : null;
|
|
792
|
+
if (column.cast && cast?.column !== name2) {
|
|
793
|
+
ctx.addIssue({
|
|
794
|
+
code: "custom",
|
|
795
|
+
message: "cast must reference the column it is declared on",
|
|
796
|
+
path: ["columns", name2, "cast"]
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (cast && cast.type !== column.type) {
|
|
800
|
+
ctx.addIssue({
|
|
801
|
+
code: "custom",
|
|
802
|
+
message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
|
|
803
|
+
path: ["columns", name2, "cast"]
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
function validateRenames(columns, names, ctx) {
|
|
809
|
+
const renameSources = /* @__PURE__ */ new Map();
|
|
810
|
+
for (const name2 of names) {
|
|
811
|
+
const renamedFrom = columns[name2]?.renamed_from;
|
|
812
|
+
if (!renamedFrom) continue;
|
|
813
|
+
if (renamedFrom === name2 || names.includes(renamedFrom)) {
|
|
814
|
+
ctx.addIssue({
|
|
815
|
+
code: "custom",
|
|
816
|
+
message: "renamed_from must name a column that no longer exists",
|
|
817
|
+
path: ["columns", name2, "renamed_from"]
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
const claimed = renameSources.get(renamedFrom);
|
|
821
|
+
if (claimed) {
|
|
822
|
+
ctx.addIssue({
|
|
823
|
+
code: "custom",
|
|
824
|
+
message: `renamed_from '${renamedFrom}' is already claimed by '${claimed}'`,
|
|
825
|
+
path: ["columns", name2, "renamed_from"]
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
renameSources.set(renamedFrom, name2);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function validateColumnTable(table, ctx) {
|
|
832
|
+
const columns = table.columns ?? {};
|
|
833
|
+
const names = Object.keys(columns);
|
|
834
|
+
if (names.length === 0) {
|
|
835
|
+
ctx.addIssue({ code: "custom", message: "a table must declare columns or a source", path: ["columns"] });
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
|
|
839
|
+
if (primaryKeys.length !== 1) {
|
|
840
|
+
ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
|
|
841
|
+
}
|
|
842
|
+
validateRenames(columns, names, ctx);
|
|
843
|
+
for (const name2 of names) {
|
|
844
|
+
const column = columns[name2];
|
|
845
|
+
if (!column) continue;
|
|
846
|
+
const cast = column.cast ? castParts(column.cast) : null;
|
|
847
|
+
if (column.cast && cast?.column !== name2) {
|
|
848
|
+
ctx.addIssue({
|
|
849
|
+
code: "custom",
|
|
850
|
+
message: "cast must reference the column it is declared on",
|
|
851
|
+
path: ["columns", name2, "cast"]
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
if (cast && cast.type !== column.type) {
|
|
855
|
+
ctx.addIssue({
|
|
856
|
+
code: "custom",
|
|
857
|
+
message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
|
|
858
|
+
path: ["columns", name2, "cast"]
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
for (const [position, index] of (table.indexes ?? []).entries()) {
|
|
863
|
+
if (new Set(index.columns).size !== index.columns.length) {
|
|
864
|
+
ctx.addIssue({
|
|
865
|
+
code: "custom",
|
|
866
|
+
message: "index names a column more than once",
|
|
867
|
+
path: ["indexes", position]
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
for (const column of index.columns) {
|
|
871
|
+
if (!names.includes(column)) {
|
|
872
|
+
ctx.addIssue({
|
|
873
|
+
code: "custom",
|
|
874
|
+
message: `index references unknown column '${column}'`,
|
|
875
|
+
path: ["indexes", position]
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
function validateLinks(table, ctx) {
|
|
882
|
+
const columnNames = Object.keys(table.columns ?? {});
|
|
883
|
+
for (const [name2, link] of Object.entries(table.links ?? {})) {
|
|
884
|
+
if (columnNames.length > 0 && !columnNames.includes(link.on)) {
|
|
885
|
+
ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links", name2, "on"] });
|
|
886
|
+
}
|
|
887
|
+
if (link.enforce && link.to.includes(".")) {
|
|
888
|
+
ctx.addIssue({
|
|
889
|
+
code: "custom",
|
|
890
|
+
message: "enforce is only valid for links inside the namespace; cross-boundary links stay soft",
|
|
891
|
+
path: ["links", name2, "enforce"]
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
if (link.enforce && table.source) {
|
|
895
|
+
ctx.addIssue({
|
|
896
|
+
code: "custom",
|
|
897
|
+
message: "a synced table cannot enforce foreign keys \u2014 its DDL is owned by the sync",
|
|
898
|
+
path: ["links", name2, "enforce"]
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
function validateDerivedNameLengths(table, ctx) {
|
|
904
|
+
const worstPolicy = policyName(table.table, "insert");
|
|
905
|
+
if (!table.source && worstPolicy.length > MAX_IDENTIFIER_LENGTH) {
|
|
906
|
+
ctx.addIssue({
|
|
907
|
+
code: "custom",
|
|
908
|
+
message: `table name is too long \u2014 the derived policy name '${worstPolicy}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit`,
|
|
909
|
+
path: ["table"]
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
for (const [position, index] of (table.indexes ?? []).entries()) {
|
|
913
|
+
const name2 = indexName(table.table, index);
|
|
914
|
+
if (name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
915
|
+
ctx.addIssue({
|
|
916
|
+
code: "custom",
|
|
917
|
+
message: `derived index name '${name2}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`,
|
|
918
|
+
path: ["indexes", position]
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
for (const [name2, link] of Object.entries(table.links ?? {})) {
|
|
923
|
+
const constraint = foreignKeyName(table.table, name2);
|
|
924
|
+
if (link.enforce && constraint.length > MAX_IDENTIFIER_LENGTH) {
|
|
925
|
+
ctx.addIssue({
|
|
926
|
+
code: "custom",
|
|
927
|
+
message: `derived foreign-key name '${constraint}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the link or table name`,
|
|
928
|
+
path: ["links", name2]
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
for (const name2 of Object.keys(table.actions ?? {})) {
|
|
933
|
+
const fnName = `action_${name2}`;
|
|
934
|
+
if (fnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
935
|
+
ctx.addIssue({
|
|
936
|
+
code: "custom",
|
|
937
|
+
message: `derived action function name '${fnName}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the action name`,
|
|
938
|
+
path: ["actions", name2]
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
var TYPE_GROUPS = {
|
|
944
|
+
integer: "numeric",
|
|
945
|
+
bigint: "numeric",
|
|
946
|
+
numeric: "numeric",
|
|
947
|
+
double: "numeric",
|
|
948
|
+
text: "text",
|
|
949
|
+
uuid: "uuid",
|
|
950
|
+
date: "temporal",
|
|
951
|
+
timestamptz: "temporal",
|
|
952
|
+
boolean: "boolean",
|
|
953
|
+
jsonb: "jsonb"
|
|
954
|
+
};
|
|
955
|
+
function validatePolicyColumns(table, ctx) {
|
|
956
|
+
const columns = table.columns ?? {};
|
|
957
|
+
const columnNames = Object.keys(columns);
|
|
958
|
+
if (columnNames.length === 0) return;
|
|
959
|
+
for (const kind of ["read", "write", "insert", "update"]) {
|
|
960
|
+
const predicate = table.policies?.[kind];
|
|
961
|
+
if (!predicate) continue;
|
|
962
|
+
for (const column of referencedColumns(predicate.ast)) {
|
|
963
|
+
if (!columnNames.includes(column)) {
|
|
964
|
+
ctx.addIssue({
|
|
965
|
+
code: "custom",
|
|
966
|
+
message: `policy references unknown column '${column}'`,
|
|
967
|
+
path: ["policies", kind]
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
walkPredicate(predicate.ast, (node) => {
|
|
972
|
+
if (node.kind !== "compare") return;
|
|
973
|
+
const issue = comparisonTypeIssue(node.left, node.right, columns);
|
|
974
|
+
if (issue) ctx.addIssue({ code: "custom", message: issue, path: ["policies", kind] });
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function comparisonTypeIssue(left, right, columns) {
|
|
979
|
+
const groupOf = (operand) => operand.kind === "column" ? columns[operand.name] ? TYPE_GROUPS[columns[operand.name].type] : null : null;
|
|
980
|
+
if (left.kind === "column" && right.kind === "column") {
|
|
981
|
+
const leftGroup = groupOf(left);
|
|
982
|
+
const rightGroup = groupOf(right);
|
|
983
|
+
if (leftGroup && rightGroup && leftGroup !== rightGroup) {
|
|
984
|
+
return `policy compares '${left.name}' (${columns[left.name].type}) with '${right.name}' (${columns[right.name].type}) \u2014 incomparable types`;
|
|
985
|
+
}
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
|
|
989
|
+
const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
|
|
990
|
+
if (!column || !literal || literal.value === null) return null;
|
|
991
|
+
const columnGroup = groupOf(column);
|
|
992
|
+
if (!columnGroup) return null;
|
|
993
|
+
if (typeof literal.value === "string") return null;
|
|
994
|
+
const literalGroup = typeof literal.value === "number" ? "numeric" : "boolean";
|
|
995
|
+
if (literalGroup !== columnGroup) {
|
|
996
|
+
return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal.value} literal \u2014 incomparable types`;
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
253
1000
|
var policyVariantSchema = zod.z.object({
|
|
254
1001
|
/** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
|
|
255
1002
|
when: zod.z.string().min(1).max(1e3),
|
|
@@ -566,7 +1313,7 @@ var CANONICAL_FORMAT = 2;
|
|
|
566
1313
|
var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
|
|
567
1314
|
var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
|
|
568
1315
|
var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
|
|
569
|
-
var
|
|
1316
|
+
var COLUMN_TYPES2 = [
|
|
570
1317
|
"uuid",
|
|
571
1318
|
"text",
|
|
572
1319
|
"integer",
|
|
@@ -578,7 +1325,7 @@ var COLUMN_TYPES = [
|
|
|
578
1325
|
"date",
|
|
579
1326
|
"jsonb"
|
|
580
1327
|
];
|
|
581
|
-
var
|
|
1328
|
+
var columnTypeSchema2 = zod.z.enum(COLUMN_TYPES2);
|
|
582
1329
|
var canonicalEnumSchema = zod.z.object({
|
|
583
1330
|
name: identifier2,
|
|
584
1331
|
values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
|
|
@@ -587,7 +1334,7 @@ var canonicalColumnSchema = zod.z.object({
|
|
|
587
1334
|
/** Property name in TS (camelCase); columnName is the Postgres name. */
|
|
588
1335
|
name: zod.z.string().min(1).max(63),
|
|
589
1336
|
columnName: columnIdentifier,
|
|
590
|
-
type: zod.z.union([
|
|
1337
|
+
type: zod.z.union([columnTypeSchema2, zod.z.object({ enum: identifier2 })]),
|
|
591
1338
|
nullable: zod.z.boolean().default(true),
|
|
592
1339
|
unique: zod.z.boolean().default(false),
|
|
593
1340
|
/** Literal default, or one of the allowlisted SQL functions. */
|
|
@@ -837,7 +1584,7 @@ var canonicalActionSchema = zod.z.object({
|
|
|
837
1584
|
name: identifier2,
|
|
838
1585
|
requires: zod.z.string().max(1e3).optional(),
|
|
839
1586
|
params: zod.z.record(zod.z.string(), zod.z.object({
|
|
840
|
-
type:
|
|
1587
|
+
type: columnTypeSchema2,
|
|
841
1588
|
nullable: zod.z.boolean().default(false)
|
|
842
1589
|
})).default({}),
|
|
843
1590
|
executes: zod.z.string().min(1).max(1e4),
|
|
@@ -938,7 +1685,8 @@ var canonicalTableSchema = zod.z.object({
|
|
|
938
1685
|
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
939
1686
|
fail4("is read-only and cannot declare write policies", ["policies"]);
|
|
940
1687
|
}
|
|
941
|
-
const
|
|
1688
|
+
const ownedIndexes = table.source === void 0 ? table.indexes : table.indexes.filter((index) => index.unique);
|
|
1689
|
+
const ownedCollections = [ownedIndexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
|
|
942
1690
|
const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
|
|
943
1691
|
if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
|
|
944
1692
|
fail4("cannot declare owned storage, constraints or identity operations");
|
|
@@ -1040,6 +1788,23 @@ function trackedCellColumns(table) {
|
|
|
1040
1788
|
return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
|
|
1041
1789
|
}
|
|
1042
1790
|
var SUFFIXES = Object.values(CELL_PART_SUFFIX);
|
|
1791
|
+
function validateSyncedIndexes(table, tableIndex, ctx) {
|
|
1792
|
+
if (table.source === void 0) return;
|
|
1793
|
+
const columns = new Set(table.columns.map((column) => column.columnName));
|
|
1794
|
+
for (const [position, index] of table.indexes.entries()) {
|
|
1795
|
+
const fail4 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "indexes", position], message: `synced table '${table.table}' ${message}` });
|
|
1796
|
+
if (columns.size === 0) fail4("must declare columns to declare indexes");
|
|
1797
|
+
if (index.unique) fail4("cannot declare a unique index");
|
|
1798
|
+
if (new Set(index.columns).size !== index.columns.length) fail4("index names a column more than once");
|
|
1799
|
+
for (const column of index.columns) {
|
|
1800
|
+
if (columns.size > 0 && !columns.has(column)) fail4(`index references unknown column '${column}'`);
|
|
1801
|
+
}
|
|
1802
|
+
const name2 = indexName(table.table, index);
|
|
1803
|
+
if (name2.length > MAX_IDENTIFIER_LENGTH) {
|
|
1804
|
+
fail4(`derived index name '${name2}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1043
1808
|
function validateCellMetadata(table, index, ctx) {
|
|
1044
1809
|
const fail4 = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
|
|
1045
1810
|
const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
|
|
@@ -1106,12 +1871,24 @@ var localDefinitionSchema = zod.z.object({
|
|
|
1106
1871
|
}).default({ uses: [] })
|
|
1107
1872
|
}).superRefine((definition, ctx) => {
|
|
1108
1873
|
const seen = /* @__PURE__ */ new Set();
|
|
1874
|
+
const syncedIndexOwners = /* @__PURE__ */ new Map();
|
|
1109
1875
|
for (const [index, table] of definition.tables.entries()) {
|
|
1110
1876
|
if (seen.has(table.table)) {
|
|
1111
1877
|
ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
|
|
1112
1878
|
}
|
|
1113
1879
|
seen.add(table.table);
|
|
1114
1880
|
validateCellMetadata(table, index, ctx);
|
|
1881
|
+
validateSyncedIndexes(table, index, ctx);
|
|
1882
|
+
if (table.source !== void 0) {
|
|
1883
|
+
for (const [position, entry] of table.indexes.entries()) {
|
|
1884
|
+
const name2 = indexName(table.table, entry);
|
|
1885
|
+
const owner = syncedIndexOwners.get(name2);
|
|
1886
|
+
if (owner !== void 0) {
|
|
1887
|
+
ctx.addIssue({ code: "custom", path: ["tables", index, "indexes", position], message: `synced index '${name2}' is also declared on '${owner}'` });
|
|
1888
|
+
}
|
|
1889
|
+
syncedIndexOwners.set(name2, table.table);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1115
1892
|
if (table.source !== void 0) {
|
|
1116
1893
|
for (const [columnIndex, column] of table.columns.entries()) {
|
|
1117
1894
|
if (column.onUpdateFn !== void 0) {
|
|
@@ -1360,9 +2137,6 @@ function normalizeCanonical(definition) {
|
|
|
1360
2137
|
return normalized;
|
|
1361
2138
|
}
|
|
1362
2139
|
|
|
1363
|
-
// src/client/sql.ts
|
|
1364
|
-
var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
|
|
1365
|
-
|
|
1366
2140
|
// src/ir/index.ts
|
|
1367
2141
|
var IR_VERSION = 6;
|
|
1368
2142
|
var LEGACY_IR_VERSION = 1;
|
|
@@ -1653,7 +2427,7 @@ var variableDefaultIrSchema = zod.z.object({
|
|
|
1653
2427
|
var variableIrSchema = zod.z.object({
|
|
1654
2428
|
name: zod.z.string(),
|
|
1655
2429
|
type: zod.z.union([
|
|
1656
|
-
zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(
|
|
2430
|
+
zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(COLUMN_TYPES2) }),
|
|
1657
2431
|
/** `values` is the declared membership whitelist — the executor validates against it. */
|
|
1658
2432
|
zod.z.object({ kind: zod.z.literal("enum"), name: zod.z.string(), values: zod.z.array(zod.z.string()).min(1) }),
|
|
1659
2433
|
/** §4f dynamic slot: validated against `tables[table]` whitelists at runtime. */
|
|
@@ -1691,8 +2465,7 @@ var compiledOperationSchema = zod.z.object({
|
|
|
1691
2465
|
maxRows: zod.z.number().int().min(1).max(1e4)
|
|
1692
2466
|
}).strict().optional(),
|
|
1693
2467
|
irVersion: irVersionSchema,
|
|
1694
|
-
name: zod.z.string(),
|
|
1695
|
-
opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
2468
|
+
name: zod.z.string().regex(/^[_A-Za-z][_0-9A-Za-z]{0,254}$/),
|
|
1696
2469
|
kind: zod.z.enum(["query", "mutation"]),
|
|
1697
2470
|
requires: zod.z.object({
|
|
1698
2471
|
capability: zod.z.string().optional(),
|
|
@@ -1708,7 +2481,7 @@ var compiledOperationSchema = zod.z.object({
|
|
|
1708
2481
|
}
|
|
1709
2482
|
if (operation.language === "sql") {
|
|
1710
2483
|
const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
|
|
1711
|
-
const builtin = operation.name === "BuiltinSqlRead" && operation.
|
|
2484
|
+
const builtin = operation.name === "BuiltinSqlRead" && operation.requires === null;
|
|
1712
2485
|
if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !builtin && !operation.requires?.capability) {
|
|
1713
2486
|
ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
|
|
1714
2487
|
}
|
|
@@ -1723,7 +2496,7 @@ var compiledOperationSchema = zod.z.object({
|
|
|
1723
2496
|
}
|
|
1724
2497
|
});
|
|
1725
2498
|
var resolvedSourceIrSchema = zod.z.object({
|
|
1726
|
-
name: zod.z.string(),
|
|
2499
|
+
name: zod.z.string().regex(/^[_A-Za-z][_0-9A-Za-z]{0,254}$/),
|
|
1727
2500
|
kind: zod.z.enum(["manual", "etl", "prefetch"]),
|
|
1728
2501
|
/** Logical table key of the backing relation. */
|
|
1729
2502
|
table: zod.z.string(),
|
|
@@ -1786,7 +2559,7 @@ var tableMetaIrSchema = zod.z.object({
|
|
|
1786
2559
|
columns: zod.z.array(zod.z.object({
|
|
1787
2560
|
name: zod.z.string(),
|
|
1788
2561
|
columnName: zod.z.string(),
|
|
1789
|
-
type: zod.z.union([zod.z.enum(
|
|
2562
|
+
type: zod.z.union([zod.z.enum(COLUMN_TYPES2), zod.z.object({ enum: zod.z.string(), values: zod.z.array(zod.z.string()) })]),
|
|
1790
2563
|
nullable: zod.z.boolean()
|
|
1791
2564
|
})),
|
|
1792
2565
|
/** Empty for keyless read-only views; keyed tables and views list logical columns. */
|
|
@@ -1837,7 +2610,6 @@ var compiledArtifactSchema = zod.z.object({
|
|
|
1837
2610
|
ownSchemaHash: zod.z.string().optional(),
|
|
1838
2611
|
dependencies: zod.z.record(zod.z.string(), zod.z.string()).optional(),
|
|
1839
2612
|
readPolicies: zod.z.record(zod.z.string(), predIrSchema).optional(),
|
|
1840
|
-
opsHash: zod.z.string(),
|
|
1841
2613
|
/** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
|
|
1842
2614
|
unbackedTables: zod.z.array(zod.z.string()).optional(),
|
|
1843
2615
|
tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
|
|
@@ -1923,7 +2695,7 @@ function graphqlScalarFor(type) {
|
|
|
1923
2695
|
}
|
|
1924
2696
|
var COLUMN_TYPE_BY_SCALAR = new Map([
|
|
1925
2697
|
["ID", "text"],
|
|
1926
|
-
...
|
|
2698
|
+
...COLUMN_TYPES2.map((type) => [graphqlScalarFor(type), type])
|
|
1927
2699
|
]);
|
|
1928
2700
|
function columnTypeForScalar(scalarName) {
|
|
1929
2701
|
return COLUMN_TYPE_BY_SCALAR.get(scalarName);
|
|
@@ -2024,14 +2796,14 @@ var PolicyCompileError = class extends Error {
|
|
|
2024
2796
|
};
|
|
2025
2797
|
|
|
2026
2798
|
// src/predicate-v2/parse.ts
|
|
2027
|
-
var
|
|
2028
|
-
var
|
|
2029
|
-
var
|
|
2030
|
-
var
|
|
2031
|
-
var
|
|
2799
|
+
var KEYWORDS2 = /* @__PURE__ */ new Set(["and", "or", "not", "true", "false", "null", "claim", "capability", "role", "exists"]);
|
|
2800
|
+
var OPERATOR_PATTERN2 = /^(!=|<=|>=|<>|=|<|>)/;
|
|
2801
|
+
var IDENTIFIER_START2 = /[a-zA-Z_]/;
|
|
2802
|
+
var IDENTIFIER_CHAR2 = /[a-zA-Z0-9_]/;
|
|
2803
|
+
var NUMBER_PATTERN2 = /^-?\d+(\.\d+)?/;
|
|
2032
2804
|
var SCALAR_CLAIM_KEYS = Object.entries(CLAIMS_CONTRACT).filter(([, type]) => !type.endsWith("[]")).map(([key]) => key);
|
|
2033
2805
|
var ARRAY_CLAIM_KEYS = Object.entries(CLAIMS_CONTRACT).filter(([, type]) => type.endsWith("[]")).map(([key]) => key);
|
|
2034
|
-
function
|
|
2806
|
+
function tokenize3(source) {
|
|
2035
2807
|
const tokens = [];
|
|
2036
2808
|
let position = 0;
|
|
2037
2809
|
while (position < source.length) {
|
|
@@ -2061,27 +2833,27 @@ function tokenize2(source) {
|
|
|
2061
2833
|
continue;
|
|
2062
2834
|
}
|
|
2063
2835
|
if (char === "'") {
|
|
2064
|
-
const { value, end } =
|
|
2836
|
+
const { value, end } = readString2(source, position);
|
|
2065
2837
|
tokens.push({ kind: "string", value, position });
|
|
2066
2838
|
position = end;
|
|
2067
2839
|
continue;
|
|
2068
2840
|
}
|
|
2069
|
-
const operator =
|
|
2841
|
+
const operator = OPERATOR_PATTERN2.exec(source.slice(position));
|
|
2070
2842
|
if (operator) {
|
|
2071
2843
|
const value = operator[0] === "<>" ? "!=" : operator[0];
|
|
2072
2844
|
tokens.push({ kind: "operator", value, position });
|
|
2073
2845
|
position += operator[0].length;
|
|
2074
2846
|
continue;
|
|
2075
2847
|
}
|
|
2076
|
-
const number =
|
|
2077
|
-
if (number && !
|
|
2848
|
+
const number = NUMBER_PATTERN2.exec(source.slice(position));
|
|
2849
|
+
if (number && !IDENTIFIER_START2.test(char)) {
|
|
2078
2850
|
tokens.push({ kind: "number", value: number[0], position });
|
|
2079
2851
|
position += number[0].length;
|
|
2080
2852
|
continue;
|
|
2081
2853
|
}
|
|
2082
|
-
if (
|
|
2854
|
+
if (IDENTIFIER_START2.test(char)) {
|
|
2083
2855
|
let end = position;
|
|
2084
|
-
while (end < source.length &&
|
|
2856
|
+
while (end < source.length && IDENTIFIER_CHAR2.test(source[end])) end += 1;
|
|
2085
2857
|
tokens.push({ kind: "identifier", value: source.slice(position, end), position });
|
|
2086
2858
|
position = end;
|
|
2087
2859
|
continue;
|
|
@@ -2091,7 +2863,7 @@ function tokenize2(source) {
|
|
|
2091
2863
|
tokens.push({ kind: "end", value: "", position: source.length });
|
|
2092
2864
|
return tokens;
|
|
2093
2865
|
}
|
|
2094
|
-
function
|
|
2866
|
+
function readString2(source, start) {
|
|
2095
2867
|
let value = "";
|
|
2096
2868
|
let position = start + 1;
|
|
2097
2869
|
while (position < source.length) {
|
|
@@ -2110,7 +2882,7 @@ function readString(source, start) {
|
|
|
2110
2882
|
}
|
|
2111
2883
|
throw new PolicyParseError("unterminated string literal", start);
|
|
2112
2884
|
}
|
|
2113
|
-
var
|
|
2885
|
+
var Parser3 = class {
|
|
2114
2886
|
constructor(tokens) {
|
|
2115
2887
|
this.tokens = tokens;
|
|
2116
2888
|
}
|
|
@@ -2252,7 +3024,7 @@ var Parser2 = class {
|
|
|
2252
3024
|
/** `ident(.ident)*` — first segment must not be a keyword; later segments may be (e.g. `author.role`). */
|
|
2253
3025
|
parsePath(description) {
|
|
2254
3026
|
const first = this.peek();
|
|
2255
|
-
if (first.kind !== "identifier" ||
|
|
3027
|
+
if (first.kind !== "identifier" || KEYWORDS2.has(first.value)) {
|
|
2256
3028
|
throw new PolicyParseError(`expected ${description}, got '${first.value || "end"}'`, first.position);
|
|
2257
3029
|
}
|
|
2258
3030
|
this.advance();
|
|
@@ -2298,7 +3070,7 @@ var Parser2 = class {
|
|
|
2298
3070
|
function parsePolicy(source) {
|
|
2299
3071
|
const trimmed = source.trim();
|
|
2300
3072
|
if (trimmed.length === 0) throw new PolicyParseError("empty policy expression", 0);
|
|
2301
|
-
return { source: trimmed, ast: new
|
|
3073
|
+
return { source: trimmed, ast: new Parser3(tokenize3(trimmed)).parse() };
|
|
2302
3074
|
}
|
|
2303
3075
|
|
|
2304
3076
|
// src/predicate-v2/compile.ts
|
|
@@ -2983,15 +3755,6 @@ function classifyInputType(name2, tableByTypename) {
|
|
|
2983
3755
|
return void 0;
|
|
2984
3756
|
}
|
|
2985
3757
|
|
|
2986
|
-
// src/errors.ts
|
|
2987
|
-
var OrmDefinitionError = class extends Error {
|
|
2988
|
-
constructor(message, file) {
|
|
2989
|
-
super(file ? `${file}: ${message}` : message);
|
|
2990
|
-
this.file = file;
|
|
2991
|
-
}
|
|
2992
|
-
name = "OrmDefinitionError";
|
|
2993
|
-
};
|
|
2994
|
-
|
|
2995
3758
|
// src/canonical/composition.ts
|
|
2996
3759
|
function localContract(definition) {
|
|
2997
3760
|
return normalizeCanonical(localDefinitionSchema.parse(definition));
|
|
@@ -3551,24 +4314,6 @@ function flattenOperation({
|
|
|
3551
4314
|
}) {
|
|
3552
4315
|
return { ...operation, selectionSet: flattenSelectionSet(operation.selectionSet, fragments, []) };
|
|
3553
4316
|
}
|
|
3554
|
-
var COMPILER_MAJOR = 1;
|
|
3555
|
-
var OP_HASH_DOMAIN = "sequence-orm-op\0";
|
|
3556
|
-
var OPS_HASH_DOMAIN = "sequence-orm-opset\0";
|
|
3557
|
-
function canonicalOperationText(definition) {
|
|
3558
|
-
const document2 = { kind: graphql.Kind.DOCUMENT, definitions: [definition] };
|
|
3559
|
-
return graphql.print(document2);
|
|
3560
|
-
}
|
|
3561
|
-
function computeOpHash({
|
|
3562
|
-
definition,
|
|
3563
|
-
schemaHash
|
|
3564
|
-
}) {
|
|
3565
|
-
const digest2 = crypto.createHash("sha256").update(OP_HASH_DOMAIN).update(`${COMPILER_MAJOR}\0${schemaHash}\0`).update(canonicalOperationText(definition)).digest("hex");
|
|
3566
|
-
return `sha256:${digest2}`;
|
|
3567
|
-
}
|
|
3568
|
-
function computeOpsHash(opHashes) {
|
|
3569
|
-
const digest2 = crypto.createHash("sha256").update(OPS_HASH_DOMAIN).update([...opHashes].sort().join("\n")).digest("hex");
|
|
3570
|
-
return `sha256:${digest2}`;
|
|
3571
|
-
}
|
|
3572
4317
|
var SOURCE_DIRECTIVES = /* @__PURE__ */ new Set(["prefetch", "writeback", "localOnly"]);
|
|
3573
4318
|
var fail = (message, code) => {
|
|
3574
4319
|
throw new DocumentCompileError(message, code);
|
|
@@ -5064,7 +5809,6 @@ function compileNamespace({ canonical, sdl, documents }) {
|
|
|
5064
5809
|
operations[restricted.name] = {
|
|
5065
5810
|
irVersion: usesServicePrincipal(roots) ? SERVICE_PRINCIPAL_IR_VERSION : canonical.imports?.length ? COMPOSED_IR_VERSION : roots.some((root) => root.kind === "mutation" && root.assertAffectedRows !== void 0) ? GUARDED_IR_VERSION : LEGACY_IR_VERSION,
|
|
5066
5811
|
name: restricted.name,
|
|
5067
|
-
opHash: computeOpHash({ definition: flattened, schemaHash }),
|
|
5068
5812
|
kind: restricted.kind,
|
|
5069
5813
|
requires: restricted.requires,
|
|
5070
5814
|
variables: restricted.variables,
|
|
@@ -5097,7 +5841,6 @@ function compileNamespace({ canonical, sdl, documents }) {
|
|
|
5097
5841
|
dependencies: Object.fromEntries(canonical.imports.map((owner) => [owner.namespace, computeSchemaHash(localContract(owner))]))
|
|
5098
5842
|
} : {},
|
|
5099
5843
|
readPolicies: Object.fromEntries(ctx.readPolicies),
|
|
5100
|
-
opsHash: computeOpsHash(Object.values(operations).map((op) => op.opHash)),
|
|
5101
5844
|
tables: ctx.tables,
|
|
5102
5845
|
operations
|
|
5103
5846
|
};
|
|
@@ -5105,11 +5848,10 @@ function compileNamespace({ canonical, sdl, documents }) {
|
|
|
5105
5848
|
const manifest = {
|
|
5106
5849
|
namespace: ctx.namespace,
|
|
5107
5850
|
schemaHash,
|
|
5108
|
-
opsHash: normalized.opsHash,
|
|
5109
5851
|
operations: Object.fromEntries(
|
|
5110
5852
|
Object.values(normalized.operations).map((op) => [
|
|
5111
5853
|
op.name,
|
|
5112
|
-
|
|
5854
|
+
op
|
|
5113
5855
|
])
|
|
5114
5856
|
)
|
|
5115
5857
|
};
|
|
@@ -5505,6 +6247,10 @@ function validateOnConflictInput({
|
|
|
5505
6247
|
}
|
|
5506
6248
|
return finish(collector, { constraint, updateColumns });
|
|
5507
6249
|
}
|
|
6250
|
+
function canonicalOperationText(definition) {
|
|
6251
|
+
const document2 = { kind: graphql.Kind.DOCUMENT, definitions: [definition] };
|
|
6252
|
+
return graphql.print(document2);
|
|
6253
|
+
}
|
|
5508
6254
|
|
|
5509
6255
|
// src/sdl/generate.ts
|
|
5510
6256
|
var SdlGenerationError = class extends Error {
|
|
@@ -6483,7 +7229,6 @@ function createSqlCatalog({ canonical, publishedObjects, executionTarget }) {
|
|
|
6483
7229
|
}
|
|
6484
7230
|
function createSqlCatalogFromContext({ canonical, publishedObjects, executionTarget, ctx, identity: suppliedIdentity }) {
|
|
6485
7231
|
if (!publishedObjects.length || new Set(publishedObjects).size !== publishedObjects.length) throw new Error("SQL objects must be explicitly declared and unique");
|
|
6486
|
-
const identity = suppliedIdentity ?? sqlHash(`ontology-sql-v1\0${computeSchemaHash(canonical)}\0${executionTarget}`);
|
|
6487
7232
|
const logicalNames = new Map(Object.entries(ctx.tables).map(([key, meta]) => [meta, key]));
|
|
6488
7233
|
const refOf = (meta) => `${meta.namespace ?? canonical.namespace}/${logicalNames.get(meta)}`;
|
|
6489
7234
|
const definitions = [canonical, ...canonical.imports ?? []];
|
|
@@ -6508,7 +7253,7 @@ function createSqlCatalogFromContext({ canonical, publishedObjects, executionTar
|
|
|
6508
7253
|
const scan = `SELECT ${projection} FROM ${quoteIdentifier(meta.schema)}.${quoteIdentifier(meta.table)} AS ${quoteIdentifier("p0")} WHERE ${compileStaticPolicySql({ table: meta, predicate: policy, tables: ctx.tables, namespace: canonical.namespace, alias: "p0", onRead: (target) => reads.add(refOf(target)) })}`;
|
|
6509
7254
|
return { ref, table: meta.table, columns, scan, read: [...reads].sort() };
|
|
6510
7255
|
});
|
|
6511
|
-
return { identity, objects };
|
|
7256
|
+
return { identity: suppliedIdentity ?? sqlHash(JSON.stringify(["ontology-sql-catalog-v2", executionTarget, objects])), objects };
|
|
6512
7257
|
}
|
|
6513
7258
|
|
|
6514
7259
|
// src/sql/ast.ts
|
|
@@ -7062,20 +7807,10 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
7062
7807
|
const catalog = createSqlCatalog({ canonical: input.canonical, publishedObjects: read, executionTarget: "registered-operation" });
|
|
7063
7808
|
if ("mode" in definition) {
|
|
7064
7809
|
if (catalog.objects.some((object) => object.read.some((ref) => !read.includes(ref)))) throw new Error("SQL policy accesses an undeclared ontology object");
|
|
7065
|
-
const opHash2 = sqlHash(JSON.stringify([
|
|
7066
|
-
"sequence-runtime-sql-operation-v1",
|
|
7067
|
-
definition.name,
|
|
7068
|
-
definition.namespace,
|
|
7069
|
-
definition.requires.capability,
|
|
7070
|
-
read,
|
|
7071
|
-
catalog.identity,
|
|
7072
|
-
definition.maxRows
|
|
7073
|
-
]));
|
|
7074
7810
|
result.artifact.operations[definition.name] = compiledOperationSchema.parse({
|
|
7075
7811
|
irVersion: RUNTIME_SQL_IR_VERSION,
|
|
7076
7812
|
language: "sql",
|
|
7077
7813
|
name: definition.name,
|
|
7078
|
-
opHash: opHash2,
|
|
7079
7814
|
kind: "query",
|
|
7080
7815
|
requires: definition.requires,
|
|
7081
7816
|
variables: [
|
|
@@ -7086,7 +7821,7 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
7086
7821
|
tableSet: read.map((ref) => ref.split("/")[1]).sort(),
|
|
7087
7822
|
sqlQuery: { catalog, read, maxRows: definition.maxRows }
|
|
7088
7823
|
});
|
|
7089
|
-
result.manifest.operations[definition.name] =
|
|
7824
|
+
result.manifest.operations[definition.name] = result.artifact.operations[definition.name];
|
|
7090
7825
|
continue;
|
|
7091
7826
|
}
|
|
7092
7827
|
const names = Object.keys(definition.parameters).sort();
|
|
@@ -7104,20 +7839,10 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
7104
7839
|
nullable: !definition.parameters[name2].required,
|
|
7105
7840
|
list: false
|
|
7106
7841
|
}));
|
|
7107
|
-
const opHash = sqlHash(JSON.stringify([
|
|
7108
|
-
"sequence-sql-operation-v1",
|
|
7109
|
-
definition.name,
|
|
7110
|
-
definition.namespace,
|
|
7111
|
-
definition.requires.capability,
|
|
7112
|
-
read,
|
|
7113
|
-
variables,
|
|
7114
|
-
plan.queryHash
|
|
7115
|
-
]));
|
|
7116
7842
|
const operation = compiledOperationSchema.parse({
|
|
7117
7843
|
irVersion: SQL_IR_VERSION,
|
|
7118
7844
|
language: "sql",
|
|
7119
7845
|
name: definition.name,
|
|
7120
|
-
opHash,
|
|
7121
7846
|
kind: "query",
|
|
7122
7847
|
requires: definition.requires,
|
|
7123
7848
|
variables,
|
|
@@ -7126,7 +7851,7 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
7126
7851
|
sql: { text: plan.text, columns: plan.columns, parameters: names, read, maxRows: plan.maxRows }
|
|
7127
7852
|
});
|
|
7128
7853
|
result.artifact.operations[definition.name] = operation;
|
|
7129
|
-
result.manifest.operations[definition.name] =
|
|
7854
|
+
result.manifest.operations[definition.name] = result.artifact.operations[definition.name];
|
|
7130
7855
|
} catch (error) {
|
|
7131
7856
|
return { ok: false, diagnostics: [...result.diagnostics, {
|
|
7132
7857
|
severity: "error",
|
|
@@ -7138,8 +7863,6 @@ async function compileSqlDocuments({ input, result, sqlDocuments }) {
|
|
|
7138
7863
|
}
|
|
7139
7864
|
const required = requiredArtifactIrVersion(Object.values(result.artifact.operations));
|
|
7140
7865
|
if (required > result.artifact.irVersion) result.artifact.irVersion = required;
|
|
7141
|
-
result.artifact.opsHash = computeOpsHash(Object.values(result.artifact.operations).map((op) => op.opHash));
|
|
7142
|
-
result.manifest.opsHash = result.artifact.opsHash;
|
|
7143
7866
|
return { ...result, artifact: normalizeIr(result.artifact) };
|
|
7144
7867
|
}
|
|
7145
7868
|
async function managedViewDiagnostics(canonical) {
|
|
@@ -7238,14 +7961,13 @@ function compileBuiltinSqlArtifact({ namespace, definitions, read, unbackedObjec
|
|
|
7238
7961
|
namespace,
|
|
7239
7962
|
executionTarget,
|
|
7240
7963
|
read,
|
|
7241
|
-
|
|
7964
|
+
objects
|
|
7242
7965
|
])) };
|
|
7243
7966
|
return normalizeIr({
|
|
7244
7967
|
irVersion: usesServicePrincipal(policies) ? SERVICE_PRINCIPAL_IR_VERSION : RUNTIME_SQL_IR_VERSION,
|
|
7245
7968
|
namespace,
|
|
7246
7969
|
schemaHash: catalog.identity,
|
|
7247
7970
|
ownSchemaHash: localHash(anchor),
|
|
7248
|
-
opsHash: BUILTIN_SQL_READ_HASH,
|
|
7249
7971
|
dependencies: Object.fromEntries(definitions.filter((definition) => definition.namespace !== namespace).map((definition) => [definition.namespace, localHash(definition)])),
|
|
7250
7972
|
tables,
|
|
7251
7973
|
readPolicies: policies,
|
|
@@ -7253,7 +7975,6 @@ function compileBuiltinSqlArtifact({ namespace, definitions, read, unbackedObjec
|
|
|
7253
7975
|
operations: { BuiltinSqlRead: {
|
|
7254
7976
|
irVersion: RUNTIME_SQL_IR_VERSION,
|
|
7255
7977
|
name: "BuiltinSqlRead",
|
|
7256
|
-
opHash: BUILTIN_SQL_READ_HASH,
|
|
7257
7978
|
language: "sql",
|
|
7258
7979
|
kind: "query",
|
|
7259
7980
|
requires: null,
|
|
@@ -7418,13 +8139,11 @@ function composeObjectSqlArtifact({ namespace, bundles }) {
|
|
|
7418
8139
|
irVersion: usesServicePrincipal(readPolicies) ? SERVICE_PRINCIPAL_IR_VERSION : RUNTIME_SQL_IR_VERSION,
|
|
7419
8140
|
namespace,
|
|
7420
8141
|
schemaHash: identity,
|
|
7421
|
-
opsHash: BUILTIN_SQL_READ_HASH,
|
|
7422
8142
|
tables,
|
|
7423
8143
|
readPolicies,
|
|
7424
8144
|
operations: { BuiltinSqlRead: {
|
|
7425
8145
|
irVersion: RUNTIME_SQL_IR_VERSION,
|
|
7426
8146
|
name: "BuiltinSqlRead",
|
|
7427
|
-
opHash: BUILTIN_SQL_READ_HASH,
|
|
7428
8147
|
language: "sql",
|
|
7429
8148
|
kind: "query",
|
|
7430
8149
|
requires: null,
|
|
@@ -7438,12 +8157,13 @@ function composeObjectSqlArtifact({ namespace, bundles }) {
|
|
|
7438
8157
|
} }
|
|
7439
8158
|
});
|
|
7440
8159
|
}
|
|
8160
|
+
|
|
8161
|
+
// src/compiler-gql/dataset-previews.ts
|
|
7441
8162
|
var DATASET_PREVIEW_LIMIT = 100;
|
|
7442
8163
|
function compileDatasetPreviews(input) {
|
|
7443
8164
|
const canonical = normalizeCanonical(input);
|
|
7444
8165
|
const { ctx, diagnostics } = buildCompileContext(canonical);
|
|
7445
8166
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) throw new NamespaceCompileError(diagnostics);
|
|
7446
|
-
const schemaHash = computeSchemaHash(canonical);
|
|
7447
8167
|
const operations = {};
|
|
7448
8168
|
for (const relation of canonicalRelations(canonical)) {
|
|
7449
8169
|
if (relation.internal || relation.source !== void 0) continue;
|
|
@@ -7468,11 +8188,9 @@ function compileDatasetPreviews(input) {
|
|
|
7468
8188
|
throw new DocumentCompileError(`object '${meta.namespace}/${table}' is not exported for reads`, "unexported-object");
|
|
7469
8189
|
}
|
|
7470
8190
|
}
|
|
7471
|
-
const hash = crypto.createHash("sha256").update("sequence-orm-dataset-preview-v1\0").update(stableJson({ schemaHash, roots, tableSet })).digest("hex");
|
|
7472
8191
|
operations[relation.table] = compiledOperationSchema.parse({
|
|
7473
8192
|
irVersion: usesServicePrincipal(roots) ? SERVICE_PRINCIPAL_IR_VERSION : canonical.imports?.length ? COMPOSED_IR_VERSION : LEGACY_IR_VERSION,
|
|
7474
8193
|
name: `DatasetPreview_${relation.table}`,
|
|
7475
|
-
opHash: `sha256:${hash}`,
|
|
7476
8194
|
kind: "query",
|
|
7477
8195
|
requires: null,
|
|
7478
8196
|
variables: [],
|
|
@@ -7541,7 +8259,6 @@ function projectOperationMetadata({ artifact, operation }) {
|
|
|
7541
8259
|
});
|
|
7542
8260
|
}
|
|
7543
8261
|
|
|
7544
|
-
exports.COMPILER_MAJOR = COMPILER_MAJOR;
|
|
7545
8262
|
exports.CONST_TRUE = CONST_TRUE;
|
|
7546
8263
|
exports.DATASET_PREVIEW_LIMIT = DATASET_PREVIEW_LIMIT;
|
|
7547
8264
|
exports.DEFAULT_LIMIT = DEFAULT_LIMIT;
|
|
@@ -7579,8 +8296,6 @@ exports.compilePolicy = compilePolicy;
|
|
|
7579
8296
|
exports.compilePredicateV2 = compilePredicateV2;
|
|
7580
8297
|
exports.compileRuntimeSql = compileRuntimeSql;
|
|
7581
8298
|
exports.composeObjectSqlArtifact = composeObjectSqlArtifact;
|
|
7582
|
-
exports.computeOpHash = computeOpHash;
|
|
7583
|
-
exports.computeOpsHash = computeOpsHash;
|
|
7584
8299
|
exports.computePinnedColumnsByTable = computePinnedColumnsByTable;
|
|
7585
8300
|
exports.computeSchemaHash = computeSchemaHash;
|
|
7586
8301
|
exports.defineOperation = defineOperation;
|