@sequenceholdings/orm 2.4.0 → 2.6.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 (58) hide show
  1. package/README.md +23 -0
  2. package/dist/authoring.cjs +53 -7
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +10 -6
  5. package/dist/authoring.d.ts +10 -6
  6. package/dist/authoring.js +53 -7
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +48 -2
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +1 -1
  11. package/dist/canonical.d.ts +1 -1
  12. package/dist/canonical.js +48 -2
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +260 -31
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +260 -31
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +11 -6
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +38 -31
  21. package/dist/client.d.ts +38 -31
  22. package/dist/client.js +11 -7
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +513 -23
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +39 -3
  27. package/dist/compiler.d.ts +39 -3
  28. package/dist/compiler.js +510 -24
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
  31. package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
  32. package/dist/dynamic-input.d.cts +1 -1
  33. package/dist/dynamic-input.d.ts +1 -1
  34. package/dist/index.cjs +461 -140
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +34 -4
  37. package/dist/index.d.ts +34 -4
  38. package/dist/index.js +460 -141
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir.cjs +55 -4
  41. package/dist/ir.cjs.map +1 -1
  42. package/dist/ir.d.cts +1 -1
  43. package/dist/ir.d.ts +1 -1
  44. package/dist/ir.js +55 -4
  45. package/dist/ir.js.map +1 -1
  46. package/dist/predicate-v2.cjs +55 -4
  47. package/dist/predicate-v2.cjs.map +1 -1
  48. package/dist/predicate-v2.d.cts +1 -1
  49. package/dist/predicate-v2.d.ts +1 -1
  50. package/dist/predicate-v2.js +55 -4
  51. package/dist/predicate-v2.js.map +1 -1
  52. package/dist/sdl.cjs +60 -4
  53. package/dist/sdl.cjs.map +1 -1
  54. package/dist/sdl.d.cts +3 -1
  55. package/dist/sdl.d.ts +3 -1
  56. package/dist/sdl.js +60 -4
  57. package/dist/sdl.js.map +1 -1
  58. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -9,6 +9,7 @@ var promises = require('fs/promises');
9
9
  var module$1 = require('module');
10
10
  var path = require('path');
11
11
  var api = require('drizzle-kit/api');
12
+ var pgParser = require('@supabase/pg-parser');
12
13
  var jsYaml = require('js-yaml');
13
14
  var customRolesManifest = require('@sequenceholdings/custom-roles-manifest');
14
15
 
@@ -157,20 +158,24 @@ var t = {
157
158
  };
158
159
 
159
160
  // src/constants.ts
160
- var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
161
+ var NAMESPACE_PATTERN = /^(?!(?:events|native_sources)$)[a-z][a-z0-9_]{0,40}$/;
161
162
  var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
162
163
  var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
163
164
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
164
165
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
165
166
  var MAX_IDENTIFIER_LENGTH = 63;
166
167
  var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
168
+ var MAX_PERSISTED_READ_ROWS = 50001;
167
169
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
168
170
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
169
171
  var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
170
172
  var ORM_SCHEMA_PREFIX = "orm_";
171
173
  var CONSUMER_ROLE_SUFFIX = "_consumer";
172
- var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
173
- var consumerRoleName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}${CONSUMER_ROLE_SUFFIX}`;
174
+ var ormSchemaName = (namespace) => {
175
+ if (!NAMESPACE_PATTERN.test(namespace)) throw new Error(`invalid or reserved ORM namespace '${namespace}'`);
176
+ return `${ORM_SCHEMA_PREFIX}${namespace}`;
177
+ };
178
+ var consumerRoleName = (namespace) => `${ormSchemaName(namespace)}${CONSUMER_ROLE_SUFFIX}`;
174
179
  var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
175
180
  var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
176
181
  var HASH_DOMAIN = "sequence-orm-definitions\0";
@@ -399,10 +404,10 @@ function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
399
404
  if (builder.isPrimaryKey) columnLevelPk.push(propertyName);
400
405
  }
401
406
  if (entries.length > 0) {
402
- const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
407
+ const hasConfigPk = config.primaryKey !== void 0;
403
408
  if (columnLevelPk.length === 0 && !hasConfigPk) {
404
409
  throw new OrmDefinitionError(
405
- `synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
410
+ `synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey(), config.primaryKey, or explicit primaryKey: [] for an unkeyed read-only source`,
406
411
  file
407
412
  );
408
413
  }
@@ -1255,10 +1260,10 @@ function validateResolvedView({ view, tables, views = [] }) {
1255
1260
  const resolved = view.resolved;
1256
1261
  if (!resolved) return [];
1257
1262
  const errors = [];
1258
- const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
1263
+ const fail3 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
1259
1264
  const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
1260
1265
  if (!pkColumn) {
1261
- fail("requires exactly one primary-key column");
1266
+ fail3("requires exactly one primary-key column");
1262
1267
  return errors;
1263
1268
  }
1264
1269
  const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
@@ -1268,92 +1273,93 @@ function validateResolvedView({ view, tables, views = [] }) {
1268
1273
  pkColumn,
1269
1274
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
1270
1275
  sourceByName: /* @__PURE__ */ new Map(),
1271
- fail
1276
+ fail: fail3
1272
1277
  };
1273
- if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
1278
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail3("requires exactly one manual source");
1274
1279
  const tableByName = new Map(tables.map((table) => [table.table, table]));
1275
1280
  for (const source of resolved.sources) {
1276
- if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
1281
+ if (scope.sourceByName.has(source.name)) fail3(`duplicate source '${source.name}'`);
1277
1282
  scope.sourceByName.set(source.name, source);
1278
1283
  const table = tableByName.get(source.table);
1279
- if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
1284
+ if (!table) fail3(`source '${source.name}' references unknown table '${source.table}'`);
1280
1285
  else validateSource({ scope, source, table });
1281
1286
  }
1282
1287
  validateRules(scope);
1283
1288
  validateViewCells(scope);
1284
1289
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1285
1290
  for (const name2 of resolved.deletedBy) {
1286
- if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
1291
+ if (scope.sourceByName.get(name2)?.kind !== "manual") fail3(`deletedBy '${name2}' must be the manual source`);
1287
1292
  }
1288
1293
  return errors;
1289
1294
  }
