@sequenceholdings/orm 2.4.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.
- package/dist/authoring.cjs +53 -7
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +11 -7
- package/dist/authoring.d.ts +11 -7
- package/dist/authoring.js +53 -7
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +48 -2
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +72 -72
- package/dist/canonical.d.ts +72 -72
- package/dist/canonical.js +48 -2
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +237 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +237 -20
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +386 -14
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +16 -3
- package/dist/compiler.d.ts +16 -3
- package/dist/compiler.js +386 -15
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-C6EG6Wq7.d.cts → constants-Dou1RkTo.d.cts} +7 -2
- package/dist/{constants-C6EG6Wq7.d.ts → constants-Dou1RkTo.d.ts} +7 -2
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +329 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -58
- package/dist/index.d.ts +66 -58
- package/dist/index.js +329 -131
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +50 -3
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +71 -71
- package/dist/ir.d.ts +71 -71
- package/dist/ir.js +50 -3
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +50 -3
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.d.cts +1 -1
- package/dist/predicate-v2.d.ts +1 -1
- package/dist/predicate-v2.js +50 -3
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +53 -3
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +3 -1
- package/dist/sdl.d.ts +3 -1
- package/dist/sdl.js +53 -3
- package/dist/sdl.js.map +1 -1
- package/package.json +1 -1
- package/dist/{definition-DjfC7Lll.d.cts → definition-B99BsOAO.d.cts} +4 -4
- package/dist/{definition-DjfC7Lll.d.ts → definition-B99BsOAO.d.ts} +4 -4
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) =>
|
|
173
|
-
|
|
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
|
|
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()
|
|
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
|
|
1263
|
+
const fail2 = (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
|
-
|
|
1266
|
+
fail2("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: fail2
|
|
1272
1277
|
};
|
|
1273
|
-
if (resolved.sources.filter((source) => source.kind === "manual").length !== 1)
|
|
1278
|
+
if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail2("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))
|
|
1281
|
+
if (scope.sourceByName.has(source.name)) fail2(`duplicate source '${source.name}'`);
|
|
1277
1282
|
scope.sourceByName.set(source.name, source);
|
|
1278
1283
|
const table = tableByName.get(source.table);
|
|
1279
|
-
if (!table)
|
|
1284
|
+
if (!table) fail2(`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")
|
|
1291
|
+
if (scope.sourceByName.get(name2)?.kind !== "manual") fail2(`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)
|
|
1296
|
+
const { fail: fail2 } = scope;
|
|
1297
|
+
if (source.kind === "etl" && table.source === void 0) fail2(`etl source '${source.name}' must reference a synced table`);
|
|
1298
|
+
if (source.kind === "etl" && table.primaryKey?.length === 0) fail2(`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
|
-
|
|
1300
|
+
fail2(`${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)
|
|
1298
|
-
if (source.kind === "prefetch" && hasRev)
|
|
1303
|
+
if (source.kind === "manual" && !hasRev) fail2(`manual source '${source.name}' must carry '__rev' cell columns`);
|
|
1304
|
+
if (source.kind === "prefetch" && hasRev) fail2(`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
|
-
|
|
1310
|
+
fail2(`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: fail2, pkColumn } = scope;
|
|
1311
1317
|
if (source.kind !== "prefetch") {
|
|
1312
1318
|
if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
|
|
1313
|
-
|
|
1319
|
+
fail2(`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
|
-
|
|
1324
|
+
fail2(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
|
|
1319
1325
|
} else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
|
|
1320
|
-
|
|
1326
|
+
fail2(`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))
|
|
1329
|
+
if (!source.columns.includes(column)) fail2(`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: fail2, 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)
|
|
1332
|
-
else if (!theirs)
|
|
1333
|
-
else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type))
|
|
1337
|
+
if (!own) fail2(`source '${source.name}' supplies unknown column '${column}'`);
|
|
1338
|
+
else if (!theirs) fail2(`source '${source.name}' table '${source.table}' has no column '${column}'`);
|
|
1339
|
+
else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail2(`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: fail2 }) {
|
|
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
|
-
|
|
1346
|
+
fail2(`resolve rule targets unknown column '${rule.column}'`);
|
|
1341
1347
|
continue;
|
|
1342
1348
|
}
|
|
1343
|
-
if (ruled.has(rule.column))
|
|
1349
|
+
if (ruled.has(rule.column)) fail2(`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
|
-
|
|
1352
|
+
fail2(`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)
|
|
1354
|
+
if (new Set(rule.candidates).size !== rule.candidates.length) fail2(`column '${rule.column}' repeats a candidate`);
|
|
1349
1355
|
for (const candidate of rule.candidates) {
|
|
1350
1356
|
const source = sourceByName.get(candidate);
|
|
1351
|
-
if (!source)
|
|
1352
|
-
else if (!source.columns.includes(rule.column))
|
|
1357
|
+
if (!source) fail2(`column '${rule.column}' names unknown candidate source '${candidate}'`);
|
|
1358
|
+
else if (!source.columns.includes(rule.column)) fail2(`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))
|
|
1362
|
+
if (column !== pkColumn.name && !ruled.has(column)) fail2(`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: fail2 }) {
|
|
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
|
-
|
|
1383
|
+
fail2(`unexpected cell column '${column.name}'`);
|
|
1378
1384
|
continue;
|
|
1379
1385
|
}
|
|
1380
|
-
if (column.type !== VIEW_CELL_TYPES[column.cell.part])
|
|
1386
|
+
if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail2(`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())
|
|
1389
|
+
for (const name2 of expected.keys()) fail2(`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: fail2 } = 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
|
-
|
|
1397
|
+
fail2(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
|
|
1392
1398
|
} else {
|
|
1393
|
-
if (columnSignature(table.columns) !== columnSignature(view.columns))
|
|
1394
|
-
if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? []))
|
|
1399
|
+
if (columnSignature(table.columns) !== columnSignature(view.columns)) fail2(`materialized table '${tableName}' must have exactly the view's columns`);
|
|
1400
|
+
if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail2(`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)
|
|
1398
|
-
else if (columnSignature(compute.columns) !== columnSignature(view.columns))
|
|
1403
|
+
if (!compute || compute.internal !== true) fail2(`compute view '${computeName}' must be an internal view`);
|
|
1404
|
+
else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail2(`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
|
-
|
|
1410
|
+
fail2(`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).
|
|
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 fail2 = (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
|
+
fail2("requires a typed synced source");
|
|
1788
|
+
}
|
|
1789
|
+
if ([table.policies.write, table.policies.insert, table.policies.update].some((policy) => policy !== void 0)) {
|
|
1790
|
+
fail2("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
|
+
fail2("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
|
+
fail2("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") fail2("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
|
|
1895
|
+
const fail2 = (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)
|
|
1899
|
+
for (const { position } of cells) fail2(`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
|
-
|
|
1903
|
+
fail2(`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)
|
|
1909
|
+
if (withRev && table.internal !== true) fail2(`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
|
-
|
|
1915
|
+
fail2(`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")
|
|
1918
|
+
if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail2(`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
|
-
|
|
1920
|
+
fail2(`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
|
-
|
|
1923
|
+
fail2(`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))
|
|
1926
|
+
if (seen.has(key)) fail2(`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
|
-
|
|
1931
|
+
fail2(`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
|
-
|
|
1934
|
+
fail2(`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}`))
|
|
1937
|
+
if (!seen.has(`${column.name}:${part}`)) fail2(`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
|
|
1956
|
-
if (table.source)
|
|
1957
|
-
if (names.has(check.name))
|
|
2005
|
+
const fail2 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
|
|
2006
|
+
if (table.source) fail2("synced tables cannot declare checks");
|
|
2007
|
+
if (names.has(check.name)) fail2(`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
|
-
|
|
2012
|
+
fail2(error instanceof Error ? error.message : String(error));
|
|
1963
2013
|
}
|
|
1964
2014
|
}
|
|
1965
2015
|
for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
|
|
1966
|
-
const
|
|
1967
|
-
if (names.has(fk.name))
|
|
2016
|
+
const fail2 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
|
|
2017
|
+
if (names.has(fk.name)) fail2(`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
|
-
|
|
2021
|
+
fail2("foreign keys must connect owned tables in the same namespace");
|
|
1972
2022
|
continue;
|
|
1973
2023
|
}
|
|
1974
|
-
if (fk.columns.length !== fk.references.length)
|
|
1975
|
-
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length)
|
|
2024
|
+
if (fk.columns.length !== fk.references.length) fail2("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) fail2("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)
|
|
1980
|
-
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type))
|
|
2029
|
+
if (!sourceColumn || !targetColumn) fail2("foreign key references an unknown column");
|
|
2030
|
+
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail2("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)))
|
|
2033
|
+
if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail2("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))))
|
|
2036
|
+
if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail2("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))
|
|
2224
|
+
const { sourceName, input: source, fail: fail2 } = input;
|
|
2225
|
+
if (!IDENTIFIER_PATTERN.test(sourceName)) fail2(`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,
|
|
2212
|
-
for (const column of
|
|
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: fail2 } = input;
|
|
2216
2266
|
const table = typeof source.table === "function" ? source.table() : source.table;
|
|
2217
|
-
if (!isSeqTable(table) || table.source === void 0)
|
|
2267
|
+
if (!isSeqTable(table) || table.source === void 0) fail2(`etl source "${sourceName}" must reference a defineSyncedTable`);
|
|
2218
2268
|
const synced = table;
|
|
2219
|
-
if (
|
|
2269
|
+
if (synced.config.primaryKey?.length === 0) fail2(`etl source "${sourceName}" cannot use an unkeyed synced table for resolved row identity`);
|
|
2270
|
+
if (!(pk in synced.columns)) fail2(`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
|
-
|
|
2274
|
+
fail2(`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: fail2 } = input;
|
|
2229
2280
|
assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
|
|
2230
2281
|
if (source.key !== pk && !source.columns.includes(source.key)) {
|
|
2231
|
-
|
|
2282
|
+
fail2(`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))
|
|
2285
|
+
if (!source.columns.includes(column)) fail2(`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
|
|
2491
|
+
const fail2 = (message) => {
|
|
2441
2492
|
throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
|
|
2442
2493
|
};
|
|
2443
|
-
if (!IDENTIFIER_PATTERN.test(name2))
|
|
2494
|
+
if (!IDENTIFIER_PATTERN.test(name2)) fail2(`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
|
-
|
|
2497
|
+
fail2(`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
|
-
|
|
2502
|
+
fail2(`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)
|
|
2506
|
+
if (pkEntries.length !== 1) fail2("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)
|
|
2509
|
+
if (dataColumns.length === 0) fail2("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)
|
|
2511
|
+
if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail2("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: fail2 })
|
|
2463
2514
|
);
|
|
2464
|
-
const rules = planRules({ config, plans, dataColumns, fail });
|
|
2515
|
+
const rules = planRules({ config, plans, dataColumns, fail: fail2 });
|
|
2465
2516
|
const deletedBy = [...config.rows?.deletedBy ?? []];
|
|
2466
2517
|
for (const source of deletedBy) {
|
|
2467
|
-
if (plans.find((plan) => plan.name === source)?.kind !== "manual")
|
|
2518
|
+
if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail2(`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: fail2 }) {
|
|
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))
|
|
2585
|
+
if (!dataColumns.includes(column)) fail2(`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)
|
|
2543
|
-
else if (!plan.columns.includes(column))
|
|
2593
|
+
if (!plan) fail2(`column "${column}" names unknown source "${candidate}"`);
|
|
2594
|
+
else if (!plan.columns.includes(column)) fail2(`column "${column}" candidate "${candidate}" does not supply it`);
|
|
2544
2595
|
}
|
|
2545
|
-
if (candidates.length === 0)
|
|
2546
|
-
if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail });
|
|
2596
|
+
if (candidates.length === 0) fail2(`column "${column}" has no candidate source`);
|
|
2597
|
+
if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail: fail2 });
|
|
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: fail2 }) {
|
|
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
|
-
|
|
2655
|
+
fail2(`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
|
-
|
|
2659
|
+
fail2(`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
|
|
3187
|
+
const fail2 = (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
|
|
3191
|
+
return fail2("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
|
|
3194
|
+
return fail2("sets a referential action without enforce: true");
|
|
3144
3195
|
}
|
|
3145
3196
|
if (link.linkKind === "toPlatformUser") {
|
|
3146
|
-
if (link.target !== void 0) return
|
|
3197
|
+
if (link.target !== void 0) return fail2("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
|
|
3208
|
+
if (link.enforce) return fail2("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: fail2 });
|
|
3170
3221
|
const targetName = relationName(target);
|
|
3171
3222
|
if (!relationsByName.has(targetName)) {
|
|
3172
|
-
return
|
|
3223
|
+
return fail2(`targets ${relationKind(target)} "${targetName}" which is not part of this namespace`);
|
|
3173
3224
|
}
|
|
3174
3225
|
if (link.enforce && (!isSeqTable(relation) || !isSeqTable(target))) {
|
|
3175
|
-
return
|
|
3226
|
+
return fail2("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
|
|
3242
|
+
function resolveRelationRef({ ref, fail: fail2 }) {
|
|
3243
|
+
if (ref === void 0) return fail2("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
|
|
3249
|
+
return fail2(
|
|
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
|
|
3254
|
+
return fail2("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
|
|
3357
|
+
if (primaryKey2 === void 0) {
|
|
3307
3358
|
throw new OrmDefinitionError(
|
|
3308
|
-
`synced table "${table.table}" has no primary key \u2014
|
|
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);
|
|
@@ -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((
|
|
4164
|
+
return zod.z.strictObject(shape).superRefine((obj2, ctx) => {
|
|
4114
4165
|
for (const [name2, param] of entries) {
|
|
4115
|
-
if (!param.optional && param.type === "jsonb" &&
|
|
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
|
}
|
|
@@ -4770,6 +4821,17 @@ function syncedViewStatements({
|
|
|
4770
4821
|
const viewName = `${schema}.${quoteIdentifier(table.table)}`;
|
|
4771
4822
|
const physical2 = splitPhysicalRelation(backing.physicalRelation);
|
|
4772
4823
|
const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
|
|
4824
|
+
const declaredColumns = Object.keys(table.columns ?? {});
|
|
4825
|
+
const projection = declaredColumns.length > 0 ? declaredColumns.map(quoteIdentifier).join(", ") : "*";
|
|
4826
|
+
if (backing.selectColumns) {
|
|
4827
|
+
if (declaredColumns.length === 0) {
|
|
4828
|
+
throw new OrmDefinitionError(`synced backing for "${table.table}" requires declared columns`);
|
|
4829
|
+
}
|
|
4830
|
+
const approved = new Set(backing.selectColumns);
|
|
4831
|
+
if (declaredColumns.some((column) => !approved.has(column))) {
|
|
4832
|
+
throw new OrmDefinitionError(`synced backing for "${table.table}" projects outside its approved column ceiling`);
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4773
4835
|
const columnTypes = mergeSyncedViewColumnTypes({
|
|
4774
4836
|
table,
|
|
4775
4837
|
canonicalColumnTypes: columnTypesByTable?.get(table.table),
|
|
@@ -4778,23 +4840,26 @@ function syncedViewStatements({
|
|
|
4778
4840
|
});
|
|
4779
4841
|
const readPolicyIr = readPolicyIrByTable?.get(table.table);
|
|
4780
4842
|
const readPolicy = table.policies?.read;
|
|
4781
|
-
const
|
|
4843
|
+
const policyClause = readPolicyIr ? ` WHERE ${compilePredIrToSql(readPolicyIr, {
|
|
4782
4844
|
namespace: definition.namespace,
|
|
4783
4845
|
columnTypes,
|
|
4784
4846
|
columnNames: columnNamesByTable?.get(table.table)
|
|
4785
4847
|
})}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
|
|
4786
4848
|
if (backingPrivileges === "grant") {
|
|
4849
|
+
const selectPrivilege = backing.selectColumns ? `SELECT (${backing.selectColumns.map(quoteIdentifier).join(", ")})` : "SELECT";
|
|
4850
|
+
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
4851
|
statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
|
|
4788
|
-
statements.push(`GRANT
|
|
4852
|
+
statements.push(`GRANT ${selectPrivilege} ON ${physicalTarget} TO ${owner}`);
|
|
4789
4853
|
statements.push(`DO ${quoteLiteral(`BEGIN
|
|
4790
4854
|
IF NOT (pg_catalog.has_schema_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physical2.schema)}, 'USAGE')
|
|
4791
|
-
AND
|
|
4855
|
+
AND ${hasSelectPrivilege}) THEN
|
|
4792
4856
|
RAISE EXCEPTION 'Synced backing owner lacks effective USAGE/SELECT; repair managed serving reader access before ORM apply' USING ERRCODE = '42501';
|
|
4793
4857
|
END IF;
|
|
4794
4858
|
END`)}`);
|
|
4795
4859
|
}
|
|
4860
|
+
const whereClause = backing.readGuard ? ` WHERE (SELECT orm_native_sources.require_read_guard(${quoteLiteral(backing.readGuard)})) AND (${policyClause.slice(" WHERE ".length)})` : policyClause;
|
|
4796
4861
|
statements.push(
|
|
4797
|
-
`CREATE OR REPLACE VIEW ${viewName} AS SELECT
|
|
4862
|
+
`CREATE OR REPLACE VIEW ${viewName} AS SELECT ${projection} FROM ${physicalTarget}${whereClause}`
|
|
4798
4863
|
);
|
|
4799
4864
|
statements.push(`GRANT SELECT ON ${viewName} TO ${role}`);
|
|
4800
4865
|
}
|
|
@@ -5065,10 +5130,142 @@ function snapshotTableNames(snapshot, schema) {
|
|
|
5065
5130
|
return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
|
|
5066
5131
|
}
|
|
5067
5132
|
|
|
5133
|
+
// src/sql/ast.ts
|
|
5134
|
+
function obj(value) {
|
|
5135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("unsupported SQL node");
|
|
5136
|
+
return value;
|
|
5137
|
+
}
|
|
5138
|
+
function list(value) {
|
|
5139
|
+
return value === void 0 ? [] : Array.isArray(value) ? value : fail("expected SQL list");
|
|
5140
|
+
}
|
|
5141
|
+
function str(value) {
|
|
5142
|
+
return typeof value === "string" ? value : fail("expected SQL name");
|
|
5143
|
+
}
|
|
5144
|
+
function fail(message) {
|
|
5145
|
+
throw new Error(message);
|
|
5146
|
+
}
|
|
5147
|
+
function strings(value) {
|
|
5148
|
+
return list(value).map((v) => str(obj(obj(v).String).sval));
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
// src/canonical/view-query.ts
|
|
5152
|
+
var parser;
|
|
5153
|
+
var functions = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
|
|
5154
|
+
var scalarTypes = /* @__PURE__ */ new Set([
|
|
5155
|
+
"bool",
|
|
5156
|
+
"boolean",
|
|
5157
|
+
"int2",
|
|
5158
|
+
"int4",
|
|
5159
|
+
"int8",
|
|
5160
|
+
"integer",
|
|
5161
|
+
"bigint",
|
|
5162
|
+
"smallint",
|
|
5163
|
+
"numeric",
|
|
5164
|
+
"decimal",
|
|
5165
|
+
"float4",
|
|
5166
|
+
"float8",
|
|
5167
|
+
"real",
|
|
5168
|
+
"text",
|
|
5169
|
+
"varchar",
|
|
5170
|
+
"bpchar",
|
|
5171
|
+
"uuid",
|
|
5172
|
+
"date",
|
|
5173
|
+
"timestamp",
|
|
5174
|
+
"timestamptz",
|
|
5175
|
+
"time",
|
|
5176
|
+
"timetz",
|
|
5177
|
+
"interval",
|
|
5178
|
+
"json",
|
|
5179
|
+
"jsonb",
|
|
5180
|
+
"bytea"
|
|
5181
|
+
]);
|
|
5182
|
+
var sqlValues = /* @__PURE__ */ new Set([
|
|
5183
|
+
"SVFOP_CURRENT_DATE",
|
|
5184
|
+
"SVFOP_CURRENT_TIME",
|
|
5185
|
+
"SVFOP_CURRENT_TIME_N",
|
|
5186
|
+
"SVFOP_CURRENT_TIMESTAMP",
|
|
5187
|
+
"SVFOP_CURRENT_TIMESTAMP_N",
|
|
5188
|
+
"SVFOP_LOCALTIME",
|
|
5189
|
+
"SVFOP_LOCALTIME_N",
|
|
5190
|
+
"SVFOP_LOCALTIMESTAMP",
|
|
5191
|
+
"SVFOP_LOCALTIMESTAMP_N",
|
|
5192
|
+
"SVFOP_CURRENT_ROLE",
|
|
5193
|
+
"SVFOP_CURRENT_USER",
|
|
5194
|
+
"SVFOP_USER",
|
|
5195
|
+
"SVFOP_SESSION_USER",
|
|
5196
|
+
"SVFOP_CURRENT_CATALOG",
|
|
5197
|
+
"SVFOP_CURRENT_SCHEMA"
|
|
5198
|
+
]);
|
|
5199
|
+
async function validateManagedViewQueries({ canonical }) {
|
|
5200
|
+
for (const definition of [canonical, ...canonical.imports ?? []]) {
|
|
5201
|
+
for (const view of definition.views ?? []) {
|
|
5202
|
+
const fail2 = (message) => {
|
|
5203
|
+
throw new OrmDefinitionError(`managed view '${definition.namespace}/${view.view}': ${message}`);
|
|
5204
|
+
};
|
|
5205
|
+
parser ??= new pgParser.PgParser({ version: 17 });
|
|
5206
|
+
const parsed = await parser.parse(view.query);
|
|
5207
|
+
if (parsed.error || !parsed.tree) fail2("invalid SELECT query");
|
|
5208
|
+
const statements = list(obj(parsed.tree).stmts);
|
|
5209
|
+
if (statements.length !== 1 || !obj(obj(statements[0]).stmt).SelectStmt) fail2("one SELECT query is required");
|
|
5210
|
+
validateTree({ tree: parsed.tree, schema: ormSchemaName(definition.namespace), enums: definition.enums.map((entry) => entry.name), fail: fail2 });
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
function pgCatalogName(parts) {
|
|
5215
|
+
if (parts.length === 1) return parts[0];
|
|
5216
|
+
if (parts.length === 2 && parts[0] === "pg_catalog") return parts[1];
|
|
5217
|
+
return void 0;
|
|
5218
|
+
}
|
|
5219
|
+
function reviewedFunctionName({ parts, format }) {
|
|
5220
|
+
return format === "COERCE_SQL_SYNTAX" && parts.length === 2 && parts[0] === "pg_catalog" && parts[1] === "position" ? "strpos" : pgCatalogName(parts);
|
|
5221
|
+
}
|
|
5222
|
+
function validateTree({ tree, ...scope }) {
|
|
5223
|
+
const pending = [tree];
|
|
5224
|
+
while (pending.length) {
|
|
5225
|
+
const value = pending.pop();
|
|
5226
|
+
if (Array.isArray(value)) {
|
|
5227
|
+
pending.push(...value);
|
|
5228
|
+
continue;
|
|
5229
|
+
}
|
|
5230
|
+
if (!value || typeof value !== "object") continue;
|
|
5231
|
+
const node = obj(value);
|
|
5232
|
+
for (const [kind, child] of Object.entries(node)) {
|
|
5233
|
+
validateNode({ kind, child, scope });
|
|
5234
|
+
pending.push(child);
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
}
|
|
5238
|
+
function validateNode({ kind, child, scope }) {
|
|
5239
|
+
const { fail: fail2 } = scope;
|
|
5240
|
+
if (kind.endsWith("Stmt") && kind !== "SelectStmt") fail2("only SELECT statements are permitted");
|
|
5241
|
+
if (kind === "FuncCall") {
|
|
5242
|
+
const call = obj(child);
|
|
5243
|
+
const parts = strings(call.funcname);
|
|
5244
|
+
const name2 = reviewedFunctionName({ parts, format: call.funcformat });
|
|
5245
|
+
if (!name2 || !functions.has(name2)) fail2(`only explicitly allowlisted pg_catalog functions are permitted (found '${parts.join(".")}')`);
|
|
5246
|
+
}
|
|
5247
|
+
if (kind === "typeName" || kind === "TypeName") validateType({ node: obj(child), ...scope });
|
|
5248
|
+
if (kind === "A_Expr" && pgCatalogName(strings(obj(child).name)) === void 0) fail2("only pg_catalog operators are permitted");
|
|
5249
|
+
if (kind === "SortBy" && list(obj(child).useOp).length && pgCatalogName(strings(obj(child).useOp)) === void 0) fail2("only pg_catalog sort operators are permitted");
|
|
5250
|
+
if (kind === "SQLValueFunction" && !sqlValues.has(String(obj(child).op))) fail2("unsupported SQL value expression");
|
|
5251
|
+
if (kind === "ParamRef") fail2("view queries cannot contain parameters");
|
|
5252
|
+
if (kind === "A_Indirection" && list(obj(child).indirection).some((entry) => !obj(entry).A_Indices)) {
|
|
5253
|
+
fail2("composite field notation is not permitted in managed views");
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
function validateType({ node, schema, enums, fail: fail2 }) {
|
|
5257
|
+
const parts = strings(node.names);
|
|
5258
|
+
const builtin = pgCatalogName(parts);
|
|
5259
|
+
const managedEnum = parts.length === 1 && enums.includes(parts[0]) || parts.length === 2 && parts[0] === schema && enums.includes(parts[1]);
|
|
5260
|
+
if (node.setof || node.pct_type || !managedEnum && (!builtin || !scalarTypes.has(builtin))) {
|
|
5261
|
+
fail2(`casts may only use supported pg_catalog scalar types or declared enums (found '${parts.join(".")}')`);
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5068
5265
|
// src/engine/materialized-refresh.ts
|
|
5069
5266
|
function materializedRefreshFunctions(canonical) {
|
|
5070
5267
|
const schema = ormSchemaName(canonical.namespace);
|
|
5071
|
-
const
|
|
5268
|
+
const functions2 = /* @__PURE__ */ new Map();
|
|
5072
5269
|
for (const view of canonical.views ?? []) {
|
|
5073
5270
|
const materialized = view.resolved?.materialized;
|
|
5074
5271
|
if (!materialized) continue;
|
|
@@ -5077,7 +5274,7 @@ function materializedRefreshFunctions(canonical) {
|
|
|
5077
5274
|
if (!table || pk.length === 0) {
|
|
5078
5275
|
throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
|
|
5079
5276
|
}
|
|
5080
|
-
|
|
5277
|
+
functions2.set(view.view, refreshFunctionSql({
|
|
5081
5278
|
schema,
|
|
5082
5279
|
name: view.view,
|
|
5083
5280
|
table: materialized.table,
|
|
@@ -5086,13 +5283,13 @@ function materializedRefreshFunctions(canonical) {
|
|
|
5086
5283
|
pk
|
|
5087
5284
|
}));
|
|
5088
5285
|
}
|
|
5089
|
-
return
|
|
5286
|
+
return functions2;
|
|
5090
5287
|
}
|
|
5091
5288
|
function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
|
|
5092
5289
|
const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
|
|
5093
5290
|
const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
|
|
5094
5291
|
const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
|
|
5095
|
-
const
|
|
5292
|
+
const list2 = columns.map(quoteIdentifier).join(", ");
|
|
5096
5293
|
const others = columns.filter((column) => !pk.includes(column));
|
|
5097
5294
|
const keys = pk.map(quoteIdentifier).join(", ");
|
|
5098
5295
|
const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
|
|
@@ -5118,8 +5315,8 @@ BEGIN
|
|
|
5118
5315
|
PERFORM set_config('request.jwt.claims', ${quoteLiteral(MATERIALIZE_CLAIMS_JSON)}, true);
|
|
5119
5316
|
RETURN QUERY
|
|
5120
5317
|
WITH up AS (
|
|
5121
|
-
INSERT INTO ${target} AS m (${
|
|
5122
|
-
SELECT ${
|
|
5318
|
+
INSERT INTO ${target} AS m (${list2})
|
|
5319
|
+
SELECT ${list2} FROM ${source}
|
|
5123
5320
|
ON CONFLICT (${keys}) ${onConflict}
|
|
5124
5321
|
RETURNING 1
|
|
5125
5322
|
), gone AS (
|
|
@@ -5730,6 +5927,7 @@ async function planWithViewRecreation({
|
|
|
5730
5927
|
previousSnapshot,
|
|
5731
5928
|
viewRecreation
|
|
5732
5929
|
}) {
|
|
5930
|
+
await validateManagedViewQueries({ canonical });
|
|
5733
5931
|
const schema = ormSchemaName(canonical.namespace);
|
|
5734
5932
|
const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
|
|
5735
5933
|
assertValidViewDependencies(canonical);
|
|
@@ -6428,7 +6626,7 @@ var limitIrSchema = zod.z.object({
|
|
|
6428
6626
|
variable: zod.z.string().optional(),
|
|
6429
6627
|
value: zod.z.number().int().positive().optional(),
|
|
6430
6628
|
default: zod.z.number().int().positive(),
|
|
6431
|
-
max: zod.z.number().int().positive()
|
|
6629
|
+
max: zod.z.number().int().positive().max(MAX_PERSISTED_READ_ROWS)
|
|
6432
6630
|
});
|
|
6433
6631
|
var selectionIrSchema = zod.z.lazy(
|
|
6434
6632
|
() => zod.z.object({
|
|
@@ -8104,6 +8302,7 @@ exports.MATERIALIZE_CLAIMS_JSON = MATERIALIZE_CLAIMS_JSON;
|
|
|
8104
8302
|
exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
|
|
8105
8303
|
exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
|
|
8106
8304
|
exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
|
|
8305
|
+
exports.MAX_PERSISTED_READ_ROWS = MAX_PERSISTED_READ_ROWS;
|
|
8107
8306
|
exports.MAX_TRACKED_COLUMN_NAME = MAX_TRACKED_COLUMN_NAME;
|
|
8108
8307
|
exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
|
|
8109
8308
|
exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
|