@sequenceholdings/orm 2.3.0 → 2.5.0

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 (59) hide show
  1. package/dist/authoring.cjs +53 -7
  2. package/dist/authoring.cjs.map +1 -1
  3. package/dist/authoring.d.cts +11 -7
  4. package/dist/authoring.d.ts +11 -7
  5. package/dist/authoring.js +53 -7
  6. package/dist/authoring.js.map +1 -1
  7. package/dist/canonical.cjs +48 -2
  8. package/dist/canonical.cjs.map +1 -1
  9. package/dist/canonical.d.cts +72 -72
  10. package/dist/canonical.d.ts +72 -72
  11. package/dist/canonical.js +48 -2
  12. package/dist/canonical.js.map +1 -1
  13. package/dist/cli.cjs +337 -86
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +337 -86
  16. package/dist/cli.js.map +1 -1
  17. package/dist/client.cjs +1 -1
  18. package/dist/client.cjs.map +1 -1
  19. package/dist/client.d.cts +1 -1
  20. package/dist/client.d.ts +1 -1
  21. package/dist/client.js +1 -1
  22. package/dist/client.js.map +1 -1
  23. package/dist/compiler.cjs +493 -52
  24. package/dist/compiler.cjs.map +1 -1
  25. package/dist/compiler.d.cts +22 -7
  26. package/dist/compiler.d.ts +22 -7
  27. package/dist/compiler.js +493 -53
  28. package/dist/compiler.js.map +1 -1
  29. package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
  30. package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
  31. package/dist/dynamic-input.d.cts +1 -1
  32. package/dist/dynamic-input.d.ts +1 -1
  33. package/dist/index.cjs +329 -130
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +66 -58
  36. package/dist/index.d.ts +66 -58
  37. package/dist/index.js +329 -131
  38. package/dist/index.js.map +1 -1
  39. package/dist/ir.cjs +50 -3
  40. package/dist/ir.cjs.map +1 -1
  41. package/dist/ir.d.cts +71 -71
  42. package/dist/ir.d.ts +71 -71
  43. package/dist/ir.js +50 -3
  44. package/dist/ir.js.map +1 -1
  45. package/dist/predicate-v2.cjs +145 -22
  46. package/dist/predicate-v2.cjs.map +1 -1
  47. package/dist/predicate-v2.d.cts +41 -4
  48. package/dist/predicate-v2.d.ts +41 -4
  49. package/dist/predicate-v2.js +145 -22
  50. package/dist/predicate-v2.js.map +1 -1
  51. package/dist/sdl.cjs +53 -3
  52. package/dist/sdl.cjs.map +1 -1
  53. package/dist/sdl.d.cts +3 -1
  54. package/dist/sdl.d.ts +3 -1
  55. package/dist/sdl.js +53 -3
  56. package/dist/sdl.js.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
  59. package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
package/dist/cli.js CHANGED
@@ -56,18 +56,22 @@ var init_rehearsal_poll = __esm({
56
56
  });
57
57
 
58
58
  // src/constants.ts
59
- var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
59
+ var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
60
60
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
61
61
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
62
62
  var MAX_IDENTIFIER_LENGTH = 63;
63
63
  var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
64
+ var MAX_PERSISTED_READ_ROWS = 50001;
64
65
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
65
66
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
66
67
  var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
67
68
  var ORM_SCHEMA_PREFIX = "orm_";
68
69
  var CONSUMER_ROLE_SUFFIX = "_consumer";
