@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.
Files changed (51) hide show
  1. package/README.md +74 -4
  2. package/dist/authoring.cjs +796 -7
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +10 -1
  5. package/dist/authoring.d.ts +10 -1
  6. package/dist/authoring.js +796 -7
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +777 -6
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.js +777 -6
  11. package/dist/canonical.js.map +1 -1
  12. package/dist/cli.cjs +7697 -7686
  13. package/dist/cli.cjs.map +1 -1
  14. package/dist/cli.js +7698 -7687
  15. package/dist/cli.js.map +1 -1
  16. package/dist/client.cjs +5 -10
  17. package/dist/client.cjs.map +1 -1
  18. package/dist/client.d.cts +2 -13
  19. package/dist/client.d.ts +2 -13
  20. package/dist/client.js +6 -10
  21. package/dist/client.js.map +1 -1
  22. package/dist/compiler.cjs +815 -100
  23. package/dist/compiler.cjs.map +1 -1
  24. package/dist/compiler.d.cts +5 -16
  25. package/dist/compiler.d.ts +5 -16
  26. package/dist/compiler.js +817 -99
  27. package/dist/compiler.js.map +1 -1
  28. package/dist/index.cjs +3611 -3287
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +577 -7
  31. package/dist/index.d.ts +577 -7
  32. package/dist/index.js +3600 -3285
  33. package/dist/index.js.map +1 -1
  34. package/dist/ir.cjs +784 -18
  35. package/dist/ir.cjs.map +1 -1
  36. package/dist/ir.d.cts +3 -6
  37. package/dist/ir.d.ts +3 -6
  38. package/dist/ir.js +784 -18
  39. package/dist/ir.js.map +1 -1
  40. package/dist/predicate-v2.cjs +796 -31
  41. package/dist/predicate-v2.cjs.map +1 -1
  42. package/dist/predicate-v2.js +796 -31
  43. package/dist/predicate-v2.js.map +1 -1
  44. package/dist/sdl.cjs +790 -28
  45. package/dist/sdl.cjs.map +1 -1
  46. package/dist/sdl.d.cts +1 -5
  47. package/dist/sdl.d.ts +1 -5
  48. package/dist/sdl.js +790 -28
  49. package/dist/sdl.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/codegen-gql/server-fns.cjs +1 -1