1290
1295
  function validateSource({ scope, source, table }) {
1291
- const { fail } = scope;
1292
- if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
1296
+ const { fail: fail3 } = scope;
1297
+ if (source.kind === "etl" && table.source === void 0) fail3(`etl source '${source.name}' must reference a synced table`);
1298
+ if (source.kind === "etl" && table.primaryKey?.length === 0) fail3(`etl source '${source.name}' cannot use an unkeyed synced table for resolved row identity`);
1293
1299
  if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
1294
- fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
1300
+ fail3(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
1295
1301
  }
1296
1302
  const hasRev = table.columns.some((column) => column.cell?.part === "rev");
1297
- if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
1298
- if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
1303
+ if (source.kind === "manual" && !hasRev) fail3(`manual source '${source.name}' must carry '__rev' cell columns`);
1304
+ if (source.kind === "prefetch" && hasRev) fail3(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
1299
1305
  const byName = new Map(table.columns.map((c) => [c.name, c]));
1300
1306
  validateSourceColumns({ scope, source, byName });
1301
1307
  if (source.kind === "etl") {
1302
1308
  const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
1303
1309
  if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
1304
- fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
1310
+ fail3(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
1305
1311
  }
1306
1312
  }
1307
1313
  validatePrefetchFields({ scope, source });
1308
1314
  }
1309
1315
  function validatePrefetchFields({ scope, source }) {
1310
- const { fail, pkColumn } = scope;
1316
+ const { fail: fail3, pkColumn } = scope;
1311
1317
  if (source.kind !== "prefetch") {
1312
1318
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
1313
- fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
1319
+ fail3(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
1314
1320
  }
1315
1321
  return;
1316
1322
  }
1317
1323
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
1318
- fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
1324
+ fail3(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
1319
1325
  } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
1320
- fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
1326
+ fail3(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
1321
1327
  }
1322
1328
  for (const column of source.writeback?.columns ?? []) {
1323
- if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
1329
+ if (!source.columns.includes(column)) fail3(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
1324
1330
  }
1325
1331
  }
1326
1332
  function validateSourceColumns({ scope, source, byName }) {
1327
- const { fail, pkColumn, dataColumns } = scope;
1333
+ const { fail: fail3, pkColumn, dataColumns } = scope;
1328
1334
  for (const column of [pkColumn.name, ...source.columns]) {
1329
1335
  const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
1330
1336
  const theirs = byName.get(column);
1331
- if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
1332
- else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
1333
- else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
1337
+ if (!own) fail3(`source '${source.name}' supplies unknown column '${column}'`);
1338
+ else if (!theirs) fail3(`source '${source.name}' table '${source.table}' has no column '${column}'`);
1339
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail3(`column '${column}' has a different type on source '${source.name}'`);
1334
1340
  }
1335
1341
  }
1336
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
1342
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail3 }) {
1337
1343
  const ruled = /* @__PURE__ */ new Set();
1338
1344
  for (const rule of resolved.resolve) {
1339
1345
  if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
1340
- fail(`resolve rule targets unknown column '${rule.column}'`);
1346
+ fail3(`resolve rule targets unknown column '${rule.column}'`);
1341
1347
  continue;
1342
1348
  }
1343
- if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
1349
+ if (ruled.has(rule.column)) fail3(`column '${rule.column}' has more than one resolve rule`);
1344
1350
  ruled.add(rule.column);
1345
1351
  if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
1346
- fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
1352
+ fail3(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
1347
1353
  }
1348
- if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
1354
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail3(`column '${rule.column}' repeats a candidate`);
1349
1355
  for (const candidate of rule.candidates) {
1350
1356
  const source = sourceByName.get(candidate);
1351
- if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
1352
- else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
1357
+ if (!source) fail3(`column '${rule.column}' names unknown candidate source '${candidate}'`);
1358
+ else if (!source.columns.includes(rule.column)) fail3(`column '${rule.column}' candidate '${candidate}' does not supply it`);
1353
1359
  }
1354
1360
  }
1355
1361
  for (const column of dataColumns.keys()) {
1356
- if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
1362
+ if (column !== pkColumn.name && !ruled.has(column)) fail3(`column '${column}' has no resolve rule`);
1357
1363
  }
1358
1364
  }