69
- var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
70
- var consumerRoleName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}${CONSUMER_ROLE_SUFFIX}`;
70
+ var ormSchemaName = (namespace) => {
71
+ if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
72
+ return `${ORM_SCHEMA_PREFIX}${namespace}`;
73
+ };
74
+ var consumerRoleName = (namespace) => `${ormSchemaName(namespace)}${CONSUMER_ROLE_SUFFIX}`;
71
75
  var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
72
76
  var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
73
77
  var HASH_DOMAIN = "sequence-orm-definitions\0";
@@ -535,6 +539,7 @@ function validateResolvedView({ view, tables, views = [] }) {
535
539
  function validateSource({ scope, source, table }) {
536
540
  const { fail: fail4 } = scope;
537
541
  if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
542
+ if (source.kind === "etl" && table.primaryKey?.length === 0) fail4(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
538
543
  if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
539
544
  fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
540
545
  }
@@ -983,7 +988,7 @@ var canonicalTableSchema = z.object({
983
988
  /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
984
989
  source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
985
990
  columns: z.array(canonicalColumnSchema).default([]),
986
- primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
991
+ primaryKey: z.array(columnIdentifier).max(8).optional(),
987
992
  links: z.array(canonicalLinkSchema).default([]),
988
993
  indexes: z.array(canonicalIndexSchema).default([]),
989
994
  /** Omitted by old definitions, preserving their existing content hashes. */
@@ -1019,6 +1024,29 @@ var canonicalTableSchema = z.object({
1019
1024
  * data plane. Omitted otherwise.
1020
1025
  */
1021
1026
  materializes: identifier2.optional()
1027
+ }).superRefine((table, ctx) => {
1028
+ if (table.primaryKey?.length !== 0) return;
1029
+ const fail4 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
1030
+ if (table.source === void 0 || table.columns.length === 0) {
1031
+ fail4("requires a typed synced source");
1032
+ }
1033
+ if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
1034
+ fail4("is read-only and cannot declare write policies", ["policies"]);
1035
+ }
1036
+ const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
1037
+ const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
1038
+ if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
1039
+ fail4("cannot declare owned storage, constraints or identity operations");
1040
+ }
1041
+ for (const [index3, column] of table.columns.entries()) {
1042
+ const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
1043
+ if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
1044
+ fail4("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index3]);
1045
+ }
1046
+ }
1047
+ for (const [index3, link] of table.links.entries()) {
1048
+ if (link.enforce || link.kind === "toMany") fail4("cannot provide an identity for this link", ["links", index3]);
1049
+ }
1022
1050
  });
1023
1051
  var MAX_VIEW_QUERY = 1e5;
1024
1052
  var MAX_RESOLVED_VIEW_QUERY = 5e6;
@@ -1075,6 +1103,26 @@ var canonicalViewSchema = z.object({
1075
1103
  }
1076
1104
  }
1077
1105
  });
1106
+ function validateUnkeyedLinkIdentities({ definition, ctx }) {
1107
+ const tables = new Map(definition.tables.map((table) => [table.table, table]));
1108
+ const relations = [
1109
+ ...definition.tables.map((table, index3) => ({ kind: "tables", index: index3, links: table.links })),
1110
+ ...(definition.views ?? []).map((view, index3) => ({ kind: "views", index: index3, links: view.links }))
1111
+ ];
1112
+ for (const relation of relations) {
1113
+ for (const [linkIndex, link] of relation.links.entries()) {
1114
+ if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
1115
+ const target = link.target === void 0 ? void 0 : tables.get(link.target);
1116
+ if (target?.source !== void 0 && target.primaryKey?.length === 0) {
1117
+ ctx.addIssue({
1118
+ code: "custom",
1119
+ path: [relation.kind, relation.index, "links", linkIndex],
1120
+ message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
1121
+ });
1122
+ }
1123
+ }
1124
+ }
1125
+ }
1078
1126
  var CELL_PART_TYPES = {
1079
1127
  at: "timestamptz",
1080
1128
  by: "text",
@@ -1138,7 +1186,7 @@ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string
1138
1186
  var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
1139
1187
  var localDefinitionSchema = z.object({
1140
1188
  definition_format: z.literal(CANONICAL_FORMAT),
1141
- namespace: identifier2.max(41),
1189
+ namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
1142
1190
  description: z.string().max(2e3).optional(),
1143
1191
  enums: z.array(canonicalEnumSchema).default([]),
1144
1192
  tables: z.array(canonicalTableSchema).min(0).max(500),
@@ -1172,6 +1220,7 @@ var localDefinitionSchema = z.object({
1172
1220
  }
1173
1221
  }
1174
1222
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1223
+ validateUnkeyedLinkIdentities({ definition, ctx });
1175
1224
  const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1176
1225
  for (const name2 of Object.keys(definition.exports ?? {})) {
1177
1226
  if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
@@ -1494,7 +1543,7 @@ var limitIrSchema = z.object({
1494
1543
  variable: z.string().optional(),
1495
1544
  value: z.number().int().positive().optional(),
1496
1545
  default: z.number().int().positive(),
1497
- max: z.number().int().positive()
1546
+ max: z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
1498
1547
  });
1499
1548
  var selectionIrSchema = z.lazy(
1500
1549
  () => z.object({
@@ -2177,6 +2226,7 @@ var Parser2 = class {
2177
2226
  return node;
2178
2227
  }
2179
2228
  if (this.peekKeyword("exists")) return this.parseExists();
2229
+ if (this.peekKeyword("visible") && this.tokens[this.index + 1]?.kind === "lparen") return this.parseVisible();
2180
2230
  return this.parseComparison();
2181
2231
  }
2182
2232
  parseBooleanCall(fn) {
@@ -2196,6 +2246,13 @@ var Parser2 = class {
2196
2246
  this.expect("rparen", "')' closing exists(...)");
2197
2247
  return { kind: "exists", path, where };
2198
2248
  }
2249
+ parseVisible() {
2250
+ this.advance();
2251
+ this.expect("lparen", "'(' after visible");
2252
+ const path = this.parsePath("a link path in visible(...)");
2253
+ this.expect("rparen", "')' closing visible(...)");
2254
+ return { kind: "visible", path };
2255
+ }
2199
2256
  parseComparison() {
2200
2257
  const left = this.parseOperand();
2201
2258
  const operator = this.expect("operator", "a comparison operator");
@@ -2311,6 +2368,24 @@ function parsePolicy(source) {
2311
2368
 
2312
2369
  // src/predicate-v2/compile.ts
2313
2370
  var POLICY_MAX_LINK_DEPTH = 3;
2371
+ function scopeToNamespace(pred, namespace) {
2372
+ switch (pred.kind) {
2373
+ case "capability":
2374
+ case "role":
2375
+ return pred.namespace ? pred : { ...pred, namespace };
2376
+ case "and":
2377
+ case "or":
2378
+ return { ...pred, operands: pred.operands.map((operand) => scopeToNamespace(operand, namespace)) };
2379
+ case "not":
2380
+ return { ...pred, operand: scopeToNamespace(pred.operand, namespace) };
2381
+ case "exists":
2382
+ return { ...pred, where: scopeToNamespace(pred.where, namespace) };
2383
+ case "aggPredicate":
2384
+ return pred.where ? { ...pred, where: scopeToNamespace(pred.where, namespace) } : pred;
2385
+ default:
2386
+ return pred;
2387
+ }
2388
+ }
2314
2389
  var CMP_BY_OP = {
2315
2390
  "=": "eq",
2316
2391
  "!=": "neq",
@@ -2344,7 +2419,7 @@ function resolveLink(info, ref) {
2344
2419
  function columnCandidates(info) {
2345
2420
  return [...info.columns.map((c) => c.name), ...info.links.map((l) => l.name)];
2346
2421
  }
2347
- var PolicyCompiler = class {
2422
+ var PolicyCompiler = class _PolicyCompiler {
2348
2423
  constructor(ctx) {
2349
2424
  this.ctx = ctx;
2350
2425
  }
@@ -2357,13 +2432,15 @@ var PolicyCompiler = class {
2357
2432
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };
2358
2433
  case "not":
2359
2434
  assertNoNegatedExternalClaim(node.operand);
2360
- return { kind: "not", operand: this.compile(node.operand, table, depth) };
2435
+ return { kind: "not", operand: new _PolicyCompiler({ ...this.ctx, negated: true }).compile(node.operand, table, depth) };
2361
2436
  case "capability":
2362
2437
  return { kind: "capability", key: node.key };
2363
2438
  case "role":
2364
2439
  return { kind: "role", key: node.key };
2365
2440
  case "exists":
2366
2441
  return this.compileExists(node.path, node.where, table, depth);
2442
+ case "visible":
2443
+ return this.compileVisible(node.path, table, depth);
2367
2444
  case "compare":
2368
2445
  return this.compileCompare(node, table, depth);
2369
2446
  }
@@ -2377,6 +2454,36 @@ var PolicyCompiler = class {
2377
2454
  }
2378
2455
  return pred;
2379
2456
  }
2457
+ /** `visible(path)` → `exists(path, <final target's read policy>)`. */
2458
+ compileVisible(path, table, depth) {
2459
+ const { links, finalTable } = this.resolveLinkPath(path, table, depth, { toOneOnly: false });
2460
+ const readPolicy = this.ctx.resolver.readPolicy;
2461
+ if (!readPolicy) {
2462
+ throw new PolicyCompileError(
2463
+ `visible(${path.join(".")}) needs the target's read policy, which this compile context does not provide`,
2464
+ "visible-unresolvable"
2465
+ );
2466
+ }
2467
+ if (this.ctx.visiting.includes(finalTable)) {
2468
+ throw new PolicyCompileError(
2469
+ `visible(${path.join(".")}) on '${table}' reaches '${finalTable}' whose read policy is already being expanded (${[...this.ctx.visiting, finalTable].join(" \u2192 ")})`,
2470
+ "visible-cycle"
2471
+ );
2472
+ }
2473
+ const expression = readPolicy(finalTable);
2474
+ let pred = expression === void 0 ? { kind: "const", value: true } : compilePolicyAt({
2475
+ expression,
2476
+ table: finalTable,
2477
+ ctx: { ...this.ctx, visiting: [...this.ctx.visiting, finalTable] },
2478
+ depth: depth + links.length
2479
+ });
2480
+ const owner = this.ctx.resolver.policyNamespace?.(finalTable);
2481
+ if (owner) pred = scopeToNamespace(pred, owner);
2482
+ for (let i = links.length - 1; i >= 0; i -= 1) {
2483
+ pred = { kind: "exists", link: links[i].name, where: pred };
2484
+ }
2485
+ return pred;
2486
+ }
2380
2487
  resolveLinkPath(path, table, depth, { toOneOnly }) {
2381
2488
  const links = [];
2382
2489
  let current = table;
@@ -2437,6 +2544,7 @@ var PolicyCompiler = class {
2437
2544
  );
2438
2545
  }
2439
2546
  if (valueSide.kind === "claim" && isExternalPolicyClaimKey(valueSide.key)) {
2547
+ if (this.ctx.negated) throw negatedExternalClaim(valueSide.key);
2440
2548
  assertExternalClaimComparison({ op, key: valueSide.key, column: info.columns.find((c) => c.name === column) });
2441
2549
  }
2442
2550
  let cmp = this.buildCmp(op, column, valueSide);
@@ -2474,22 +2582,22 @@ function assertExternalClaimComparison({ op, key, column }) {
2474
2582
  );
2475
2583
  }