package/dist/sdl.cjs CHANGED
@@ -205,10 +205,16 @@ function compileCheckExpression(source, columns) {
205
205
 
206
206
  // src/constants.ts
207
207
  var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
208
+ var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
208
209
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
209
210
  var MAX_IDENTIFIER_LENGTH = 63;
211
+ var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
210
212
  var MAX_PERSISTED_READ_ROWS = 50001;
213
+ var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
211
214
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
215
+ var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
216
+ var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
217
+ var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
212
218
  var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
213
219
  var CELL_PART_SUFFIX = {
214
220
  at: "__at",
@@ -224,6 +230,748 @@ function cellColumnName(column, part) {
224
230
  }
225
231
  var MATERIALIZE_ACTOR = "orm:materialize";
226
232
  JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
233
+
234
+ // src/predicate/ast.ts
235
+ var COMPARE_OPS = ["=", "!=", "<", "<=", ">", ">="];
236
+ function walkPredicate(predicate, visit) {
237
+ visit(predicate);
238
+ if (predicate.kind === "and" || predicate.kind === "or") {
239
+ for (const operand of predicate.operands) walkPredicate(operand, visit);
240
+ }
241
+ if (predicate.kind === "not") walkPredicate(predicate.operand, visit);
242
+ }
243
+ function predicateOperands(predicate) {
244
+ const operands = [];
245
+ walkPredicate(predicate, (node) => {
246
+ if (node.kind === "compare") operands.push(node.left, node.right);
247
+ });
248
+ return operands;
249
+ }
250
+ function referencedColumns(predicate) {
251
+ const names = predicateOperands(predicate).flatMap(
252
+ (operand) => operand.kind === "column" ? [operand.name] : []
253
+ );
254
+ return [...new Set(names)];
255
+ }
256
+ var identifierSchema = zod.z.string().max(MAX_IDENTIFIER_LENGTH).regex(IDENTIFIER_PATTERN, "must be a lowercase identifier (a-z, 0-9, _)");
257
+ var namespaceSchema = zod.z.string().regex(NAMESPACE_PATTERN, "must be lowercase, start with a letter, and be at most 41 characters");
258
+ var tableReferenceSchema = zod.z.string().refine(
259
+ (value) => IDENTIFIER_PATTERN.test(value) || QUALIFIED_TABLE_PATTERN.test(value),
260
+ "must be a table name or a schema-qualified table reference"
261
+ );
262
+
263
+ // src/schema/columns.ts
264
+ var COLUMN_TYPES = [
265
+ "uuid",
266
+ "text",
267
+ "integer",
268
+ "bigint",
269
+ "numeric",
270
+ "double",
271
+ "boolean",
272
+ "timestamptz",
273
+ "date",
274
+ "jsonb"
275
+ ];
276
+ var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
277
+ var FUNCTION_DEFAULTS = {
278
+ "gen_random_uuid()": ["uuid"],
279
+ "now()": ["timestamptz", "date"]
280
+ };
281
+ var CAST_PATTERN = /^([a-z][a-z0-9_]*)::([a-z]+)$/;
282
+ function defaultIssue(value, type) {
283
+ if (typeof value === "boolean") return type === "boolean" ? null : `boolean default is invalid for type '${type}'`;
284
+ if (typeof value === "number") {
285
+ if (!["integer", "bigint", "numeric", "double"].includes(type)) {
286
+ return `numeric default is invalid for type '${type}'`;
287
+ }
288
+ if (["integer", "bigint"].includes(type)) {
289
+ if (!Number.isInteger(value)) return `default for type '${type}' must be an integer`;
290
+ if (!Number.isSafeInteger(value)) {
291
+ return `default for type '${type}' exceeds the safe integer range \u2014 YAML numbers lose precision past 2^53`;
292
+ }
293
+ }
294
+ if (type === "numeric" && Math.abs(value) > Number.MAX_SAFE_INTEGER) {
295
+ return "numeric default exceeds the safe float range \u2014 write it as a string (e.g. '9007199254740993') to keep exact precision";
296
+ }
297
+ return null;
298
+ }
299
+ const allowedTypes = FUNCTION_DEFAULTS[value];
300
+ if (allowedTypes) return allowedTypes.includes(type) ? null : `default '${value}' is invalid for type '${type}'`;
301
+ return null;
302
+ }
303
+ var columnSchema = zod.z.strictObject({
304
+ type: columnTypeSchema,
305
+ primary_key: zod.z.boolean().optional(),
306
+ nullable: zod.z.boolean().optional(),
307
+ unique: zod.z.boolean().optional(),
308
+ default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean()]).optional(),
309
+ description: zod.z.string().optional(),
310
+ renamed_from: identifierSchema.optional(),
311
+ cast: zod.z.string().optional(),
312
+ deprecated: zod.z.boolean().optional()
313
+ }).superRefine((column, ctx) => {
314
+ if (column.default !== void 0) {
315
+ const issue = defaultIssue(column.default, column.type);
316
+ if (issue) ctx.addIssue({ code: "custom", message: issue, path: ["default"] });
317
+ }
318
+ if (column.primary_key && column.nullable) {
319
+ ctx.addIssue({ code: "custom", message: "a primary key column cannot be nullable", path: ["nullable"] });
320
+ }
321
+ if (column.primary_key && column.deprecated) {
322
+ ctx.addIssue({ code: "custom", message: "a primary key column cannot be deprecated", path: ["deprecated"] });
323
+ }
324
+ if (column.cast && !CAST_PATTERN.test(column.cast)) {
325
+ ctx.addIssue({ code: "custom", message: "cast must have the shape '<column>::<type>'", path: ["cast"] });
326
+ }
327
+ });
328
+ function castParts(cast) {
329
+ const match = CAST_PATTERN.exec(cast);
330
+ if (!match) return null;
331
+ return { column: match[1], type: match[2] };
332
+ }
333
+
334
+ // src/errors.ts
335
+ var OrmDefinitionError = class extends Error {
336
+ constructor(message, file) {
337
+ super(file ? `${file}: ${message}` : message);
338
+ this.file = file;
339
+ }
340
+ name = "OrmDefinitionError";
341
+ };
342
+ var PredicateParseError = class extends Error {
343
+ constructor(message, position) {
344
+ super(`${message} (at position ${position})`);
345
+ this.position = position;
346
+ }
347
+ name = "PredicateParseError";
348
+ };
349
+
350
+ // src/predicate/parse.ts
351
+ var KEYWORDS = /* @__PURE__ */ new Set(["and", "or", "not", "true", "false", "null", "claim", "capability"]);
352
+ var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
353
+ var IDENTIFIER_START = /[a-z_]/;
354
+ var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
355
+ var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
356
+ function tokenize2(source) {
357
+ const tokens = [];
358
+ let position = 0;
359
+ while (position < source.length) {
360
+ const char = source[position];
361
+ if (/\s/.test(char)) {
362
+ position += 1;
363
+ continue;
364
+ }
365
+ if (char === "(") {
366
+ tokens.push({ kind: "lparen", value: char, position });
367
+ position += 1;
368
+ continue;
369
+ }
370
+ if (char === ")") {
371
+ tokens.push({ kind: "rparen", value: char, position });
372
+ position += 1;
373
+ continue;
374
+ }
375
+ if (char === ",") {
376
+ tokens.push({ kind: "comma", value: char, position });
377
+ position += 1;
378
+ continue;
379
+ }
380
+ if (char === "'") {
381
+ const { value, end } = readString(source, position);
382
+ tokens.push({ kind: "string", value, position });
383
+ position = end;
384
+ continue;
385
+ }
386
+ const operator = OPERATOR_PATTERN.exec(source.slice(position));
387
+ if (operator) {
388
+ const value = operator[0] === "<>" ? "!=" : operator[0];
389
+ tokens.push({ kind: "operator", value, position });
390
+ position += operator[0].length;
391
+ continue;
392
+ }
393
+ const number = NUMBER_PATTERN.exec(source.slice(position));
394
+ if (number && !IDENTIFIER_START.test(char)) {
395
+ tokens.push({ kind: "number", value: number[0], position });
396
+ position += number[0].length;
397
+ continue;
398
+ }
399
+ if (IDENTIFIER_START.test(char)) {
400
+ let end = position;
401
+ while (end < source.length && IDENTIFIER_CHAR.test(source[end])) end += 1;
402
+ tokens.push({ kind: "identifier", value: source.slice(position, end), position });
403
+ position = end;
404
+ continue;
405
+ }
406
+ throw new PredicateParseError(`unexpected character '${char}'`, position);
407
+ }
408
+ tokens.push({ kind: "end", value: "", position: source.length });
409
+ return tokens;
410
+ }
411
+ function readString(source, start) {
412
+ let value = "";
413
+ let position = start + 1;
414
+ while (position < source.length) {
415
+ const char = source[position];
416
+ if (char !== "'") {
417
+ value += char;
418
+ position += 1;
419
+ continue;
420
+ }
421
+ if (source[position + 1] === "'") {
422
+ value += "'";
423
+ position += 2;
424
+ continue;
425
+ }
426
+ return { value, end: position + 1 };
427
+ }
428
+ throw new PredicateParseError("unterminated string literal", start);
429
+ }
430
+ var Parser2 = class {
431
+ constructor(tokens) {
432
+ this.tokens = tokens;
433
+ }
434
+ index = 0;
435
+ parse() {
436
+ const predicate = this.parseOr();
437
+ this.expect("end", "end of expression");
438
+ return predicate;
439
+ }
440
+ parseOr() {
441
+ const operands = [this.parseAnd()];
442
+ while (this.matchKeyword("or")) operands.push(this.parseAnd());
443
+ return operands.length === 1 ? operands[0] : { kind: "or", operands };
444
+ }
445
+ parseAnd() {
446
+ const operands = [this.parseNot()];
447
+ while (this.matchKeyword("and")) operands.push(this.parseNot());
448
+ return operands.length === 1 ? operands[0] : { kind: "and", operands };
449
+ }
450
+ parseNot() {
451
+ if (this.matchKeyword("not")) return { kind: "not", operand: this.parseNot() };
452
+ return this.parsePrimary();
453
+ }
454
+ parsePrimary() {
455
+ if (this.peek().kind === "lparen") {
456
+ this.advance();
457
+ const inner = this.parseOr();
458
+ this.expect("rparen", "')'");
459
+ return inner;
460
+ }
461
+ if (this.peekKeyword("capability")) {
462
+ const capability = this.parseCapability();
463
+ const next = this.peek();
464
+ if (next.kind === "operator") {
465
+ throw new PredicateParseError("capability() is boolean-valued and cannot appear in a comparison", next.position);
466
+ }
467
+ return capability;
468
+ }
469
+ return this.parseComparison();
470
+ }
471
+ parseCapability() {
472
+ this.advance();
473
+ const key = this.parseCallArgument("capability");
474
+ if (!CAPABILITY_KEY_PATTERN.test(key)) {
475
+ throw new PredicateParseError(`invalid capability key '${key}'`, this.peek().position);
476
+ }
477
+ return { kind: "capability", key };
478
+ }
479
+ parseComparison() {
480
+ const left = this.parseOperand();
481
+ const operator = this.expect("operator", "a comparison operator");
482
+ if (!COMPARE_OPS.includes(operator.value)) {
483
+ throw new PredicateParseError(`unsupported operator '${operator.value}'`, operator.position);
484
+ }
485
+ const right = this.parseOperand();
486
+ return { kind: "compare", op: operator.value, left, right };
487
+ }
488
+ parseOperand() {
489
+ const token = this.peek();
490
+ if (token.kind === "string") {
491
+ this.advance();
492
+ return { kind: "literal", value: token.value };
493
+ }
494
+ if (token.kind === "number") {
495
+ this.advance();
496
+ const value = Number(token.value);
497
+ if (!Number.isFinite(value) || Number.isInteger(value) && !Number.isSafeInteger(value)) {
498
+ throw new PredicateParseError(
499
+ `numeric literal '${token.value}' exceeds safe integer precision \u2014 quote it as a string`,
500
+ token.position
501
+ );
502
+ }
503
+ return { kind: "literal", value };
504
+ }
505
+ if (token.kind !== "identifier") {
506
+ throw new PredicateParseError(`expected an operand, got '${token.value || "end"}'`, token.position);
507
+ }
508
+ this.advance();
509
+ if (token.value === "true" || token.value === "false") {
510
+ return { kind: "literal", value: token.value === "true" };
511
+ }
512
+ if (token.value === "null") return { kind: "literal", value: null };
513
+ if (token.value === "claim") {
514
+ const key = this.parseCallArgument("claim");
515
+ if (!CLAIM_KEY_PATTERN.test(key)) {
516
+ throw new PredicateParseError(`invalid claim key '${key}'`, token.position);
517
+ }
518
+ return { kind: "claim", key };
519
+ }
520
+ if (token.value === "capability") {
521
+ throw new PredicateParseError("capability() is boolean-valued and cannot appear in a comparison", token.position);
522
+ }
523
+ if (KEYWORDS.has(token.value) || !IDENTIFIER_PATTERN.test(token.value)) {
524
+ throw new PredicateParseError(`invalid column reference '${token.value}'`, token.position);
525
+ }
526
+ return { kind: "column", name: token.value };
527
+ }
528
+ parseCallArgument(fn) {
529
+ this.expect("lparen", `'(' after ${fn}`);
530
+ const argument = this.expect("string", `a quoted key in ${fn}(...)`);
531
+ this.expect("rparen", `')' after ${fn} key`);
532
+ return argument.value;
533
+ }
534
+ peek() {
535
+ return this.tokens[this.index];
536
+ }
537
+ peekKeyword(keyword) {
538
+ const token = this.peek();
539
+ return token.kind === "identifier" && token.value === keyword;
540
+ }
541
+ matchKeyword(keyword) {
542
+ if (!this.peekKeyword(keyword)) return false;
543
+ this.advance();
544
+ return true;
545
+ }
546
+ advance() {
547
+ const token = this.peek();
548
+ if (token.kind !== "end") this.index += 1;
549
+ return token;
550
+ }
551
+ expect(kind, description) {
552
+ const token = this.peek();
553
+ if (token.kind !== kind) {
554
+ throw new PredicateParseError(`expected ${description}, got '${token.value || "end"}'`, token.position);
555
+ }
556
+ return this.advance();
557
+ }
558
+ };
559
+ function parsePredicate(source) {
560
+ const trimmed = source.trim();
561
+ if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
562
+ return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
563
+ }
564
+
565
+ // src/schema/predicates.ts
566
+ var parsedPredicateSchema = zod.z.string().transform((source, ctx) => {
567
+ try {
568
+ return parsePredicate(source);
569
+ } catch (error) {
570
+ ctx.addIssue({ code: "custom", message: error instanceof Error ? error.message : String(error) });
571
+ return zod.z.NEVER;
572
+ }
573
+ });
574
+
575
+ // src/schema/actions.ts
576
+ var EXECUTES_VERB_PATTERN = /^\s*(insert|update|delete)\b/i;
577
+ var DOLLAR_QUOTE_PATTERN = /\$[a-zA-Z0-9_]*\$/;
578
+ var RETURNING_PATTERN = /\breturning\b/i;
579
+ var ESCAPE_STRING_PATTERN = /(?<![a-zA-Z0-9_])[Ee]'/;
580
+ var MASKED_CLAIM_CALL_PATTERN = /\b(?:claim|capability)\(\s*''\s*\)/g;
581
+ var BARE_CLAIM_CALL_PATTERN = /\b(?:claim|capability)\s*\(/;
582
+ var EXISTS_ENTRY_PATTERN = /^exists\(\s*'([^']+)'\s*,\s*([a-z][a-z0-9_]*)\s*\)$/;
583
+ var NAMED_PARAM_PATTERN = /(?<!:):([a-z][a-z0-9_]*)/g;
584
+ var QUOTED_STRING_PATTERN = /'(?:[^']|'')*'/g;
585
+ var SQL_COMMENT_PATTERN = /--|\/\*/;
586
+ var actionParamSchema = zod.z.strictObject({
587
+ type: columnTypeSchema,
588
+ optional: zod.z.boolean().optional(),
589
+ description: zod.z.string().optional()
590
+ });
591
+ var validatesEntrySchema = zod.z.string().transform((source, ctx) => {
592
+ const match = EXISTS_ENTRY_PATTERN.exec(source.trim());
593
+ if (!match) {
594
+ ctx.addIssue({ code: "custom", message: "validates entries must have the shape exists('<table>', <param>)" });
595
+ return zod.z.NEVER;
596
+ }
597
+ const table = tableReferenceSchema.safeParse(match[1]);
598
+ if (!table.success) {
599
+ ctx.addIssue({ code: "custom", message: `invalid table reference '${match[1]}'` });
600
+ return zod.z.NEVER;
601
+ }
602
+ return { source: source.trim(), table: table.data, param: match[2] };
603
+ });
604
+ var AUDIT_ACTIONS = ["create", "update", "delete"];
605
+ var auditSchema = zod.z.strictObject({
606
+ action: zod.z.enum(AUDIT_ACTIONS),
607
+ resource: identifierSchema.optional()
608
+ });
609
+ var actionSchema = zod.z.strictObject({
610
+ description: zod.z.string().optional(),
611
+ requires: parsedPredicateSchema.optional(),
612
+ params: zod.z.record(identifierSchema, actionParamSchema).optional(),
613
+ validates: zod.z.array(validatesEntrySchema).optional(),
614
+ // One statement, or an ordered list run atomically in the action function.
615
+ executes: zod.z.union([zod.z.string().min(1), zod.z.array(zod.z.string().min(1)).min(1)]),
616
+ // Declares the columns the action's RETURNING yields. Present → the
617
+ // function returns those rows (typed in the client); absent → the action
618
+ // reports rows_affected only. The column names/types must match the
619
+ // RETURNING list, which Postgres enforces at CREATE FUNCTION.
620
+ returns: zod.z.record(identifierSchema, columnTypeSchema).optional(),
621
+ audit: auditSchema.optional()
622
+ }).superRefine((action, ctx) => {
623
+ const params = Object.keys(action.params ?? {});
624
+ if (action.requires && referencedColumns(action.requires.ast).length > 0) {
625
+ ctx.addIssue({
626
+ code: "custom",
627
+ message: "requires may reference claims and capabilities only, not columns",
628
+ path: ["requires"]
629
+ });
630
+ }
631
+ for (const issue of executesIssues(action.executes, new Set(params), action.returns !== void 0)) {
632
+ ctx.addIssue({ code: "custom", message: issue, path: ["executes"] });
633
+ }
634
+ if (action.returns && Object.keys(action.returns).length === 0) {
635
+ ctx.addIssue({ code: "custom", message: "returns must declare at least one column", path: ["returns"] });
636
+ }
637
+ for (const [index, entry] of (action.validates ?? []).entries()) {
638
+ if (!params.includes(entry.param)) {
639
+ ctx.addIssue({
640
+ code: "custom",
641
+ message: `validates references undeclared param '${entry.param}'`,
642
+ path: ["validates", index]
643
+ });
644
+ }
645
+ }
646
+ });
647
+ function extractNamedParams(sql) {
648
+ const withoutStrings = sql.replace(QUOTED_STRING_PATTERN, "''");
649
+ const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
650
+ return [...new Set(names)];
651
+ }
652
+ function actionStatementSources(executes) {
653
+ return (Array.isArray(executes) ? executes : [executes]).map((statement) => statement.trim());
654
+ }
655
+ function executesIssues(executes, declaredParams, hasReturns) {
656
+ const issues = [];
657
+ const statements = actionStatementSources(executes);
658
+ statements.forEach((statement, index) => {
659
+ const withoutStrings = statement.replace(QUOTED_STRING_PATTERN, "''");
660
+ const at = statements.length > 1 ? ` (statement ${index + 1})` : "";
661
+ if (!EXECUTES_VERB_PATTERN.test(statement)) issues.push(`executes must be INSERT, UPDATE, or DELETE${at}`);
662
+ if (withoutStrings.replace(/;\s*$/, "").includes(";")) issues.push(`each executes entry must be a single statement${at}`);
663
+ if (SQL_COMMENT_PATTERN.test(withoutStrings)) issues.push(`executes must not contain SQL comments${at}`);
664
+ if (DOLLAR_QUOTE_PATTERN.test(withoutStrings)) issues.push(`executes must not use dollar-quoted strings${at}`);
665
+ if (ESCAPE_STRING_PATTERN.test(withoutStrings)) issues.push(`executes must not use escape strings (E'\u2026')${at}`);
666
+ if (withoutStrings.includes('"')) issues.push(`executes must not use double-quoted identifiers${at}`);
667
+ if (BARE_CLAIM_CALL_PATTERN.test(withoutStrings.replace(MASKED_CLAIM_CALL_PATTERN, ""))) {
668
+ issues.push(`executes has a malformed claim()/capability() call \u2014 use e.g. claim('sub')${at}`);
669
+ }
670
+ if (RETURNING_PATTERN.test(withoutStrings) && !(hasReturns && index === statements.length - 1)) {
671
+ issues.push(
672
+ hasReturns ? `only the last executes statement may use RETURNING${at}` : `executes must not use RETURNING unless the action declares returns${at}`
673
+ );
674
+ }
675
+ });
676
+ const last = statements.at(-1);
677
+ if (hasReturns && last && !RETURNING_PATTERN.test(last.replace(QUOTED_STRING_PATTERN, "''"))) {
678
+ issues.push("the last executes statement must use RETURNING to populate the declared returns columns");
679
+ }
680
+ const undeclared = extractNamedParams(statements.join("\n")).filter((name) => !declaredParams.has(name));
681
+ if (undeclared.length > 0) issues.push(`executes references undeclared params: ${undeclared.join(", ")}`);
682
+ return issues;
683
+ }
684
+
685
+ // src/schema/table.ts
686
+ var linkSchema = zod.z.strictObject({
687
+ to: tableReferenceSchema,
688
+ on: identifierSchema,
689
+ enforce: zod.z.boolean().optional(),
690
+ description: zod.z.string().optional()
691
+ });
692
+ var indexSchema = zod.z.union([
693
+ zod.z.array(identifierSchema).min(1),
694
+ zod.z.strictObject({
695
+ columns: zod.z.array(identifierSchema).min(1),
696
+ unique: zod.z.boolean().optional(),
697
+ name: identifierSchema.optional()
698
+ })
699
+ ]).transform((index) => Array.isArray(index) ? { columns: index } : index);
700
+ var policiesSchema = zod.z.strictObject({
701
+ read: parsedPredicateSchema.optional(),
702
+ write: parsedPredicateSchema.optional(),
703
+ insert: parsedPredicateSchema.optional(),
704
+ update: parsedPredicateSchema.optional()
705
+ });
706
+ zod.z.strictObject({
707
+ namespace: namespaceSchema,
708
+ table: identifierSchema,
709
+ description: zod.z.string().optional(),
710
+ columns: zod.z.record(identifierSchema, columnSchema).optional(),
711
+ source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must be a qualified lowercase schema.table identifier").optional(),
712
+ links: zod.z.record(identifierSchema, linkSchema).optional(),
713
+ indexes: zod.z.array(indexSchema).optional(),
714
+ policies: policiesSchema.optional(),
715
+ actions: zod.z.record(identifierSchema, actionSchema).optional()
716
+ }).superRefine((table, ctx) => {
717
+ if (RESERVED_TABLE_NAMES.has(table.table)) {
718
+ ctx.addIssue({ code: "custom", message: `'${table.table}' is a reserved table name`, path: ["table"] });
719
+ }
720
+ for (const name of Object.keys(table.columns ?? {})) {
721
+ if (RESERVED_COLUMN_NAMES.has(name)) {
722
+ ctx.addIssue({
723
+ code: "custom",
724
+ message: `column '${name}' collides with the Data API's reserved query parameters (select, order, limit, offset) \u2014 it could never be filtered`,
725
+ path: ["columns", name]
726
+ });
727
+ }
728
+ }
729
+ if (table.source) validateSyncedTable(table, ctx);
730
+ else validateColumnTable(table, ctx);
731
+ validateLinks(table, ctx);
732
+ validatePolicyColumns(table, ctx);
733
+ validateDerivedNameLengths(table, ctx);
734
+ });
735
+ var indexName = (table, definition) => definition.name ?? `ix_${table}_${definition.columns.join("_")}`;
736
+ var policyName = (table, suffix) => `${table}_${suffix}`;
737
+ var foreignKeyName = (table, link) => `fk_${table}_${link}`;
738
+ function validateSyncedTable(table, ctx) {
739
+ const forbidden = [
740
+ ["actions", "synced tables are read-only; actions are not allowed"],
741
+ ["indexes", "v1 synced tables cannot declare indexes; declare them with defineSyncedTable({ indexes }) in a v2 definition"]
742
+ ];
743
+ for (const [field, message] of forbidden) {
744
+ if (table[field] !== void 0) ctx.addIssue({ code: "custom", message, path: [field] });
745
+ }
746
+ if (table.policies?.write) {
747
+ ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "write"] });
748
+ }
749
+ if (table.policies?.insert) {
750
+ ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "insert"] });
751
+ }
752
+ if (table.policies?.update) {
753
+ ctx.addIssue({ code: "custom", message: "synced tables are read-only", path: ["policies", "update"] });
754
+ }
755
+ const columns = table.columns ?? {};
756
+ const names = Object.keys(columns);
757
+ if (names.length === 0) return;
758
+ const primaryKeys = names.filter((name) => columns[name]?.primary_key);
759
+ if (primaryKeys.length !== 1) {
760
+ ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
761
+ }
762
+ validateRenames(columns, names, ctx);
763
+ for (const name of names) {
764
+ const column = columns[name];
765
+ if (!column) continue;
766
+ const cast = column.cast ? castParts(column.cast) : null;
767
+ if (column.cast && cast?.column !== name) {
768
+ ctx.addIssue({
769
+ code: "custom",
770
+ message: "cast must reference the column it is declared on",
771
+ path: ["columns", name, "cast"]
772
+ });
773
+ }
774
+ if (cast && cast.type !== column.type) {
775
+ ctx.addIssue({
776
+ code: "custom",
777
+ message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
778
+ path: ["columns", name, "cast"]
779
+ });
780
+ }
781
+ }
782
+ }
783
+ function validateRenames(columns, names, ctx) {
784
+ const renameSources = /* @__PURE__ */ new Map();
785
+ for (const name of names) {
786
+ const renamedFrom = columns[name]?.renamed_from;
787
+ if (!renamedFrom) continue;
788
+ if (renamedFrom === name || names.includes(renamedFrom)) {
789
+ ctx.addIssue({
790
+ code: "custom",
791
+ message: "renamed_from must name a column that no longer exists",
792
+ path: ["columns", name, "renamed_from"]
793
+ });
794
+ }
795
+ const claimed = renameSources.get(renamedFrom);
796
+ if (claimed) {
797
+ ctx.addIssue({
798
+ code: "custom",
799
+ message: `renamed_from '${renamedFrom}' is already claimed by '${claimed}'`,
800
+ path: ["columns", name, "renamed_from"]
801
+ });
802
+ }
803
+ renameSources.set(renamedFrom, name);
804
+ }
805
+ }
806
+ function validateColumnTable(table, ctx) {
807
+ const columns = table.columns ?? {};
808
+ const names = Object.keys(columns);
809
+ if (names.length === 0) {
810
+ ctx.addIssue({ code: "custom", message: "a table must declare columns or a source", path: ["columns"] });
811
+ return;
812
+ }
813
+ const primaryKeys = names.filter((name) => columns[name]?.primary_key);
814
+ if (primaryKeys.length !== 1) {
815
+ ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
816
+ }
817
+ validateRenames(columns, names, ctx);
818
+ for (const name of names) {
819
+ const column = columns[name];
820
+ if (!column) continue;
821
+ const cast = column.cast ? castParts(column.cast) : null;
822
+ if (column.cast && cast?.column !== name) {
823
+ ctx.addIssue({
824
+ code: "custom",
825
+ message: "cast must reference the column it is declared on",
826
+ path: ["columns", name, "cast"]
827
+ });
828
+ }
829
+ if (cast && cast.type !== column.type) {
830
+ ctx.addIssue({
831
+ code: "custom",
832
+ message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
833
+ path: ["columns", name, "cast"]
834
+ });
835
+ }
836
+ }
837
+ for (const [position, index] of (table.indexes ?? []).entries()) {
838
+ if (new Set(index.columns).size !== index.columns.length) {
839
+ ctx.addIssue({
840
+ code: "custom",
841
+ message: "index names a column more than once",
842
+ path: ["indexes", position]
843
+ });
844
+ }
845
+ for (const column of index.columns) {
846
+ if (!names.includes(column)) {
847
+ ctx.addIssue({
848
+ code: "custom",
849
+ message: `index references unknown column '${column}'`,
850
+ path: ["indexes", position]
851
+ });
852
+ }
853
+ }
854
+ }
855
+ }
856
+ function validateLinks(table, ctx) {
857
+ const columnNames = Object.keys(table.columns ?? {});
858
+ for (const [name, link] of Object.entries(table.links ?? {})) {
859
+ if (columnNames.length > 0 && !columnNames.includes(link.on)) {
860
+ ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links", name, "on"] });
861
+ }
862
+ if (link.enforce && link.to.includes(".")) {
863
+ ctx.addIssue({
864
+ code: "custom",
865
+ message: "enforce is only valid for links inside the namespace; cross-boundary links stay soft",
866
+ path: ["links", name, "enforce"]
867
+ });
868
+ }
869
+ if (link.enforce && table.source) {
870
+ ctx.addIssue({
871
+ code: "custom",
872
+ message: "a synced table cannot enforce foreign keys \u2014 its DDL is owned by the sync",
873
+ path: ["links", name, "enforce"]
874
+ });
875
+ }
876
+ }
877
+ }
878
+ function validateDerivedNameLengths(table, ctx) {
879
+ const worstPolicy = policyName(table.table, "insert");
880
+ if (!table.source && worstPolicy.length > MAX_IDENTIFIER_LENGTH) {
881
+ ctx.addIssue({
882
+ code: "custom",
883
+ message: `table name is too long \u2014 the derived policy name '${worstPolicy}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit`,
884
+ path: ["table"]
885
+ });
886
+ }
887
+ for (const [position, index] of (table.indexes ?? []).entries()) {
888
+ const name = indexName(table.table, index);
889
+ if (name.length > MAX_IDENTIFIER_LENGTH) {
890
+ ctx.addIssue({
891
+ code: "custom",
892
+ message: `derived index name '${name}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`,
893
+ path: ["indexes", position]
894
+ });
895
+ }
896
+ }
897
+ for (const [name, link] of Object.entries(table.links ?? {})) {
898
+ const constraint = foreignKeyName(table.table, name);
899
+ if (link.enforce && constraint.length > MAX_IDENTIFIER_LENGTH) {
900
+ ctx.addIssue({
901
+ code: "custom",
902
+ message: `derived foreign-key name '${constraint}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the link or table name`,
903
+ path: ["links", name]
904
+ });
905
+ }
906
+ }
907
+ for (const name of Object.keys(table.actions ?? {})) {
908
+ const fnName = `action_${name}`;
909
+ if (fnName.length > MAX_IDENTIFIER_LENGTH) {
910
+ ctx.addIssue({
911
+ code: "custom",
912
+ message: `derived action function name '${fnName}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the action name`,
913
+ path: ["actions", name]
914
+ });
915
+ }
916
+ }
917
+ }
918
+ var TYPE_GROUPS = {
919
+ integer: "numeric",
920
+ bigint: "numeric",
921
+ numeric: "numeric",
922
+ double: "numeric",
923
+ text: "text",
924
+ uuid: "uuid",
925
+ date: "temporal",
926
+ timestamptz: "temporal",
927
+ boolean: "boolean",
928
+ jsonb: "jsonb"
929
+ };
930
+ function validatePolicyColumns(table, ctx) {
931
+ const columns = table.columns ?? {};
932
+ const columnNames = Object.keys(columns);
933
+ if (columnNames.length === 0) return;
934
+ for (const kind of ["read", "write", "insert", "update"]) {
935
+ const predicate = table.policies?.[kind];
936
+ if (!predicate) continue;
937
+ for (const column of referencedColumns(predicate.ast)) {
938
+ if (!columnNames.includes(column)) {
939
+ ctx.addIssue({
940
+ code: "custom",
941
+ message: `policy references unknown column '${column}'`,
942
+ path: ["policies", kind]
943
+ });
944
+ }
945
+ }
946
+ walkPredicate(predicate.ast, (node) => {
947
+ if (node.kind !== "compare") return;
948
+ const issue = comparisonTypeIssue(node.left, node.right, columns);
949
+ if (issue) ctx.addIssue({ code: "custom", message: issue, path: ["policies", kind] });
950
+ });
951
+ }
952
+ }
953
+ function comparisonTypeIssue(left, right, columns) {
954
+ const groupOf = (operand) => operand.kind === "column" ? columns[operand.name] ? TYPE_GROUPS[columns[operand.name].type] : null : null;
955
+ if (left.kind === "column" && right.kind === "column") {
956
+ const leftGroup = groupOf(left);
957
+ const rightGroup = groupOf(right);
958
+ if (leftGroup && rightGroup && leftGroup !== rightGroup) {
959
+ return `policy compares '${left.name}' (${columns[left.name].type}) with '${right.name}' (${columns[right.name].type}) \u2014 incomparable types`;
960
+ }
961
+ return null;
962
+ }
963
+ const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
964
+ const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
965
+ if (!column || !literal || literal.value === null) return null;
966
+ const columnGroup = groupOf(column);
967
+ if (!columnGroup) return null;
968
+ if (typeof literal.value === "string") return null;
969
+ const literalGroup = typeof literal.value === "number" ? "numeric" : "boolean";
970
+ if (literalGroup !== columnGroup) {
971
+ return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal.value} literal \u2014 incomparable types`;
972
+ }
973
+ return null;
974
+ }
227
975
  var policyVariantSchema = zod.z.object({
228
976
  /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
229
977
  when: zod.z.string().min(1).max(1e3),
@@ -532,7 +1280,7 @@ var CANONICAL_FORMAT = 2;
532
1280
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
533
1281
  var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
534
1282
  var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
535
- var COLUMN_TYPES = [
1283
+ var COLUMN_TYPES2 = [
536
1284
  "uuid",
537
1285
  "text",
538
1286
  "integer",
@@ -544,7 +1292,7 @@ var COLUMN_TYPES = [
544
1292
  "date",
545
1293
  "jsonb"
546
1294
  ];
547
- var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
1295
+ var columnTypeSchema2 = zod.z.enum(COLUMN_TYPES2);
548
1296
  var canonicalEnumSchema = zod.z.object({
549
1297
  name: identifier2,
550
1298
  values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
@@ -553,7 +1301,7 @@ var canonicalColumnSchema = zod.z.object({
553
1301
  /** Property name in TS (camelCase); columnName is the Postgres name. */
554
1302
  name: zod.z.string().min(1).max(63),
555
1303
  columnName: columnIdentifier,
556
- type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
1304
+ type: zod.z.union([columnTypeSchema2, zod.z.object({ enum: identifier2 })]),
557
1305
  nullable: zod.z.boolean().default(true),
558
1306
  unique: zod.z.boolean().default(false),
559
1307
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -646,7 +1394,7 @@ var canonicalActionSchema = zod.z.object({
646
1394
  name: identifier2,
647
1395
  requires: zod.z.string().max(1e3).optional(),
648
1396
  params: zod.z.record(zod.z.string(), zod.z.object({
649
- type: columnTypeSchema,
1397
+ type: columnTypeSchema2,
650
1398
  nullable: zod.z.boolean().default(false)
651
1399
  })).default({}),
652
1400
  executes: zod.z.string().min(1).max(1e4),
@@ -747,7 +1495,8 @@ var canonicalTableSchema = zod.z.object({
747
1495
  if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
748
1496
  fail("is read-only and cannot declare write policies", ["policies"]);
749
1497
  }
750
- const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
1498
+ const ownedIndexes = table.source === void 0 ? table.indexes : table.indexes.filter((index) => index.unique);
1499
+ const ownedCollections = [ownedIndexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
751
1500
  const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
752
1501
  if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
753
1502
  fail("cannot declare owned storage, constraints or identity operations");
@@ -849,6 +1598,23 @@ function trackedCellColumns(table) {
849
1598
  return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
850
1599
  }
851
1600
  var SUFFIXES = Object.values(CELL_PART_SUFFIX);
1601
+ function validateSyncedIndexes(table, tableIndex, ctx) {
1602
+ if (table.source === void 0) return;
1603
+ const columns = new Set(table.columns.map((column) => column.columnName));
1604
+ for (const [position, index] of table.indexes.entries()) {
1605
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "indexes", position], message: `synced table '${table.table}' ${message}` });
1606
+ if (columns.size === 0) fail("must declare columns to declare indexes");
1607
+ if (index.unique) fail("cannot declare a unique index");
1608
+ if (new Set(index.columns).size !== index.columns.length) fail("index names a column more than once");
1609
+ for (const column of index.columns) {
1610
+ if (columns.size > 0 && !columns.has(column)) fail(`index references unknown column '${column}'`);
1611
+ }
1612
+ const name = indexName(table.table, index);
1613
+ if (name.length > MAX_IDENTIFIER_LENGTH) {
1614
+ fail(`derived index name '${name}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`);
1615
+ }
1616
+ }
1617
+ }
852
1618
  function validateCellMetadata(table, index, ctx) {
853
1619
  const fail = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
854
1620
  const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
@@ -915,12 +1681,24 @@ var localDefinitionSchema = zod.z.object({
915
1681
  }).default({ uses: [] })
916
1682
  }).superRefine((definition, ctx) => {
917
1683
  const seen = /* @__PURE__ */ new Set();
1684
+ const syncedIndexOwners = /* @__PURE__ */ new Map();
918
1685
  for (const [index, table] of definition.tables.entries()) {
919
1686
  if (seen.has(table.table)) {
920
1687
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
921
1688
  }
922
1689
  seen.add(table.table);
923
1690
  validateCellMetadata(table, index, ctx);
1691
+ validateSyncedIndexes(table, index, ctx);
1692
+ if (table.source !== void 0) {
1693
+ for (const [position, entry] of table.indexes.entries()) {
1694
+ const name = indexName(table.table, entry);
1695
+ const owner = syncedIndexOwners.get(name);
1696
+ if (owner !== void 0) {
1697
+ ctx.addIssue({ code: "custom", path: ["tables", index, "indexes", position], message: `synced index '${name}' is also declared on '${owner}'` });
1698
+ }
1699
+ syncedIndexOwners.set(name, table.table);
1700
+ }
1701
+ }
924
1702
  if (table.source !== void 0) {
925
1703
  for (const [columnIndex, column] of table.columns.entries()) {
926
1704
  if (column.onUpdateFn !== void 0) {
@@ -1224,7 +2002,7 @@ function graphqlScalarFor(type) {
1224
2002
  }
1225
2003
  var COLUMN_TYPE_BY_SCALAR = new Map([
1226
2004
  ["ID", "text"],
1227
- ...COLUMN_TYPES.map((type) => [graphqlScalarFor(type), type])
2005
+ ...COLUMN_TYPES2.map((type) => [graphqlScalarFor(type), type])
1228
2006
  ]);
1229
2007
  function columnTypeForScalar(scalarName) {
1230
2008
  return COLUMN_TYPE_BY_SCALAR.get(scalarName);
@@ -1296,11 +2074,6 @@ var CELL_TYPENAMES = {
1296
2074
  function cellsTypename(typename) {
1297
2075
  return `${typename}Cells`;
1298
2076
  }
1299
-
1300
- // src/client/sql.ts
1301
- var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
1302
-
1303
- // src/ir/index.ts
1304
2077
  var LEGACY_IR_VERSION = 1;
1305
2078
  var GUARDED_IR_VERSION = 2;
1306
2079
  var COMPOSED_IR_VERSION = 3;
@@ -1570,7 +2343,7 @@ var variableDefaultIrSchema = zod.z.object({
1570
2343
  var variableIrSchema = zod.z.object({
1571
2344
  name: zod.z.string(),
1572
2345
  type: zod.z.union([
1573
- zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(COLUMN_TYPES) }),
2346
+ zod.z.object({ kind: zod.z.literal("scalar"), scalar: zod.z.enum(COLUMN_TYPES2) }),
1574
2347
  /** `values` is the declared membership whitelist — the executor validates against it. */
1575
2348
  zod.z.object({ kind: zod.z.literal("enum"), name: zod.z.string(), values: zod.z.array(zod.z.string()).min(1) }),
1576
2349
  /** §4f dynamic slot: validated against `tables[table]` whitelists at runtime. */
@@ -1608,8 +2381,7 @@ var compiledOperationSchema = zod.z.object({
1608
2381
  maxRows: zod.z.number().int().min(1).max(1e4)
1609
2382
  }).strict().optional(),
1610
2383
  irVersion: irVersionSchema,
1611
- name: zod.z.string(),
1612
- opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
2384
+ name: zod.z.string().regex(/^[_A-Za-z][_0-9A-Za-z]{0,254}$/),
1613
2385
  kind: zod.z.enum(["query", "mutation"]),
1614
2386
  requires: zod.z.object({
1615
2387
  capability: zod.z.string().optional(),
@@ -1625,7 +2397,7 @@ var compiledOperationSchema = zod.z.object({
1625
2397
  }
1626
2398
  if (operation.language === "sql") {
1627
2399
  const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
1628
- const builtin = operation.name === "BuiltinSqlRead" && operation.opHash === BUILTIN_SQL_READ_HASH && operation.requires === null;
2400
+ const builtin = operation.name === "BuiltinSqlRead" && operation.requires === null;
1629
2401
  if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !builtin && !operation.requires?.capability) {
1630
2402
  ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
1631
2403
  }
@@ -1640,7 +2412,7 @@ var compiledOperationSchema = zod.z.object({
1640
2412
  }
1641
2413
  });
1642
2414
  var resolvedSourceIrSchema = zod.z.object({
1643
- name: zod.z.string(),
2415
+ name: zod.z.string().regex(/^[_A-Za-z][_0-9A-Za-z]{0,254}$/),
1644
2416
  kind: zod.z.enum(["manual", "etl", "prefetch"]),
1645
2417
  /** Logical table key of the backing relation. */
1646
2418
  table: zod.z.string(),
@@ -1703,7 +2475,7 @@ var tableMetaIrSchema = zod.z.object({
1703
2475
  columns: zod.z.array(zod.z.object({
1704
2476
  name: zod.z.string(),
1705
2477
  columnName: zod.z.string(),
1706
- type: zod.z.union([zod.z.enum(COLUMN_TYPES), zod.z.object({ enum: zod.z.string(), values: zod.z.array(zod.z.string()) })]),
2478
+ type: zod.z.union([zod.z.enum(COLUMN_TYPES2), zod.z.object({ enum: zod.z.string(), values: zod.z.array(zod.z.string()) })]),
1707
2479
  nullable: zod.z.boolean()
1708
2480
  })),
1709
2481
  /** Empty for keyless read-only views; keyed tables and views list logical columns. */
@@ -1754,7 +2526,6 @@ zod.z.object({
1754
2526
  ownSchemaHash: zod.z.string().optional(),
1755
2527
  dependencies: zod.z.record(zod.z.string(), zod.z.string()).optional(),
1756
2528
  readPolicies: zod.z.record(zod.z.string(), predIrSchema).optional(),
1757
- opsHash: zod.z.string(),
1758
2529
  /** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
1759
2530
  unbackedTables: zod.z.array(zod.z.string()).optional(),
1760
2531
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
@@ -1772,15 +2543,6 @@ zod.z.object({
1772
2543
  }
1773
2544
  }
1774
2545
  });
1775
-
1776
- // src/errors.ts
1777
- var OrmDefinitionError = class extends Error {
1778
- constructor(message, file) {
1779
- super(file ? `${file}: ${message}` : message);
1780
- this.file = file;
1781
- }
1782
- name = "OrmDefinitionError";
1783
- };
1784
2546
  function stableJson(value) {
1785
2547
  if (value === null || typeof value !== "object") return JSON.stringify(value);
1786
2548
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
@@ -2526,7 +3288,7 @@ var manifestOperationSchema = zod.z.object({
2526
3288
  requires: manifestRequiresSchema
2527
3289
  });
2528
3290
  var operationsManifestSchema = zod.z.object({
2529
- manifestVersion: zod.z.union([zod.z.literal(1), zod.z.literal(MANIFEST_VERSION)]),
3291
+ manifestVersion: zod.z.literal(MANIFEST_VERSION),
2530
3292
  operations: zod.z.record(zod.z.string().regex(GRAPHQL_NAME_RE2), manifestOperationSchema)
2531
3293
  });
2532
3294
  function buildOperationsManifest({