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