2476
2584
  }
2585
+ function negatedExternalClaim(key) {
2586
+ return new PolicyCompileError(
2587
+ `claim('${key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2588
+ "unsupported-comparison"
2589
+ );
2590
+ }
2477
2591
  function assertNoNegatedExternalClaim(operand) {
2478
2592
  walkPolicy(operand, (node) => {
2479
2593
  if (node.kind !== "compare") return;
2480
2594
  const claim = [node.left, node.right].find((side) => side.kind === "claim" && isExternalPolicyClaimKey(side.key));
2481
- if (claim?.kind === "claim") {
2482
- throw new PolicyCompileError(
2483
- `claim('${claim.key}') cannot appear under not(): a negated borrower comparison admits other borrowers' rows`,
2484
- "unsupported-comparison"
2485
- );
2486
- }
2595
+ if (claim?.kind === "claim") throw negatedExternalClaim(claim.key);
2487
2596
  });
2488
2597
  }
2489
- function compilePredicateV2({ expression, table, resolver, maxLinkDepth }) {
2598
+ function compilePredicateAt({ expression, table, ctx, depth }) {
2490
2599
  const parsed = typeof expression === "string" ? parsePolicy(expression) : expression;
2491
- const compiler = new PolicyCompiler({ resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH });
2492
- return compiler.compile(parsed.ast, table, 0);
2600
+ return new PolicyCompiler(ctx).compile(parsed.ast, table, depth);
2493
2601
  }
2494
2602
  function assertClaimsOnlyWhen(parsed) {
2495
2603
  walkPolicy(parsed.ast, (node) => {
@@ -2506,25 +2614,34 @@ function assertClaimsOnlyWhen(parsed) {
2506
2614
  "when-not-claims-only"
2507
2615
  );
2508
2616
  }
2509
- if (node.kind === "exists") {
2617
+ if (node.kind === "exists" || node.kind === "visible") {
2510
2618
  throw new PolicyCompileError(
2511
- "variant 'when' guards are claims-only \u2014 exists() is not allowed",
2619
+ `variant 'when' guards are claims-only \u2014 ${node.kind}() is not allowed`,
2512
2620
  "when-not-claims-only"
2513
2621
  );
2514
2622
  }
2515
2623
  });
2516
2624
  }
2517
- function compilePolicy({ expression, table, resolver, maxLinkDepth }) {
2518
- if (typeof expression === "string") {
2519
- return compilePredicateV2({ expression, table, resolver, maxLinkDepth });
2520
- }
2521
- const defaultPred = compilePredicateV2({ expression: expression.default, table, resolver, maxLinkDepth });
2625
+ function initialVisiting({ table, access }) {
2626
+ return (access ?? "read") === "read" ? [table] : [];
2627
+ }
2628
+ function compilePolicy({ expression, table, resolver, maxLinkDepth, access }) {
2629
+ return compilePolicyAt({
2630
+ expression,
2631
+ table,
2632
+ depth: 0,
2633
+ ctx: { resolver, maxLinkDepth: maxLinkDepth ?? POLICY_MAX_LINK_DEPTH, visiting: initialVisiting({ table, access }) }
2634
+ });
2635
+ }
2636
+ function compilePolicyAt({ expression, table, ctx, depth }) {
2637
+ if (typeof expression === "string") return compilePredicateAt({ expression, table, ctx, depth });
2638
+ const defaultPred = compilePredicateAt({ expression: expression.default, table, ctx, depth });
2522
2639
  if (expression.variants.length === 0) return defaultPred;
2523
2640
  const branches = expression.variants.map((variant) => {
2524
2641
  const parsedWhen = parsePolicy(variant.when);
2525
2642
  assertClaimsOnlyWhen(parsedWhen);
2526
- const when = compilePredicateV2({ expression: parsedWhen, table, resolver, maxLinkDepth });
2527
- const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateV2({ expression: variant.policy, table, resolver, maxLinkDepth });
2643
+ const when = compilePredicateAt({ expression: parsedWhen, table, ctx, depth });
2644
+ const grant = variant.allow === true ? { kind: "const", value: true } : compilePredicateAt({ expression: variant.policy, table, ctx, depth });
2528
2645
  return { kind: "and", operands: [when, grant] };
2529
2646
  });
2530
2647
  return { kind: "or", operands: [defaultPred, ...branches] };
@@ -2562,33 +2679,6 @@ function andPred(...preds) {
2562
2679
  if (operands.length === 1) return operands[0];
2563
2680
  return { kind: "and", operands };
2564
2681
  }
2565
- function walkPred(pred, visit2) {
2566
- visit2(pred);
2567
- switch (pred.kind) {
2568
- case "and":
2569
- case "or":
2570
- for (const operand of pred.operands) walkPred(operand, visit2);
2571
- return;
2572
- case "not":
2573
- walkPred(pred.operand, visit2);
2574
- return;
2575
- case "exists":
2576
- walkPred(pred.where, visit2);
2577
- return;
2578
- case "aggPredicate":
2579
- if (pred.where) walkPred(pred.where, visit2);
2580
- return;
2581
- default:
2582
- return;
2583
- }
2584
- }
2585
- function predContainsExists(pred) {
2586
- let found = false;
2587
- walkPred(pred, (node) => {
2588
- if (node.kind === "exists" || node.kind === "aggPredicate") found = true;
2589
- });
2590
- return found;
2591
- }
2592
2682
 
2593
2683
  // src/compiler-gql/inject.ts
2594
2684
  function collectPinnedColumns(writePolicy) {
@@ -2732,12 +2822,6 @@ var PolicyInjector = class {
2732
2822
  if (root.op === "resolvedWrite") return this.injectResolvedWrite(root, returning);
2733
2823
  if (root.op === "insert" || root.op === "insertOne") {
2734
2824
  const insertPolicy = this.insert(table);
2735
- if (predContainsExists(insertPolicy)) {
2736
- throw new DocumentCompileError(
2737
- `table '${table}' cannot be inserted into: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2738
- "insert-check-unrepresentable"
2739
- );
2740
- }
2741
2825
  const pins = (this.ctx.pins.get(table) ?? []).map(
2742
2826
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2743
2827
  );
@@ -2752,22 +2836,14 @@ var PolicyInjector = class {
2752
2836
  const updatePolicy = this.update(table);
2753
2837
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2754
2838
  if (root.op === "update" || root.op === "updateByPk") {
2755
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2756
- return { ...root, where, ...check, returning };
2839
+ return { ...root, where, insertCheck: updatePolicy, returning };
2757
2840
  }
2758
2841
  if (root.op === "cowOverride") {
2759
2842
  const insertPolicy = this.insert(table);
2760
- if (predContainsExists(insertPolicy)) {
2761
- throw new DocumentCompileError(
2762
- `table '${table}' cannot be copy-on-write overridden: v2.0 insert checks are claims + inserted-values only, but its insert policy traverses a link (exists)`,
2763
- "insert-check-unrepresentable"
2764
- );
2765
- }
2766
2843
  const pins = (this.ctx.pins.get(table) ?? []).map(
2767
2844
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2768
2845
  );
2769
- const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2770
- return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2846
+ return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, insertCheck: updatePolicy, returning };
2771
2847
  }
2772
2848
  return { ...root, where, returning };
2773
2849
  }
@@ -3001,7 +3077,7 @@ function scopePolicy(pred, namespace) {
3001
3077
  switch (pred.kind) {
3002
3078
  case "capability":
3003
3079
  case "role":
3004
- return { ...pred, namespace };
3080
+ return pred.namespace ? pred : { ...pred, namespace };
3005
3081
  case "and":
3006
3082
  case "or":
3007
3083
  return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
@@ -3091,6 +3167,7 @@ function buildCompileContext(rawCanonical) {
3091
3167
  const platformMeta = usesPlatformUsers ? platformUserMeta() : void 0;
3092
3168
  if (platformMeta) tables[PLATFORM_USER_TABLE] = platformMeta;
3093
3169
  const syncedTableNames = new Set(canonical.tables.filter((t2) => t2.source !== void 0).map((t2) => t2.table));
3170
+ const unkeyedSyncedTableNames = new Set(relations.filter((table) => table.source !== void 0 && table.primaryKey?.length === 0).map((table) => table.table));
3094
3171
  const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
3095
3172
  const claimsGatedMemo = /* @__PURE__ */ new Map();
3096
3173
  const relationIsClaimsGated = (name2) => {
@@ -3104,6 +3181,7 @@ function buildCompileContext(rawCanonical) {
3104
3181
  };
3105
3182
  for (const table of relations) {
3106
3183
  const isView = viewNames.has(table.table);
3184
+ const readOnly = isView || unkeyedSyncedTableNames.has(table.table);
3107
3185
  const owner = owners.get(table.table);
3108
3186
  const entries = resolveColumnEntries(table, enums, diagnostics);
3109
3187
  columnEntries.set(table.table, entries);
@@ -3151,13 +3229,13 @@ function buildCompileContext(rawCanonical) {
3151
3229
  // are claims-gated — see tableMetaIrSchema.claimsGated.
3152
3230
  claimsGated: relationIsClaimsGated(table.table),
3153
3231
  columns: entries.map(({ deprecated: _deprecated, onUpdateFn: _onUpdateFn, ...column }) => column),
3154
- primaryKey: isView ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
3232
+ primaryKey: isView || unkeyedSyncedTableNames.has(table.table) ? primaryKey2 : primaryKey2.length > 0 ? primaryKey2 : ["id"],
3155
3233
  links: [],
3156
3234
  filterable: data.map((c) => c.name),
3157
3235
  orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
3158
- insertable: resolved ? resolvedWritable(data) : isView ? [] : active.map((c) => c.name),
3159
- settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) : isView ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
3160
- appendable: isView ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3236
+ insertable: resolved ? resolvedWritable(data) : readOnly ? [] : active.map((c) => c.name),
3237
+ settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey2.includes(name2)) : readOnly ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
3238
+ appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3161
3239
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
3162
3240
  ...cells ? { cells } : {},
3163
3241
  ...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
@@ -3181,6 +3259,16 @@ function buildCompileContext(rawCanonical) {
3181
3259
  });
3182
3260
  continue;
3183
3261
  }
3262
+ const identityTable = link.kind === "toOne" ? targetName : link.kind === "toMany" ? table.table : void 0;
3263
+ if (identityTable !== void 0 && unkeyedSyncedTableNames.has(identityTable)) {
3264
+ diagnostics.push({
3265
+ severity: "error",
3266
+ code: "unkeyed-link-identity",
3267
+ table: table.table,
3268
+ message: `link '${link.name}' cannot use unkeyed table '${identityTable}' as a row identity`
3269
+ });
3270
+ continue;
3271
+ }
3184
3272
  const targetEntries = targetName === PLATFORM_USER_TABLE ? target.columns.map((c) => ({ ...c, deprecated: false, onUpdateFn: false })) : columnEntries.get(targetName);
3185
3273
  const onEntries = link.kind === "toMany" ? targetEntries : entries;
3186
3274
  const referencesEntries = link.kind === "toMany" ? entries : targetEntries;
@@ -3210,7 +3298,14 @@ function buildCompileContext(rawCanonical) {
3210
3298
  const updatePolicies = /* @__PURE__ */ new Map();
3211
3299
  const pins = /* @__PURE__ */ new Map();
3212
3300
  const splitWritePolicyTables = /* @__PURE__ */ new Set();
3213
- const resolver = { tables };
3301
+ const readPolicyExpressions = new Map(relations.map((relation) => [relation.table, relation.policies.read]));
3302
+ const resolver = {
3303
+ tables,
3304
+ readPolicy: (table) => readPolicyExpressions.get(table),
3305
+ // In a composed namespace, an imported table's read policy (inlined by
3306
+ // visible()) is checked against the namespace that owns it.
3307
+ ...normalized.imports?.length ? { policyNamespace: (table) => owners.get(table)?.namespace } : {}
3308
+ };
3214
3309
  for (const table of relations) {
3215
3310
  const isView = viewNames.has(table.table);
3216
3311
  const compilePolicyFor = (access, store) => {
@@ -3219,7 +3314,7 @@ function buildCompileContext(rawCanonical) {
3219
3314
  return;
3220
3315
  }
3221
3316
  try {
3222
- const pred = compilePolicy({ expression, table: table.table, resolver });
3317
+ const pred = compilePolicy({ expression, table: table.table, resolver, access });
3223
3318
  store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
3224
3319
  } catch (error) {
3225
3320
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {
@@ -3239,7 +3334,7 @@ function buildCompileContext(rawCanonical) {
3239
3334
  compilePolicyFor("write", writePolicies);
3240
3335
  compilePolicyFor("insert", insertPolicies);
3241
3336
  compilePolicyFor("update", updatePolicies);
3242
- if (isView && table.resolved === void 0) {
3337
+ if (isView && table.resolved === void 0 || unkeyedSyncedTableNames.has(table.table)) {
3243
3338
  writePolicies.set(table.table, CONST_FALSE);
3244
3339
  insertPolicies.set(table.table, CONST_FALSE);
3245
3340
  updatePolicies.set(table.table, CONST_FALSE);
@@ -3314,7 +3409,7 @@ function buildCompileContext(rawCanonical) {
3314
3409
  const queryRoots = /* @__PURE__ */ new Map();
3315
3410
  const mutationRoots = /* @__PURE__ */ new Map();
3316
3411
  for (const table of relations) {
3317
- if (table.internal === true) continue;
3412
+ if (table.internal === true || unkeyedSyncedTableNames.has(table.table)) continue;
3318
3413
  const roots = queryRootNames(table.table);
3319
3414
  queryRoots.set(roots.select, { kind: "select", table: table.table });
3320
3415
  if ((tables[table.table]?.primaryKey.length ?? 0) > 0) {
@@ -3694,8 +3789,10 @@ var OperationLowerer = class {
3694
3789
  constructor(ctx, restricted) {
3695
3790
  this.ctx = ctx;
3696
3791
  this.variables = new Map(restricted.variables.map((v) => [v.name, v]));
3792
+ this.maxRows = restricted.readBudget ?? MAX_LIMIT;
3697
3793
  }
3698
3794
  variables;
3795
+ maxRows;
3699
3796
  meta(table) {
3700
3797
  return this.ctx.tables[table];
3701
3798
  }
@@ -3899,13 +3996,13 @@ var OperationLowerer = class {
3899
3996
  });
3900
3997
  }
3901
3998
  lowerLimit(node) {
3902
- const base2 = { default: DEFAULT_LIMIT, max: MAX_LIMIT };
3999
+ const base2 = { default: DEFAULT_LIMIT, max: this.maxRows };
3903
4000
  if (!node) return base2;
3904
4001
  if (node.kind === Kind.VARIABLE) return { ...base2, variable: node.name.value };
3905
4002
  if (node.kind === Kind.INT) {
3906
4003
  const value = Number(node.value);
3907
4004
  if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
3908
- return { ...base2, value: Math.min(value, MAX_LIMIT) };
4005
+ return { ...base2, value: Math.min(value, this.maxRows) };
3909
4006
  }
3910
4007
  fail2("limit must be an integer literal or a variable", "invalid-limit");
3911
4008
  }
@@ -4704,7 +4801,13 @@ var OperationLowerer = class {
4704
4801
  };
4705
4802
  function lowerOperation({ restricted, ctx }) {
4706
4803
  const lowerer = new OperationLowerer(ctx, restricted);
4707
- return lowerer.lowerRoots(restricted.definition, restricted.kind);
4804
+ const roots = lowerer.lowerRoots(restricted.definition, restricted.kind);
4805
+ if (restricted.readBudget === void 0) return roots;
4806
+ const root = roots[0];
4807
+ if (restricted.kind !== "query" || restricted.definition.selectionSet.selections.length !== 1 || roots.length !== 1 || root?.kind !== "select" || root.selection.relations.length > 0 || root.selection.aggregates.length > 0 || root.selection.cells !== void 0 || root.prefetch?.length) {
4808
+ fail2("@readBudget requires one flat select root without related selections, aggregates, cells, or prefetch", "invalid-read-budget");
4809
+ }
4810
+ return roots;
4708
4811
  }
4709
4812
  function isCellColumn(meta, name2) {
4710
4813
  if (!meta.cells) return false;
@@ -4763,6 +4866,19 @@ function parseDocuments({
4763
4866
  }
4764
4867
  return { documents: parsed, diagnostics };
4765
4868
  }
4869
+ function parseReadBudget(operation) {
4870
+ const directives = operation.directives?.filter((directive) => directive.name.value === "readBudget") ?? [];
4871
+ if (directives.length === 0) return void 0;
4872
+ const value = directives[0]?.arguments?.find((argument) => argument.name.value === "maxRows")?.value;
4873
+ if (operation.operation !== "query" || directives.length !== 1 || value?.kind !== Kind.INT) {
4874
+ throw new DocumentCompileError("@readBudget requires a literal maxRows on a query", "invalid-read-budget");
4875
+ }
4876
+ const maxRows = Number(value.value);
4877
+ if (!Number.isSafeInteger(maxRows) || maxRows < 1 || maxRows > MAX_PERSISTED_READ_ROWS) {
4878
+ throw new DocumentCompileError(`@readBudget(maxRows:) must be between 1 and ${MAX_PERSISTED_READ_ROWS}`, "invalid-read-budget");
4879
+ }
4880
+ return maxRows;
4881
+ }
4766
4882
  function unwrapType(node, variable) {
4767
4883
  let nullable = true;
4768
4884
  let list2 = false;
@@ -4923,6 +5039,7 @@ function restrictOperation({
4923
5039
  name: name2,
4924
5040
  kind: operation.operation === "mutation" ? "mutation" : "query",
4925
5041
  requires: parseRequires(operation),
5042
+ readBudget: parseReadBudget(operation),
4926
5043
  variables,
4927
5044
  definition: operation
4928
5045
  };
@@ -5580,6 +5697,118 @@ function bindNamedParameters({ sql: sql4, names }) {
5580
5697
  }
5581
5698
  return result;
5582
5699
  }
5700
+ var parser2;
5701
+ var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
5702
+ var scalarTypes = /* @__PURE__ */ new Set([
5703
+ "bool",
5704
+ "boolean",
5705
+ "int2",
5706
+ "int4",
5707
+ "int8",
5708
+ "integer",
5709
+ "bigint",
5710
+ "smallint",
5711
+ "numeric",
5712
+ "decimal",
5713
+ "float4",
5714
+ "float8",
5715
+ "real",
5716
+ "text",
5717
+ "varchar",
5718
+ "bpchar",
5719
+ "uuid",
5720
+ "date",
5721
+ "timestamp",
5722
+ "timestamptz",
5723
+ "time",
5724
+ "timetz",
5725
+ "interval",
5726
+ "json",
5727
+ "jsonb",
5728
+ "bytea"
5729
+ ]);
5730
+ var sqlValues = /* @__PURE__ */ new Set([
5731
+ "SVFOP_CURRENT_DATE",
5732
+ "SVFOP_CURRENT_TIME",
5733
+ "SVFOP_CURRENT_TIME_N",
5734
+ "SVFOP_CURRENT_TIMESTAMP",
5735
+ "SVFOP_CURRENT_TIMESTAMP_N",
5736
+ "SVFOP_LOCALTIME",
5737
+ "SVFOP_LOCALTIME_N",
5738
+ "SVFOP_LOCALTIMESTAMP",
5739
+ "SVFOP_LOCALTIMESTAMP_N",
5740
+ "SVFOP_CURRENT_ROLE",
5741
+ "SVFOP_CURRENT_USER",
5742
+ "SVFOP_USER",
5743
+ "SVFOP_SESSION_USER",
5744
+ "SVFOP_CURRENT_CATALOG",
5745
+ "SVFOP_CURRENT_SCHEMA"
5746
+ ]);
5747
+ async function validateManagedViewQueries({ canonical }) {
5748
+ for (const definition of [canonical, ...canonical.imports ?? []]) {
5749
+ for (const view of definition.views ?? []) {
5750
+ const fail4 = (message) => {
5751
+ throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
5752
+ };
5753
+ parser2 ??= new PgParser({ version: 17 });
5754
+ const parsed = await parser2.parse(view.query);
5755
+ if (parsed.error || !parsed.tree) fail4("invalid SELECT query");
5756
+ const statements = list(obj(parsed.tree).stmts);
5757
+ if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail4("one SELECT query is required");
5758
+ validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail4 });
5759
+ }
5760
+ }
5761
+ }
5762
+ function pgCatalogName(parts) {
5763
+ if (parts.length === 1) return parts[0];
5764
+ if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
5765
+ return void 0;
5766
+ }
5767
+ function reviewedFunctionName({ parts, format }) {
5768
+ return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
5769
+ }
5770
+ function validateTree({ tree, ...scope }) {
5771
+ const pending = [tree];
5772
+ while (pending.length) {
5773
+ const value = pending.pop();
5774
+ if (Array.isArray(value)) {
5775
+ pending.push(...value);
5776
+ continue;
5777
+ }
5778
+ if (!value || typeof value !== "object") continue;
5779
+ const node = obj(value);
5780
+ for (const [kind, child] of Object.entries(node)) {
5781
+ validateNode({ kind, child, scope });
5782
+ pending.push(child);
5783
+ }
5784
+ }
5785
+ }
5786
+ function validateNode({ kind, child, scope }) {
5787
+ const { fail: fail4 } = scope;
5788
+ if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail4("only SELECT statements are permitted");
5789
+ if (kind === "FuncCall") {
5790
+ const call = obj(child);
5791
+ const parts = strings(call.funcname);
5792
+ const name2 = reviewedFunctionName({ parts, format: call.funcformat });
5793
+ if (!name2 || !functions.has(name2)) fail4(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
5794
+ }
5795
+ if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
5796
+ if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail4("only pg_catalog operators are permitted");
5797
+ if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail4("only pg_catalog sort operators are permitted");
5798
+ if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail4("unsupported SQL value expression");
5799
+ if (kind === "ParamRef") fail4("view queries cannot contain parameters");
5800
+ if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
5801
+ fail4("composite field notation is not permitted in managed views");
5802
+ }
5803
+ }
5804
+ function validateType({ node, schema, enums, fail: fail4 }) {
5805
+ const parts = strings(node.names);
5806
+ const builtin = pgCatalogName(parts);
5807
+ const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
5808
+ if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
5809
+ fail4(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
5810
+ }
5811
+ }
5583
5812
 
5584
5813
  // src/sql/operations.ts
5585
5814
  function isSqlOperationDocument(path, contents) {
@@ -5595,7 +5824,13 @@ function isSqlOperationDocument(path, contents) {
5595
5824
  async function compileOperationDocuments(input) {
5596
5825
  const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
5597
5826
  const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
5598
- if (!result.ok || !sqlDocuments.length) return result;
5827
+ if (!result.ok) return result;
5828
+ const viewDiagnostics = await managedViewDiagnostics(input.canonical);
5829
+ if (viewDiagnostics.length) return { ok: false, diagnostics: [...result.diagnostics, ...viewDiagnostics] };
5830
+ if (!sqlDocuments.length) return result;
5831
+ return compileSqlDocuments({ input, result, sqlDocuments });
5832
+ }
5833
+ async function compileSqlDocuments({ input, result, sqlDocuments }) {
5599
5834
  for (const document of sqlDocuments) {
5600
5835
  try {
5601
5836
  const definition = defineOperation(JSON.parse(document.contents));
@@ -5689,6 +5924,18 @@ async function compileOperationDocuments(input) {
5689
5924
  result.manifest.opsHash = result.artifact.opsHash;
5690
5925
  return { ...result, artifact: normalizeIr(result.artifact) };
5691
5926
  }
5927
+ async function managedViewDiagnostics(canonical) {
5928
+ try {
5929
+ await validateManagedViewQueries({ canonical });
5930
+ return [];
5931
+ } catch (error) {
5932
+ return [{
5933
+ severity: "error",
5934
+ code: "invalid-view-query",
5935
+ message: error instanceof Error ? error.message : "invalid managed view query"
5936
+ }];
5937
+ }
5938
+ }
5692
5939
 
5693
5940
  // src/sql/codegen.ts
5694
5941
  var typeOf = (type) => ["integer", "double"].includes(type) ? "number" : type === "boolean" ? "boolean" : "string";
@@ -7784,7 +8031,7 @@ function snapshotTableNames(snapshot, schema) {
7784
8031
  // src/engine/materialized-refresh.ts
7785
8032
  function materializedRefreshFunctions(canonical) {
7786
8033
  const schema = ormSchemaName(canonical.namespace);
7787
- const functions = /* @__PURE__ */ new Map();
8034
+ const functions2 = /* @__PURE__ */ new Map();
7788
8035
  for (const view of canonical.views ?? []) {
7789
8036
  const materialized = view.resolved?.materialized;
7790
8037
  if (!materialized) continue;
@@ -7793,7 +8040,7 @@ function materializedRefreshFunctions(canonical) {
7793
8040
  if (!table || pk.length === 0) {
7794
8041
  throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
7795
8042
  }
7796
- functions.set(view.view, refreshFunctionSql({
8043
+ functions2.set(view.view, refreshFunctionSql({
7797
8044
  schema,
7798
8045
  name: view.view,
7799
8046
  table: materialized.table,
@@ -7802,7 +8049,7 @@ function materializedRefreshFunctions(canonical) {
7802
8049
  pk
7803
8050
  }));
7804
8051
  }
7805
- return functions;
8052
+ return functions2;
7806
8053
  }
7807
8054
  function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
7808
8055
  const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
@@ -8446,6 +8693,7 @@ async function planWithViewRecreation({
8446
8693
  previousSnapshot,
8447
8694
  viewRecreation
8448
8695
  }) {
8696
+ await validateManagedViewQueries({ canonical });
8449
8697
  const schema = ormSchemaName(canonical.namespace);
8450
8698
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
8451
8699
  assertValidViewDependencies(canonical);
@@ -9736,6 +9984,9 @@ function generateSdl(canonical, options = {}) {
9736
9984
  blocks.push(
9737
9985
  '"""Gates an operation behind a platform capability or custom role \u2014 checked by the data plane before execution."""\ndirective @requires(capability: String, role: String) on QUERY | MUTATION'
9738
9986
  );
9987
+ if (!options.omitReadBudgetDirective) blocks.push(
9988
+ '"""Sets a document-authored ceiling of 1\u201350001 rows for one flat select root. Runtime variables cannot raise it."""\ndirective @readBudget(maxRows: Int!) on QUERY'
9989
+ );
9739
9990
  blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
9740
9991
  blocks.push(
9741
9992
  '"""Abort the entire mutation unless this root affects exactly the document-authored number of rows. Mutation roots only; cannot be conditional."""\ndirective @assertAffectedRows(exactly: Int!) on FIELD'
@@ -11940,7 +12191,7 @@ function emitSyncedTable(table) {
11940
12191
  lines.push(" },", " {");
11941
12192
  lines.push(` source: ${quote(table.source ?? "")},`);
11942
12193
  if (table.description !== void 0) lines.push(` description: ${quote(table.description)},`);
11943
- if (singlePrimaryKey === void 0 && table.primaryKey && table.primaryKey.length > 0) {
12194
+ if (singlePrimaryKey === void 0 && table.primaryKey !== void 0) {
11944
12195
  lines.push(` primaryKey: ${jsonLiteral(table.primaryKey, 4)},`);
11945
12196
  }
11946
12197
  if (table.links.length > 0) {