1359
1365
  var VIEW_CELL_TYPES = {
@@ -1362,7 +1368,7 @@ var VIEW_CELL_TYPES = {
1362
1368
  by: "text",
1363
1369
  conflicts: "jsonb"
1364
1370
  };
1365
- function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1371
+ function validateViewCells({ view, dataColumns, pkColumn, fail: fail3 }) {
1366
1372
  const expected = /* @__PURE__ */ new Map();
1367
1373
  for (const column of dataColumns.values()) {
1368
1374
  if (column.name === pkColumn.name) continue;
@@ -1374,34 +1380,34 @@ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1374
1380
  if (!column.cell) continue;
1375
1381
  const want = expected.get(column.name);
1376
1382
  if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
1377
- fail(`unexpected cell column '${column.name}'`);
1383
+ fail3(`unexpected cell column '${column.name}'`);
1378
1384
  continue;
1379
1385
  }
1380
- if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1386
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail3(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1381
1387
  expected.delete(column.name);
1382
1388
  }
1383
- for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
1389
+ for (const name2 of expected.keys()) fail3(`missing cell column '${name2}'`);
1384
1390
  }
1385
1391
  var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1386
1392
  function validateMaterialized({ scope, tableByName, views }) {
1387
- const { view, resolved, fail } = scope;
1393
+ const { view, resolved, fail: fail3 } = scope;
1388
1394
  const { table: tableName, compute: computeName } = resolved.materialized;
1389
1395
  const table = tableByName.get(tableName);
1390
1396
  if (!table || table.internal !== true || table.materializes !== view.view) {
1391
- fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
1397
+ fail3(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
1392
1398
  } else {
1393
- if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
1394
- if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
1399
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail3(`materialized table '${tableName}' must have exactly the view's columns`);
1400
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail3(`materialized table '${tableName}' must share the view's primary key`);
1395
1401
  }
1396
1402
  const compute = views.find((candidate) => candidate.view === computeName);
1397
- if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
1398
- else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
1403
+ if (!compute || compute.internal !== true) fail3(`compute view '${computeName}' must be an internal view`);
1404
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail3(`compute view '${computeName}' must have exactly the view's columns`);
1399
1405
  for (const source of resolved.sources) {
1400
1406
  if (source.kind !== "etl") continue;
1401
1407
  const read = tableByName.get(source.table)?.policies?.read;
1402
1408
  const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
1403
1409
  if (!unconditional) {
1404
- fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
1410
+ fail3(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
1405
1411
  }
1406
1412
  }
1407
1413
  }
@@ -1738,7 +1744,7 @@ var canonicalTableSchema = zod.z.object({
1738
1744
  /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
1739
1745
  source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
1740
1746
  columns: zod.z.array(canonicalColumnSchema).default([]),
1741
- primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1747
+ primaryKey: zod.z.array(columnIdentifier).max(8).optional(),
1742
1748
  links: zod.z.array(canonicalLinkSchema).default([]),
1743
1749
  indexes: zod.z.array(canonicalIndexSchema).default([]),
1744
1750
  /** Omitted by old definitions, preserving their existing content hashes. */
@@ -1774,6 +1780,29 @@ var canonicalTableSchema = zod.z.object({
1774
1780
  * data plane. Omitted otherwise.
1775
1781
  */
1776
1782
  materializes: identifier2.optional()
1783
+ }).superRefine((table, ctx) => {
1784
+ if (table.primaryKey?.length !== 0) return;
1785
+ const fail3 = (message, path = ["primaryKey"]) => ctx.addIssue({ code: "custom", path, message: `unkeyed table '${table.table}' ${message}` });
1786
+ if (table.source === void 0 || table.columns.length === 0) {
1787
+ fail3("requires a typed synced source");
1788
+ }
1789
+ if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
1790
+ fail3("is read-only and cannot declare write policies", ["policies"]);
1791
+ }
1792
+ const ownedCollections = [table.indexes, table.actions, table.checks, table.foreignKeys, table.cowMutations];
1793
+ const ownedMetadata = [table.cellMetadata, table.internal, table.materializes, table.renamedFrom];
1794
+ if (ownedCollections.some((items) => items !== void 0 && items.length > 0) || ownedMetadata.some((value) => value !== void 0)) {
1795
+ fail3("cannot declare owned storage, constraints or identity operations");
1796
+ }
1797
+ for (const [index3, column] of table.columns.entries()) {
1798
+ const ownedColumnMetadata = [column.default, column.defaultFn, column.onUpdateFn, column.renamedFrom, column.cast, column.cell];
1799
+ if (column.unique || ownedColumnMetadata.some((value) => value !== void 0)) {
1800
+ fail3("cannot declare column uniqueness, mutation defaults or migration metadata", ["columns", index3]);
1801
+ }
1802
+ }
1803
+ for (const [index3, link] of table.links.entries()) {
1804
+ if (link.enforce || link.kind === "toMany") fail3("cannot provide an identity for this link", ["links", index3]);
1805
+ }
1777
1806
  });
1778
1807
  var MAX_VIEW_QUERY = 1e5;
1779
1808
  var MAX_RESOLVED_VIEW_QUERY = 5e6;
@@ -1830,6 +1859,26 @@ var canonicalViewSchema = zod.z.object({
1830
1859
  }
1831
1860
  }
1832
1861
  });
1862
+ function validateUnkeyedLinkIdentities({ definition, ctx }) {
1863
+ const tables = new Map(definition.tables.map((table) => [table.table, table]));
1864
+ const relations = [
1865
+ ...definition.tables.map((table, index3) => ({ kind: "tables", index: index3, links: table.links })),
1866
+ ...(definition.views ?? []).map((view, index3) => ({ kind: "views", index: index3, links: view.links }))
1867
+ ];
1868
+ for (const relation of relations) {
1869
+ for (const [linkIndex, link] of relation.links.entries()) {
1870
+ if (link.kind !== "toOne" || link.namespace !== void 0 && link.namespace !== definition.namespace) continue;
1871
+ const target = link.target === void 0 ? void 0 : tables.get(link.target);
1872
+ if (target?.source !== void 0 && target.primaryKey?.length === 0) {
1873
+ ctx.addIssue({
1874
+ code: "custom",
1875
+ path: [relation.kind, relation.index, "links", linkIndex],
1876
+ message: `link '${link.name}' cannot use unkeyed table '${target.table}' as a to-one identity`
1877
+ });
1878
+ }
1879
+ }
1880
+ }
1881
+ }
1833
1882
  var CELL_PART_TYPES = {
1834
1883
  at: "timestamptz",
1835
1884
  by: "text",
@@ -1843,49 +1892,49 @@ function trackedCellColumns(table) {
1843
1892
  }
1844
1893
  var SUFFIXES = Object.values(CELL_PART_SUFFIX);
1845
1894
  function validateCellMetadata(table, index3, ctx) {
1846
- const fail = (message, path = ["tables", index3]) => ctx.addIssue({ code: "custom", path, message });
1895
+ const fail3 = (message, path = ["tables", index3]) => ctx.addIssue({ code: "custom", path, message });
1847
1896
  const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
1848
1897
  if (table.materializes !== void 0) return;
1849
1898
  if (table.cellMetadata !== true) {
1850
- for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index3, "columns", position]);
1899
+ for (const { position } of cells) fail3(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index3, "columns", position]);
1851
1900
  return;
1852
1901
  }
1853
1902
  if (table.source !== void 0) {
1854
- fail(`synced table '${table.table}' cannot track cell metadata`);
1903
+ fail3(`synced table '${table.table}' cannot track cell metadata`);
1855
1904
  return;
1856
1905
  }
1857
1906
  const tracked = trackedCellColumns(table);
1858
1907
  const byName = new Map(tracked.map((column) => [column.name, column]));
1859
1908
  const withRev = cells.some(({ cell }) => cell.part === "rev");
1860
- if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
1909
+ if (withRev && table.internal !== true) fail3(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
1861
1910
  const seen = /* @__PURE__ */ new Set();
1862
1911
  for (const { column, position, cell } of cells) {
1863
1912
  const path = ["tables", index3, "columns", position];
1864
1913
  const owner = byName.get(cell.of);
1865
1914
  if (!owner) {
1866
- fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
1915
+ fail3(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
1867
1916
  continue;
1868
1917
  }
1869
- if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
1918
+ if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail3(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
1870
1919
  if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
1871
- fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
1920
+ fail3(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
1872
1921
  }
1873
1922
  if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
1874
- fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
1923
+ fail3(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
1875
1924
  }
1876
1925
  const key = `${cell.of}:${cell.part}`;
1877
- if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
1926
+ if (seen.has(key)) fail3(`duplicate cell column '${column.name}'`, path);
1878
1927
  seen.add(key);
1879
1928
  }
1880
1929
  for (const column of tracked) {
1881
1930
  if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
1882
- fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
1931
+ fail3(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
1883
1932
  }
1884
1933
  if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
1885
- fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
1934
+ fail3(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
1886
1935
  }
1887
1936
  for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
1888
- if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
1937
+ if (!seen.has(`${column.name}:${part}`)) fail3(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
1889
1938
  }
1890
1939
  }
1891
1940
  }
@@ -1893,7 +1942,7 @@ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capabili
1893
1942
  var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
1894
1943
  var localDefinitionSchema = zod.z.object({
1895
1944
  definition_format: zod.z.literal(CANONICAL_FORMAT),
1896
- namespace: identifier2.max(41),
1945
+ namespace: identifier2.max(41).regex(NAMESPACE_PATTERN, "invalid or reserved ORM namespace"),
1897
1946
  description: zod.z.string().max(2e3).optional(),
1898
1947
  enums: zod.z.array(canonicalEnumSchema).default([]),
1899
1948
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
@@ -1927,6 +1976,7 @@ var localDefinitionSchema = zod.z.object({
1927
1976
  }
1928
1977
  }
1929
1978
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1979
+ validateUnkeyedLinkIdentities({ definition, ctx });
1930
1980
  const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1931
1981
  for (const name2 of Object.keys(definition.exports ?? {})) {
1932
1982
  if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
@@ -1952,38 +2002,38 @@ var localDefinitionSchema = zod.z.object({
1952
2002
  }
1953
2003
  }
1954
2004
  for (const [index3, check] of (table.checks ?? []).entries()) {
1955
- const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
1956
- if (table.source) fail("synced tables cannot declare checks");
1957
- if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
2005
+ const fail3 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
2006
+ if (table.source) fail3("synced tables cannot declare checks");
2007
+ if (names.has(check.name)) fail3(`duplicate constraint '${check.name}'`);
1958
2008
  names.add(check.name);
1959
2009
  try {
1960
2010
  compileCheckExpression(check.expression, table.columns);
1961
2011
  } catch (error) {
1962
- fail(error instanceof Error ? error.message : String(error));
2012
+ fail3(error instanceof Error ? error.message : String(error));
1963
2013
  }
1964
2014
  }
1965
2015
  for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
1966
- const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
1967
- if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
2016
+ const fail3 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
2017
+ if (names.has(fk.name)) fail3(`duplicate foreign key '${fk.name}'`);
1968
2018
  names.add(fk.name);
1969
2019
  const target = fkTables.get(fk.target);
1970
2020
  if (table.source || !target || target.source) {
1971
- fail("foreign keys must connect owned tables in the same namespace");
2021
+ fail3("foreign keys must connect owned tables in the same namespace");
1972
2022
  continue;
1973
2023
  }
1974
- if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1975
- if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
2024
+ if (fk.columns.length !== fk.references.length) fail3("foreign key columns and references must have the same number of columns");
2025
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail3("foreign key columns must not repeat");
1976
2026
  for (const [position, name2] of fk.columns.entries()) {
1977
2027
  const sourceColumn = table.columns.find((column) => column.columnName === name2);
1978
2028
  const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1979
- if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1980
- else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
2029
+ if (!sourceColumn || !targetColumn) fail3("foreign key references an unknown column");
2030
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail3("foreign key column types must match");
1981
2031
  }
1982
2032
  if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1983
- if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail("SET NULL requires nullable source columns");
2033
+ if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail3("SET NULL requires nullable source columns");
1984
2034
  }
1985
2035
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1986
- if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
2036
+ if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail3("foreign key target columns must form a unique key");
1987
2037
  }
1988
2038
  }
1989
2039
  for (const [viewIndex, view] of (definition.views ?? []).entries()) {
@@ -2171,8 +2221,8 @@ function cloneColumn({ builder, property, notNull, primaryKey: primaryKey2 }) {
2171
2221
  return clone;
2172
2222
  }
2173
2223
  function planSource(input) {
2174
- const { sourceName, input: source, fail } = input;
2175
- if (!IDENTIFIER_PATTERN.test(sourceName)) fail(`source name "${sourceName}" must match ${IDENTIFIER_PATTERN}`);
2224
+ const { sourceName, input: source, fail: fail3 } = input;
2225
+ if (!IDENTIFIER_PATTERN.test(sourceName)) fail3(`source name "${sourceName}" must match ${IDENTIFIER_PATTERN}`);
2176
2226
  switch (source.kind) {
2177
2227
  case "manual":
2178
2228
  return {
@@ -2208,30 +2258,31 @@ function internalTable(input, supplied, system, options = {}) {
2208
2258
  internal: true
2209
2259
  });
2210
2260
  }
2211
- function assertColumns(input, list, what) {
2212
- for (const column of list) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
2261
+ function assertColumns(input, list2, what) {
2262
+ for (const column of list2) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
2213
2263
  }
2214
2264
  function planEtl(input, source) {
2215
- const { sourceName, pk, dataColumns, fail } = input;
2265
+ const { sourceName, pk, dataColumns, fail: fail3 } = input;
2216
2266
  const table = typeof source.table === "function" ? source.table() : source.table;
2217
- if (!isSeqTable(table) || table.source === void 0) fail(`etl source "${sourceName}" must reference a defineSyncedTable`);
2267
+ if (!isSeqTable(table) || table.source === void 0) fail3(`etl source "${sourceName}" must reference a defineSyncedTable`);
2218
2268
  const synced = table;
2219
- if (!(pk in synced.columns)) fail(`etl source "${sourceName}" table "${synced.tableName}" has no "${pk}" column`);
2269
+ if (synced.config.primaryKey?.length === 0) fail3(`etl source "${sourceName}" cannot use an unkeyed synced table for resolved row identity`);
2270
+ if (!(pk in synced.columns)) fail3(`etl source "${sourceName}" table "${synced.tableName}" has no "${pk}" column`);
2220
2271
  const supplied = source.columns ? [...source.columns] : dataColumns.filter((column) => column in synced.columns);
2221
2272
  assertColumns(input, supplied, `etl source "${sourceName}"`);
2222
2273
  if (source.updatedAt !== void 0 && !(source.updatedAt in synced.columns)) {
2223
- fail(`etl source "${sourceName}" updatedAt "${source.updatedAt}" is not a column of "${synced.tableName}"`);
2274
+ fail3(`etl source "${sourceName}" updatedAt "${source.updatedAt}" is not a column of "${synced.tableName}"`);
2224
2275
  }
2225
2276
  return { name: sourceName, kind: "etl", table: synced, columns: supplied, ...source.updatedAt ? { updatedAt: source.updatedAt } : {} };
2226
2277
  }
2227
2278
  function planPrefetch(input, source) {
2228
- const { sourceName, pk, fail } = input;
2279
+ const { sourceName, pk, fail: fail3 } = input;
2229
2280
  assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
2230
2281
  if (source.key !== pk && !source.columns.includes(source.key)) {
2231
- fail(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
2282
+ fail3(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
2232
2283
  }
2233
2284
  for (const column of source.writeback?.columns ?? []) {
2234
- if (!source.columns.includes(column)) fail(`prefetch source "${sourceName}" writes back "${column}", which it does not supply`);
2285
+ if (!source.columns.includes(column)) fail3(`prefetch source "${sourceName}" writes back "${column}", which it does not supply`);
2235
2286
  }
2236
2287
  const writeback = source.writeback;
2237
2288
  return {
@@ -2437,34 +2488,34 @@ var SYSTEM_VIEW_COLUMNS = {
2437
2488
  };
2438
2489
  function defineResolvedTable(name2, columns, config) {
2439
2490
  const file = activeAuthoringContext()?.currentFile;
2440
- const fail = (message) => {
2491
+ const fail3 = (message) => {
2441
2492
  throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
2442
2493
  };
2443
- if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2494
+ if (!IDENTIFIER_PATTERN.test(name2)) fail3(`name must match ${IDENTIFIER_PATTERN}`);
2444
2495
  for (const property of Object.keys(columns)) {
2445
2496
  if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2446
- fail(`column "${property}" collides with a reserved system column`);
2497
+ fail3(`column "${property}" collides with a reserved system column`);
2447
2498
  }
2448
2499
  }
2449
2500
  for (const property of Object.keys(columns)) {
2450
2501
  if (columns[property].columnNameFor(property).length > MAX_TRACKED_COLUMN_NAME) {
2451
- fail(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
2502
+ fail3(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
2452
2503
  }
2453
2504
  }
2454
2505
  const pkEntries = Object.entries(columns).filter(([, builder]) => builder.isPrimaryKey);
2455
- if (pkEntries.length !== 1) fail("declare exactly one .primaryKey() column");
2506
+ if (pkEntries.length !== 1) fail3("declare exactly one .primaryKey() column");
2456
2507
  const [pk, pkBuilder] = pkEntries[0];
2457
2508
  const dataColumns = Object.keys(columns).filter((property) => property !== pk);
2458
- if (dataColumns.length === 0) fail("declare at least one non-key column");
2509
+ if (dataColumns.length === 0) fail3("declare at least one non-key column");
2459
2510
  const sourceEntries = Object.entries(config.sources);
2460
- if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2511
+ if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail3("declare exactly one manualSource()");
2461
2512
  const plans = sourceEntries.map(
2462
- ([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2513
+ ([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail: fail3 })
2463
2514
  );
2464
- const rules = planRules({ config, plans, dataColumns, fail });
2515
+ const rules = planRules({ config, plans, dataColumns, fail: fail3 });
2465
2516
  const deletedBy = [...config.rows?.deletedBy ?? []];
2466
2517
  for (const source of deletedBy) {
2467
- if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail(`rows.deletedBy "${source}" must be the manual source`);
2518
+ if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail3(`rows.deletedBy "${source}" must be the manual source`);
2468
2519
  }
2469
2520
  const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
2470
2521
  const viewColumns = {};
@@ -2527,11 +2578,11 @@ function defineResolvedTable(name2, columns, config) {
2527
2578
  }
2528
2579
  });
2529
2580
  }
2530
- function planRules({ config, plans, dataColumns, fail }) {
2581
+ function planRules({ config, plans, dataColumns, fail: fail3 }) {
2531
2582
  const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
2532
2583
  const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));
2533
2584
  for (const column of Object.keys(config.resolve ?? {})) {
2534
- if (!dataColumns.includes(column)) fail(`resolve names unknown column "${column}"`);
2585
+ if (!dataColumns.includes(column)) fail3(`resolve names unknown column "${column}"`);
2535
2586
  }
2536
2587
  return dataColumns.map((column) => {
2537
2588
  const explicit = config.resolve?.[column];
@@ -2539,11 +2590,11 @@ function planRules({ config, plans, dataColumns, fail }) {
2539
2590
  const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
2540
2591
  for (const candidate of candidates) {
2541
2592
  const plan = plans.find((entry) => entry.name === candidate);
2542
- if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
2543
- else if (!plan.columns.includes(column)) fail(`column "${column}" candidate "${candidate}" does not supply it`);
2593
+ if (!plan) fail3(`column "${column}" names unknown source "${candidate}"`);
2594
+ else if (!plan.columns.includes(column)) fail3(`column "${column}" candidate "${candidate}" does not supply it`);
2544
2595
  }
2545
- if (candidates.length === 0) fail(`column "${column}" has no candidate source`);
2546
- if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail });
2596
+ if (candidates.length === 0) fail3(`column "${column}" has no candidate source`);
2597
+ if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail: fail3 });
2547
2598
  return {
2548
2599
  column,
2549
2600
  comparator: comparator.kind,
@@ -2594,18 +2645,18 @@ var ORDER_BY_KEYWORDS = /* @__PURE__ */ new Set([
2594
2645
  ]);
2595
2646
  var ALLOWED_FUNCTIONS = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
2596
2647
  var ORDER_BY_TOKEN = /\s+|'(?:[^']|'')*'|\d+(?:\.\d+)?|[a-z_][a-z0-9_]*|::|<=|>=|<>|!=|[-+*/%<>=(),|]/giy;
2597
- function assertSafeOrderBy({ orderBy, column, fail }) {
2648
+ function assertSafeOrderBy({ orderBy, column, fail: fail3 }) {
2598
2649
  ORDER_BY_TOKEN.lastIndex = 0;
2599
2650
  let consumed = 0;
2600
2651
  for (let match = ORDER_BY_TOKEN.exec(orderBy); match; match = ORDER_BY_TOKEN.exec(orderBy)) {
2601
2652
  consumed = ORDER_BY_TOKEN.lastIndex;
2602
2653
  const token = match[0].toLowerCase();
2603
2654
  if (/^[a-z_]/.test(token) && !ORDER_BY_KEYWORDS.has(token) && !ALLOWED_FUNCTIONS.has(token)) {
2604
- fail(`column "${column}" custom orderBy uses "${match[0]}" \u2014 only value, updated_at, source, actor, ord, SQL keywords and allowlisted functions`);
2655
+ fail3(`column "${column}" custom orderBy uses "${match[0]}" \u2014 only value, updated_at, source, actor, ord, SQL keywords and allowlisted functions`);
2605
2656
  }
2606
2657
  }
2607
2658
  if (consumed !== orderBy.length || orderBy.trim() === "") {
2608
- fail(`column "${column}" custom orderBy contains an unsupported character near "${orderBy.slice(consumed, consumed + 10)}"`);
2659
+ fail3(`column "${column}" custom orderBy contains an unsupported character near "${orderBy.slice(consumed, consumed + 10)}"`);
2609
2660
  }
2610
2661
  }
2611
2662
  function stableJson(value) {
@@ -3133,17 +3184,17 @@ function buildCanonicalLink({
3133
3184
  }) {
3134
3185
  const ownerKind = relationKind(relation);
3135
3186
  const ownerName = relationName(relation);
3136
- const fail = (message) => {
3187
+ const fail3 = (message) => {
3137
3188
  throw new OrmDefinitionError(`${ownerKind} "${ownerName}": link "${linkName}" ${message}`, relation.file);
3138
3189
  };
3139
3190
  if (link.enforce && link.linkKind !== "toOne") {
3140
- return fail("sets enforce: true, which is only supported on toOne links");
3191
+ return fail3("sets enforce: true, which is only supported on toOne links");
3141
3192
  }
3142
3193
  if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && !link.enforce) {
3143
- return fail("sets a referential action without enforce: true");
3194
+ return fail3("sets a referential action without enforce: true");
3144
3195
  }
3145
3196
  if (link.linkKind === "toPlatformUser") {
3146
- if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
3197
+ if (link.target !== void 0) return fail3("is toPlatformUser and must not declare a target table");
3147
3198
  return {
3148
3199
  name: linkName,
3149
3200
  kind: "toPlatformUser",
@@ -3154,7 +3205,7 @@ function buildCanonicalLink({
3154
3205
  };
3155
3206
  }
3156
3207
  if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
3157
- if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
3208
+ if (link.enforce) return fail3("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
3158
3209
  return {
3159
3210
  name: linkName,
3160
3211
  kind: link.linkKind,
@@ -3166,13 +3217,13 @@ function buildCanonicalLink({
3166
3217
  ...link.description !== void 0 ? { description: link.description } : {}
3167
3218
  };
3168
3219
  }
3169
- const target = resolveRelationRef({ ref: link.target, fail });
3220
+ const target = resolveRelationRef({ ref: link.target, fail: fail3 });
3170
3221
  const targetName = relationName(target);
3171
3222
  if (!relationsByName.has(targetName)) {
3172
- return fail(`targets ${relationKind(target)} "${targetName}" which is not part of this namespace`);
3223
+ return fail3(`targets ${relationKind(target)} "${targetName}" which is not part of this namespace`);
3173
3224
  }
3174
3225
  if (link.enforce && (!isSeqTable(relation) || !isSeqTable(target))) {
3175
- return fail("cannot enforce a PostgreSQL foreign key involving a view");
3226
+ return fail3("cannot enforce a PostgreSQL foreign key involving a view");
3176
3227
  }
3177
3228
  const owningSide = link.linkKind === "toOne" ? relation : target;
3178
3229
  const referencedSide = link.linkKind === "toOne" ? target : relation;
@@ -3188,19 +3239,19 @@ function buildCanonicalLink({
3188
3239
  ...link.description !== void 0 ? { description: link.description } : {}
3189
3240
  };
3190
3241
  }
3191
- function resolveRelationRef({ ref, fail }) {
3192
- if (ref === void 0) return fail("declares no target relation");
3242
+ function resolveRelationRef({ ref, fail: fail3 }) {
3243
+ if (ref === void 0) return fail3("declares no target relation");
3193
3244
  if (typeof ref !== "function") return ref;
3194
3245
  let resolved;
3195
3246
  try {
3196
3247
  resolved = ref();
3197
3248
  } catch (error) {
3198
- return fail(
3249
+ return fail3(
3199
3250
  `target thunk threw (${error instanceof Error ? error.message : String(error)}) \u2014 lazy refs resolve at serialize time; make sure the target module ran`
3200
3251
  );
3201
3252
  }
3202
3253
  if (!isSeqTable(resolved) && !isSeqView(resolved)) {
3203
- return fail("target thunk did not return a relation defined with defineTable/defineView");
3254
+ return fail3("target thunk did not return a relation defined with defineTable/defineView");
3204
3255
  }
3205
3256
  return resolved;
3206
3257
  }
@@ -3303,9 +3354,9 @@ function extractOrmSyncedTableContract({
3303
3354
  );
3304
3355
  }
3305
3356
  const primaryKey2 = table.primaryKey;
3306
- if (primaryKey2 === void 0 || primaryKey2.length === 0) {
3357
+ if (primaryKey2 === void 0) {
3307
3358
  throw new OrmDefinitionError(
3308
- `synced table "${table.table}" has no primary key \u2014 typed synced contracts require a primary key`
3359
+ `synced table "${table.table}" has no primary key contract \u2014 declare a key or explicit primaryKey: []`
3309
3360
  );
3310
3361
  }
3311
3362
  const pkSet = new Set(primaryKey2);
@@ -4072,19 +4123,19 @@ function compileComparison(op, left, right, columnTypes) {
4072
4123
  }
4073
4124
  return `${compileOperand(left, right, columnTypes)} ${SQL_COMPARE_OPS[op]} ${compileOperand(right, left, columnTypes)}`;
4074
4125
  }
4075
- function compilePredicate(predicate, columnTypes) {
4126
+ function compilePredicate(predicate, columnTypes, options = {}) {
4076
4127
  switch (predicate.kind) {
4077
4128
  case "and":
4078
4129
  case "or": {
4079
- const joined = predicate.operands.map((operand) => compilePredicate(operand, columnTypes)).join(` ${predicate.kind.toUpperCase()} `);
4130
+ const joined = predicate.operands.map((operand) => compilePredicate(operand, columnTypes, options)).join(` ${predicate.kind.toUpperCase()} `);
4080
4131
  return `(${joined})`;
4081
4132
  }
4082
4133
  case "not":
4083
- return `(NOT ${compilePredicate(predicate.operand, columnTypes)})`;
4134
+ return `(NOT ${compilePredicate(predicate.operand, columnTypes, options)})`;
4084
4135
  case "compare":
4085
4136
  return `(${compileComparison(predicate.op, predicate.left, predicate.right, columnTypes)})`;
4086
4137
  case "capability":
4087
- return `(${capabilityExpression(predicate.key)})`;
4138
+ return `(${options.statementClaimChecks ? "SELECT " : ""}${capabilityExpression(predicate.key)})`;
4088
4139
  }
4089
4140
  }
4090
4141
 
@@ -4110,9 +4161,9 @@ function buildActionParamsSchema(action) {
4110
4161
  for (const [name2, param] of entries) {
4111
4162
  shape[name2] = param.optional ? PARAM_SCHEMAS[param.type].nullish() : PARAM_SCHEMAS[param.type];
4112
4163
  }
4113
- return zod.z.strictObject(shape).superRefine((obj, ctx) => {
4164
+ return zod.z.strictObject(shape).superRefine((obj2, ctx) => {
4114
4165
  for (const [name2, param] of entries) {
4115
- if (!param.optional && param.type === "jsonb" && obj[name2] === void 0) {
4166
+ if (!param.optional && param.type === "jsonb" && obj2[name2] === void 0) {
4116
4167
  ctx.addIssue({ code: "custom", message: "Required", path: [name2] });
4117
4168
  }
4118
4169
  }
@@ -4170,9 +4221,9 @@ function compilePredIrToSql(pred, options = {}) {
4170
4221
  case "cmp":
4171
4222
  return compileCmp(pred.op, pred.column, pred.value, options);
4172
4223
  case "capability":
4173
- return `(${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
4224
+ return `(${options.statementClaimChecks ? "SELECT " : ""}${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
4174
4225
  case "role":
4175
- return `(${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
4226
+ return `(${options.statementClaimChecks ? "SELECT " : ""}${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
4176
4227
  case "exists":
4177
4228
  if (options.exists) return options.exists(pred);
4178
4229
  throw new OrmDefinitionError("synced view read policy cannot use 'exists' without a relationship resolver");
@@ -4748,7 +4799,9 @@ function syncedViewStatements({
4748
4799
  columnNamesByTable,
4749
4800
  columnTypesByTable,
4750
4801
  executionOwnerRole = SYNCED_VIEW_EXECUTION_OWNER_ROLE,
4751
- backingPrivileges = "grant"
4802
+ backingPrivileges = "grant",
4803
+ viewPrivileges = "grant",
4804
+ statementClaimChecks = true
4752
4805
  }) {
4753
4806
  const schema = quoteIdentifier(ormSchemaName(definition.namespace));
4754
4807
  const role = quoteIdentifier(consumerRoleName(definition.namespace));
@@ -4770,6 +4823,17 @@ function syncedViewStatements({
4770
4823
  const viewName = `${schema}.${quoteIdentifier(table.table)}`;
4771
4824
  const physical2 = splitPhysicalRelation(backing.physicalRelation);
4772
4825
  const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
4826
+ const declaredColumns = Object.keys(table.columns ?? {});
4827
+ const projection = declaredColumns.length > 0 ? declaredColumns.map(quoteIdentifier).join(", ") : "*";
4828
+ if (backing.selectColumns) {
4829
+ if (declaredColumns.length === 0) {
4830
+ throw new OrmDefinitionError(`synced backing for "${table.table}" requires declared columns`);
4831
+ }
4832
+ const approved = new Set(backing.selectColumns);
4833
+ if (declaredColumns.some((column) => !approved.has(column))) {
4834
+ throw new OrmDefinitionError(`synced backing for "${table.table}" projects outside its approved column ceiling`);
4835
+ }
4836
+ }
4773
4837
  const columnTypes = mergeSyncedViewColumnTypes({
4774
4838
  table,
4775
4839
  canonicalColumnTypes: columnTypesByTable?.get(table.table),
@@ -4778,25 +4842,29 @@ function syncedViewStatements({
4778
4842
  });
4779
4843
  const readPolicyIr = readPolicyIrByTable?.get(table.table);
4780
4844
  const readPolicy = table.policies?.read;
4781
- const whereClause = readPolicyIr ? ` WHERE ${compilePredIrToSql(readPolicyIr, {
4845
+ const policyClause = readPolicyIr ? ` WHERE ${compilePredIrToSql(readPolicyIr, {
4782
4846
  namespace: definition.namespace,
4847
+ statementClaimChecks,
4783
4848
  columnTypes,
4784
4849
  columnNames: columnNamesByTable?.get(table.table)
4785
- })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
4850
+ })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes), { statementClaimChecks })}` : " WHERE false";
4786
4851
  if (backingPrivileges === "grant") {
4852
+ const selectPrivilege = backing.selectColumns ? `SELECT (${backing.selectColumns.map(quoteIdentifier).join(", ")})` : "SELECT";
4853
+ const hasSelectPrivilege = backing.selectColumns ? backing.selectColumns.map((column) => `pg_catalog.has_column_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physicalTarget)}, ${quoteLiteral(column)}, 'SELECT')`).join("\n AND ") : `pg_catalog.has_table_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physicalTarget)}, 'SELECT')`;
4787
4854
  statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
4788
- statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
4855
+ statements.push(`GRANT ${selectPrivilege} ON ${physicalTarget} TO ${owner}`);
4789
4856
  statements.push(`DO ${quoteLiteral(`BEGIN
4790
4857
  IF NOT (pg_catalog.has_schema_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physical2.schema)}, 'USAGE')
4791
- AND pg_catalog.has_table_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physicalTarget)}, 'SELECT')) THEN
4858
+ AND ${hasSelectPrivilege}) THEN
4792
4859
  RAISE EXCEPTION 'Synced backing owner lacks effective USAGE/SELECT; repair managed serving reader access before ORM apply' USING ERRCODE = '42501';
4793
4860
  END IF;
4794
4861
  END`)}`);
4795
4862
  }
4863
+ const whereClause = backing.readGuard ? ` WHERE (SELECT orm_native_sources.require_read_guard(${quoteLiteral(backing.readGuard)})) AND (${policyClause.slice(" WHERE ".length)})` : policyClause;
4796
4864
  statements.push(
4797
- `CREATE OR REPLACE VIEW ${viewName} AS SELECT * FROM ${physicalTarget}${whereClause}`
4865
+ `CREATE OR REPLACE VIEW ${viewName} AS SELECT ${projection} FROM ${physicalTarget}${whereClause}`
4798
4866
  );
4799
- statements.push(`GRANT SELECT ON ${viewName} TO ${role}`);
4867
+ if (viewPrivileges === "grant") statements.push(`GRANT SELECT ON ${viewName} TO ${role}`);
4800
4868
  }
4801
4869
  return statements;
4802
4870
  }
@@ -5065,10 +5133,142 @@ function snapshotTableNames(snapshot, schema) {
5065
5133
  return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
5066
5134
  }
5067
5135
 
5136
+ // src/sql/ast.ts
5137
+ function obj(value) {
5138
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("unsupported SQL node");
5139
+ return value;
5140
+ }
5141
+ function list(value) {
5142
+ return value === void 0 ? [] : Array.isArray(value) ? value : fail("expected SQL list");
5143
+ }
5144
+ function str(value) {
5145
+ return typeof value === "string" ? value : fail("expected SQL name");
5146
+ }
5147
+ function fail(message) {
5148
+ throw new Error(message);
5149
+ }
5150
+ function strings(value) {
5151
+ return list(value).map((v) => str(obj(obj(v).String).sval));
5152
+ }
5153
+
5154
+ // src/canonical/view-query.ts
5155
+ var parser;
5156
+ var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
5157
+ var scalarTypes = /* @__PURE__ */ new Set([
5158
+ "bool",
5159
+ "boolean",
5160
+ "int2",
5161
+ "int4",
5162
+ "int8",
5163
+ "integer",
5164
+ "bigint",
5165
+ "smallint",
5166
+ "numeric",
5167
+ "decimal",
5168
+ "float4",
5169
+ "float8",
5170
+ "real",
5171
+ "text",
5172
+ "varchar",
5173
+ "bpchar",
5174
+ "uuid",
5175
+ "date",
5176
+ "timestamp",
5177
+ "timestamptz",
5178
+ "time",
5179
+ "timetz",
5180
+ "interval",
5181
+ "json",
5182
+ "jsonb",
5183
+ "bytea"
5184
+ ]);
5185
+ var sqlValues = /* @__PURE__ */ new Set([
5186
+ "SVFOP_CURRENT_DATE",
5187
+ "SVFOP_CURRENT_TIME",
5188
+ "SVFOP_CURRENT_TIME_N",
5189
+ "SVFOP_CURRENT_TIMESTAMP",
5190
+ "SVFOP_CURRENT_TIMESTAMP_N",
5191
+ "SVFOP_LOCALTIME",
5192
+ "SVFOP_LOCALTIME_N",
5193
+ "SVFOP_LOCALTIMESTAMP",
5194
+ "SVFOP_LOCALTIMESTAMP_N",
5195
+ "SVFOP_CURRENT_ROLE",
5196
+ "SVFOP_CURRENT_USER",
5197
+ "SVFOP_USER",
5198
+ "SVFOP_SESSION_USER",
5199
+ "SVFOP_CURRENT_CATALOG",
5200
+ "SVFOP_CURRENT_SCHEMA"
5201
+ ]);
5202
+ async function validateManagedViewQueries({ canonical }) {
5203
+ for (const definition of [canonical, ...canonical.imports ?? []]) {
5204
+ for (const view of definition.views ?? []) {
5205
+ const fail3 = (message) => {
5206
+ throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
5207
+ };
5208
+ parser ??= new pgParser.PgParser({ version: 17 });
5209
+ const parsed = await parser.parse(view.query);
5210
+ if (parsed.error || !parsed.tree) fail3("invalid SELECT query");
5211
+ const statements = list(obj(parsed.tree).stmts);
5212
+ if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail3("one SELECT query is required");
5213
+ validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail3 });
5214
+ }
5215
+ }
5216
+ }
5217
+ function pgCatalogName(parts) {
5218
+ if (parts.length === 1) return parts[0];
5219
+ if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
5220
+ return void 0;
5221
+ }
5222
+ function reviewedFunctionName({ parts, format }) {
5223
+ return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
5224
+ }
5225
+ function validateTree({ tree, ...scope }) {
5226
+ const pending = [tree];
5227
+ while (pending.length) {
5228
+ const value = pending.pop();
5229
+ if (Array.isArray(value)) {
5230
+ pending.push(...value);
5231
+ continue;
5232
+ }
5233
+ if (!value || typeof value !== "object") continue;
5234
+ const node = obj(value);
5235
+ for (const [kind, child] of Object.entries(node)) {
5236
+ validateNode({ kind, child, scope });
5237
+ pending.push(child);
5238
+ }
5239
+ }
5240
+ }
5241
+ function validateNode({ kind, child, scope }) {
5242
+ const { fail: fail3 } = scope;
5243
+ if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail3("only SELECT statements are permitted");
5244
+ if (kind === "FuncCall") {
5245
+ const call = obj(child);
5246
+ const parts = strings(call.funcname);
5247
+ const name2 = reviewedFunctionName({ parts, format: call.funcformat });
5248
+ if (!name2 || !functions.has(name2)) fail3(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
5249
+ }
5250
+ if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
5251
+ if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail3("only pg_catalog operators are permitted");
5252
+ if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail3("only pg_catalog sort operators are permitted");
5253
+ if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail3("unsupported SQL value expression");
5254
+ if (kind === "ParamRef") fail3("view queries cannot contain parameters");
5255
+ if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
5256
+ fail3("composite field notation is not permitted in managed views");
5257
+ }
5258
+ }
5259
+ function validateType({ node, schema, enums, fail: fail3 }) {
5260
+ const parts = strings(node.names);
5261
+ const builtin = pgCatalogName(parts);
5262
+ const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
5263
+ if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
5264
+ fail3(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
5265
+ }
5266
+ }
5267
+
5068
5268
  // src/engine/materialized-refresh.ts
5069
5269
  function materializedRefreshFunctions(canonical) {
5070
5270
  const schema = ormSchemaName(canonical.namespace);
5071
- const functions = /* @__PURE__ */ new Map();
5271
+ const functions2 = /* @__PURE__ */ new Map();
5072
5272
  for (const view of canonical.views ?? []) {
5073
5273
  const materialized = view.resolved?.materialized;
5074
5274
  if (!materialized) continue;
@@ -5077,7 +5277,7 @@ function materializedRefreshFunctions(canonical) {
5077
5277
  if (!table || pk.length === 0) {
5078
5278
  throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
5079
5279
  }
5080
- functions.set(view.view, refreshFunctionSql({
5280
+ functions2.set(view.view, refreshFunctionSql({
5081
5281
  schema,
5082
5282
  name: view.view,
5083
5283
  table: materialized.table,
@@ -5086,13 +5286,13 @@ function materializedRefreshFunctions(canonical) {
5086
5286
  pk
5087
5287
  }));
5088
5288
  }
5089
- return functions;
5289
+ return functions2;
5090
5290
  }
5091
5291
  function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
5092
5292
  const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
5093
5293
  const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
5094
5294
  const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
5095
- const list = columns.map(quoteIdentifier).join(", ");
5295
+ const list2 = columns.map(quoteIdentifier).join(", ");
5096
5296
  const others = columns.filter((column) => !pk.includes(column));
5097
5297
  const keys = pk.map(quoteIdentifier).join(", ");
5098
5298
  const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
@@ -5118,8 +5318,8 @@ BEGIN
5118
5318
  PERFORM set_config('request.jwt.claims', ${quoteLiteral(MATERIALIZE_CLAIMS_JSON)}, true);
5119
5319
  RETURN QUERY
5120
5320
  WITH up AS (
5121
- INSERT INTO ${target} AS m (${list})
5122
- SELECT ${list} FROM ${source}
5321
+ INSERT INTO ${target} AS m (${list2})
5322
+ SELECT ${list2} FROM ${source}
5123
5323
  ON CONFLICT (${keys}) ${onConflict}
5124
5324
  RETURNING 1
5125
5325
  ), gone AS (
@@ -5730,6 +5930,7 @@ async function planWithViewRecreation({
5730
5930
  previousSnapshot,
5731
5931
  viewRecreation
5732
5932
  }) {
5933
+ await validateManagedViewQueries({ canonical });
5733
5934
  const schema = ormSchemaName(canonical.namespace);
5734
5935
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
5735
5936
  assertValidViewDependencies(canonical);
@@ -6272,6 +6473,118 @@ async function guardedRun(run) {
6272
6473
  process.exit = originalExit;
6273
6474
  }
6274
6475
  }
6476
+ var parser2;
6477
+ var locations = /* @__PURE__ */ new Set(["location", "stmt_location", "stmt_len"]);
6478
+ async function assertSyncedViewDefinitionMatches({
6479
+ viewDefinition,
6480
+ expectedCreateStatements
6481
+ }) {
6482
+ if (!expectedCreateStatements.length) fail2();
6483
+ const expected = [];
6484
+ for (const statement of expectedCreateStatements) {
6485
+ const node = await parseStatement(statement);
6486
+ const view = obj(node.ViewStmt);
6487
+ requireKeys(view, ["view", "query", "replace", "withCheckOption"]);
6488
+ if (view.replace !== true || view.withCheckOption !== "NO_CHECK_OPTION") fail2();
6489
+ backingOf(obj(view.view));
6490
+ expected.push(canonicalSelect(obj(obj(view.query).SelectStmt)));
6491
+ }
6492
+ const actual = await parseStatement(viewDefinition);
6493
+ if (!expected.includes(canonicalSelect(obj(actual.SelectStmt)))) fail2();
6494
+ }
6495
+ function fail2() {
6496
+ throw new OrmDefinitionError("synced view definition does not match the compiler");
6497
+ }
6498
+ async function parseStatement(sql7) {
6499
+ if (!sql7.length || sql7.length > 262144) fail2();
6500
+ parser2 ??= new pgParser.PgParser({ version: 17 });
6501
+ const parsed = await parser2.parse(sql7);
6502
+ if (parsed.error || !parsed.tree) fail2();
6503
+ const statements = list(obj(parsed.tree).stmts);
6504
+ if (statements.length !== 1) fail2();
6505
+ return obj(obj(statements[0]).stmt);
6506
+ }
6507
+ function requireKeys(node, allowed) {
6508
+ for (const key of Object.keys(node)) if (!locations.has(key) && !allowed.includes(key)) fail2();
6509
+ }
6510
+ function backingOf(node) {
6511
+ requireKeys(node, ["catalogname", "schemaname", "relname", "inh", "relpersistence"]);
6512
+ if (node.catalogname !== "" || node.inh !== true || node.relpersistence !== "p") fail2();
6513
+ const schema = str(node.schemaname);
6514
+ const relation = str(node.relname);
6515
+ if (!schema || !relation) fail2();
6516
+ return { schema, relation };
6517
+ }
6518
+ function validateSelect(select, scalar2) {
6519
+ requireKeys(select, ["targetList", "fromClause", "whereClause", "groupDistinct", "limitOption", "op", "all"]);
6520
+ if (select.groupDistinct !== false || select.limitOption !== "LIMIT_OPTION_DEFAULT" || select.op !== "SETOP_NONE" || select.all !== false) fail2();
6521
+ if (scalar2 && (list(select.fromClause).length || select.whereClause !== void 0 || list(select.targetList).length !== 1)) fail2();
6522
+ }
6523
+ function canonicalSelect(select) {
6524
+ validateSelect(select, false);
6525
+ const from = list(select.fromClause);
6526
+ if (from.length !== 1) fail2();
6527
+ const backing = backingOf(obj(obj(from[0]).RangeVar));
6528
+ const targets = list(select.targetList);
6529
+ if (!targets.length) fail2();
6530
+ for (const target of targets) {
6531
+ const node = obj(obj(target).ResTarget);
6532
+ requireKeys(node, ["name", "val"]);
6533
+ if (node.name !== "" || !obj(node.val).ColumnRef) fail2();
6534
+ }
6535
+ return JSON.stringify(normalize({ value: select, backing, scalar: false }));
6536
+ }
6537
+ function normalize({ value, backing, scalar: scalar2 }) {
6538
+ if (Array.isArray(value)) return value.map((entry) => normalize({ value: entry, backing, scalar: scalar2 }));
6539
+ if (!value || typeof value !== "object") return value;
6540
+ const node = obj(value);
6541
+ if (node.ColumnRef) return normalizeColumnRef({ ref: obj(node.ColumnRef), backing, scalar: scalar2 });
6542
+ if (node.TypeCast && isLiteralTextCast(obj(node.TypeCast))) {
6543
+ return normalize({ value: obj(node.TypeCast).arg, backing, scalar: scalar2 });
6544
+ }
6545
+ if (node.SubLink) return normalizeScalarSubLink({ sublink: obj(node.SubLink), backing });
6546
+ const result = {};
6547
+ for (const key of Object.keys(node).sort()) {
6548
+ if (locations.has(key)) continue;
6549
+ if (key === "ParamRef" || key === "RangeSubselect" || key === "JoinExpr" || key.endsWith("Stmt") && key !== "SelectStmt" || scalar2 && key === "RangeVar") fail2();
6550
+ result[key] = normalize({ value: node[key], backing, scalar: scalar2 });
6551
+ }
6552
+ return result;
6553
+ }
6554
+ function normalizeColumnRef({ ref, backing, scalar: scalar2 }) {
6555
+ if (scalar2) fail2();
6556
+ requireKeys(ref, ["fields"]);
6557
+ const fields = strings(ref.fields);
6558
+ const qualified = fields.length === 2 && fields[0] === backing.relation || fields.length === 3 && fields[0] === backing.schema && fields[1] === backing.relation;
6559
+ if (fields.length !== 1 && !qualified) fail2();
6560
+ const column = fields.at(-1);
6561
+ if (!column) fail2();
6562
+ return { ColumnRef: { fields: [{ String: { sval: column } }] } };
6563
+ }
6564
+ function normalizeScalarSubLink({ sublink, backing }) {
6565
+ requireKeys(sublink, ["subLinkType", "subLinkId", "subselect"]);
6566
+ if (sublink.subLinkType !== "EXPR_SUBLINK" || sublink.subLinkId !== 0) fail2();
6567
+ const select = obj(obj(sublink.subselect).SelectStmt);
6568
+ validateSelect(select, true);
6569
+ const target = obj(obj(list(select.targetList)[0]).ResTarget);
6570
+ requireKeys(target, ["name", "val"]);
6571
+ const normalizedSelect = { ...select, targetList: [{ ResTarget: { ...target, name: "" } }] };
6572
+ return { SubLink: normalize({ value: { ...sublink, subselect: { SelectStmt: normalizedSelect } }, backing, scalar: true }) };
6573
+ }
6574
+ function isLiteralTextCast(cast) {
6575
+ requireKeys(cast, ["arg", "typeName"]);
6576
+ const arg = obj(cast.arg);
6577
+ if (!arg.A_Const) return false;
6578
+ const constant = obj(arg.A_Const);
6579
+ if (constant.isnull !== false || !constant.sval || typeof obj(constant.sval).sval !== "string") return false;
6580
+ const type = obj(cast.typeName);
6581
+ const names = strings(type.names);
6582
+ if (!(names.length === 1 && names[0] === "text" || names.length === 2 && names[0] === "pg_catalog" && names[1] === "text")) return false;
6583
+ if (list(type.arrayBounds).length || list(type.typmods).length) return false;
6584
+ requireKeys(type, ["names", "typeOid", "setof", "pct_type", "typemod"]);
6585
+ if (type.typeOid !== 0 || type.setof !== false || type.pct_type !== false || type.typemod !== -1) return false;
6586
+ return true;
6587
+ }
6275
6588
 
6276
6589
  // src/engine/index.ts
6277
6590
  var STATEMENT_KINDS = ["hint", "ddl", "security", "function", "synced_view"];
@@ -6341,6 +6654,11 @@ function renderOrmMigrationExport(exported) {
6341
6654
  if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
6342
6655
  return text4;
6343
6656
  }
6657
+
6658
+ // src/client/sql.ts
6659
+ var BUILTIN_SQL_READ_HASH = "sha256:a50c477c952a5e0fe68ed158a4530e4aab573c09bcbb1a8ac454dc88a39a6258";
6660
+
6661
+ // src/ir/index.ts
6344
6662
  var IR_VERSION = 5;
6345
6663
  var LEGACY_IR_VERSION = 1;
6346
6664
  var GUARDED_IR_VERSION = 2;
@@ -6428,7 +6746,7 @@ var limitIrSchema = zod.z.object({
6428
6746
  variable: zod.z.string().optional(),
6429
6747
  value: zod.z.number().int().positive().optional(),
6430
6748
  default: zod.z.number().int().positive(),
6431
- max: zod.z.number().int().positive()
6749
+ max: zod.z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
6432
6750
  });
6433
6751
  var selectionIrSchema = zod.z.lazy(
6434
6752
  () => zod.z.object({
@@ -6673,7 +6991,8 @@ var compiledOperationSchema = zod.z.object({
6673
6991
  }).superRefine((operation, ctx) => {
6674
6992
  if (operation.language === "sql") {
6675
6993
  const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
6676
- if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
6994
+ const builtin = operation.name === "BuiltinSqlRead" && operation.opHash === BUILTIN_SQL_READ_HASH && operation.requires === null;
6995
+ if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !builtin && !operation.requires?.capability) {
6677
6996
  ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
6678
6997
  }
6679
6998
  } else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
@@ -8104,6 +8423,7 @@ exports.MATERIALIZE_CLAIMS_JSON = MATERIALIZE_CLAIMS_JSON;
8104
8423
  exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
8105
8424
  exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
8106
8425
  exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
8426
+ exports.MAX_PERSISTED_READ_ROWS = MAX_PERSISTED_READ_ROWS;
8107
8427
  exports.MAX_TRACKED_COLUMN_NAME = MAX_TRACKED_COLUMN_NAME;
8108
8428
  exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
8109
8429
  exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
@@ -8144,6 +8464,7 @@ exports.actionsInterfaceName = actionsInterfaceName;
8144
8464
  exports.activeAuthoringContext = activeAuthoringContext;
8145
8465
  exports.applyMigrationHints = applyMigrationHints;
8146
8466
  exports.applyMigrationHintsV2 = applyMigrationHintsV2;
8467
+ exports.assertSyncedViewDefinitionMatches = assertSyncedViewDefinitionMatches;
8147
8468
  exports.beginAuthoringContext = beginAuthoringContext;
8148
8469
  exports.buildActionParamsSchema = buildActionParamsSchema;
8149
8470
  exports.buildDrizzleEntities = buildDrizzleEntities;