@sequenceholdings/orm 2.0.0 → 2.1.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/README.md +57 -2
- package/dist/authoring.cjs +122 -15
- package/dist/authoring.cjs.map +1 -1
- package/dist/authoring.d.cts +20 -3
- package/dist/authoring.d.ts +20 -3
- package/dist/authoring.js +122 -15
- package/dist/authoring.js.map +1 -1
- package/dist/canonical.cjs +83 -9
- package/dist/canonical.cjs.map +1 -1
- package/dist/canonical.d.cts +47 -1
- package/dist/canonical.d.ts +47 -1
- package/dist/canonical.js +83 -10
- package/dist/canonical.js.map +1 -1
- package/dist/cli.cjs +721 -132
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +723 -134
- package/dist/cli.js.map +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/compiler.cjs +482 -96
- package/dist/compiler.cjs.map +1 -1
- package/dist/compiler.d.cts +34 -2
- package/dist/compiler.d.ts +34 -2
- package/dist/compiler.js +483 -97
- package/dist/compiler.js.map +1 -1
- package/dist/index.cjs +414 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -9
- package/dist/index.d.ts +266 -9
- package/dist/index.js +404 -72
- package/dist/index.js.map +1 -1
- package/dist/ir.cjs +146 -12
- package/dist/ir.cjs.map +1 -1
- package/dist/ir.d.cts +161 -5
- package/dist/ir.d.ts +161 -5
- package/dist/ir.js +144 -13
- package/dist/ir.js.map +1 -1
- package/dist/{naming-CTvWSRTf.d.ts → naming-DCn94LJ4.d.ts} +15 -1
- package/dist/{naming-DRyinU4u.d.cts → naming-DPglwzkB.d.cts} +15 -1
- package/dist/predicate-v2.cjs +130 -11
- package/dist/predicate-v2.cjs.map +1 -1
- package/dist/predicate-v2.js +130 -11
- package/dist/predicate-v2.js.map +1 -1
- package/dist/sdl.cjs +460 -13
- 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 +459 -14
- package/dist/sdl.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ var ColumnBuilder = class {
|
|
|
36
36
|
hasDefault: false,
|
|
37
37
|
defaultValue: void 0,
|
|
38
38
|
defaultFn: void 0,
|
|
39
|
+
onUpdateFn: void 0,
|
|
39
40
|
description: void 0,
|
|
40
41
|
renamedFrom: void 0,
|
|
41
42
|
cast: void 0,
|
|
@@ -71,6 +72,15 @@ var ColumnBuilder = class {
|
|
|
71
72
|
this.state.defaultFn = void 0;
|
|
72
73
|
return this;
|
|
73
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Stamp this column with Postgres `now()` on every UPDATE. Only valid on
|
|
77
|
+
* timestamptz/date; the compiler drops it from SetInput so callers cannot
|
|
78
|
+
* supply a value.
|
|
79
|
+
*/
|
|
80
|
+
onUpdate(value) {
|
|
81
|
+
this.state.onUpdateFn = value;
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
74
84
|
description(text4) {
|
|
75
85
|
this.state.description = text4;
|
|
76
86
|
return this;
|
|
@@ -114,6 +124,7 @@ var ColumnBuilder = class {
|
|
|
114
124
|
};
|
|
115
125
|
if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
|
|
116
126
|
if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
|
|
127
|
+
if (this.state.onUpdateFn !== void 0) column.onUpdateFn = this.state.onUpdateFn;
|
|
117
128
|
if (this.state.description !== void 0) column.description = this.state.description;
|
|
118
129
|
if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
|
|
119
130
|
if (this.state.cast !== void 0) column.cast = this.state.cast;
|
|
@@ -141,6 +152,7 @@ var t = {
|
|
|
141
152
|
// src/constants.ts
|
|
142
153
|
var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
|
|
143
154
|
var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
|
|
155
|
+
var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
|
|
144
156
|
var MAX_IDENTIFIER_LENGTH = 63;
|
|
145
157
|
var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
|
|
146
158
|
var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
|
|
@@ -156,6 +168,8 @@ var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
|
|
|
156
168
|
var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
|
|
157
169
|
var HASH_DOMAIN = "sequence-orm-definitions\0";
|
|
158
170
|
var MAX_DEFINITION_FILE_BYTES = 1024 * 1024;
|
|
171
|
+
var MAX_GENERATED_SDL_BYTES = 16 * 1024 * 1024;
|
|
172
|
+
var ORM_REGISTRATION_MAX_BYTES = 64 * 1024 * 1024;
|
|
159
173
|
var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
|
|
160
174
|
var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
|
|
161
175
|
|
|
@@ -264,10 +278,10 @@ function isSeqLink(value) {
|
|
|
264
278
|
function isSeqTable(value) {
|
|
265
279
|
return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
|
|
266
280
|
}
|
|
267
|
-
function assertIdentifier({ value, what, file }) {
|
|
268
|
-
if (!
|
|
281
|
+
function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
|
|
282
|
+
if (!pattern.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
|
|
269
283
|
throw new OrmDefinitionError(
|
|
270
|
-
`${what} "${value}" must match ${
|
|
284
|
+
`${what} "${value}" must match ${pattern} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
|
|
271
285
|
file
|
|
272
286
|
);
|
|
273
287
|
}
|
|
@@ -288,7 +302,7 @@ function defineTable(name, columns, config = {}) {
|
|
|
288
302
|
);
|
|
289
303
|
}
|
|
290
304
|
const columnName = builder.columnNameFor(propertyName);
|
|
291
|
-
assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
|
|
305
|
+
assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
|
|
292
306
|
const existing = seenColumnNames.get(columnName);
|
|
293
307
|
if (existing !== void 0) {
|
|
294
308
|
throw new OrmDefinitionError(
|
|
@@ -328,7 +342,8 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
|
|
|
328
342
|
assertIdentifier({
|
|
329
343
|
value: columnName,
|
|
330
344
|
what: `synced table "${name}": column "${propertyName}" column name`,
|
|
331
|
-
file
|
|
345
|
+
file,
|
|
346
|
+
pattern: COLUMN_IDENTIFIER_PATTERN
|
|
332
347
|
});
|
|
333
348
|
const existing = seenColumnNames.get(columnName);
|
|
334
349
|
if (existing !== void 0) {
|
|
@@ -417,7 +432,7 @@ function defineView(name, columns, config) {
|
|
|
417
432
|
);
|
|
418
433
|
}
|
|
419
434
|
const columnName = builder.columnNameFor(propertyName);
|
|
420
|
-
if (!
|
|
435
|
+
if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
|
|
421
436
|
throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
|
|
422
437
|
}
|
|
423
438
|
const existing = seenColumnNames.get(columnName);
|
|
@@ -866,6 +881,7 @@ function scalarColumnType(table, property, helper) {
|
|
|
866
881
|
var CANONICAL_FORMAT = 2;
|
|
867
882
|
var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
|
|
868
883
|
var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
|
|
884
|
+
var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
|
|
869
885
|
var COLUMN_TYPES = [
|
|
870
886
|
"uuid",
|
|
871
887
|
"text",
|
|
@@ -886,18 +902,33 @@ var canonicalEnumSchema = zod.z.object({
|
|
|
886
902
|
var canonicalColumnSchema = zod.z.object({
|
|
887
903
|
/** Property name in TS (camelCase); columnName is the Postgres name. */
|
|
888
904
|
name: zod.z.string().min(1).max(63),
|
|
889
|
-
columnName:
|
|
905
|
+
columnName: columnIdentifier,
|
|
890
906
|
type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
|
|
891
907
|
nullable: zod.z.boolean().default(true),
|
|
892
908
|
unique: zod.z.boolean().default(false),
|
|
893
909
|
/** Literal default, or one of the allowlisted SQL functions. */
|
|
894
910
|
default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
|
|
895
911
|
defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
|
|
912
|
+
/**
|
|
913
|
+
* Stamp this column from the database clock on UPDATE (`now()`). The
|
|
914
|
+
* column stays insertable (defaults fill gaps) but is omitted from
|
|
915
|
+
* SetInput / settable so callers cannot backdate it.
|
|
916
|
+
*/
|
|
917
|
+
onUpdateFn: zod.z.enum(["now()"]).optional(),
|
|
896
918
|
description: zod.z.string().max(500).optional(),
|
|
897
919
|
/** Migration hints — same semantics as v1. */
|
|
898
|
-
renamedFrom:
|
|
920
|
+
renamedFrom: columnIdentifier.optional(),
|
|
899
921
|
cast: zod.z.string().max(200).optional(),
|
|
900
922
|
deprecated: zod.z.boolean().default(false)
|
|
923
|
+
}).superRefine((column, ctx) => {
|
|
924
|
+
if (column.onUpdateFn === void 0) return;
|
|
925
|
+
if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
|
|
926
|
+
ctx.addIssue({
|
|
927
|
+
code: "custom",
|
|
928
|
+
path: ["onUpdateFn"],
|
|
929
|
+
message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
|
|
930
|
+
});
|
|
931
|
+
}
|
|
901
932
|
});
|
|
902
933
|
var canonicalLinkSchema = zod.z.object({
|
|
903
934
|
name: zod.z.string().min(1).max(63),
|
|
@@ -905,15 +936,15 @@ var canonicalLinkSchema = zod.z.object({
|
|
|
905
936
|
/** Target table name (namespace-local). Absent for toPlatformUser. */
|
|
906
937
|
target: identifier.optional(),
|
|
907
938
|
/** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
|
|
908
|
-
on:
|
|
939
|
+
on: columnIdentifier,
|
|
909
940
|
/** Referenced column on the target (default: target's PK / platform user `sub`). */
|
|
910
|
-
references:
|
|
941
|
+
references: columnIdentifier.optional(),
|
|
911
942
|
/** enforce: true emits a real FK constraint (same-namespace toOne only). */
|
|
912
943
|
enforce: zod.z.boolean().default(false),
|
|
913
944
|
description: zod.z.string().max(500).optional()
|
|
914
945
|
});
|
|
915
946
|
var canonicalIndexSchema = zod.z.object({
|
|
916
|
-
columns: zod.z.array(
|
|
947
|
+
columns: zod.z.array(columnIdentifier).min(1),
|
|
917
948
|
unique: zod.z.boolean().default(false),
|
|
918
949
|
name: identifier.optional()
|
|
919
950
|
});
|
|
@@ -1161,6 +1192,13 @@ var canonicalCowMutationSchema = zod.z.object({
|
|
|
1161
1192
|
}
|
|
1162
1193
|
}
|
|
1163
1194
|
});
|
|
1195
|
+
var canonicalForeignKeySchema = zod.z.object({
|
|
1196
|
+
name: identifier,
|
|
1197
|
+
columns: zod.z.array(columnIdentifier).min(1).max(8),
|
|
1198
|
+
target: identifier,
|
|
1199
|
+
references: zod.z.array(columnIdentifier).min(1).max(8),
|
|
1200
|
+
deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
|
|
1201
|
+
}).strict();
|
|
1164
1202
|
var canonicalTableSchema = zod.z.object({
|
|
1165
1203
|
table: identifier,
|
|
1166
1204
|
description: zod.z.string().max(2e3).optional(),
|
|
@@ -1169,7 +1207,7 @@ var canonicalTableSchema = zod.z.object({
|
|
|
1169
1207
|
/** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
|
|
1170
1208
|
source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
|
|
1171
1209
|
columns: zod.z.array(canonicalColumnSchema).default([]),
|
|
1172
|
-
primaryKey: zod.z.array(
|
|
1210
|
+
primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
|
|
1173
1211
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
1174
1212
|
indexes: zod.z.array(canonicalIndexSchema).default([]),
|
|
1175
1213
|
policies: canonicalPoliciesSchema.default({}),
|
|
@@ -1180,14 +1218,16 @@ var canonicalTableSchema = zod.z.object({
|
|
|
1180
1218
|
*/
|
|
1181
1219
|
actions: zod.z.array(canonicalActionSchema).default([]),
|
|
1182
1220
|
/** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
|
|
1183
|
-
cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
|
|
1221
|
+
cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
|
|
1222
|
+
/** Omitted by old definitions, preserving their existing content hashes. */
|
|
1223
|
+
foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
|
|
1184
1224
|
});
|
|
1185
1225
|
var canonicalViewSchema = zod.z.object({
|
|
1186
1226
|
view: identifier,
|
|
1187
1227
|
description: zod.z.string().max(2e3).optional(),
|
|
1188
1228
|
columns: zod.z.array(canonicalColumnSchema).min(1),
|
|
1189
1229
|
/** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
|
|
1190
|
-
primaryKey: zod.z.array(
|
|
1230
|
+
primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
|
|
1191
1231
|
links: zod.z.array(canonicalLinkSchema).default([]),
|
|
1192
1232
|
policies: zod.z.object({ read: policyExpressionSchema }).strict(),
|
|
1193
1233
|
query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
|
|
@@ -1200,6 +1240,9 @@ var canonicalViewSchema = zod.z.object({
|
|
|
1200
1240
|
if (column.default !== void 0 || column.defaultFn !== void 0) {
|
|
1201
1241
|
ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare a default` });
|
|
1202
1242
|
}
|
|
1243
|
+
if (column.onUpdateFn !== void 0) {
|
|
1244
|
+
ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare onUpdateFn` });
|
|
1245
|
+
}
|
|
1203
1246
|
if (column.unique) {
|
|
1204
1247
|
ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare unique` });
|
|
1205
1248
|
}
|
|
@@ -1233,6 +1276,45 @@ var canonicalDefinitionSchema = zod.z.object({
|
|
|
1233
1276
|
ctx.addIssue({ code: "custom", path: ["tables", index3, "table"], message: `duplicate relation '${table.table}'` });
|
|
1234
1277
|
}
|
|
1235
1278
|
seen.add(table.table);
|
|
1279
|
+
if (table.source !== void 0) {
|
|
1280
|
+
for (const [columnIndex, column] of table.columns.entries()) {
|
|
1281
|
+
if (column.onUpdateFn !== void 0) {
|
|
1282
|
+
ctx.addIssue({
|
|
1283
|
+
code: "custom",
|
|
1284
|
+
path: ["tables", index3, "columns", columnIndex],
|
|
1285
|
+
message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1292
|
+
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
1293
|
+
const names = /* @__PURE__ */ new Set([
|
|
1294
|
+
...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
|
|
1295
|
+
...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
|
|
1296
|
+
...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
|
|
1297
|
+
]);
|
|
1298
|
+
for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
|
|
1299
|
+
const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
|
|
1300
|
+
if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
|
|
1301
|
+
names.add(fk.name);
|
|
1302
|
+
const target = fkTables.get(fk.target);
|
|
1303
|
+
if (table.source || !target || target.source) {
|
|
1304
|
+
fail("foreign keys must connect owned tables in the same namespace");
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
|
|
1308
|
+
if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
|
|
1309
|
+
for (const [position, name] of fk.columns.entries()) {
|
|
1310
|
+
const sourceColumn = table.columns.find((column) => column.columnName === name);
|
|
1311
|
+
const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
|
|
1312
|
+
if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
|
|
1313
|
+
else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
|
|
1314
|
+
}
|
|
1315
|
+
const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
|
|
1316
|
+
if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
|
|
1317
|
+
}
|
|
1236
1318
|
}
|
|
1237
1319
|
const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
|
|
1238
1320
|
for (const [tableIndex, table] of definition.tables.entries()) {
|
|
@@ -1368,12 +1450,13 @@ function normalizeCanonical(definition) {
|
|
|
1368
1450
|
...definition,
|
|
1369
1451
|
enums: sortByName(definition.enums),
|
|
1370
1452
|
tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
|
|
1371
|
-
const { cowMutations, ...rest } = t2;
|
|
1453
|
+
const { cowMutations, foreignKeys, ...rest } = t2;
|
|
1372
1454
|
return {
|
|
1373
1455
|
...rest,
|
|
1374
1456
|
links: sortByName(t2.links),
|
|
1375
1457
|
indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
|
|
1376
1458
|
actions: sortByName(t2.actions),
|
|
1459
|
+
...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
|
|
1377
1460
|
// Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
|
|
1378
1461
|
...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
|
|
1379
1462
|
};
|
|
@@ -1647,6 +1730,22 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
|
|
|
1647
1730
|
} else if (columnLevelPk.length > 0) {
|
|
1648
1731
|
candidate.primaryKey = columnLevelPk;
|
|
1649
1732
|
}
|
|
1733
|
+
if (table.config.foreignKeys?.length) {
|
|
1734
|
+
if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
|
|
1735
|
+
candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
|
|
1736
|
+
const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
|
|
1737
|
+
throw new OrmDefinitionError(message, table.file);
|
|
1738
|
+
} });
|
|
1739
|
+
if (!isSeqTable(target) || !relationsByName.has(target.tableName)) throw new OrmDefinitionError("foreign key target must be a table in this namespace", table.file);
|
|
1740
|
+
return {
|
|
1741
|
+
name: fk.name,
|
|
1742
|
+
columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
|
|
1743
|
+
target: target.tableName,
|
|
1744
|
+
references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
|
|
1745
|
+
...fk.deferrable ? { deferrable: fk.deferrable } : {}
|
|
1746
|
+
};
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1650
1749
|
for (const index3 of table.config.indexes ?? []) {
|
|
1651
1750
|
const entry = {
|
|
1652
1751
|
columns: index3.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
|
|
@@ -1699,6 +1798,12 @@ function buildCanonicalView({
|
|
|
1699
1798
|
if (column.default !== void 0 || column.defaultFn !== void 0) {
|
|
1700
1799
|
throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare a default`, view.file);
|
|
1701
1800
|
}
|
|
1801
|
+
if (column.onUpdateFn !== void 0) {
|
|
1802
|
+
throw new OrmDefinitionError(
|
|
1803
|
+
`view "${view.viewName}": column "${propertyName}" cannot declare onUpdateFn`,
|
|
1804
|
+
view.file
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1702
1807
|
if (column.unique) {
|
|
1703
1808
|
throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare unique`, view.file);
|
|
1704
1809
|
}
|
|
@@ -1959,6 +2064,10 @@ async function loadNamespaceFromDir(dir, options = {}) {
|
|
|
1959
2064
|
fsCache: false,
|
|
1960
2065
|
...options.ormModulePath !== void 0 ? {
|
|
1961
2066
|
alias: {
|
|
2067
|
+
// The public /drizzle barrel re-exports this pinned dependency.
|
|
2068
|
+
// Explicitly map it before the root alias: otherwise jiti appends
|
|
2069
|
+
// "/drizzle" to the overridden module's filename.
|
|
2070
|
+
"@sequenceholdings/orm/drizzle": module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).resolve("drizzle-orm"),
|
|
1962
2071
|
"@sequenceholdings/orm": options.ormModulePath,
|
|
1963
2072
|
"@sequenceholdings/orm/authoring": options.ormModulePath
|
|
1964
2073
|
}
|
|
@@ -2521,8 +2630,8 @@ var actionSchema = zod.z.strictObject({
|
|
|
2521
2630
|
}
|
|
2522
2631
|
}
|
|
2523
2632
|
});
|
|
2524
|
-
function extractNamedParams(
|
|
2525
|
-
const withoutStrings =
|
|
2633
|
+
function extractNamedParams(sql5) {
|
|
2634
|
+
const withoutStrings = sql5.replace(QUOTED_STRING_PATTERN, "''");
|
|
2526
2635
|
const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
|
|
2527
2636
|
return [...new Set(names)];
|
|
2528
2637
|
}
|
|
@@ -2679,8 +2788,8 @@ var actionParamArg = (name) => `p_${name}`;
|
|
|
2679
2788
|
function compileActionStatements(action, renderParam = actionParamArg) {
|
|
2680
2789
|
return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
|
|
2681
2790
|
}
|
|
2682
|
-
function compileStatement(
|
|
2683
|
-
const compiled =
|
|
2791
|
+
function compileStatement(sql5, renderParam) {
|
|
2792
|
+
const compiled = sql5.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
|
|
2684
2793
|
if (claimKey !== void 0) {
|
|
2685
2794
|
if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
|
|
2686
2795
|
return claimExpression(claimKey);
|
|
@@ -2845,8 +2954,8 @@ function actionPrepareChecks(definition) {
|
|
|
2845
2954
|
const order = actionParamOrder(action);
|
|
2846
2955
|
const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
|
|
2847
2956
|
const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
|
|
2848
|
-
compiled.forEach((
|
|
2849
|
-
const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns,
|
|
2957
|
+
compiled.forEach((sql5, index3) => {
|
|
2958
|
+
const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql5) : sql5;
|
|
2850
2959
|
statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
|
|
2851
2960
|
});
|
|
2852
2961
|
}
|
|
@@ -3300,7 +3409,8 @@ function syncedViewStatements({
|
|
|
3300
3409
|
readPolicyIrByTable,
|
|
3301
3410
|
columnNamesByTable,
|
|
3302
3411
|
columnTypesByTable,
|
|
3303
|
-
executionOwnerRole = SYNCED_VIEW_EXECUTION_OWNER_ROLE
|
|
3412
|
+
executionOwnerRole = SYNCED_VIEW_EXECUTION_OWNER_ROLE,
|
|
3413
|
+
backingPrivileges = "grant"
|
|
3304
3414
|
}) {
|
|
3305
3415
|
const schema = quoteIdentifier(ormSchemaName(definition.namespace));
|
|
3306
3416
|
const role = quoteIdentifier(consumerRoleName(definition.namespace));
|
|
@@ -3334,8 +3444,10 @@ function syncedViewStatements({
|
|
|
3334
3444
|
columnTypes,
|
|
3335
3445
|
columnNames: columnNamesByTable?.get(table.table)
|
|
3336
3446
|
})}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
|
|
3337
|
-
|
|
3338
|
-
|
|
3447
|
+
if (backingPrivileges === "grant") {
|
|
3448
|
+
statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical.schema)} TO ${owner}`);
|
|
3449
|
+
statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
|
|
3450
|
+
}
|
|
3339
3451
|
statements.push(
|
|
3340
3452
|
`CREATE OR REPLACE VIEW ${viewName} AS SELECT * FROM ${physicalTarget}${whereClause}`
|
|
3341
3453
|
);
|
|
@@ -3624,7 +3736,7 @@ var FACTORIES2 = {
|
|
|
3624
3736
|
var SCHEMA_IMPORT_KEY2 = "$schema";
|
|
3625
3737
|
var enumImportKey = (name) => `$enum_${name}`;
|
|
3626
3738
|
var columnTablesV2 = (definition) => definition.tables.filter((table) => !table.source);
|
|
3627
|
-
function buildDrizzleEntitiesV2(definition) {
|
|
3739
|
+
function buildDrizzleEntitiesV2(definition, options = {}) {
|
|
3628
3740
|
const schemaName = ormSchemaName(definition.namespace);
|
|
3629
3741
|
const schema = pgCore.pgSchema(schemaName);
|
|
3630
3742
|
const imports = { [SCHEMA_IMPORT_KEY2]: schema };
|
|
@@ -3639,11 +3751,19 @@ function buildDrizzleEntitiesV2(definition) {
|
|
|
3639
3751
|
}
|
|
3640
3752
|
const byName = new Map(definition.tables.map((table) => [table.table, table]));
|
|
3641
3753
|
for (const table of sortByEnforcedLinks2(columnTablesV2(definition))) {
|
|
3642
|
-
const drizzleTable = buildTable2(schema, table, built, enums, byName);
|
|
3754
|
+
const drizzleTable = buildTable2(schema, table, built, enums, byName, options.introspection ?? false);
|
|
3643
3755
|
built.set(table.table, drizzleTable);
|
|
3644
3756
|
imports[table.table] = drizzleTable;
|
|
3645
3757
|
tables[table.table] = drizzleTable;
|
|
3646
3758
|
}
|
|
3759
|
+
if (options.introspection) {
|
|
3760
|
+
for (const table of definition.tables.filter((table2) => table2.source)) {
|
|
3761
|
+
const columns = Object.fromEntries(
|
|
3762
|
+
table.columns.map((column) => [column.columnName, buildViewColumnV2({ view: table.table, column, enums })])
|
|
3763
|
+
);
|
|
3764
|
+
imports[table.table] = schema.view(table.table, columns).existing();
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3647
3767
|
for (const view of definition.views ?? []) {
|
|
3648
3768
|
const columns = Object.fromEntries(
|
|
3649
3769
|
view.columns.map((column) => [column.columnName, buildViewColumnV2({ view: view.view, column, enums })])
|
|
@@ -3711,18 +3831,18 @@ function rewriteSyncedSourceRelationsInViewQuery({
|
|
|
3711
3831
|
return rewritten;
|
|
3712
3832
|
});
|
|
3713
3833
|
}
|
|
3714
|
-
function transformSqlOutsideLiterals(
|
|
3834
|
+
function transformSqlOutsideLiterals(sql5, transform) {
|
|
3715
3835
|
const out = [];
|
|
3716
3836
|
let state = { kind: "normal" };
|
|
3717
3837
|
let i = 0;
|
|
3718
3838
|
let codeStart = 0;
|
|
3719
3839
|
const emitCode = (end) => {
|
|
3720
|
-
if (end > codeStart) out.push(transform(
|
|
3840
|
+
if (end > codeStart) out.push(transform(sql5.slice(codeStart, end)));
|
|
3721
3841
|
codeStart = end;
|
|
3722
3842
|
};
|
|
3723
|
-
while (i <
|
|
3724
|
-
const c =
|
|
3725
|
-
const next = i + 1 <
|
|
3843
|
+
while (i < sql5.length) {
|
|
3844
|
+
const c = sql5[i];
|
|
3845
|
+
const next = i + 1 < sql5.length ? sql5[i + 1] : "";
|
|
3726
3846
|
switch (state.kind) {
|
|
3727
3847
|
case "normal": {
|
|
3728
3848
|
if (c === "-" && next === "-") {
|
|
@@ -3742,7 +3862,7 @@ function transformSqlOutsideLiterals(sql4, transform) {
|
|
|
3742
3862
|
break;
|
|
3743
3863
|
}
|
|
3744
3864
|
if ((c === "E" || c === "e") && next === "'") {
|
|
3745
|
-
const prev = i > 0 ?
|
|
3865
|
+
const prev = i > 0 ? sql5[i - 1] : "";
|
|
3746
3866
|
if (!/[A-Za-z0-9_]/.test(prev)) {
|
|
3747
3867
|
emitCode(i);
|
|
3748
3868
|
state = { kind: "e-single" };
|
|
@@ -3761,9 +3881,9 @@ function transformSqlOutsideLiterals(sql4, transform) {
|
|
|
3761
3881
|
break;
|
|
3762
3882
|
}
|
|
3763
3883
|
if (c === "$") {
|
|
3764
|
-
const prev = i > 0 ?
|
|
3884
|
+
const prev = i > 0 ? sql5[i - 1] : "";
|
|
3765
3885
|
if (!/[A-Za-z0-9_$]/.test(prev)) {
|
|
3766
|
-
const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(
|
|
3886
|
+
const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql5.slice(i));
|
|
3767
3887
|
if (tagMatch) {
|
|
3768
3888
|
emitCode(i);
|
|
3769
3889
|
state = { kind: "dollar", tag: tagMatch[1] ?? "" };
|
|
@@ -3795,8 +3915,8 @@ function transformSqlOutsideLiterals(sql4, transform) {
|
|
|
3795
3915
|
break;
|
|
3796
3916
|
}
|
|
3797
3917
|
case "e-single": {
|
|
3798
|
-
if (c === "\\" && i + 1 <
|
|
3799
|
-
out.push(c,
|
|
3918
|
+
if (c === "\\" && i + 1 < sql5.length) {
|
|
3919
|
+
out.push(c, sql5[i + 1]);
|
|
3800
3920
|
i += 2;
|
|
3801
3921
|
break;
|
|
3802
3922
|
}
|
|
@@ -3818,7 +3938,7 @@ function transformSqlOutsideLiterals(sql4, transform) {
|
|
|
3818
3938
|
}
|
|
3819
3939
|
case "dollar": {
|
|
3820
3940
|
const closer = `$${state.tag}$`;
|
|
3821
|
-
if (
|
|
3941
|
+
if (sql5.startsWith(closer, i)) {
|
|
3822
3942
|
out.push(closer);
|
|
3823
3943
|
state = { kind: "normal" };
|
|
3824
3944
|
i += closer.length;
|
|
@@ -3862,15 +3982,15 @@ function transformSqlOutsideLiterals(sql4, transform) {
|
|
|
3862
3982
|
}
|
|
3863
3983
|
}
|
|
3864
3984
|
}
|
|
3865
|
-
emitCode(
|
|
3985
|
+
emitCode(sql5.length);
|
|
3866
3986
|
return out.join("");
|
|
3867
3987
|
}
|
|
3868
3988
|
function escapeRegExp(value) {
|
|
3869
3989
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3870
3990
|
}
|
|
3871
|
-
function sqlReferencesQualifiedRelation(
|
|
3991
|
+
function sqlReferencesQualifiedRelation(sql5, sourceSchema, sourceName) {
|
|
3872
3992
|
let referenced = false;
|
|
3873
|
-
transformSqlOutsideLiterals(
|
|
3993
|
+
transformSqlOutsideLiterals(sql5, (segment) => {
|
|
3874
3994
|
if (qualifiedRelationPattern(sourceSchema, sourceName).test(segment)) referenced = true;
|
|
3875
3995
|
return segment;
|
|
3876
3996
|
});
|
|
@@ -3881,15 +4001,16 @@ function qualifiedRelationPattern(sourceSchema, sourceName) {
|
|
|
3881
4001
|
const namePattern = `(?:"${escapeRegExp(sourceName)}"|${escapeRegExp(sourceName)})`;
|
|
3882
4002
|
return new RegExp(`(?<![\\w."])${schemaPattern}\\.${namePattern}(?![\\w"])`, "g");
|
|
3883
4003
|
}
|
|
3884
|
-
function replaceQualifiedRelation(
|
|
3885
|
-
return
|
|
4004
|
+
function replaceQualifiedRelation(sql5, sourceSchema, sourceName, boundSchema, boundTable) {
|
|
4005
|
+
return sql5.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
|
|
3886
4006
|
}
|
|
3887
4007
|
function generateSnapshotV2(definition, previousId3) {
|
|
3888
4008
|
const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
|
|
3889
4009
|
const snapshot = api.generateDrizzleJson(imports, previousId3, [schemaName]);
|
|
3890
|
-
if ((definition.views?.length ?? 0) > 0) {
|
|
4010
|
+
if ((definition.views?.length ?? 0) > 0 || definition.tables.some((table) => table.foreignKeys?.length)) {
|
|
3891
4011
|
const extended = snapshot;
|
|
3892
4012
|
extended._sequenceOrm = {
|
|
4013
|
+
...definition.tables.some((table) => table.foreignKeys?.length) ? { foreignKeys: Object.fromEntries(definition.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => [`${table.table}.${fk.name}`, structuredClone(fk)]))) } : {},
|
|
3893
4014
|
viewDependencies: Object.fromEntries(
|
|
3894
4015
|
(definition.views ?? []).map((view) => [view.view, [...view.dependencies].sort()])
|
|
3895
4016
|
)
|
|
@@ -3907,7 +4028,7 @@ function buildViewColumnV2({
|
|
|
3907
4028
|
if (!column.nullable) builder = builder.notNull();
|
|
3908
4029
|
return builder;
|
|
3909
4030
|
}
|
|
3910
|
-
function buildTable2(schema, table, built, enums, byName) {
|
|
4031
|
+
function buildTable2(schema, table, built, enums, byName, introspection) {
|
|
3911
4032
|
const primaryKeyColumns = table.primaryKey ?? [];
|
|
3912
4033
|
if (primaryKeyColumns.length === 0) {
|
|
3913
4034
|
throw new OrmDefinitionError(`table '${table.table}' declares no primaryKey`);
|
|
@@ -3923,13 +4044,22 @@ function buildTable2(schema, table, built, enums, byName) {
|
|
|
3923
4044
|
const columns = Object.fromEntries(
|
|
3924
4045
|
table.columns.map((column) => [
|
|
3925
4046
|
column.columnName,
|
|
3926
|
-
buildColumnV2({ table: table.table, column, singlePrimaryKey, compositeMembers, enums })
|
|
4047
|
+
buildColumnV2({ table: table.table, column, singlePrimaryKey, compositeMembers, enums, introspection })
|
|
3927
4048
|
])
|
|
3928
4049
|
);
|
|
3929
4050
|
const drizzleTable = schema.table(table.table, columns, (self) => [
|
|
3930
4051
|
...buildCompositePrimaryKey(table, primaryKeyColumns, self),
|
|
3931
4052
|
...buildIndexes2(table, self),
|
|
3932
|
-
...buildForeignKeys2(table, self, built, byName)
|
|
4053
|
+
...buildForeignKeys2(table, self, built, byName),
|
|
4054
|
+
...(table.foreignKeys ?? []).map((fk) => {
|
|
4055
|
+
const target = fk.target === table.table ? self : built.get(fk.target);
|
|
4056
|
+
if (!target) throw new OrmDefinitionError(`foreign key '${fk.name}' has no target '${fk.target}'`);
|
|
4057
|
+
return pgCore.foreignKey({
|
|
4058
|
+
name: fk.name,
|
|
4059
|
+
columns: fk.columns.map((column) => self[column]),
|
|
4060
|
+
foreignColumns: fk.references.map((column) => target[column])
|
|
4061
|
+
});
|
|
4062
|
+
})
|
|
3933
4063
|
]);
|
|
3934
4064
|
return drizzleTable.enableRLS();
|
|
3935
4065
|
}
|
|
@@ -3946,7 +4076,8 @@ function buildColumnV2({
|
|
|
3946
4076
|
column,
|
|
3947
4077
|
singlePrimaryKey,
|
|
3948
4078
|
compositeMembers,
|
|
3949
|
-
enums
|
|
4079
|
+
enums,
|
|
4080
|
+
introspection
|
|
3950
4081
|
}) {
|
|
3951
4082
|
const factory = resolveColumnFactory(table, column, enums);
|
|
3952
4083
|
let builder = factory(column.columnName);
|
|
@@ -3967,7 +4098,7 @@ function buildColumnV2({
|
|
|
3967
4098
|
}
|
|
3968
4099
|
builder = builder.default(drizzleOrm.sql.raw(column.defaultFn));
|
|
3969
4100
|
} else if (column.default !== void 0 && column.default !== null) {
|
|
3970
|
-
builder = builder.default(column.default);
|
|
4101
|
+
builder = introspection && column.type === "jsonb" && typeof column.default === "string" ? builder.default(drizzleOrm.sql.raw(`${quoteLiteral(column.default)}::jsonb`)) : builder.default(column.default);
|
|
3971
4102
|
}
|
|
3972
4103
|
return builder;
|
|
3973
4104
|
}
|
|
@@ -4152,7 +4283,8 @@ async function planMigrationV2({
|
|
|
4152
4283
|
assertValidViewDependencies(canonical);
|
|
4153
4284
|
const hints = applyMigrationHintsV2(previous, canonical);
|
|
4154
4285
|
const snapshot = generateSnapshotV2(canonical, previousId(previous));
|
|
4155
|
-
const
|
|
4286
|
+
const generatedBaseDdl = await diffStatements(withoutViews(hints.adjusted), withoutViews(snapshot));
|
|
4287
|
+
const baseDdl = orderForeignKeyAdditions(canonical, generatedBaseDdl);
|
|
4156
4288
|
const previousViews = snapshotViews(hints.adjusted);
|
|
4157
4289
|
const currentViews = snapshotViews(snapshot);
|
|
4158
4290
|
const previousDependencies = snapshotViewDependencies(hints.adjusted);
|
|
@@ -4189,6 +4321,7 @@ async function planMigrationV2({
|
|
|
4189
4321
|
...dropOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name, schema, "DROP") })),
|
|
4190
4322
|
...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
|
|
4191
4323
|
...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
|
|
4324
|
+
...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql5) => ({ kind: "ddl", sql: sql5 })),
|
|
4192
4325
|
...createOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name, schema, "CREATE") })),
|
|
4193
4326
|
...createOrder.map((name) => {
|
|
4194
4327
|
const view = currentViewDefinitions.get(name);
|
|
@@ -4198,6 +4331,29 @@ async function planMigrationV2({
|
|
|
4198
4331
|
];
|
|
4199
4332
|
return { namespace: canonical.namespace, schema, statements, snapshot };
|
|
4200
4333
|
}
|
|
4334
|
+
function orderForeignKeyAdditions(canonical, statements) {
|
|
4335
|
+
if (!canonical.tables.some((table) => table.foreignKeys?.length)) return [...statements];
|
|
4336
|
+
const addsForeignKey = (statement) => /^ALTER TABLE[\s\S]* ADD CONSTRAINT[\s\S]* FOREIGN KEY\b/.test(statement);
|
|
4337
|
+
return [...statements.filter((statement) => !addsForeignKey(statement)), ...statements.filter(addsForeignKey)];
|
|
4338
|
+
}
|
|
4339
|
+
function foreignKeyTimingStatements(canonical, previous, baseDdl) {
|
|
4340
|
+
const prior = previous._sequenceOrm?.foreignKeys ?? {};
|
|
4341
|
+
const result = [];
|
|
4342
|
+
for (const table of canonical.tables) {
|
|
4343
|
+
for (const fk of table.foreignKeys ?? []) {
|
|
4344
|
+
const before = prior[`${table.table}.${fk.name}`];
|
|
4345
|
+
const recreated = baseDdl.some((statement) => statement.includes(`ALTER TABLE ${quoteIdentifier(ormSchemaName(canonical.namespace))}.${quoteIdentifier(table.table)} ADD CONSTRAINT ${quoteIdentifier(fk.name)}`));
|
|
4346
|
+
if (fk.deferrable ? !before || before.deferrable !== fk.deferrable || recreated : before?.deferrable && !recreated) {
|
|
4347
|
+
result.push(foreignKeyTimingSql(ormSchemaName(canonical.namespace), table.table, fk.name, fk.deferrable));
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
return result;
|
|
4352
|
+
}
|
|
4353
|
+
function foreignKeyTimingSql(schema, table, name, timing) {
|
|
4354
|
+
const clause = timing === void 0 ? "NOT DEFERRABLE" : timing === "initially-deferred" ? "DEFERRABLE INITIALLY DEFERRED" : "DEFERRABLE INITIALLY IMMEDIATE";
|
|
4355
|
+
return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(name)} ${clause}`;
|
|
4356
|
+
}
|
|
4201
4357
|
function validateViewCatalogStatement({
|
|
4202
4358
|
canonical,
|
|
4203
4359
|
view,
|
|
@@ -4452,7 +4608,63 @@ async function pendingKitStatementsV2({
|
|
|
4452
4608
|
canonical,
|
|
4453
4609
|
db
|
|
4454
4610
|
}) {
|
|
4455
|
-
|
|
4611
|
+
const { desired, defaultDrift } = await normalizeJsonDefaultsForIntrospection(canonical, db);
|
|
4612
|
+
const entities = buildDrizzleEntitiesV2(desired, { introspection: true });
|
|
4613
|
+
const pending = await pendingStatements({ entities, db, owned: V2_OWNED_STATEMENT });
|
|
4614
|
+
return [...orderForeignKeyAdditions(canonical, pending), ...defaultDrift, ...await pendingForeignKeyTimingStatements(canonical, db)];
|
|
4615
|
+
}
|
|
4616
|
+
async function normalizeJsonDefaultsForIntrospection(canonical, db) {
|
|
4617
|
+
const defaults = canonical.tables.flatMap((table, tableIndex) => table.source ? [] : table.columns.flatMap((column, columnIndex) => column.type === "jsonb" && typeof column.default === "string" ? [{ tableIndex, columnIndex, table: table.table, column: column.columnName, value: column.default }] : []));
|
|
4618
|
+
if (defaults.length === 0) return { desired: canonical, defaultDrift: [] };
|
|
4619
|
+
const array = (values) => drizzleOrm.sql`ARRAY[${drizzleOrm.sql.join(values.map((value) => drizzleOrm.sql`${value}`), drizzleOrm.sql`, `)}]::text[]`;
|
|
4620
|
+
const schema = ormSchemaName(canonical.namespace);
|
|
4621
|
+
const result = await shimExecute(db).execute(drizzleOrm.sql`
|
|
4622
|
+
SELECT desired.ordinal, desired.value::jsonb::text AS normalized,
|
|
4623
|
+
attribute.attnum IS NOT NULL AS column_exists,
|
|
4624
|
+
coalesce(pg_get_expr(ad.adbin, ad.adrelid) = quote_literal(desired.value::jsonb::text) || '::jsonb', false) AS matches
|
|
4625
|
+
FROM unnest(${array(defaults.map((item) => item.table))}, ${array(defaults.map((item) => item.column))}, ${array(defaults.map((item) => item.value))})
|
|
4626
|
+
WITH ORDINALITY AS desired(table_name, column_name, value, ordinal)
|
|
4627
|
+
LEFT JOIN pg_catalog.pg_namespace namespace ON namespace.nspname = ${schema}
|
|
4628
|
+
LEFT JOIN pg_catalog.pg_class relation ON relation.relnamespace = namespace.oid AND relation.relname = desired.table_name
|
|
4629
|
+
LEFT JOIN pg_catalog.pg_attribute attribute ON attribute.attrelid = relation.oid AND attribute.attname = desired.column_name AND NOT attribute.attisdropped
|
|
4630
|
+
LEFT JOIN pg_catalog.pg_attrdef ad ON ad.adrelid = relation.oid AND ad.adnum = attribute.attnum
|
|
4631
|
+
ORDER BY desired.ordinal
|
|
4632
|
+
`);
|
|
4633
|
+
const rows = result.rows;
|
|
4634
|
+
if (!Array.isArray(rows) || rows.length !== defaults.length || rows.some((row, index3) => Number(row.ordinal) !== index3 + 1 || typeof row.normalized !== "string" || typeof row.column_exists !== "boolean" || typeof row.matches !== "boolean")) {
|
|
4635
|
+
throw new Error("invalid JSON default introspection result");
|
|
4636
|
+
}
|
|
4637
|
+
const desired = structuredClone(canonical);
|
|
4638
|
+
const defaultDrift = [];
|
|
4639
|
+
defaults.forEach((item, index3) => {
|
|
4640
|
+
const row = rows[index3];
|
|
4641
|
+
desired.tables[item.tableIndex].columns[item.columnIndex].default = JSON.stringify(JSON.parse(row.normalized));
|
|
4642
|
+
if (row.column_exists && !row.matches) defaultDrift.push(
|
|
4643
|
+
`ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(item.table)} ALTER COLUMN ${quoteIdentifier(item.column)} SET DEFAULT ${quoteLiteral(item.value)}::jsonb;`
|
|
4644
|
+
);
|
|
4645
|
+
});
|
|
4646
|
+
return { desired, defaultDrift };
|
|
4647
|
+
}
|
|
4648
|
+
async function pendingForeignKeyTimingStatements(canonical, db) {
|
|
4649
|
+
const keys = canonical.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => ({ table: table.table, fk })));
|
|
4650
|
+
if (keys.length === 0) return [];
|
|
4651
|
+
const schema = ormSchemaName(canonical.namespace);
|
|
4652
|
+
const result = await shimExecute(db).execute(drizzleOrm.sql`
|
|
4653
|
+
SELECT relation.relname AS table_name, constraint_row.conname AS name,
|
|
4654
|
+
constraint_row.condeferrable AS deferrable, constraint_row.condeferred AS deferred
|
|
4655
|
+
FROM pg_catalog.pg_constraint AS constraint_row
|
|
4656
|
+
JOIN pg_catalog.pg_class AS relation ON relation.oid = constraint_row.conrelid
|
|
4657
|
+
JOIN pg_catalog.pg_namespace AS namespace ON namespace.oid = relation.relnamespace
|
|
4658
|
+
WHERE namespace.nspname = ${schema} AND constraint_row.contype = 'f'
|
|
4659
|
+
`);
|
|
4660
|
+
const rows = result.rows;
|
|
4661
|
+
return keys.flatMap(({ table, fk }) => {
|
|
4662
|
+
const actual = rows.find((row) => row.table_name === table && row.name === fk.name);
|
|
4663
|
+
const deferrable = fk.deferrable !== void 0;
|
|
4664
|
+
const deferred = fk.deferrable === "initially-deferred";
|
|
4665
|
+
if (actual ? actual.deferrable === deferrable && actual.deferred === deferred : !deferrable) return [];
|
|
4666
|
+
return [foreignKeyTimingSql(schema, table, fk.name, fk.deferrable)];
|
|
4667
|
+
});
|
|
4456
4668
|
}
|
|
4457
4669
|
async function pendingStatements({
|
|
4458
4670
|
entities,
|
|
@@ -4465,11 +4677,31 @@ async function pendingStatements({
|
|
|
4465
4677
|
);
|
|
4466
4678
|
return statementsToExecute.filter((statement) => !owned.test(statement));
|
|
4467
4679
|
}
|
|
4680
|
+
function correctIntrospectionQuery(query) {
|
|
4681
|
+
if (!drizzleOrm.is(query, drizzleOrm.SQL)) return query;
|
|
4682
|
+
const compiled = new pgCore.PgDialect().sqlToQuery(query);
|
|
4683
|
+
let text4 = compiled.sql;
|
|
4684
|
+
const indexJoin = "pg_constraint con ON con.conindid = idx.indexrelid";
|
|
4685
|
+
if (text4.includes("AS generated_by_constraint") && text4.includes(indexJoin)) {
|
|
4686
|
+
text4 = text4.replace(indexJoin, `${indexJoin} AND con.contype IN ('p', 'u', 'x')`);
|
|
4687
|
+
}
|
|
4688
|
+
const sourceJoin = "LEFT JOIN pg_catalog.pg_attribute att ON att.attnum = ANY (con.conkey)";
|
|
4689
|
+
const targetJoin = "LEFT JOIN pg_catalog.pg_attribute fatt ON fatt.attnum = ANY (con.confkey)";
|
|
4690
|
+
if (text4.includes(sourceJoin) && text4.includes(targetJoin) && text4.includes("AND con.contype IN ('f')")) {
|
|
4691
|
+
text4 = text4.replace(
|
|
4692
|
+
sourceJoin,
|
|
4693
|
+
"JOIN LATERAL unnest(con.conkey, con.confkey) WITH ORDINALITY AS fk_key(source_num, target_num, ordinal) ON true LEFT JOIN pg_catalog.pg_attribute att ON att.attnum = fk_key.source_num"
|
|
4694
|
+
).replace(targetJoin, "LEFT JOIN pg_catalog.pg_attribute fatt ON fatt.attnum = fk_key.target_num").replace(/;\s*$/, "") + " ORDER BY con.conname, fk_key.ordinal";
|
|
4695
|
+
}
|
|
4696
|
+
if (text4 === compiled.sql) return query;
|
|
4697
|
+
if (compiled.params.length !== 0) throw new Error("parameterized Drizzle catalog query cannot be normalized");
|
|
4698
|
+
return drizzleOrm.sql.raw(text4);
|
|
4699
|
+
}
|
|
4468
4700
|
function shimExecute(db) {
|
|
4469
4701
|
const execute = db.execute.bind(db);
|
|
4470
4702
|
return {
|
|
4471
4703
|
execute: async (query) => {
|
|
4472
|
-
const result = await execute(query);
|
|
4704
|
+
const result = await execute(correctIntrospectionQuery(query));
|
|
4473
4705
|
if (result && typeof result === "object" && "rows" in result) return result;
|
|
4474
4706
|
return { rows: result };
|
|
4475
4707
|
}
|
|
@@ -4509,21 +4741,73 @@ async function planMigration({
|
|
|
4509
4741
|
const ddl = await diffStatements(hints.adjusted, snapshot);
|
|
4510
4742
|
const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
|
|
4511
4743
|
const statements = [
|
|
4512
|
-
...policyDrops.map((
|
|
4513
|
-
...hints.statements.map((
|
|
4514
|
-
...ddl.map((
|
|
4515
|
-
...securityStatements(definition).map((
|
|
4744
|
+
...policyDrops.map((sql5) => ({ kind: "security", sql: sql5 })),
|
|
4745
|
+
...hints.statements.map((sql5) => ({ kind: "hint", sql: sql5 })),
|
|
4746
|
+
...ddl.map((sql5) => ({ kind: "ddl", sql: sql5.replace(/;\s*$/, "") })),
|
|
4747
|
+
...securityStatements(definition).map((sql5) => ({ kind: "security", sql: sql5 })),
|
|
4516
4748
|
// PREPARE each action's executes first (plpgsql bodies don't check columns
|
|
4517
4749
|
// at CREATE), then create the functions. Both reference the tables the DDL
|
|
4518
4750
|
// above creates, so they close the plan.
|
|
4519
|
-
...actionPrepareChecks(definition).map((
|
|
4520
|
-
...actionFunctionStatements(definition).map((
|
|
4751
|
+
...actionPrepareChecks(definition).map((sql5) => ({ kind: "function", sql: sql5 })),
|
|
4752
|
+
...actionFunctionStatements(definition).map((sql5) => ({ kind: "function", sql: sql5 }))
|
|
4521
4753
|
];
|
|
4522
4754
|
return { namespace: definition.namespace, schema, statements, snapshot };
|
|
4523
4755
|
}
|
|
4524
4756
|
var compileNamespace = (definition) => planMigration({ definition });
|
|
4525
4757
|
var previousId2 = (snapshot) => snapshot.id;
|
|
4526
|
-
var
|
|
4758
|
+
var ORM_MIGRATION_EXPORT_FILE = "orm.migration.json";
|
|
4759
|
+
var ORM_MIGRATION_EXPORT_MAX_BYTES = ORM_REGISTRATION_MAX_BYTES;
|
|
4760
|
+
var hash = zod.z.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
4761
|
+
var ormMigrationPayloadSchema = zod.z.object({
|
|
4762
|
+
canonical_definition: canonicalDefinitionSchema,
|
|
4763
|
+
documents: zod.z.array(zod.z.object({ path: zod.z.string().min(1).max(2048), contents: zod.z.string().max(1e6) }).strict()).max(5e3),
|
|
4764
|
+
client_manifest: zod.z.object({ ops_hash: hash, operations: zod.z.record(zod.z.string(), hash) }).strict(),
|
|
4765
|
+
sdl: zod.z.string().refine((value) => Buffer.byteLength(value, "utf8") <= MAX_GENERATED_SDL_BYTES, {
|
|
4766
|
+
message: `sdl exceeds ${MAX_GENERATED_SDL_BYTES} bytes`
|
|
4767
|
+
}),
|
|
4768
|
+
schema_snapshot: zod.z.json(),
|
|
4769
|
+
// The existing ORM committed-migration validator and replay engine remain
|
|
4770
|
+
// authoritative. This envelope hashes the provenance, never authorizes SQL.
|
|
4771
|
+
migration: zod.z.json().optional()
|
|
4772
|
+
}).strict();
|
|
4773
|
+
var ormMigrationExportSchema = zod.z.object({
|
|
4774
|
+
schema_version: zod.z.literal(1),
|
|
4775
|
+
schema_hash: hash,
|
|
4776
|
+
payload: ormMigrationPayloadSchema,
|
|
4777
|
+
checksum: zod.z.string().regex(/^[0-9a-f]{64}$/)
|
|
4778
|
+
}).strict();
|
|
4779
|
+
function checksum(value) {
|
|
4780
|
+
return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
|
|
4781
|
+
}
|
|
4782
|
+
function createOrmMigrationExport({ schemaHash, payload }) {
|
|
4783
|
+
const contents = { schema_version: 1, schema_hash: hash.parse(schemaHash), payload: ormMigrationPayloadSchema.parse(payload) };
|
|
4784
|
+
return { ...contents, checksum: checksum(contents) };
|
|
4785
|
+
}
|
|
4786
|
+
function parseOrmMigrationExport(text4) {
|
|
4787
|
+
if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
|
|
4788
|
+
const exported = ormMigrationExportSchema.parse(JSON.parse(text4));
|
|
4789
|
+
const { checksum: expected, ...contents } = exported;
|
|
4790
|
+
if (checksum(contents) !== expected) throw new Error("ORM migration export checksum mismatch; regenerate and commit it");
|
|
4791
|
+
return exported;
|
|
4792
|
+
}
|
|
4793
|
+
function renderOrmMigrationExport(exported) {
|
|
4794
|
+
const text4 = `${JSON.stringify(exported, null, 2)}
|
|
4795
|
+
`;
|
|
4796
|
+
if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
|
|
4797
|
+
return text4;
|
|
4798
|
+
}
|
|
4799
|
+
var IR_VERSION = 2;
|
|
4800
|
+
var LEGACY_IR_VERSION = 1;
|
|
4801
|
+
var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
|
|
4802
|
+
var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
|
|
4803
|
+
function requiredArtifactIrVersion(operations) {
|
|
4804
|
+
let required = LEGACY_IR_VERSION;
|
|
4805
|
+
for (const operation of operations) {
|
|
4806
|
+
const { irVersion } = operationVersionSchema.parse(operation);
|
|
4807
|
+
if (irVersion > required) required = irVersion;
|
|
4808
|
+
}
|
|
4809
|
+
return required;
|
|
4810
|
+
}
|
|
4527
4811
|
var CLAIMS_CONTRACT = {
|
|
4528
4812
|
sub: "string",
|
|
4529
4813
|
org: "string?",
|
|
@@ -4650,6 +4934,33 @@ var aggregateRootSchema = zod.z.object({
|
|
|
4650
4934
|
nodes: selectionIrSchema.optional(),
|
|
4651
4935
|
includeIf: includeIfSchema.optional()
|
|
4652
4936
|
});
|
|
4937
|
+
var DATE_BUCKETS = ["year", "quarter", "month", "week", "day", "hour"];
|
|
4938
|
+
var groupByKeyIrSchema = zod.z.object({
|
|
4939
|
+
column: zod.z.string(),
|
|
4940
|
+
bucket: zod.z.enum(DATE_BUCKETS).optional()
|
|
4941
|
+
});
|
|
4942
|
+
var aggregateGroupsRootSchema = zod.z.object({
|
|
4943
|
+
kind: zod.z.literal("aggregateGroups"),
|
|
4944
|
+
alias: zod.z.string(),
|
|
4945
|
+
table: zod.z.string(),
|
|
4946
|
+
/** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
|
|
4947
|
+
where: predIrSchema,
|
|
4948
|
+
groupBy: zod.z.array(groupByKeyIrSchema).min(1),
|
|
4949
|
+
/**
|
|
4950
|
+
* The `key { … }` projection: which group columns appear in each entry's key
|
|
4951
|
+
* object, in selection order. A subset of `groupBy` (the compiler rejects a
|
|
4952
|
+
* key field that is not a group key — it would be indeterminate within the
|
|
4953
|
+
* group). Empty when the document selects only `aggregate`.
|
|
4954
|
+
*/
|
|
4955
|
+
key: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })).default([]),
|
|
4956
|
+
count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
|
|
4957
|
+
sum: zod.z.array(zod.z.string()).optional(),
|
|
4958
|
+
avg: zod.z.array(zod.z.string()).optional(),
|
|
4959
|
+
/** Bounds the GROUP count, not the scanned rows — see MAX_GROUPS. */
|
|
4960
|
+
limit: limitIrSchema,
|
|
4961
|
+
offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
|
|
4962
|
+
includeIf: includeIfSchema.optional()
|
|
4963
|
+
});
|
|
4653
4964
|
var writeValuesSchema = zod.z.union([
|
|
4654
4965
|
zod.z.object({ slot: zod.z.string() }),
|
|
4655
4966
|
zod.z.object({ entries: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })) })
|
|
@@ -4695,12 +5006,15 @@ var mutationRootSchema = zod.z.object({
|
|
|
4695
5006
|
insertCheck: predIrSchema.optional(),
|
|
4696
5007
|
returning: selectionIrSchema.nullable(),
|
|
4697
5008
|
affectedRows: zod.z.boolean().default(false),
|
|
5009
|
+
/** v2: abort the transaction when the actual write count differs, regardless of selection. */
|
|
5010
|
+
assertAffectedRows: zod.z.number().int().min(0).max(2147483647).optional(),
|
|
4698
5011
|
includeIf: includeIfSchema.optional()
|
|
4699
5012
|
});
|
|
4700
5013
|
var rootIrSchema = zod.z.discriminatedUnion("kind", [
|
|
4701
5014
|
selectRootSchema,
|
|
4702
5015
|
byPkRootSchema,
|
|
4703
5016
|
aggregateRootSchema,
|
|
5017
|
+
aggregateGroupsRootSchema,
|
|
4704
5018
|
mutationRootSchema
|
|
4705
5019
|
]);
|
|
4706
5020
|
var INPUT_REF_KINDS = [
|
|
@@ -4730,7 +5044,7 @@ var variableIrSchema = zod.z.object({
|
|
|
4730
5044
|
defaultValue: variableDefaultIrSchema.optional()
|
|
4731
5045
|
});
|
|
4732
5046
|
var compiledOperationSchema = zod.z.object({
|
|
4733
|
-
irVersion:
|
|
5047
|
+
irVersion: irVersionSchema,
|
|
4734
5048
|
name: zod.z.string(),
|
|
4735
5049
|
opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
4736
5050
|
kind: zod.z.enum(["query", "mutation"]),
|
|
@@ -4742,6 +5056,13 @@ var compiledOperationSchema = zod.z.object({
|
|
|
4742
5056
|
/** Every table the op touches — data-token scoping + audit. */
|
|
4743
5057
|
tableSet: zod.z.array(zod.z.string()).min(1),
|
|
4744
5058
|
roots: zod.z.array(rootIrSchema).min(1)
|
|
5059
|
+
}).superRefine((operation, ctx) => {
|
|
5060
|
+
for (const [index3, root] of operation.roots.entries()) {
|
|
5061
|
+
if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
|
|
5062
|
+
if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
|
|
5063
|
+
ctx.addIssue({ code: "custom", path: ["roots", index3], message: "affected-row assertions require unconditional mutation roots and IR v2" });
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
4745
5066
|
});
|
|
4746
5067
|
var tableMetaIrSchema = zod.z.object({
|
|
4747
5068
|
table: zod.z.string(),
|
|
@@ -4778,18 +5099,29 @@ var tableMetaIrSchema = zod.z.object({
|
|
|
4778
5099
|
insertable: zod.z.array(zod.z.string()),
|
|
4779
5100
|
settable: zod.z.array(zod.z.string()),
|
|
4780
5101
|
appendable: zod.z.array(zod.z.string()),
|
|
5102
|
+
/**
|
|
5103
|
+
* Logical columns stamped with Postgres `now()` on UPDATE (canonical
|
|
5104
|
+
* `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
|
|
5105
|
+
*/
|
|
5106
|
+
touchOnUpdate: zod.z.array(zod.z.string()).optional(),
|
|
4781
5107
|
constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
|
|
4782
5108
|
/** Depth/node caps for dynamic filter trees on this table. */
|
|
4783
5109
|
dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
|
|
4784
5110
|
dynamicFilterMaxNodes: zod.z.number().int().positive().default(64)
|
|
4785
5111
|
});
|
|
4786
5112
|
var compiledArtifactSchema = zod.z.object({
|
|
4787
|
-
irVersion:
|
|
5113
|
+
irVersion: irVersionSchema,
|
|
4788
5114
|
namespace: zod.z.string(),
|
|
4789
5115
|
schemaHash: zod.z.string(),
|
|
4790
5116
|
opsHash: zod.z.string(),
|
|
4791
5117
|
tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
|
|
4792
5118
|
operations: zod.z.record(zod.z.string(), compiledOperationSchema)
|
|
5119
|
+
}).superRefine((artifact, ctx) => {
|
|
5120
|
+
for (const [name, operation] of Object.entries(artifact.operations)) {
|
|
5121
|
+
if (operation.irVersion > artifact.irVersion) {
|
|
5122
|
+
ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
4793
5125
|
});
|
|
4794
5126
|
var IrVersionError = class extends Error {
|
|
4795
5127
|
constructor(found) {
|
|
@@ -4800,7 +5132,7 @@ var IrVersionError = class extends Error {
|
|
|
4800
5132
|
function normalizeIr(raw) {
|
|
4801
5133
|
if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
|
|
4802
5134
|
const version = raw.irVersion;
|
|
4803
|
-
if (version !== IR_VERSION) throw new IrVersionError(version);
|
|
5135
|
+
if (version !== LEGACY_IR_VERSION && version !== IR_VERSION) throw new IrVersionError(version);
|
|
4804
5136
|
return compiledArtifactSchema.parse(raw);
|
|
4805
5137
|
}
|
|
4806
5138
|
function indexByHash(artifact) {
|
|
@@ -5116,12 +5448,12 @@ async function writeMigration({
|
|
|
5116
5448
|
const tip = workspace.journal.entries.at(-1);
|
|
5117
5449
|
const ordinal = (tip?.ordinal ?? 0) + 1;
|
|
5118
5450
|
const name = migrationName(ordinal, contentHash);
|
|
5119
|
-
const
|
|
5451
|
+
const sql5 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
|
|
5120
5452
|
const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
|
|
5121
5453
|
await promises.mkdir(metaDir, { recursive: true });
|
|
5122
5454
|
const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
|
|
5123
5455
|
`;
|
|
5124
|
-
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`),
|
|
5456
|
+
await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql5);
|
|
5125
5457
|
await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
|
|
5126
5458
|
const journal = {
|
|
5127
5459
|
...workspace.journal,
|
|
@@ -5132,7 +5464,7 @@ async function writeMigration({
|
|
|
5132
5464
|
name,
|
|
5133
5465
|
content_hash: contentHash,
|
|
5134
5466
|
previous_content_hash: tip?.content_hash ?? null,
|
|
5135
|
-
checksum: checksumOf(
|
|
5467
|
+
checksum: checksumOf(sql5),
|
|
5136
5468
|
snapshot_checksum: checksumOf(snapshotText),
|
|
5137
5469
|
destructive
|
|
5138
5470
|
}
|
|
@@ -5247,8 +5579,8 @@ async function checkWorkspace({
|
|
|
5247
5579
|
let previousId3;
|
|
5248
5580
|
let previousHash = null;
|
|
5249
5581
|
for (const entry of workspace.journal.entries) {
|
|
5250
|
-
const
|
|
5251
|
-
if (checksumOf(
|
|
5582
|
+
const sql5 = await readMigrationSql(dir, entry);
|
|
5583
|
+
if (checksumOf(sql5) !== entry.checksum) {
|
|
5252
5584
|
log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
|
|
5253
5585
|
return 1;
|
|
5254
5586
|
}
|
|
@@ -5288,7 +5620,7 @@ async function checkWorkspace({
|
|
|
5288
5620
|
log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
|
|
5289
5621
|
return 0;
|
|
5290
5622
|
}
|
|
5291
|
-
var firstLine = (
|
|
5623
|
+
var firstLine = (sql5) => sql5.split("\n")[0] ?? sql5;
|
|
5292
5624
|
|
|
5293
5625
|
// src/migrations/from-yaml.ts
|
|
5294
5626
|
function convertV1DefinitionToCanonical(definition) {
|
|
@@ -5394,7 +5726,7 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
|
|
|
5394
5726
|
throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
|
|
5395
5727
|
}
|
|
5396
5728
|
const name = migrationName(ordinal, newHash);
|
|
5397
|
-
const
|
|
5729
|
+
const sql5 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
|
|
5398
5730
|
const snapshotText = `${JSON.stringify(snapshot, null, 2)}
|
|
5399
5731
|
`;
|
|
5400
5732
|
const entry = {
|
|
@@ -5402,11 +5734,11 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
|
|
|
5402
5734
|
name,
|
|
5403
5735
|
content_hash: newHash,
|
|
5404
5736
|
previous_content_hash: previousHash,
|
|
5405
|
-
checksum: checksumOf(
|
|
5737
|
+
checksum: checksumOf(sql5),
|
|
5406
5738
|
snapshot_checksum: checksumOf(snapshotText),
|
|
5407
5739
|
destructive: false
|
|
5408
5740
|
};
|
|
5409
|
-
return { name, entry, sql:
|
|
5741
|
+
return { name, entry, sql: sql5, snapshotText };
|
|
5410
5742
|
}
|
|
5411
5743
|
function renderRebaseMigrationSql({
|
|
5412
5744
|
name,
|
|
@@ -5536,10 +5868,12 @@ exports.CLAIM_KEY_PATTERN = CLAIM_KEY_PATTERN;
|
|
|
5536
5868
|
exports.CMP_OPS = CMP_OPS;
|
|
5537
5869
|
exports.COLUMN_BRAND = COLUMN_BRAND;
|
|
5538
5870
|
exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
|
|
5871
|
+
exports.COLUMN_IDENTIFIER_PATTERN = COLUMN_IDENTIFIER_PATTERN;
|
|
5539
5872
|
exports.COLUMN_TYPES = COLUMN_TYPES2;
|
|
5540
5873
|
exports.COMPARE_OPS = COMPARE_OPS;
|
|
5541
5874
|
exports.CONSUMER_ROLE_SUFFIX = CONSUMER_ROLE_SUFFIX;
|
|
5542
5875
|
exports.ColumnBuilder = ColumnBuilder;
|
|
5876
|
+
exports.DATE_BUCKETS = DATE_BUCKETS;
|
|
5543
5877
|
exports.DEFINITION_FILE_EXTENSIONS = DEFINITION_FILE_EXTENSIONS;
|
|
5544
5878
|
exports.ENUM_BRAND = ENUM_BRAND;
|
|
5545
5879
|
exports.HASH_DOMAIN = HASH_DOMAIN;
|
|
@@ -5548,8 +5882,10 @@ exports.IDENTIFIER_RE = IDENTIFIER_RE;
|
|
|
5548
5882
|
exports.INPUT_REF_KINDS = INPUT_REF_KINDS;
|
|
5549
5883
|
exports.IR_VERSION = IR_VERSION;
|
|
5550
5884
|
exports.IrVersionError = IrVersionError;
|
|
5885
|
+
exports.LEGACY_IR_VERSION = LEGACY_IR_VERSION;
|
|
5551
5886
|
exports.LINK_BRAND = LINK_BRAND;
|
|
5552
5887
|
exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
|
|
5888
|
+
exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
|
|
5553
5889
|
exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
|
|
5554
5890
|
exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
|
|
5555
5891
|
exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
|
|
@@ -5557,6 +5893,9 @@ exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
|
|
|
5557
5893
|
exports.NAMESPACE_CONFIG_FILENAMES = NAMESPACE_CONFIG_FILENAMES;
|
|
5558
5894
|
exports.NAMESPACE_PATTERN = NAMESPACE_PATTERN;
|
|
5559
5895
|
exports.ORM_DATA_TOKEN_HEADER = ORM_DATA_TOKEN_HEADER;
|
|
5896
|
+
exports.ORM_MIGRATION_EXPORT_FILE = ORM_MIGRATION_EXPORT_FILE;
|
|
5897
|
+
exports.ORM_MIGRATION_EXPORT_MAX_BYTES = ORM_MIGRATION_EXPORT_MAX_BYTES;
|
|
5898
|
+
exports.ORM_REGISTRATION_MAX_BYTES = ORM_REGISTRATION_MAX_BYTES;
|
|
5560
5899
|
exports.ORM_SCHEMA_PREFIX = ORM_SCHEMA_PREFIX;
|
|
5561
5900
|
exports.ORM_SERVICE_DATA_TOKEN_HEADER = ORM_SERVICE_DATA_TOKEN_HEADER;
|
|
5562
5901
|
exports.ORM_TO_PIPELINE_TYPE = ORM_TO_PIPELINE_TYPE;
|
|
@@ -5621,6 +5960,7 @@ exports.computeDefinitionHash = computeDefinitionHash;
|
|
|
5621
5960
|
exports.computeSyncedContractHash = computeSyncedContractHash;
|
|
5622
5961
|
exports.consumerRoleName = consumerRoleName;
|
|
5623
5962
|
exports.convertV1DefinitionToCanonical = convertV1DefinitionToCanonical;
|
|
5963
|
+
exports.createOrmMigrationExport = createOrmMigrationExport;
|
|
5624
5964
|
exports.declaredCapabilityKeys = declaredCapabilityKeys;
|
|
5625
5965
|
exports.defineEnum = defineEnum;
|
|
5626
5966
|
exports.defineNamespace = defineNamespace;
|
|
@@ -5662,11 +6002,13 @@ exports.normalizeCanonical = normalizeCanonical;
|
|
|
5662
6002
|
exports.normalizeIr = normalizeIr;
|
|
5663
6003
|
exports.normalizeSnapshot = normalizeSnapshot;
|
|
5664
6004
|
exports.orderIrSchema = orderIrSchema;
|
|
6005
|
+
exports.ormMigrationExportSchema = ormMigrationExportSchema;
|
|
5665
6006
|
exports.ormSchemaName = ormSchemaName;
|
|
5666
6007
|
exports.overlayWinsUnion = overlayWinsUnion;
|
|
5667
6008
|
exports.overrideRowMutation = overrideRowMutation;
|
|
5668
6009
|
exports.parseCanonicalDefinition = parseCanonicalDefinition;
|
|
5669
6010
|
exports.parseDefinitionSources = parseDefinitionSources;
|
|
6011
|
+
exports.parseOrmMigrationExport = parseOrmMigrationExport;
|
|
5670
6012
|
exports.parsePredicate = parsePredicate;
|
|
5671
6013
|
exports.parseTableFile = parseTableFile;
|
|
5672
6014
|
exports.parsedPredicateSchema = parsedPredicateSchema;
|
|
@@ -5687,6 +6029,8 @@ exports.rebaseViolations = rebaseViolations;
|
|
|
5687
6029
|
exports.referencedCapabilities = referencedCapabilities;
|
|
5688
6030
|
exports.referencedColumns = referencedColumns;
|
|
5689
6031
|
exports.renderMigrationSql = renderMigrationSql;
|
|
6032
|
+
exports.renderOrmMigrationExport = renderOrmMigrationExport;
|
|
6033
|
+
exports.requiredArtifactIrVersion = requiredArtifactIrVersion;
|
|
5690
6034
|
exports.revertOverlayMutation = revertOverlayMutation;
|
|
5691
6035
|
exports.rewriteSyncedSourceRelationsInViewQuery = rewriteSyncedSourceRelationsInViewQuery;
|
|
5692
6036
|
exports.roleExpression = roleExpression;
|