@sequenceholdings/orm 2.8.4 → 2.9.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 +142 -56
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +7 -3
- package/dist/authoring.d.ts +7 -3
- package/dist/authoring.js +142 -56
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +63 -13
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +77 -72
- package/dist/canonical.d.ts +77 -72
- package/dist/canonical.js +63 -13
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +170 -72
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +23 -1
- package/dist/cli.d.ts +23 -1
- package/dist/cli.js +167 -73
- package/dist/cli.js.map +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.map +1 -1
- package/dist/compiler.cjs +77 -20
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +2 -2
- package/dist/compiler.d.ts +2 -2
- package/dist/compiler.js +77 -20
- package/dist/compiler.js.map +1 -1
- package/dist/{constants-Dou1RkTo.d.cts → constants-D2o0vK6U.d.cts} +8 -1
- package/dist/{constants-Dou1RkTo.d.ts → constants-D2o0vK6U.d.ts} +8 -1
- package/dist/dynamic-input.d.cts +1 -1
- package/dist/dynamic-input.d.ts +1 -1
- package/dist/index.cjs +193 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +62 -59
- package/dist/index.d.ts +62 -59
- package/dist/index.js +192 -83
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +70 -15
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +76 -76
- package/dist/ir.d.ts +76 -76
- package/dist/ir.js +70 -15
- package/dist/ir.js.map +1 -1
- package/dist/predicate-v2.cjs +70 -15
- 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 +70 -15
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +70 -15
- package/dist/sdl.cjs.map +1 -1
- package/dist/sdl.d.cts +1 -1
- package/dist/sdl.d.ts +1 -1
- package/dist/sdl.js +70 -15
- package/dist/sdl.js.map +1 -1
- package/package.json +3 -3
- package/templates/orm-namespace/package.json +2 -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
|
@@ -100,5 +100,12 @@ declare function materializedRefreshFunctionName(table: string): string;
|
|
|
100
100
|
* a full refresh never reads a snapshot older than a write it then overwrites.
|
|
101
101
|
*/
|
|
102
102
|
declare function materializeLockKey(schema: string, table: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Committed copy of the exact operation documents a package registers —
|
|
105
|
+
* GraphQL text and SQL operations already materialized as inert JSON. Atlas
|
|
106
|
+
* recompiles it to check that a registered operation set is what a commit holds.
|
|
107
|
+
*/
|
|
108
|
+
declare const OPERATION_DOCUMENTS_FILE = "operations.documents.json";
|
|
109
|
+
declare const OPERATION_DOCUMENTS_VERSION: 1;
|
|
103
110
|
|
|
104
|
-
export { CELLS_FIELD as C, DEFINITION_FILE_EXTENSIONS as D, HASH_DOMAIN as H, IDENTIFIER_PATTERN as I, MATERIALIZE_ACTOR as M, NAMESPACE_PATTERN as N, ORM_DATA_TOKEN_HEADER as O, QUALIFIED_TABLE_PATTERN as Q, RESERVED_COLUMN_NAMES as R, SYNCED_SOURCE_PATTERN as S, CELL_PARTS as a, type CellPart as b, cellColumnName as c, ORM_SERVICE_DATA_TOKEN_HEADER as d, CAPABILITY_KEY_PATTERN as e, CELL_PART_SUFFIX as f, CLAIM_KEY_PATTERN as g, COLUMN_IDENTIFIER_PATTERN as h, CONSUMER_ROLE_SUFFIX as i, MATERIALIZE_CLAIMS_JSON as j, MAX_DEFINITION_FILE_BYTES as k, MAX_GENERATED_SDL_BYTES as l, MAX_IDENTIFIER_LENGTH as m, MAX_PERSISTED_READ_ROWS as n, MAX_TRACKED_COLUMN_NAME as o, NAMESPACE_CONFIG_FILENAMES as p,
|
|
111
|
+
export { CELLS_FIELD as C, DEFINITION_FILE_EXTENSIONS as D, HASH_DOMAIN as H, IDENTIFIER_PATTERN as I, MATERIALIZE_ACTOR as M, NAMESPACE_PATTERN as N, ORM_DATA_TOKEN_HEADER as O, QUALIFIED_TABLE_PATTERN as Q, RESERVED_COLUMN_NAMES as R, SYNCED_SOURCE_PATTERN as S, CELL_PARTS as a, type CellPart as b, cellColumnName as c, ORM_SERVICE_DATA_TOKEN_HEADER as d, CAPABILITY_KEY_PATTERN as e, CELL_PART_SUFFIX as f, CLAIM_KEY_PATTERN as g, COLUMN_IDENTIFIER_PATTERN as h, CONSUMER_ROLE_SUFFIX as i, MATERIALIZE_CLAIMS_JSON as j, MAX_DEFINITION_FILE_BYTES as k, MAX_GENERATED_SDL_BYTES as l, MAX_IDENTIFIER_LENGTH as m, MAX_PERSISTED_READ_ROWS as n, MAX_TRACKED_COLUMN_NAME as o, NAMESPACE_CONFIG_FILENAMES as p, OPERATION_DOCUMENTS_FILE as q, OPERATION_DOCUMENTS_VERSION as r, ORM_REGISTRATION_MAX_BYTES as s, ORM_SCHEMA_PREFIX as t, RESERVED_TABLE_NAMES as u, consumerRoleName as v, materializeLockKey as w, materializedRefreshFunctionName as x, ormSchemaName as y };
|
|
@@ -100,5 +100,12 @@ declare function materializedRefreshFunctionName(table: string): string;
|
|
|
100
100
|
* a full refresh never reads a snapshot older than a write it then overwrites.
|
|
101
101
|
*/
|
|
102
102
|
declare function materializeLockKey(schema: string, table: string): string;
|
|
103
|
+
/**
|
|
104
|
+
* Committed copy of the exact operation documents a package registers —
|
|
105
|
+
* GraphQL text and SQL operations already materialized as inert JSON. Atlas
|
|
106
|
+
* recompiles it to check that a registered operation set is what a commit holds.
|
|
107
|
+
*/
|
|
108
|
+
declare const OPERATION_DOCUMENTS_FILE = "operations.documents.json";
|
|
109
|
+
declare const OPERATION_DOCUMENTS_VERSION: 1;
|
|
103
110
|
|
|
104
|
-
export { CELLS_FIELD as C, DEFINITION_FILE_EXTENSIONS as D, HASH_DOMAIN as H, IDENTIFIER_PATTERN as I, MATERIALIZE_ACTOR as M, NAMESPACE_PATTERN as N, ORM_DATA_TOKEN_HEADER as O, QUALIFIED_TABLE_PATTERN as Q, RESERVED_COLUMN_NAMES as R, SYNCED_SOURCE_PATTERN as S, CELL_PARTS as a, type CellPart as b, cellColumnName as c, ORM_SERVICE_DATA_TOKEN_HEADER as d, CAPABILITY_KEY_PATTERN as e, CELL_PART_SUFFIX as f, CLAIM_KEY_PATTERN as g, COLUMN_IDENTIFIER_PATTERN as h, CONSUMER_ROLE_SUFFIX as i, MATERIALIZE_CLAIMS_JSON as j, MAX_DEFINITION_FILE_BYTES as k, MAX_GENERATED_SDL_BYTES as l, MAX_IDENTIFIER_LENGTH as m, MAX_PERSISTED_READ_ROWS as n, MAX_TRACKED_COLUMN_NAME as o, NAMESPACE_CONFIG_FILENAMES as p,
|
|
111
|
+
export { CELLS_FIELD as C, DEFINITION_FILE_EXTENSIONS as D, HASH_DOMAIN as H, IDENTIFIER_PATTERN as I, MATERIALIZE_ACTOR as M, NAMESPACE_PATTERN as N, ORM_DATA_TOKEN_HEADER as O, QUALIFIED_TABLE_PATTERN as Q, RESERVED_COLUMN_NAMES as R, SYNCED_SOURCE_PATTERN as S, CELL_PARTS as a, type CellPart as b, cellColumnName as c, ORM_SERVICE_DATA_TOKEN_HEADER as d, CAPABILITY_KEY_PATTERN as e, CELL_PART_SUFFIX as f, CLAIM_KEY_PATTERN as g, COLUMN_IDENTIFIER_PATTERN as h, CONSUMER_ROLE_SUFFIX as i, MATERIALIZE_CLAIMS_JSON as j, MAX_DEFINITION_FILE_BYTES as k, MAX_GENERATED_SDL_BYTES as l, MAX_IDENTIFIER_LENGTH as m, MAX_PERSISTED_READ_ROWS as n, MAX_TRACKED_COLUMN_NAME as o, NAMESPACE_CONFIG_FILENAMES as p, OPERATION_DOCUMENTS_FILE as q, OPERATION_DOCUMENTS_VERSION as r, ORM_REGISTRATION_MAX_BYTES as s, ORM_SCHEMA_PREFIX as t, RESERVED_TABLE_NAMES as u, consumerRoleName as v, materializeLockKey as w, materializedRefreshFunctionName as x, ormSchemaName as y };
|
package/dist/dynamic-input.d.cts
CHANGED
package/dist/dynamic-input.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -205,6 +205,8 @@ function materializedRefreshFunctionName(table) {
|
|
|
205
205
|
function materializeLockKey(schema, table) {
|
|
206
206
|
return `orm:materialize:${schema}.${table}`;
|
|
207
207
|
}
|
|
208
|
+
var OPERATION_DOCUMENTS_FILE = "operations.documents.json";
|
|
209
|
+
var OPERATION_DOCUMENTS_VERSION = 1;
|
|
208
210
|
|
|
209
211
|
// src/errors.ts
|
|
210
212
|
var OrmDefinitionError = class extends Error {
|
|
@@ -1189,6 +1191,48 @@ var policyExpressionSchema = zod.z.union([
|
|
|
1189
1191
|
variants: zod.z.array(policyVariantSchema).max(10).default([])
|
|
1190
1192
|
})
|
|
1191
1193
|
]);
|
|
1194
|
+
|
|
1195
|
+
// src/canonical/resolved-rows.ts
|
|
1196
|
+
function logicalKey(table) {
|
|
1197
|
+
return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
|
|
1198
|
+
}
|
|
1199
|
+
function sameKey(key, expected) {
|
|
1200
|
+
return key.length === expected.length && key.every((name2) => expected.includes(name2));
|
|
1201
|
+
}
|
|
1202
|
+
function validateRowIdentity(input) {
|
|
1203
|
+
validateExistsIn(input);
|
|
1204
|
+
if (input.keys.length > 1) validateCompositeKey(input);
|
|
1205
|
+
}
|
|
1206
|
+
function validateExistsIn({ resolved, sourceByName, fail: fail3 }) {
|
|
1207
|
+
const name2 = resolved.existsIn?.[0];
|
|
1208
|
+
if (name2 === void 0) return;
|
|
1209
|
+
if (sourceByName.get(name2)?.kind !== "etl") fail3(`existsIn '${name2}' must be an etl source`);
|
|
1210
|
+
if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
|
|
1211
|
+
fail3("existsIn allows only an update policy: rows come from the etl source");
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
function validateCompositeKey({ resolved, keys, tableByName, fail: fail3 }) {
|
|
1215
|
+
const keyList = `(${keys.join(", ")})`;
|
|
1216
|
+
if (resolved.existsIn === void 0) {
|
|
1217
|
+
fail3(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
|
|
1221
|
+
if (writes.length > 0) fail3(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
|
|
1222
|
+
if (resolved.materialized) fail3(`composite key ${keyList} cannot be materialized`);
|
|
1223
|
+
for (const source of resolved.sources) {
|
|
1224
|
+
if (source.kind === "prefetch") {
|
|
1225
|
+
fail3(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
|
|
1226
|
+
continue;
|
|
1227
|
+
}
|
|
1228
|
+
const table = tableByName.get(source.table);
|
|
1229
|
+
if (table && !sameKey(logicalKey(table), keys)) {
|
|
1230
|
+
fail3(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// src/canonical/resolved.ts
|
|
1192
1236
|
function parseDurationMs(input) {
|
|
1193
1237
|
if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
|
|
1194
1238
|
const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
|
|
@@ -1265,6 +1309,13 @@ var canonicalResolvedSchema = zod.z.object({
|
|
|
1265
1309
|
resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
|
|
1266
1310
|
/** Sources whose tombstone hides the resolved row (v1: the manual source only). */
|
|
1267
1311
|
deletedBy: zod.z.array(identifier).max(1).default([]),
|
|
1312
|
+
/**
|
|
1313
|
+
* `rows.existsIn`: the one etl source whose rows are the table's rows (read
|
|
1314
|
+
* directly as the row source). Only an update policy may write. It is also
|
|
1315
|
+
* what admits a composite primary key: a read-only table keyed exactly like
|
|
1316
|
+
* that source.
|
|
1317
|
+
*/
|
|
1318
|
+
existsIn: zod.z.array(identifier).length(1).optional(),
|
|
1268
1319
|
/**
|
|
1269
1320
|
* Materialized: the view reads an indexed internal table (`table`) that the
|
|
1270
1321
|
* data plane keeps in step with `compute`, the internal view holding the
|
|
@@ -1283,8 +1334,8 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
1283
1334
|
if (!resolved) return [];
|
|
1284
1335
|
const errors = [];
|
|
1285
1336
|
const fail3 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
|
|
1286
|
-
const
|
|
1287
|
-
if (
|
|
1337
|
+
const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
|
|
1338
|
+
if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
|
|
1288
1339
|
fail3("requires exactly one primary-key column");
|
|
1289
1340
|
return errors;
|
|
1290
1341
|
}
|
|
@@ -1292,7 +1343,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
1292
1343
|
const scope = {
|
|
1293
1344
|
view,
|
|
1294
1345
|
resolved,
|
|
1295
|
-
|
|
1346
|
+
keys: new Map(keyColumns.map((column) => [column.name, column])),
|
|
1296
1347
|
dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
|
|
1297
1348
|
sourceByName: /* @__PURE__ */ new Map(),
|
|
1298
1349
|
fail: fail3
|
|
@@ -1306,6 +1357,7 @@ function validateResolvedView({ view, tables, views = [] }) {
|
|
|
1306
1357
|
if (!table) fail3(`source '${source.name}' references unknown table '${source.table}'`);
|
|
1307
1358
|
else validateSource({ scope, source, table });
|
|
1308
1359
|
}
|
|
1360
|
+
validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail3 });
|
|
1309
1361
|
validateRules(scope);
|
|
1310
1362
|
validateViewCells(scope);
|
|
1311
1363
|
if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
|
|
@@ -1335,7 +1387,7 @@ function validateSource({ scope, source, table }) {
|
|
|
1335
1387
|
validatePrefetchFields({ scope, source });
|
|
1336
1388
|
}
|
|
1337
1389
|
function validatePrefetchFields({ scope, source }) {
|
|
1338
|
-
const { fail: fail3,
|
|
1390
|
+
const { fail: fail3, keys } = scope;
|
|
1339
1391
|
if (source.kind !== "prefetch") {
|
|
1340
1392
|
if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
|
|
1341
1393
|
fail3(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
|
|
@@ -1344,7 +1396,7 @@ function validatePrefetchFields({ scope, source }) {
|
|
|
1344
1396
|
}
|
|
1345
1397
|
if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
|
|
1346
1398
|
fail3(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
|
|
1347
|
-
} else if (source.key
|
|
1399
|
+
} else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
|
|
1348
1400
|
fail3(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
|
|
1349
1401
|
}
|
|
1350
1402
|
for (const column of source.writeback?.columns ?? []) {
|
|
@@ -1352,19 +1404,19 @@ function validatePrefetchFields({ scope, source }) {
|
|
|
1352
1404
|
}
|
|
1353
1405
|
}
|
|
1354
1406
|
function validateSourceColumns({ scope, source, byName }) {
|
|
1355
|
-
const { fail: fail3,
|
|
1356
|
-
for (const column of [
|
|
1357
|
-
const own = column
|
|
1407
|
+
const { fail: fail3, keys, dataColumns } = scope;
|
|
1408
|
+
for (const column of [...keys.keys(), ...source.columns]) {
|
|
1409
|
+
const own = keys.get(column) ?? dataColumns.get(column);
|
|
1358
1410
|
const theirs = byName.get(column);
|
|
1359
1411
|
if (!own) fail3(`source '${source.name}' supplies unknown column '${column}'`);
|
|
1360
1412
|
else if (!theirs) fail3(`source '${source.name}' table '${source.table}' has no column '${column}'`);
|
|
1361
1413
|
else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail3(`column '${column}' has a different type on source '${source.name}'`);
|
|
1362
1414
|
}
|
|
1363
1415
|
}
|
|
1364
|
-
function validateRules({ resolved,
|
|
1416
|
+
function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail3 }) {
|
|
1365
1417
|
const ruled = /* @__PURE__ */ new Set();
|
|
1366
1418
|
for (const rule of resolved.resolve) {
|
|
1367
|
-
if (!dataColumns.has(rule.column) || rule.column
|
|
1419
|
+
if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
|
|
1368
1420
|
fail3(`resolve rule targets unknown column '${rule.column}'`);
|
|
1369
1421
|
continue;
|
|
1370
1422
|
}
|
|
@@ -1381,7 +1433,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
|
|
|
1381
1433
|
}
|
|
1382
1434
|
}
|
|
1383
1435
|
for (const column of dataColumns.keys()) {
|
|
1384
|
-
if (column
|
|
1436
|
+
if (!keys.has(column) && !ruled.has(column)) fail3(`column '${column}' has no resolve rule`);
|
|
1385
1437
|
}
|
|
1386
1438
|
}
|
|
1387
1439
|
var VIEW_CELL_TYPES = {
|
|
@@ -1390,10 +1442,10 @@ var VIEW_CELL_TYPES = {
|
|
|
1390
1442
|
by: "text",
|
|
1391
1443
|
conflicts: "jsonb"
|
|
1392
1444
|
};
|
|
1393
|
-
function validateViewCells({ view, dataColumns,
|
|
1445
|
+
function validateViewCells({ view, dataColumns, keys, fail: fail3 }) {
|
|
1394
1446
|
const expected = /* @__PURE__ */ new Map();
|
|
1395
1447
|
for (const column of dataColumns.values()) {
|
|
1396
|
-
if (column.name
|
|
1448
|
+
if (keys.has(column.name)) continue;
|
|
1397
1449
|
for (const part of RESOLVED_VIEW_CELL_PARTS) {
|
|
1398
1450
|
expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
|
|
1399
1451
|
}
|
|
@@ -2266,8 +2318,9 @@ function planSource(input) {
|
|
|
2266
2318
|
}
|
|
2267
2319
|
var tombstoned = () => t.boolean(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName).notNull().default(false);
|
|
2268
2320
|
function internalTable(input, supplied, system, options = {}) {
|
|
2269
|
-
const { viewName, sourceName, columns,
|
|
2270
|
-
const tableColumns = {
|
|
2321
|
+
const { viewName, sourceName, columns, keys } = input;
|
|
2322
|
+
const tableColumns = {};
|
|
2323
|
+
for (const key of keys) tableColumns[key] = cloneColumn({ builder: columns[key], property: key, notNull: true, primaryKey: true });
|
|
2271
2324
|
for (const property of supplied) {
|
|
2272
2325
|
tableColumns[property] = cloneColumn({ builder: columns[property], property, notNull: false, primaryKey: false });
|
|
2273
2326
|
}
|
|
@@ -2284,12 +2337,14 @@ function assertColumns(input, list2, what) {
|
|
|
2284
2337
|
for (const column of list2) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
|
|
2285
2338
|
}
|
|
2286
2339
|
function planEtl(input, source) {
|
|
2287
|
-
const { sourceName,
|
|
2340
|
+
const { sourceName, keys, dataColumns, fail: fail3 } = input;
|
|
2288
2341
|
const table = typeof source.table === "function" ? source.table() : source.table;
|
|
2289
2342
|
if (!isSeqTable(table) || table.source === void 0) fail3(`etl source "${sourceName}" must reference a defineSyncedTable`);
|
|
2290
2343
|
const synced = table;
|
|
2291
2344
|
if (synced.config.primaryKey?.length === 0) fail3(`etl source "${sourceName}" cannot use an unkeyed synced table for resolved row identity`);
|
|
2292
|
-
|
|
2345
|
+
for (const key of keys) {
|
|
2346
|
+
if (!(key in synced.columns)) fail3(`etl source "${sourceName}" table "${synced.tableName}" has no "${key}" column`);
|
|
2347
|
+
}
|
|
2293
2348
|
const supplied = source.columns ? [...source.columns] : dataColumns.filter((column) => column in synced.columns);
|
|
2294
2349
|
assertColumns(input, supplied, `etl source "${sourceName}"`);
|
|
2295
2350
|
if (source.updatedAt !== void 0 && !(source.updatedAt in synced.columns)) {
|
|
@@ -2298,9 +2353,9 @@ function planEtl(input, source) {
|
|
|
2298
2353
|
return { name: sourceName, kind: "etl", table: synced, columns: supplied, ...source.updatedAt ? { updatedAt: source.updatedAt } : {} };
|
|
2299
2354
|
}
|
|
2300
2355
|
function planPrefetch(input, source) {
|
|
2301
|
-
const { sourceName,
|
|
2356
|
+
const { sourceName, keys, fail: fail3 } = input;
|
|
2302
2357
|
assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
|
|
2303
|
-
if (source.key
|
|
2358
|
+
if (!keys.includes(source.key) && !source.columns.includes(source.key)) {
|
|
2304
2359
|
fail3(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
|
|
2305
2360
|
}
|
|
2306
2361
|
for (const column of source.writeback?.columns ?? []) {
|
|
@@ -2353,7 +2408,7 @@ var PG_TYPE = {
|
|
|
2353
2408
|
var ident = (value) => `"${value.replace(/"/g, '""')}"`;
|
|
2354
2409
|
var literal = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
2355
2410
|
var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
|
|
2356
|
-
function candidateSql({ source, column,
|
|
2411
|
+
function candidateSql({ source, column, key, pgType }) {
|
|
2357
2412
|
const a = ident(`s_${source.name}`);
|
|
2358
2413
|
const name2 = physical({ source, property: column });
|
|
2359
2414
|
const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
|
|
@@ -2361,7 +2416,7 @@ function candidateSql({ source, column, pk, pgType }) {
|
|
|
2361
2416
|
return {
|
|
2362
2417
|
source: source.name,
|
|
2363
2418
|
value,
|
|
2364
|
-
has: `(${a}.${ident(physical({ source, property:
|
|
2419
|
+
has: `(${a}.${ident(physical({ source, property: key }))} IS NOT NULL)`,
|
|
2365
2420
|
at: source.updatedAt ? `CAST(${a}.${ident(physical({ source, property: source.updatedAt }))} AS timestamptz)` : "CAST(NULL AS timestamptz)",
|
|
2366
2421
|
by: "CAST(NULL AS text)"
|
|
2367
2422
|
};
|
|
@@ -2449,39 +2504,44 @@ function customCell({ candidates, orderBy, index: index3 }) {
|
|
|
2449
2504
|
}
|
|
2450
2505
|
function buildResolvedQuery({ context, plan }) {
|
|
2451
2506
|
const { qb, relation } = context;
|
|
2452
|
-
const
|
|
2507
|
+
const keyColumns = plan.keys.map((key) => plan.columns[key].columnNameFor(key));
|
|
2453
2508
|
const alias = (source) => ident(`s_${source}`);
|
|
2454
2509
|
const tombstoned2 = RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName;
|
|
2455
2510
|
const sourceByName = new Map(plan.plans.map((source) => [source.name, source]));
|
|
2456
2511
|
const schema = pgCore.getTableConfig(relation(plan.plans[0].table)).schema;
|
|
2457
2512
|
const keySource = plan.existsIn === void 0 ? void 0 : sourceByName.get(plan.existsIn);
|
|
2458
|
-
const
|
|
2513
|
+
const keyRefs = plan.keys.map((key, index3) => keySource ? `${alias(keySource.name)}.${ident(physical({ source: keySource, property: key }))}` : `"k".${ident(keyColumns[index3])}`);
|
|
2459
2514
|
const keyBranches = plan.plans.map((source) => {
|
|
2460
2515
|
const where = source.kind === "prefetch" ? drizzleOrm.sql.raw(` WHERE NOT ${ident(tombstoned2)}`) : drizzleOrm.sql.raw("");
|
|
2461
|
-
const
|
|
2462
|
-
|
|
2463
|
-
|
|
2516
|
+
const selectedKey = plan.keys.map((key, index3) => {
|
|
2517
|
+
const sourceKey = ident(physical({ source, property: key }));
|
|
2518
|
+
return sourceKey === ident(keyColumns[index3]) ? sourceKey : `${sourceKey} AS ${ident(keyColumns[index3])}`;
|
|
2519
|
+
});
|
|
2520
|
+
return drizzleOrm.sql`SELECT ${drizzleOrm.sql.raw(selectedKey.join(", "))} FROM ${relation(source.table)}${where}`;
|
|
2464
2521
|
});
|
|
2465
2522
|
const base2 = keySource ? drizzleOrm.sql`${relation(keySource.table)} AS ${drizzleOrm.sql.raw(alias(keySource.name))}` : drizzleOrm.sql`(${drizzleOrm.sql.join(keyBranches, drizzleOrm.sql.raw(" UNION "))}) AS "k"`;
|
|
2466
|
-
const joins = plan.plans.filter((source) => source !== keySource).map(
|
|
2467
|
-
|
|
2468
|
-
|
|
2523
|
+
const joins = plan.plans.filter((source) => source !== keySource).map((source) => {
|
|
2524
|
+
const on = plan.keys.map((key, index3) => `${alias(source.name)}.${ident(physical({ source, property: key }))} = ${keyRefs[index3]}`);
|
|
2525
|
+
return drizzleOrm.sql` LEFT JOIN ${relation(source.table)} AS ${drizzleOrm.sql.raw(alias(source.name))} ON ${drizzleOrm.sql.raw(on.join(" AND "))}`;
|
|
2526
|
+
});
|
|
2469
2527
|
const cells = /* @__PURE__ */ new Map();
|
|
2470
2528
|
plan.rules.forEach((rule, index3) => {
|
|
2471
2529
|
const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
|
|
2472
2530
|
const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
|
|
2473
|
-
const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column,
|
|
2531
|
+
const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, key: plan.keys[0], pgType }));
|
|
2474
2532
|
cells.set(rule.column, keySource && rule.candidates.length === 1 && rule.candidates[0] === keySource.name ? existsInCell(candidates[0]) : rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index: index3 }) : builtinCell({ comparator: rule.comparator, candidates }));
|
|
2475
2533
|
});
|
|
2476
2534
|
const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [drizzleOrm.sql.raw(cell.lateral)] : []);
|
|
2477
2535
|
const from = drizzleOrm.sql.join([base2, ...joins, ...laterals], drizzleOrm.sql.raw(""));
|
|
2478
|
-
const selected = {
|
|
2479
|
-
[plan.pk]: drizzleOrm.sql.raw(keyRef).as(pkColumn)
|
|
2480
|
-
};
|
|
2536
|
+
const selected = {};
|
|
2481
2537
|
for (const [property, builder] of Object.entries(plan.columns)) {
|
|
2482
|
-
if (property === plan.pk) continue;
|
|
2483
|
-
const cell = cells.get(property);
|
|
2484
2538
|
const columnName = builder.columnNameFor(property);
|
|
2539
|
+
const keyIndex = plan.keys.indexOf(property);
|
|
2540
|
+
if (keyIndex !== -1) {
|
|
2541
|
+
selected[property] = drizzleOrm.sql.raw(keyRefs[keyIndex]).as(columnName);
|
|
2542
|
+
continue;
|
|
2543
|
+
}
|
|
2544
|
+
const cell = cells.get(property);
|
|
2485
2545
|
selected[property] = drizzleOrm.sql.raw(cell.value).as(columnName);
|
|
2486
2546
|
for (const [part, expression] of Object.entries(cell.parts)) {
|
|
2487
2547
|
selected[cellColumnName(property, part)] = drizzleOrm.sql.raw(expression).as(cellColumnName(columnName, part));
|
|
@@ -2495,6 +2555,44 @@ function buildResolvedQuery({ context, plan }) {
|
|
|
2495
2555
|
const query = qb.select(selected).from(from);
|
|
2496
2556
|
return hidden.length > 0 ? query.where(drizzleOrm.sql.raw(hidden.join(" AND "))) : query;
|
|
2497
2557
|
}
|
|
2558
|
+
|
|
2559
|
+
// src/authoring/resolved-rows.ts
|
|
2560
|
+
function planKeys({ columns, config, fail: fail3 }) {
|
|
2561
|
+
const keys = Object.entries(columns).filter(([, builder]) => builder.isPrimaryKey).map(([property]) => property);
|
|
2562
|
+
if (keys.length === 0) fail3("declare exactly one .primaryKey() column");
|
|
2563
|
+
if (keys.length === 1) return keys;
|
|
2564
|
+
const key = `(${keys.join(", ")})`;
|
|
2565
|
+
if (config.rows?.existsIn === void 0) fail3(`declare exactly one .primaryKey() column: composite key ${key} requires rows.existsIn`);
|
|
2566
|
+
const writes = ["insert", "update", "write"].filter((kind) => config.policies[kind] !== void 0);
|
|
2567
|
+
if (writes.length > 0) fail3(`composite key ${key} requires a read-only table: remove the ${writes.join("/")} policy`);
|
|
2568
|
+
if (config.materialize) fail3(`composite key ${key} cannot be materialized`);
|
|
2569
|
+
const prefetch = Object.entries(config.sources).find(([, source]) => source.kind === "prefetch");
|
|
2570
|
+
if (prefetch) fail3(`composite key ${key} cannot have prefetch source "${prefetch[0]}"`);
|
|
2571
|
+
return keys;
|
|
2572
|
+
}
|
|
2573
|
+
function assertCompositeSourceKeys({ keys, plans, fail: fail3 }) {
|
|
2574
|
+
if (keys.length === 1) return;
|
|
2575
|
+
for (const plan of plans) {
|
|
2576
|
+
if (plan.kind !== "etl") continue;
|
|
2577
|
+
const sourceKey = declaredPrimaryKeyProperties(plan.table);
|
|
2578
|
+
if (sourceKey.length !== keys.length || !keys.every((key) => sourceKey.includes(key))) {
|
|
2579
|
+
fail3(`composite key (${keys.join(", ")}) requires etl source "${plan.name}" to be keyed by it; table "${plan.table.tableName}" is keyed by (${sourceKey.join(", ")})`);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function planExistsIn({ config, plans, fail: fail3 }) {
|
|
2584
|
+
const names = config.rows?.existsIn;
|
|
2585
|
+
if (names === void 0) return void 0;
|
|
2586
|
+
if (names.length !== 1) fail3("rows.existsIn names exactly one etl source");
|
|
2587
|
+
const source = plans.find((plan) => plan.name === names[0]);
|
|
2588
|
+
if (source?.kind !== "etl") return fail3(`rows.existsIn "${names[0]}" must be an etl source`);
|
|
2589
|
+
if (config.policies.insert !== void 0 || config.policies.write !== void 0) fail3("rows.existsIn allows only an update policy: rows come from the etl source");
|
|
2590
|
+
return source;
|
|
2591
|
+
}
|
|
2592
|
+
function inheritedNotNullColumns({ existsIn, rules }) {
|
|
2593
|
+
if (!existsIn) return /* @__PURE__ */ new Set();
|
|
2594
|
+
return new Set(rules.filter((rule) => rule.candidates.length === 1 && rule.candidates[0] === existsIn.name).map((rule) => rule.column).filter((column) => existsIn.table.columns[column]?.toCanonicalColumn(column).nullable === false));
|
|
2595
|
+
}
|
|
2498
2596
|
function managedFn(id) {
|
|
2499
2597
|
return { kind: "managed", id };
|
|
2500
2598
|
}
|
|
@@ -2533,28 +2631,28 @@ function defineResolvedTable(name2, columns, config) {
|
|
|
2533
2631
|
fail3(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
|
|
2534
2632
|
}
|
|
2535
2633
|
}
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
const [pk, pkBuilder] = pkEntries[0];
|
|
2539
|
-
const dataColumns = Object.keys(columns).filter((property) => property !== pk);
|
|
2634
|
+
const keys = planKeys({ columns, config, fail: fail3 });
|
|
2635
|
+
const dataColumns = Object.keys(columns).filter((property) => !keys.includes(property));
|
|
2540
2636
|
if (dataColumns.length === 0) fail3("declare at least one non-key column");
|
|
2541
2637
|
const sourceEntries = Object.entries(config.sources);
|
|
2542
2638
|
if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail3("declare exactly one manualSource()");
|
|
2543
2639
|
const plans = sourceEntries.map(
|
|
2544
|
-
([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns,
|
|
2640
|
+
([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, keys, dataColumns, fail: fail3 })
|
|
2545
2641
|
);
|
|
2546
2642
|
const rules = planRules({ config, plans, dataColumns, fail: fail3 });
|
|
2547
2643
|
const deletedBy = [...config.rows?.deletedBy ?? []];
|
|
2548
2644
|
for (const source of deletedBy) {
|
|
2549
2645
|
if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail3(`rows.deletedBy "${source}" must be the manual source`);
|
|
2550
2646
|
}
|
|
2647
|
+
assertCompositeSourceKeys({ keys, plans, fail: fail3 });
|
|
2551
2648
|
const existsIn = planExistsIn({ config, plans, fail: fail3 });
|
|
2552
|
-
const notNullColumns =
|
|
2649
|
+
const notNullColumns = inheritedNotNullColumns({ existsIn, rules });
|
|
2553
2650
|
const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
|
|
2554
2651
|
const viewColumns = {};
|
|
2555
2652
|
for (const [property, builder] of Object.entries(columns)) {
|
|
2556
|
-
|
|
2557
|
-
|
|
2653
|
+
const isKey = keys.includes(property);
|
|
2654
|
+
viewColumns[property] = cloneColumn({ builder, property, notNull: isKey || notNullColumns.has(property), primaryKey: isKey });
|
|
2655
|
+
if (isKey) continue;
|
|
2558
2656
|
const columnName = builder.columnNameFor(property);
|
|
2559
2657
|
for (const part of RESOLVED_VIEW_CELL_PARTS) {
|
|
2560
2658
|
viewColumns[cellColumnName(property, part)] = partType[part](cellColumnName(columnName, part)).cellPart(property, part);
|
|
@@ -2567,9 +2665,10 @@ function defineResolvedTable(name2, columns, config) {
|
|
|
2567
2665
|
sources: plans,
|
|
2568
2666
|
rules,
|
|
2569
2667
|
deletedBy,
|
|
2668
|
+
...existsIn ? { existsIn: [existsIn.name] } : {},
|
|
2570
2669
|
policies: writePolicies
|
|
2571
2670
|
};
|
|
2572
|
-
const queryPlan = { columns,
|
|
2671
|
+
const queryPlan = { columns, keys, plans, rules, deletedBy, ...existsIn ? { existsIn: existsIn.name } : {} };
|
|
2573
2672
|
const typedColumns = viewColumns;
|
|
2574
2673
|
const surface = {
|
|
2575
2674
|
...config.description !== void 0 ? { description: config.description } : {},
|
|
@@ -2611,18 +2710,6 @@ function defineResolvedTable(name2, columns, config) {
|
|
|
2611
2710
|
}
|
|
2612
2711
|
});
|
|
2613
2712
|
}
|
|
2614
|
-
function planExistsIn({ config, plans, fail: fail3 }) {
|
|
2615
|
-
const names = config.rows?.existsIn;
|
|
2616
|
-
if (names === void 0) return void 0;
|
|
2617
|
-
if (names.length !== 1) fail3("rows.existsIn names exactly one etl source");
|
|
2618
|
-
const source = plans.find((plan) => plan.name === names[0]);
|
|
2619
|
-
if (source?.kind !== "etl") fail3(`rows.existsIn "${names[0]}" must be an etl source`);
|
|
2620
|
-
if (config.policies.insert !== void 0 || config.policies.write !== void 0) fail3("rows.existsIn allows only an update policy: rows come from the etl source");
|
|
2621
|
-
return source;
|
|
2622
|
-
}
|
|
2623
|
-
function inheritedNotNullColumns({ existsIn, rules }) {
|
|
2624
|
-
return new Set(rules.filter((rule) => rule.candidates.length === 1 && rule.candidates[0] === existsIn.name).map((rule) => rule.column).filter((column) => existsIn.table.columns[column]?.toCanonicalColumn(column).nullable === false));
|
|
2625
|
-
}
|
|
2626
2713
|
function planRules({ config, plans, dataColumns, fail: fail3 }) {
|
|
2627
2714
|
const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
|
|
2628
2715
|
const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));
|
|
@@ -3222,6 +3309,7 @@ function buildCanonicalResolved(resolved) {
|
|
|
3222
3309
|
})),
|
|
3223
3310
|
resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
|
|
3224
3311
|
deletedBy: [...resolved.deletedBy],
|
|
3312
|
+
...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
|
|
3225
3313
|
...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
|
|
3226
3314
|
policies
|
|
3227
3315
|
};
|
|
@@ -7147,8 +7235,13 @@ var resolvedSourceIrSchema = zod.z.object({
|
|
|
7147
7235
|
}).optional()
|
|
7148
7236
|
});
|
|
7149
7237
|
var resolvedMetaIrSchema = zod.z.object({
|
|
7150
|
-
/**
|
|
7151
|
-
|
|
7238
|
+
/**
|
|
7239
|
+
* Logical pk column of the resolved view. Absent for a composite key, which
|
|
7240
|
+
* only a read-only `existsIn` table may declare: it has no writes, prefetch
|
|
7241
|
+
* or materialization (the paths that address one key column), and its reads
|
|
7242
|
+
* use the view's `primaryKey` like any keyed view.
|
|
7243
|
+
*/
|
|
7244
|
+
pk: zod.z.string().optional(),
|
|
7152
7245
|
/** Source name of the manual source. */
|
|
7153
7246
|
manual: zod.z.string(),
|
|
7154
7247
|
sources: zod.z.array(resolvedSourceIrSchema),
|
|
@@ -7543,6 +7636,42 @@ async function readSnapshot(dir, ordinal) {
|
|
|
7543
7636
|
const path$1 = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
|
|
7544
7637
|
return parseJson(path$1, await readSnapshotText(dir, ordinal));
|
|
7545
7638
|
}
|
|
7639
|
+
async function verifyCommittedLedger(dir, entries) {
|
|
7640
|
+
const firstRequiredSnapshot = entries.length - 2;
|
|
7641
|
+
let previousId3;
|
|
7642
|
+
let previousSnapshotPresent = false;
|
|
7643
|
+
let previousHash = null;
|
|
7644
|
+
for (const [index3, entry] of entries.entries()) {
|
|
7645
|
+
const sql7 = await readMigrationSql(dir, entry);
|
|
7646
|
+
if (checksumOf(sql7) !== entry.checksum) {
|
|
7647
|
+
return `${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`;
|
|
7648
|
+
}
|
|
7649
|
+
const snapshotText = index3 >= firstRequiredSnapshot ? await readSnapshotText(dir, entry.ordinal) : await readPrunableSnapshotText(dir, entry.ordinal);
|
|
7650
|
+
if (snapshotText !== void 0) {
|
|
7651
|
+
if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
|
|
7652
|
+
return `the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`;
|
|
7653
|
+
}
|
|
7654
|
+
const snapshot = JSON.parse(snapshotText);
|
|
7655
|
+
if (previousSnapshotPresent && snapshot.prevId !== previousId3) {
|
|
7656
|
+
return `snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``;
|
|
7657
|
+
}
|
|
7658
|
+
previousId3 = snapshot.id;
|
|
7659
|
+
}
|
|
7660
|
+
previousSnapshotPresent = snapshotText !== void 0;
|
|
7661
|
+
if (entry.previous_content_hash !== previousHash) {
|
|
7662
|
+
return `content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``;
|
|
7663
|
+
}
|
|
7664
|
+
previousHash = entry.content_hash;
|
|
7665
|
+
}
|
|
7666
|
+
return void 0;
|
|
7667
|
+
}
|
|
7668
|
+
async function readPrunableSnapshotText(dir, ordinal) {
|
|
7669
|
+
const path$1 = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME, `${pad(ordinal)}.snapshot.json`);
|
|
7670
|
+
return promises.readFile(path$1, "utf8").catch((error) => {
|
|
7671
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return void 0;
|
|
7672
|
+
throw error;
|
|
7673
|
+
});
|
|
7674
|
+
}
|
|
7546
7675
|
async function readMigrationSql(dir, entry) {
|
|
7547
7676
|
const path$1 = path.join(dir, MIGRATIONS_DIRNAME, `${entry.name}.sql`);
|
|
7548
7677
|
return promises.readFile(path$1, "utf8").catch(() => {
|
|
@@ -7703,30 +7832,10 @@ async function checkWorkspace({
|
|
|
7703
7832
|
log(`no committed migrations for '${loaded.definition.namespace}' \u2014 run \`seq-studio orm diff\` and commit the result`);
|
|
7704
7833
|
return 1;
|
|
7705
7834
|
}
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
if (checksumOf(sql7) !== entry.checksum) {
|
|
7711
|
-
log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
|
|
7712
|
-
return 1;
|
|
7713
|
-
}
|
|
7714
|
-
const snapshotText = await readSnapshotText(dir, entry.ordinal);
|
|
7715
|
-
if (checksumOf(snapshotText) !== entry.snapshot_checksum) {
|
|
7716
|
-
log(`the snapshot for ${entry.name} does not match its journal checksum \u2014 committed snapshots must not be edited (the next diff builds on it)`);
|
|
7717
|
-
return 1;
|
|
7718
|
-
}
|
|
7719
|
-
const snapshot = JSON.parse(snapshotText);
|
|
7720
|
-
if (entry.ordinal > 1 && snapshot.prevId !== previousId3) {
|
|
7721
|
-
log(`snapshot chain broken at ${entry.name} \u2014 regenerate the workspace with \`seq-studio orm diff\``);
|
|
7722
|
-
return 1;
|
|
7723
|
-
}
|
|
7724
|
-
if (entry.previous_content_hash !== previousHash) {
|
|
7725
|
-
log(`content-hash chain broken at ${entry.name} \u2014 migrations must be appended in order by \`seq-studio orm diff\``);
|
|
7726
|
-
return 1;
|
|
7727
|
-
}
|
|
7728
|
-
previousId3 = snapshot.id;
|
|
7729
|
-
previousHash = entry.content_hash;
|
|
7835
|
+
const broken = await verifyCommittedLedger(dir, workspace.journal.entries);
|
|
7836
|
+
if (broken) {
|
|
7837
|
+
log(broken);
|
|
7838
|
+
return 1;
|
|
7730
7839
|
}
|
|
7731
7840
|
if (tip.content_hash !== loaded.contentHash) {
|
|
7732
7841
|
log(`definitions changed since ${tip.name} \u2014 run \`seq-studio orm diff\` and commit the new migration`);
|
|
@@ -8550,6 +8659,8 @@ exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
|
|
|
8550
8659
|
exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
|
|
8551
8660
|
exports.NAMESPACE_CONFIG_FILENAMES = NAMESPACE_CONFIG_FILENAMES;
|
|
8552
8661
|
exports.NAMESPACE_PATTERN = NAMESPACE_PATTERN;
|
|
8662
|
+
exports.OPERATION_DOCUMENTS_FILE = OPERATION_DOCUMENTS_FILE;
|
|
8663
|
+
exports.OPERATION_DOCUMENTS_VERSION = OPERATION_DOCUMENTS_VERSION;
|
|
8553
8664
|
exports.ORM_DATA_TOKEN_HEADER = ORM_DATA_TOKEN_HEADER;
|
|
8554
8665
|
exports.ORM_MIGRATION_EXPORT_FILE = ORM_MIGRATION_EXPORT_FILE;
|
|
8555
8666
|
exports.ORM_MIGRATION_EXPORT_MAX_BYTES = ORM_MIGRATION_EXPORT_MAX_BYTES;
|