@vertz/db 0.2.12 → 0.2.13
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 +65 -4
- package/dist/d1/index.d.ts +3 -11
- package/dist/d1/index.js +1 -1
- package/dist/index.d.ts +73 -72
- package/dist/index.js +188 -663
- package/dist/internals.d.ts +41 -12
- package/dist/internals.js +3 -1
- package/dist/shared/chunk-1x9z0p5e.js +977 -0
- package/dist/shared/chunk-8v70x3hq.js +105 -0
- package/dist/shared/{chunk-pnk6yzjv.js → chunk-ndxe1h28.js} +1 -1
- package/dist/shared/{chunk-sfmyxz6r.js → chunk-pkv8w501.js} +3 -2
- package/dist/shared/{chunk-0e1vy9qd.js → chunk-pxjcpnpx.js} +4 -85
- package/dist/sql/index.js +2 -2
- package/dist/sqlite/index.d.ts +3 -11
- package/dist/sqlite/index.js +1 -1
- package/package.json +3 -3
- package/dist/shared/chunk-agyds4jw.js +0 -302
- package/dist/shared/chunk-v2qm94qp.js +0 -23
package/dist/index.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
PostgresDialect,
|
|
3
|
-
SqliteDialect,
|
|
4
2
|
buildDelete,
|
|
5
3
|
buildInsert,
|
|
6
4
|
buildSelect,
|
|
7
5
|
buildUpdate,
|
|
8
|
-
buildWhere
|
|
9
|
-
|
|
10
|
-
defaultSqliteDialect
|
|
11
|
-
} from "./shared/chunk-0e1vy9qd.js";
|
|
6
|
+
buildWhere
|
|
7
|
+
} from "./shared/chunk-pxjcpnpx.js";
|
|
12
8
|
import {
|
|
13
9
|
CheckConstraintError,
|
|
14
10
|
ConnectionError,
|
|
@@ -18,23 +14,31 @@ import {
|
|
|
18
14
|
NotFoundError,
|
|
19
15
|
NotNullError,
|
|
20
16
|
UniqueConstraintError,
|
|
17
|
+
computeChecksum,
|
|
18
|
+
computeDiff,
|
|
19
|
+
createMigrationRunner,
|
|
21
20
|
executeQuery,
|
|
21
|
+
generateMigrationSql,
|
|
22
22
|
getAutoUpdateColumns,
|
|
23
23
|
getPrimaryKeyColumns,
|
|
24
24
|
getReadOnlyColumns,
|
|
25
25
|
getTimestampColumns,
|
|
26
26
|
mapRow,
|
|
27
27
|
mapRows,
|
|
28
|
+
parseMigrationName,
|
|
28
29
|
parsePgError,
|
|
29
|
-
resolveSelectColumns
|
|
30
|
-
|
|
30
|
+
resolveSelectColumns,
|
|
31
|
+
validateIndexes
|
|
32
|
+
} from "./shared/chunk-1x9z0p5e.js";
|
|
31
33
|
import"./shared/chunk-kb4tnn2k.js";
|
|
32
34
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
PostgresDialect,
|
|
36
|
+
SqliteDialect,
|
|
37
|
+
camelToSnake,
|
|
38
|
+
defaultPostgresDialect,
|
|
39
|
+
defaultSqliteDialect
|
|
40
|
+
} from "./shared/chunk-8v70x3hq.js";
|
|
41
|
+
import"./shared/chunk-ssga2xea.js";
|
|
38
42
|
import {
|
|
39
43
|
diagnoseError,
|
|
40
44
|
explainError,
|
|
@@ -43,10 +47,10 @@ import {
|
|
|
43
47
|
import {
|
|
44
48
|
createD1Adapter,
|
|
45
49
|
createD1Driver
|
|
46
|
-
} from "./shared/chunk-
|
|
50
|
+
} from "./shared/chunk-ndxe1h28.js";
|
|
47
51
|
import {
|
|
48
52
|
generateId
|
|
49
|
-
} from "./shared/chunk-
|
|
53
|
+
} from "./shared/chunk-pkv8w501.js";
|
|
50
54
|
// src/adapters/database-bridge-adapter.ts
|
|
51
55
|
function createDatabaseBridgeAdapter(db, tableName) {
|
|
52
56
|
const delegate = db[tableName];
|
|
@@ -98,537 +102,6 @@ function createDatabaseBridgeAdapter(db, tableName) {
|
|
|
98
102
|
}
|
|
99
103
|
};
|
|
100
104
|
}
|
|
101
|
-
// src/migration/auto-migrate.ts
|
|
102
|
-
import { isErr } from "@vertz/errors";
|
|
103
|
-
|
|
104
|
-
// src/migration/differ.ts
|
|
105
|
-
function columnSimilarity(a, b) {
|
|
106
|
-
let score = 0;
|
|
107
|
-
let total = 0;
|
|
108
|
-
total += 3;
|
|
109
|
-
if (a.type === b.type)
|
|
110
|
-
score += 3;
|
|
111
|
-
total += 1;
|
|
112
|
-
if (a.nullable === b.nullable)
|
|
113
|
-
score += 1;
|
|
114
|
-
total += 1;
|
|
115
|
-
if (a.primary === b.primary)
|
|
116
|
-
score += 1;
|
|
117
|
-
total += 1;
|
|
118
|
-
if (a.unique === b.unique)
|
|
119
|
-
score += 1;
|
|
120
|
-
return score / total;
|
|
121
|
-
}
|
|
122
|
-
function indexKey(columns) {
|
|
123
|
-
return columns.join(",");
|
|
124
|
-
}
|
|
125
|
-
function computeDiff(before, after) {
|
|
126
|
-
const changes = [];
|
|
127
|
-
for (const tableName of Object.keys(after.tables)) {
|
|
128
|
-
if (!(tableName in before.tables)) {
|
|
129
|
-
changes.push({ type: "table_added", table: tableName });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
for (const tableName of Object.keys(before.tables)) {
|
|
133
|
-
if (!(tableName in after.tables)) {
|
|
134
|
-
changes.push({ type: "table_removed", table: tableName });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
for (const tableName of Object.keys(after.tables)) {
|
|
138
|
-
if (!(tableName in before.tables))
|
|
139
|
-
continue;
|
|
140
|
-
const beforeTable = before.tables[tableName];
|
|
141
|
-
const afterTable = after.tables[tableName];
|
|
142
|
-
if (!beforeTable || !afterTable)
|
|
143
|
-
continue;
|
|
144
|
-
const removedCols = Object.keys(beforeTable.columns).filter((c) => !(c in afterTable.columns));
|
|
145
|
-
const addedCols = Object.keys(afterTable.columns).filter((c) => !(c in beforeTable.columns));
|
|
146
|
-
const renames = [];
|
|
147
|
-
const matchedRemoved = new Set;
|
|
148
|
-
const matchedAdded = new Set;
|
|
149
|
-
for (const removed of removedCols) {
|
|
150
|
-
const removedSnap = beforeTable.columns[removed];
|
|
151
|
-
if (!removedSnap)
|
|
152
|
-
continue;
|
|
153
|
-
let bestMatch = null;
|
|
154
|
-
let bestScore = 0;
|
|
155
|
-
for (const added of addedCols) {
|
|
156
|
-
if (matchedAdded.has(added))
|
|
157
|
-
continue;
|
|
158
|
-
const addedSnap = afterTable.columns[added];
|
|
159
|
-
if (!addedSnap)
|
|
160
|
-
continue;
|
|
161
|
-
const score = columnSimilarity(removedSnap, addedSnap);
|
|
162
|
-
if (score > bestScore) {
|
|
163
|
-
bestScore = score;
|
|
164
|
-
bestMatch = added;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (bestMatch && bestScore >= 0.7) {
|
|
168
|
-
renames.push({ oldCol: removed, newCol: bestMatch, confidence: bestScore });
|
|
169
|
-
matchedRemoved.add(removed);
|
|
170
|
-
matchedAdded.add(bestMatch);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
for (const rename of renames) {
|
|
174
|
-
changes.push({
|
|
175
|
-
type: "column_renamed",
|
|
176
|
-
table: tableName,
|
|
177
|
-
oldColumn: rename.oldCol,
|
|
178
|
-
newColumn: rename.newCol,
|
|
179
|
-
confidence: rename.confidence
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
for (const colName of addedCols) {
|
|
183
|
-
if (!matchedAdded.has(colName)) {
|
|
184
|
-
changes.push({ type: "column_added", table: tableName, column: colName });
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
for (const colName of removedCols) {
|
|
188
|
-
if (!matchedRemoved.has(colName)) {
|
|
189
|
-
changes.push({ type: "column_removed", table: tableName, column: colName });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
for (const colName of Object.keys(afterTable.columns)) {
|
|
193
|
-
if (!(colName in beforeTable.columns))
|
|
194
|
-
continue;
|
|
195
|
-
const beforeCol = beforeTable.columns[colName];
|
|
196
|
-
const afterCol = afterTable.columns[colName];
|
|
197
|
-
if (!beforeCol || !afterCol)
|
|
198
|
-
continue;
|
|
199
|
-
if (beforeCol.type !== afterCol.type || beforeCol.nullable !== afterCol.nullable || beforeCol.default !== afterCol.default) {
|
|
200
|
-
const change = {
|
|
201
|
-
type: "column_altered",
|
|
202
|
-
table: tableName,
|
|
203
|
-
column: colName
|
|
204
|
-
};
|
|
205
|
-
if (beforeCol.type !== afterCol.type) {
|
|
206
|
-
change.oldType = beforeCol.type;
|
|
207
|
-
change.newType = afterCol.type;
|
|
208
|
-
}
|
|
209
|
-
if (beforeCol.nullable !== afterCol.nullable) {
|
|
210
|
-
change.oldNullable = beforeCol.nullable;
|
|
211
|
-
change.newNullable = afterCol.nullable;
|
|
212
|
-
}
|
|
213
|
-
if (beforeCol.default !== afterCol.default) {
|
|
214
|
-
change.oldDefault = beforeCol.default;
|
|
215
|
-
change.newDefault = afterCol.default;
|
|
216
|
-
}
|
|
217
|
-
changes.push(change);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
const beforeIndexKeys = new Set(beforeTable.indexes.map((i) => indexKey(i.columns)));
|
|
221
|
-
const afterIndexKeys = new Set(afterTable.indexes.map((i) => indexKey(i.columns)));
|
|
222
|
-
for (const idx of afterTable.indexes) {
|
|
223
|
-
const key = indexKey(idx.columns);
|
|
224
|
-
if (!beforeIndexKeys.has(key)) {
|
|
225
|
-
changes.push({ type: "index_added", table: tableName, columns: [...idx.columns] });
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (const idx of beforeTable.indexes) {
|
|
229
|
-
const key = indexKey(idx.columns);
|
|
230
|
-
if (!afterIndexKeys.has(key)) {
|
|
231
|
-
changes.push({ type: "index_removed", table: tableName, columns: [...idx.columns] });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
for (const enumName of Object.keys(after.enums)) {
|
|
236
|
-
if (!(enumName in before.enums)) {
|
|
237
|
-
changes.push({ type: "enum_added", enumName });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
for (const enumName of Object.keys(before.enums)) {
|
|
241
|
-
if (!(enumName in after.enums)) {
|
|
242
|
-
changes.push({ type: "enum_removed", enumName });
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
for (const enumName of Object.keys(after.enums)) {
|
|
246
|
-
if (!(enumName in before.enums))
|
|
247
|
-
continue;
|
|
248
|
-
const beforeVals = before.enums[enumName];
|
|
249
|
-
const afterVals = after.enums[enumName];
|
|
250
|
-
if (!beforeVals || !afterVals)
|
|
251
|
-
continue;
|
|
252
|
-
const beforeSet = new Set(beforeVals);
|
|
253
|
-
const afterSet = new Set(afterVals);
|
|
254
|
-
const addedValues = afterVals.filter((v) => !beforeSet.has(v));
|
|
255
|
-
const removedValues = beforeVals.filter((v) => !afterSet.has(v));
|
|
256
|
-
if (addedValues.length > 0 || removedValues.length > 0) {
|
|
257
|
-
changes.push({ type: "enum_altered", enumName, addedValues, removedValues });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return { changes };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// src/migration/runner.ts
|
|
264
|
-
import {
|
|
265
|
-
createMigrationQueryError,
|
|
266
|
-
err,
|
|
267
|
-
ok
|
|
268
|
-
} from "@vertz/errors";
|
|
269
|
-
var HISTORY_TABLE = "_vertz_migrations";
|
|
270
|
-
function buildCreateHistorySql(dialect) {
|
|
271
|
-
if (dialect.name === "sqlite") {
|
|
272
|
-
return `
|
|
273
|
-
CREATE TABLE IF NOT EXISTS "${HISTORY_TABLE}" (
|
|
274
|
-
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
275
|
-
"name" TEXT NOT NULL UNIQUE,
|
|
276
|
-
"checksum" TEXT NOT NULL,
|
|
277
|
-
"applied_at" TEXT NOT NULL DEFAULT (datetime('now'))
|
|
278
|
-
);
|
|
279
|
-
`;
|
|
280
|
-
}
|
|
281
|
-
return `
|
|
282
|
-
CREATE TABLE IF NOT EXISTS "${HISTORY_TABLE}" (
|
|
283
|
-
"id" serial PRIMARY KEY,
|
|
284
|
-
"name" text NOT NULL UNIQUE,
|
|
285
|
-
"checksum" text NOT NULL,
|
|
286
|
-
"applied_at" timestamp with time zone NOT NULL DEFAULT now()
|
|
287
|
-
);
|
|
288
|
-
`;
|
|
289
|
-
}
|
|
290
|
-
async function computeChecksum(sql) {
|
|
291
|
-
return sha256Hex(sql);
|
|
292
|
-
}
|
|
293
|
-
function parseMigrationName(filename) {
|
|
294
|
-
const match = filename.match(/^(\d+)_(.+)\.sql$/);
|
|
295
|
-
if (!match?.[1] || !match[2])
|
|
296
|
-
return null;
|
|
297
|
-
return {
|
|
298
|
-
timestamp: Number.parseInt(match[1], 10),
|
|
299
|
-
name: filename
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
function createMigrationRunner(options) {
|
|
303
|
-
const dialect = options?.dialect ?? defaultPostgresDialect;
|
|
304
|
-
const createHistorySql = buildCreateHistorySql(dialect);
|
|
305
|
-
return {
|
|
306
|
-
async createHistoryTable(queryFn) {
|
|
307
|
-
try {
|
|
308
|
-
await queryFn(createHistorySql, []);
|
|
309
|
-
return ok(undefined);
|
|
310
|
-
} catch (cause) {
|
|
311
|
-
return err(createMigrationQueryError("Failed to create migration history table", {
|
|
312
|
-
sql: createHistorySql,
|
|
313
|
-
cause
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
async apply(queryFn, sql, name, options2) {
|
|
318
|
-
const checksum = await computeChecksum(sql);
|
|
319
|
-
const recordSql = `INSERT INTO "${HISTORY_TABLE}" ("name", "checksum") VALUES (${dialect.param(1)}, ${dialect.param(2)})`;
|
|
320
|
-
const statements = [sql, recordSql];
|
|
321
|
-
if (options2?.dryRun) {
|
|
322
|
-
return ok({
|
|
323
|
-
name,
|
|
324
|
-
sql,
|
|
325
|
-
checksum,
|
|
326
|
-
dryRun: true,
|
|
327
|
-
statements
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
try {
|
|
331
|
-
await queryFn(sql, []);
|
|
332
|
-
await queryFn(recordSql, [name, checksum]);
|
|
333
|
-
return ok({
|
|
334
|
-
name,
|
|
335
|
-
sql,
|
|
336
|
-
checksum,
|
|
337
|
-
dryRun: false,
|
|
338
|
-
statements
|
|
339
|
-
});
|
|
340
|
-
} catch (cause) {
|
|
341
|
-
return err(createMigrationQueryError(`Failed to apply migration: ${name}`, {
|
|
342
|
-
sql,
|
|
343
|
-
cause
|
|
344
|
-
}));
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
async getApplied(queryFn) {
|
|
348
|
-
try {
|
|
349
|
-
const result = await queryFn(`SELECT "name", "checksum", "applied_at" FROM "${HISTORY_TABLE}" ORDER BY "id" ASC`, []);
|
|
350
|
-
return ok(result.rows.map((row) => ({
|
|
351
|
-
name: row.name,
|
|
352
|
-
checksum: row.checksum,
|
|
353
|
-
appliedAt: new Date(row.applied_at)
|
|
354
|
-
})));
|
|
355
|
-
} catch (cause) {
|
|
356
|
-
return err(createMigrationQueryError("Failed to retrieve applied migrations", {
|
|
357
|
-
cause
|
|
358
|
-
}));
|
|
359
|
-
}
|
|
360
|
-
},
|
|
361
|
-
getPending(files, applied) {
|
|
362
|
-
const appliedNames = new Set(applied.map((a) => a.name));
|
|
363
|
-
return files.filter((f) => !appliedNames.has(f.name)).sort((a, b) => a.timestamp - b.timestamp);
|
|
364
|
-
},
|
|
365
|
-
async detectDrift(files, applied) {
|
|
366
|
-
const drifted = [];
|
|
367
|
-
const appliedMap = new Map(applied.map((a) => [a.name, a.checksum]));
|
|
368
|
-
for (const file of files) {
|
|
369
|
-
const appliedChecksum = appliedMap.get(file.name);
|
|
370
|
-
if (appliedChecksum && appliedChecksum !== await computeChecksum(file.sql)) {
|
|
371
|
-
drifted.push(file.name);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return drifted;
|
|
375
|
-
},
|
|
376
|
-
detectOutOfOrder(files, applied) {
|
|
377
|
-
if (applied.length === 0)
|
|
378
|
-
return [];
|
|
379
|
-
const appliedNames = new Set(applied.map((a) => a.name));
|
|
380
|
-
const lastApplied = applied[applied.length - 1];
|
|
381
|
-
if (!lastApplied)
|
|
382
|
-
return [];
|
|
383
|
-
const lastAppliedFile = files.find((f) => f.name === lastApplied.name);
|
|
384
|
-
if (!lastAppliedFile)
|
|
385
|
-
return [];
|
|
386
|
-
return files.filter((f) => !appliedNames.has(f.name) && f.timestamp < lastAppliedFile.timestamp).map((f) => f.name);
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// src/migration/sql-generator.ts
|
|
392
|
-
function escapeSqlString(value) {
|
|
393
|
-
return value.replace(/'/g, "''");
|
|
394
|
-
}
|
|
395
|
-
function isEnumType(col, enums) {
|
|
396
|
-
const typeLower = col.type.toLowerCase();
|
|
397
|
-
if (enums) {
|
|
398
|
-
for (const enumName of Object.keys(enums)) {
|
|
399
|
-
if (typeLower === enumName.toLowerCase() || typeLower === enumName) {
|
|
400
|
-
return true;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
return false;
|
|
405
|
-
}
|
|
406
|
-
function getEnumValues(col, enums) {
|
|
407
|
-
if (!enums)
|
|
408
|
-
return;
|
|
409
|
-
for (const [enumName, values] of Object.entries(enums)) {
|
|
410
|
-
if (col.type.toLowerCase() === enumName.toLowerCase() || col.type === enumName) {
|
|
411
|
-
return values;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
function columnDef(name, col, dialect, enums) {
|
|
417
|
-
const snakeName = camelToSnake(name);
|
|
418
|
-
const isEnum = isEnumType(col, enums);
|
|
419
|
-
let sqlType;
|
|
420
|
-
let checkConstraint;
|
|
421
|
-
if (isEnum && dialect.name === "sqlite") {
|
|
422
|
-
sqlType = dialect.mapColumnType("text");
|
|
423
|
-
const enumValues = getEnumValues(col, enums);
|
|
424
|
-
if (enumValues && enumValues.length > 0) {
|
|
425
|
-
const escapedValues = enumValues.map((v) => `'${escapeSqlString(v)}'`).join(", ");
|
|
426
|
-
checkConstraint = `CHECK("${snakeName}" IN (${escapedValues}))`;
|
|
427
|
-
}
|
|
428
|
-
} else {
|
|
429
|
-
if (dialect.name === "postgres" && col.type === col.type.toLowerCase()) {
|
|
430
|
-
sqlType = col.type;
|
|
431
|
-
} else {
|
|
432
|
-
const normalizedType = normalizeColumnType(col.type);
|
|
433
|
-
sqlType = dialect.mapColumnType(normalizedType);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
const parts = [`"${snakeName}" ${sqlType}`];
|
|
437
|
-
if (checkConstraint) {
|
|
438
|
-
parts.push(checkConstraint);
|
|
439
|
-
}
|
|
440
|
-
if (!col.nullable) {
|
|
441
|
-
parts.push("NOT NULL");
|
|
442
|
-
}
|
|
443
|
-
if (col.unique) {
|
|
444
|
-
parts.push("UNIQUE");
|
|
445
|
-
}
|
|
446
|
-
if (col.default !== undefined) {
|
|
447
|
-
parts.push(`DEFAULT ${col.default}`);
|
|
448
|
-
}
|
|
449
|
-
return parts.join(" ");
|
|
450
|
-
}
|
|
451
|
-
function normalizeColumnType(type) {
|
|
452
|
-
const typeUpper = type.toUpperCase();
|
|
453
|
-
const typeMap = {
|
|
454
|
-
UUID: "uuid",
|
|
455
|
-
TEXT: "text",
|
|
456
|
-
INTEGER: "integer",
|
|
457
|
-
SERIAL: "serial",
|
|
458
|
-
BOOLEAN: "boolean",
|
|
459
|
-
TIMESTAMPTZ: "timestamp",
|
|
460
|
-
TIMESTAMP: "timestamp",
|
|
461
|
-
"DOUBLE PRECISION": "float",
|
|
462
|
-
JSONB: "json",
|
|
463
|
-
JSON: "json",
|
|
464
|
-
NUMERIC: "decimal",
|
|
465
|
-
REAL: "float",
|
|
466
|
-
VARCHAR: "varchar"
|
|
467
|
-
};
|
|
468
|
-
return typeMap[typeUpper] || type.toLowerCase();
|
|
469
|
-
}
|
|
470
|
-
function generateMigrationSql(changes, ctx, dialect = defaultPostgresDialect) {
|
|
471
|
-
const statements = [];
|
|
472
|
-
const tables = ctx?.tables;
|
|
473
|
-
const enums = ctx?.enums;
|
|
474
|
-
for (const change of changes) {
|
|
475
|
-
switch (change.type) {
|
|
476
|
-
case "enum_added": {
|
|
477
|
-
if (!change.enumName)
|
|
478
|
-
break;
|
|
479
|
-
if (dialect.name === "postgres") {
|
|
480
|
-
const values = enums?.[change.enumName];
|
|
481
|
-
if (!values || values.length === 0)
|
|
482
|
-
break;
|
|
483
|
-
const enumSnakeName = camelToSnake(change.enumName);
|
|
484
|
-
const valuesStr = values.map((v) => `'${escapeSqlString(v)}'`).join(", ");
|
|
485
|
-
statements.push(`CREATE TYPE "${enumSnakeName}" AS ENUM (${valuesStr});`);
|
|
486
|
-
}
|
|
487
|
-
break;
|
|
488
|
-
}
|
|
489
|
-
case "table_added": {
|
|
490
|
-
if (!change.table)
|
|
491
|
-
break;
|
|
492
|
-
const table = tables?.[change.table];
|
|
493
|
-
if (!table)
|
|
494
|
-
break;
|
|
495
|
-
const tableName = camelToSnake(change.table);
|
|
496
|
-
const cols = [];
|
|
497
|
-
const primaryKeys = [];
|
|
498
|
-
if (dialect.name === "postgres" && enums) {
|
|
499
|
-
for (const [, col] of Object.entries(table.columns)) {
|
|
500
|
-
if (isEnumType(col, enums)) {
|
|
501
|
-
const enumValues = getEnumValues(col, enums);
|
|
502
|
-
if (enumValues && enumValues.length > 0) {
|
|
503
|
-
const enumSnakeName = camelToSnake(col.type);
|
|
504
|
-
const alreadyEmitted = statements.some((s) => s.includes(`CREATE TYPE "${enumSnakeName}"`));
|
|
505
|
-
if (!alreadyEmitted) {
|
|
506
|
-
const valuesStr = enumValues.map((v) => `'${escapeSqlString(v)}'`).join(", ");
|
|
507
|
-
statements.push(`CREATE TYPE "${enumSnakeName}" AS ENUM (${valuesStr});`);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
for (const [colName, col] of Object.entries(table.columns)) {
|
|
514
|
-
cols.push(` ${columnDef(colName, col, dialect, enums)}`);
|
|
515
|
-
if (col.primary) {
|
|
516
|
-
primaryKeys.push(`"${camelToSnake(colName)}"`);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
if (primaryKeys.length > 0) {
|
|
520
|
-
cols.push(` PRIMARY KEY (${primaryKeys.join(", ")})`);
|
|
521
|
-
}
|
|
522
|
-
for (const fk of table.foreignKeys) {
|
|
523
|
-
const fkCol = camelToSnake(fk.column);
|
|
524
|
-
const fkTarget = camelToSnake(fk.targetTable);
|
|
525
|
-
const fkTargetCol = camelToSnake(fk.targetColumn);
|
|
526
|
-
cols.push(` FOREIGN KEY ("${fkCol}") REFERENCES "${fkTarget}" ("${fkTargetCol}")`);
|
|
527
|
-
}
|
|
528
|
-
statements.push(`CREATE TABLE "${tableName}" (
|
|
529
|
-
${cols.join(`,
|
|
530
|
-
`)}
|
|
531
|
-
);`);
|
|
532
|
-
for (const idx of table.indexes) {
|
|
533
|
-
const idxCols = idx.columns.map((c) => `"${camelToSnake(c)}"`).join(", ");
|
|
534
|
-
const idxName = `idx_${tableName}_${idx.columns.map((c) => camelToSnake(c)).join("_")}`;
|
|
535
|
-
statements.push(`CREATE INDEX "${idxName}" ON "${tableName}" (${idxCols});`);
|
|
536
|
-
}
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
case "table_removed": {
|
|
540
|
-
if (!change.table)
|
|
541
|
-
break;
|
|
542
|
-
statements.push(`DROP TABLE "${camelToSnake(change.table)}";`);
|
|
543
|
-
break;
|
|
544
|
-
}
|
|
545
|
-
case "column_added": {
|
|
546
|
-
if (!change.table || !change.column)
|
|
547
|
-
break;
|
|
548
|
-
const col = tables?.[change.table]?.columns[change.column];
|
|
549
|
-
if (!col)
|
|
550
|
-
break;
|
|
551
|
-
statements.push(`ALTER TABLE "${camelToSnake(change.table)}" ADD COLUMN ${columnDef(change.column, col, dialect, enums)};`);
|
|
552
|
-
break;
|
|
553
|
-
}
|
|
554
|
-
case "column_removed": {
|
|
555
|
-
if (!change.table || !change.column)
|
|
556
|
-
break;
|
|
557
|
-
statements.push(`ALTER TABLE "${camelToSnake(change.table)}" DROP COLUMN "${camelToSnake(change.column)}";`);
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
case "column_altered": {
|
|
561
|
-
if (!change.table || !change.column)
|
|
562
|
-
break;
|
|
563
|
-
const snakeTable = camelToSnake(change.table);
|
|
564
|
-
const snakeCol = camelToSnake(change.column);
|
|
565
|
-
if (change.newType !== undefined) {
|
|
566
|
-
statements.push(`ALTER TABLE "${snakeTable}" ALTER COLUMN "${snakeCol}" TYPE ${change.newType};`);
|
|
567
|
-
}
|
|
568
|
-
if (change.newNullable !== undefined) {
|
|
569
|
-
if (change.newNullable) {
|
|
570
|
-
statements.push(`ALTER TABLE "${snakeTable}" ALTER COLUMN "${snakeCol}" DROP NOT NULL;`);
|
|
571
|
-
} else {
|
|
572
|
-
statements.push(`ALTER TABLE "${snakeTable}" ALTER COLUMN "${snakeCol}" SET NOT NULL;`);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
if (change.newDefault !== undefined) {
|
|
576
|
-
if (change.newDefault) {
|
|
577
|
-
statements.push(`ALTER TABLE "${snakeTable}" ALTER COLUMN "${snakeCol}" SET DEFAULT ${change.newDefault};`);
|
|
578
|
-
} else {
|
|
579
|
-
statements.push(`ALTER TABLE "${snakeTable}" ALTER COLUMN "${snakeCol}" DROP DEFAULT;`);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
break;
|
|
583
|
-
}
|
|
584
|
-
case "column_renamed": {
|
|
585
|
-
if (!change.table || !change.oldColumn || !change.newColumn)
|
|
586
|
-
break;
|
|
587
|
-
statements.push(`ALTER TABLE "${camelToSnake(change.table)}" RENAME COLUMN "${camelToSnake(change.oldColumn)}" TO "${camelToSnake(change.newColumn)}";`);
|
|
588
|
-
break;
|
|
589
|
-
}
|
|
590
|
-
case "index_added": {
|
|
591
|
-
if (!change.table || !change.columns)
|
|
592
|
-
break;
|
|
593
|
-
const snakeTable = camelToSnake(change.table);
|
|
594
|
-
const idxCols = change.columns.map((c) => `"${camelToSnake(c)}"`).join(", ");
|
|
595
|
-
const idxName = `idx_${snakeTable}_${change.columns.map((c) => camelToSnake(c)).join("_")}`;
|
|
596
|
-
statements.push(`CREATE INDEX "${idxName}" ON "${snakeTable}" (${idxCols});`);
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
case "index_removed": {
|
|
600
|
-
if (!change.table || !change.columns)
|
|
601
|
-
break;
|
|
602
|
-
const snakeTable = camelToSnake(change.table);
|
|
603
|
-
const idxName = `idx_${snakeTable}_${change.columns.map((c) => camelToSnake(c)).join("_")}`;
|
|
604
|
-
statements.push(`DROP INDEX "${idxName}";`);
|
|
605
|
-
break;
|
|
606
|
-
}
|
|
607
|
-
case "enum_removed": {
|
|
608
|
-
if (!change.enumName)
|
|
609
|
-
break;
|
|
610
|
-
if (dialect.name === "postgres") {
|
|
611
|
-
statements.push(`DROP TYPE "${camelToSnake(change.enumName)}";`);
|
|
612
|
-
}
|
|
613
|
-
break;
|
|
614
|
-
}
|
|
615
|
-
case "enum_altered": {
|
|
616
|
-
if (!change.enumName || !change.addedValues)
|
|
617
|
-
break;
|
|
618
|
-
if (dialect.name === "postgres") {
|
|
619
|
-
const enumSnakeName = camelToSnake(change.enumName);
|
|
620
|
-
for (const val of change.addedValues) {
|
|
621
|
-
statements.push(`ALTER TYPE "${enumSnakeName}" ADD VALUE '${escapeSqlString(val)}';`);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
break;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
return statements.join(`
|
|
629
|
-
|
|
630
|
-
`);
|
|
631
|
-
}
|
|
632
105
|
// src/migration/files.ts
|
|
633
106
|
function formatMigrationFilename(num, description) {
|
|
634
107
|
return `${String(num).padStart(4, "0")}_${description}.sql`;
|
|
@@ -690,6 +163,13 @@ async function introspectSqlite(queryFn) {
|
|
|
690
163
|
columns[colName] = colSnap;
|
|
691
164
|
}
|
|
692
165
|
const indexes = [];
|
|
166
|
+
const { rows: indexSqlRows } = await queryFn(`SELECT name, sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ?`, [tableName]);
|
|
167
|
+
const indexSqlMap = new Map;
|
|
168
|
+
for (const row2 of indexSqlRows) {
|
|
169
|
+
if (row2.name && row2.sql) {
|
|
170
|
+
indexSqlMap.set(row2.name, row2.sql);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
693
173
|
const { rows: indexRows } = await queryFn(`PRAGMA index_list("${validateIdentifier(tableName)}")`, []);
|
|
694
174
|
for (const idx of indexRows) {
|
|
695
175
|
const idxName = idx.name;
|
|
@@ -704,11 +184,19 @@ async function introspectSqlite(queryFn) {
|
|
|
704
184
|
}
|
|
705
185
|
}
|
|
706
186
|
if (origin === "c") {
|
|
707
|
-
|
|
187
|
+
const snap = {
|
|
708
188
|
columns: idxColumns,
|
|
709
189
|
name: idxName,
|
|
710
190
|
unique: isUnique
|
|
711
|
-
}
|
|
191
|
+
};
|
|
192
|
+
const createSql = indexSqlMap.get(idxName);
|
|
193
|
+
if (createSql) {
|
|
194
|
+
const whereMatch = createSql.match(/\bWHERE\s+(.+)$/i);
|
|
195
|
+
if (whereMatch?.[1]) {
|
|
196
|
+
snap.where = whereMatch[1];
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
indexes.push(snap);
|
|
712
200
|
}
|
|
713
201
|
}
|
|
714
202
|
const foreignKeys = [];
|
|
@@ -819,24 +307,36 @@ async function introspectPostgres(queryFn) {
|
|
|
819
307
|
const indexes = [];
|
|
820
308
|
const { rows: idxRows } = await queryFn(`SELECT i.relname AS index_name,
|
|
821
309
|
array_agg(a.attname ORDER BY k.n) AS columns,
|
|
822
|
-
ix.indisunique AS is_unique
|
|
310
|
+
ix.indisunique AS is_unique,
|
|
311
|
+
am.amname AS access_method,
|
|
312
|
+
pg_get_expr(ix.indpred, ix.indrelid) AS predicate
|
|
823
313
|
FROM pg_index ix
|
|
824
314
|
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
825
315
|
JOIN pg_class t ON t.oid = ix.indrelid
|
|
826
316
|
JOIN pg_namespace ns ON ns.oid = t.relnamespace
|
|
317
|
+
JOIN pg_am am ON am.oid = i.relam
|
|
827
318
|
JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n) ON true
|
|
828
319
|
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum
|
|
829
320
|
WHERE t.relname = $1
|
|
830
321
|
AND ns.nspname = 'public'
|
|
831
322
|
AND NOT ix.indisprimary
|
|
832
323
|
AND NOT ix.indisunique
|
|
833
|
-
GROUP BY i.relname, ix.indisunique`, [tableName]);
|
|
324
|
+
GROUP BY i.relname, ix.indisunique, am.amname, ix.indpred, ix.indrelid`, [tableName]);
|
|
834
325
|
for (const idx of idxRows) {
|
|
835
|
-
|
|
326
|
+
const snap = {
|
|
836
327
|
columns: idx.columns,
|
|
837
328
|
name: idx.index_name,
|
|
838
329
|
unique: idx.is_unique
|
|
839
|
-
}
|
|
330
|
+
};
|
|
331
|
+
const accessMethod = idx.access_method;
|
|
332
|
+
if (accessMethod && accessMethod !== "btree") {
|
|
333
|
+
snap.type = accessMethod;
|
|
334
|
+
}
|
|
335
|
+
const predicate = idx.predicate;
|
|
336
|
+
if (predicate) {
|
|
337
|
+
snap.where = predicate;
|
|
338
|
+
}
|
|
339
|
+
indexes.push(snap);
|
|
840
340
|
}
|
|
841
341
|
snapshot.tables[tableName] = {
|
|
842
342
|
columns,
|
|
@@ -917,15 +417,57 @@ function detectCollisions(journal, existingFiles) {
|
|
|
917
417
|
return collisions;
|
|
918
418
|
}
|
|
919
419
|
// src/migration/snapshot.ts
|
|
920
|
-
function
|
|
420
|
+
function isModelDef(v) {
|
|
421
|
+
return v !== null && typeof v === "object" && "table" in v && "relations" in v && typeof v.table === "object";
|
|
422
|
+
}
|
|
423
|
+
function resolveTable(entry) {
|
|
424
|
+
return isModelDef(entry) ? entry.table : entry;
|
|
425
|
+
}
|
|
426
|
+
function resolveRelations(entry) {
|
|
427
|
+
return isModelDef(entry) ? entry.relations : {};
|
|
428
|
+
}
|
|
429
|
+
function findPkColumn(table) {
|
|
430
|
+
for (const [colName, col] of Object.entries(table._columns)) {
|
|
431
|
+
if (col._meta.primary)
|
|
432
|
+
return colName;
|
|
433
|
+
}
|
|
434
|
+
throw new Error(`Table "${table._name}" has no primary key column`);
|
|
435
|
+
}
|
|
436
|
+
function deriveForeignKeys(table, relations) {
|
|
437
|
+
const foreignKeys = [];
|
|
438
|
+
for (const [relName, rel] of Object.entries(relations)) {
|
|
439
|
+
if (rel._type !== "one")
|
|
440
|
+
continue;
|
|
441
|
+
if (!rel._foreignKey)
|
|
442
|
+
continue;
|
|
443
|
+
if (!(rel._foreignKey in table._columns)) {
|
|
444
|
+
throw new Error(`Relation "${relName}" on table "${table._name}" references column "${rel._foreignKey}" which does not exist`);
|
|
445
|
+
}
|
|
446
|
+
const targetTable = rel._target();
|
|
447
|
+
let targetColumn;
|
|
448
|
+
try {
|
|
449
|
+
targetColumn = findPkColumn(targetTable);
|
|
450
|
+
} catch {
|
|
451
|
+
throw new Error(`Target table "${targetTable._name}" referenced by relation "${relName}" on table "${table._name}" has no primary key column`);
|
|
452
|
+
}
|
|
453
|
+
foreignKeys.push({
|
|
454
|
+
column: rel._foreignKey,
|
|
455
|
+
targetTable: targetTable._name,
|
|
456
|
+
targetColumn
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return foreignKeys;
|
|
460
|
+
}
|
|
461
|
+
function createSnapshot(entries) {
|
|
921
462
|
const snapshot = {
|
|
922
463
|
version: 1,
|
|
923
464
|
tables: {},
|
|
924
465
|
enums: {}
|
|
925
466
|
};
|
|
926
|
-
for (const
|
|
467
|
+
for (const entry of entries) {
|
|
468
|
+
const table = resolveTable(entry);
|
|
469
|
+
const relations = resolveRelations(entry);
|
|
927
470
|
const columns = {};
|
|
928
|
-
const foreignKeys = [];
|
|
929
471
|
const indexes = [];
|
|
930
472
|
for (const [colName, col] of Object.entries(table._columns)) {
|
|
931
473
|
const meta = col._meta;
|
|
@@ -944,20 +486,23 @@ function createSnapshot(tables) {
|
|
|
944
486
|
colSnap.annotations = annotationNames;
|
|
945
487
|
}
|
|
946
488
|
columns[colName] = colSnap;
|
|
947
|
-
if (meta.references) {
|
|
948
|
-
foreignKeys.push({
|
|
949
|
-
column: colName,
|
|
950
|
-
targetTable: meta.references.table,
|
|
951
|
-
targetColumn: meta.references.column
|
|
952
|
-
});
|
|
953
|
-
}
|
|
954
489
|
if (meta.enumName && meta.enumValues) {
|
|
955
490
|
snapshot.enums[meta.enumName] = [...meta.enumValues];
|
|
956
491
|
}
|
|
957
492
|
}
|
|
958
493
|
for (const idx of table._indexes) {
|
|
959
|
-
|
|
960
|
-
|
|
494
|
+
const snap = { columns: [...idx.columns] };
|
|
495
|
+
if (idx.name)
|
|
496
|
+
snap.name = idx.name;
|
|
497
|
+
if (idx.unique)
|
|
498
|
+
snap.unique = idx.unique;
|
|
499
|
+
if (idx.type)
|
|
500
|
+
snap.type = idx.type;
|
|
501
|
+
if (idx.where)
|
|
502
|
+
snap.where = idx.where;
|
|
503
|
+
indexes.push(snap);
|
|
504
|
+
}
|
|
505
|
+
const foreignKeys = deriveForeignKeys(table, relations);
|
|
961
506
|
snapshot.tables[table._name] = {
|
|
962
507
|
columns,
|
|
963
508
|
indexes,
|
|
@@ -1169,8 +714,8 @@ async function push(options) {
|
|
|
1169
714
|
return { sql, tablesAffected };
|
|
1170
715
|
}
|
|
1171
716
|
// src/cli/reset.ts
|
|
1172
|
-
import { createMigrationQueryError
|
|
1173
|
-
var
|
|
717
|
+
import { createMigrationQueryError, err } from "@vertz/errors";
|
|
718
|
+
var HISTORY_TABLE = "_vertz_migrations";
|
|
1174
719
|
function getUserTablesQuery(dialect) {
|
|
1175
720
|
if (dialect.name === "sqlite") {
|
|
1176
721
|
return `SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;
|
|
@@ -1191,23 +736,23 @@ async function reset(options) {
|
|
|
1191
736
|
const tablesResult = await options.queryFn(getUserTablesQuery(dialect), []);
|
|
1192
737
|
tableNames = tablesResult.rows.map((row) => row.name);
|
|
1193
738
|
} catch (cause) {
|
|
1194
|
-
return
|
|
739
|
+
return err(createMigrationQueryError("Failed to list user tables", { cause }));
|
|
1195
740
|
}
|
|
1196
741
|
const tablesDropped = [];
|
|
1197
742
|
for (const tableName of tableNames) {
|
|
1198
743
|
try {
|
|
1199
744
|
await options.queryFn(buildDropTableSql(tableName, dialect), []);
|
|
1200
|
-
if (tableName !==
|
|
745
|
+
if (tableName !== HISTORY_TABLE) {
|
|
1201
746
|
tablesDropped.push(tableName);
|
|
1202
747
|
}
|
|
1203
748
|
} catch (cause) {
|
|
1204
|
-
return
|
|
749
|
+
return err(createMigrationQueryError(`Failed to drop table: ${tableName}`, { cause }));
|
|
1205
750
|
}
|
|
1206
751
|
}
|
|
1207
752
|
try {
|
|
1208
|
-
await options.queryFn(buildDropTableSql(
|
|
753
|
+
await options.queryFn(buildDropTableSql(HISTORY_TABLE, dialect), []);
|
|
1209
754
|
} catch (cause) {
|
|
1210
|
-
return
|
|
755
|
+
return err(createMigrationQueryError("Failed to drop history table", { cause }));
|
|
1211
756
|
}
|
|
1212
757
|
const createResult = await runner.createHistoryTable(options.queryFn);
|
|
1213
758
|
if (!createResult.ok) {
|
|
@@ -1405,7 +950,7 @@ async function migrateStatus(options) {
|
|
|
1405
950
|
};
|
|
1406
951
|
}
|
|
1407
952
|
// src/client/database.ts
|
|
1408
|
-
import { err as
|
|
953
|
+
import { err as err2, ok } from "@vertz/schema";
|
|
1409
954
|
// src/errors/error-codes.ts
|
|
1410
955
|
var DbErrorCode = {
|
|
1411
956
|
UNIQUE_VIOLATION: "23505",
|
|
@@ -2404,18 +1949,21 @@ function computeTenantGraph(registry) {
|
|
|
2404
1949
|
shared.push(key);
|
|
2405
1950
|
continue;
|
|
2406
1951
|
}
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
1952
|
+
if (entry._tenant) {
|
|
1953
|
+
const tenantRel = entry.relations[entry._tenant];
|
|
1954
|
+
if (!tenantRel) {
|
|
1955
|
+
throw new Error(`Model "${key}": tenant relation "${entry._tenant}" not found in relations`);
|
|
1956
|
+
}
|
|
1957
|
+
if (!directlyScoped.includes(key)) {
|
|
1958
|
+
directlyScoped.push(key);
|
|
1959
|
+
}
|
|
1960
|
+
const rootTableName = tenantRel._target()._name;
|
|
1961
|
+
const rootKey = tableNameToKey.get(rootTableName);
|
|
1962
|
+
if (rootKey) {
|
|
1963
|
+
if (root !== null && root !== rootKey) {
|
|
1964
|
+
throw new Error(`Conflicting tenant roots: "${root}" and "${rootKey}". All tenant declarations must point to the same root table.`);
|
|
2418
1965
|
}
|
|
1966
|
+
root = rootKey;
|
|
2419
1967
|
}
|
|
2420
1968
|
}
|
|
2421
1969
|
}
|
|
@@ -2441,18 +1989,14 @@ function computeTenantGraph(registry) {
|
|
|
2441
1989
|
if (key === root || directlyScoped.includes(key) || shared.includes(key) || indirectlyScopedNames.has(entry.table._name)) {
|
|
2442
1990
|
continue;
|
|
2443
1991
|
}
|
|
2444
|
-
const
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
scopedTableNames.add(entry.table._name);
|
|
2453
|
-
changed = true;
|
|
2454
|
-
break;
|
|
2455
|
-
}
|
|
1992
|
+
for (const rel of Object.values(entry.relations)) {
|
|
1993
|
+
const targetTableName = rel._target()._name;
|
|
1994
|
+
if (scopedTableNames.has(targetTableName) || indirectlyScopedNames.has(targetTableName)) {
|
|
1995
|
+
indirectlyScoped.push(key);
|
|
1996
|
+
indirectlyScopedNames.add(entry.table._name);
|
|
1997
|
+
scopedTableNames.add(entry.table._name);
|
|
1998
|
+
changed = true;
|
|
1999
|
+
break;
|
|
2456
2000
|
}
|
|
2457
2001
|
}
|
|
2458
2002
|
}
|
|
@@ -2591,8 +2135,8 @@ function createDb(options) {
|
|
|
2591
2135
|
replicaIndex = (replicaIndex + 1) % replicaDrivers.length;
|
|
2592
2136
|
try {
|
|
2593
2137
|
return await targetReplica.queryFn(sqlStr, params);
|
|
2594
|
-
} catch (
|
|
2595
|
-
console.warn("[vertz/db] replica query failed, falling back to primary:",
|
|
2138
|
+
} catch (err3) {
|
|
2139
|
+
console.warn("[vertz/db] replica query failed, falling back to primary:", err3.message);
|
|
2596
2140
|
}
|
|
2597
2141
|
}
|
|
2598
2142
|
if (!driver) {
|
|
@@ -2612,11 +2156,11 @@ function createDb(options) {
|
|
|
2612
2156
|
const result = await get(queryFn, entry.table, opts, dialectObj);
|
|
2613
2157
|
if (result !== null && opts?.include) {
|
|
2614
2158
|
const rows = await loadRelations(queryFn, [result], entry.relations, opts.include, 0, modelsRegistry, entry.table);
|
|
2615
|
-
return
|
|
2159
|
+
return ok(rows[0] ?? null);
|
|
2616
2160
|
}
|
|
2617
|
-
return
|
|
2161
|
+
return ok(result);
|
|
2618
2162
|
} catch (e) {
|
|
2619
|
-
return
|
|
2163
|
+
return err2(toReadError(e));
|
|
2620
2164
|
}
|
|
2621
2165
|
})();
|
|
2622
2166
|
}
|
|
@@ -2626,7 +2170,7 @@ function createDb(options) {
|
|
|
2626
2170
|
const entry = resolveModel(models, name);
|
|
2627
2171
|
const result = await get(queryFn, entry.table, opts, dialectObj);
|
|
2628
2172
|
if (result === null) {
|
|
2629
|
-
return
|
|
2173
|
+
return err2({
|
|
2630
2174
|
code: "NotFound",
|
|
2631
2175
|
message: `Record not found in table ${name}`,
|
|
2632
2176
|
table: name
|
|
@@ -2634,11 +2178,11 @@ function createDb(options) {
|
|
|
2634
2178
|
}
|
|
2635
2179
|
if (opts?.include) {
|
|
2636
2180
|
const rows = await loadRelations(queryFn, [result], entry.relations, opts.include, 0, modelsRegistry, entry.table);
|
|
2637
|
-
return
|
|
2181
|
+
return ok(rows[0]);
|
|
2638
2182
|
}
|
|
2639
|
-
return
|
|
2183
|
+
return ok(result);
|
|
2640
2184
|
} catch (e) {
|
|
2641
|
-
return
|
|
2185
|
+
return err2(toReadError(e));
|
|
2642
2186
|
}
|
|
2643
2187
|
})();
|
|
2644
2188
|
}
|
|
@@ -2649,11 +2193,11 @@ function createDb(options) {
|
|
|
2649
2193
|
const results = await list(queryFn, entry.table, opts, dialectObj);
|
|
2650
2194
|
if (opts?.include && results.length > 0) {
|
|
2651
2195
|
const withRelations = await loadRelations(queryFn, results, entry.relations, opts.include, 0, modelsRegistry, entry.table);
|
|
2652
|
-
return
|
|
2196
|
+
return ok(withRelations);
|
|
2653
2197
|
}
|
|
2654
|
-
return
|
|
2198
|
+
return ok(results);
|
|
2655
2199
|
} catch (e) {
|
|
2656
|
-
return
|
|
2200
|
+
return err2(toReadError(e));
|
|
2657
2201
|
}
|
|
2658
2202
|
})();
|
|
2659
2203
|
}
|
|
@@ -2664,11 +2208,11 @@ function createDb(options) {
|
|
|
2664
2208
|
const { data, total } = await listAndCount(queryFn, entry.table, opts, dialectObj);
|
|
2665
2209
|
if (opts?.include && data.length > 0) {
|
|
2666
2210
|
const withRelations = await loadRelations(queryFn, data, entry.relations, opts.include, 0, modelsRegistry, entry.table);
|
|
2667
|
-
return
|
|
2211
|
+
return ok({ data: withRelations, total });
|
|
2668
2212
|
}
|
|
2669
|
-
return
|
|
2213
|
+
return ok({ data, total });
|
|
2670
2214
|
} catch (e) {
|
|
2671
|
-
return
|
|
2215
|
+
return err2(toReadError(e));
|
|
2672
2216
|
}
|
|
2673
2217
|
})();
|
|
2674
2218
|
}
|
|
@@ -2677,9 +2221,9 @@ function createDb(options) {
|
|
|
2677
2221
|
try {
|
|
2678
2222
|
const entry = resolveModel(models, name);
|
|
2679
2223
|
const result = await create(queryFn, entry.table, opts, dialectObj);
|
|
2680
|
-
return
|
|
2224
|
+
return ok(result);
|
|
2681
2225
|
} catch (e) {
|
|
2682
|
-
return
|
|
2226
|
+
return err2(toWriteError(e));
|
|
2683
2227
|
}
|
|
2684
2228
|
})();
|
|
2685
2229
|
}
|
|
@@ -2688,9 +2232,9 @@ function createDb(options) {
|
|
|
2688
2232
|
try {
|
|
2689
2233
|
const entry = resolveModel(models, name);
|
|
2690
2234
|
const result = await createMany(queryFn, entry.table, opts, dialectObj);
|
|
2691
|
-
return
|
|
2235
|
+
return ok(result);
|
|
2692
2236
|
} catch (e) {
|
|
2693
|
-
return
|
|
2237
|
+
return err2(toWriteError(e));
|
|
2694
2238
|
}
|
|
2695
2239
|
})();
|
|
2696
2240
|
}
|
|
@@ -2699,9 +2243,9 @@ function createDb(options) {
|
|
|
2699
2243
|
try {
|
|
2700
2244
|
const entry = resolveModel(models, name);
|
|
2701
2245
|
const result = await createManyAndReturn(queryFn, entry.table, opts, dialectObj);
|
|
2702
|
-
return
|
|
2246
|
+
return ok(result);
|
|
2703
2247
|
} catch (e) {
|
|
2704
|
-
return
|
|
2248
|
+
return err2(toWriteError(e));
|
|
2705
2249
|
}
|
|
2706
2250
|
})();
|
|
2707
2251
|
}
|
|
@@ -2710,9 +2254,9 @@ function createDb(options) {
|
|
|
2710
2254
|
try {
|
|
2711
2255
|
const entry = resolveModel(models, name);
|
|
2712
2256
|
const result = await update(queryFn, entry.table, opts, dialectObj);
|
|
2713
|
-
return
|
|
2257
|
+
return ok(result);
|
|
2714
2258
|
} catch (e) {
|
|
2715
|
-
return
|
|
2259
|
+
return err2(toWriteError(e));
|
|
2716
2260
|
}
|
|
2717
2261
|
})();
|
|
2718
2262
|
}
|
|
@@ -2721,9 +2265,9 @@ function createDb(options) {
|
|
|
2721
2265
|
try {
|
|
2722
2266
|
const entry = resolveModel(models, name);
|
|
2723
2267
|
const result = await updateMany(queryFn, entry.table, opts, dialectObj);
|
|
2724
|
-
return
|
|
2268
|
+
return ok(result);
|
|
2725
2269
|
} catch (e) {
|
|
2726
|
-
return
|
|
2270
|
+
return err2(toWriteError(e));
|
|
2727
2271
|
}
|
|
2728
2272
|
})();
|
|
2729
2273
|
}
|
|
@@ -2732,9 +2276,9 @@ function createDb(options) {
|
|
|
2732
2276
|
try {
|
|
2733
2277
|
const entry = resolveModel(models, name);
|
|
2734
2278
|
const result = await upsert(queryFn, entry.table, opts, dialectObj);
|
|
2735
|
-
return
|
|
2279
|
+
return ok(result);
|
|
2736
2280
|
} catch (e) {
|
|
2737
|
-
return
|
|
2281
|
+
return err2(toWriteError(e));
|
|
2738
2282
|
}
|
|
2739
2283
|
})();
|
|
2740
2284
|
}
|
|
@@ -2743,9 +2287,9 @@ function createDb(options) {
|
|
|
2743
2287
|
try {
|
|
2744
2288
|
const entry = resolveModel(models, name);
|
|
2745
2289
|
const result = await deleteOne(queryFn, entry.table, opts, dialectObj);
|
|
2746
|
-
return
|
|
2290
|
+
return ok(result);
|
|
2747
2291
|
} catch (e) {
|
|
2748
|
-
return
|
|
2292
|
+
return err2(toWriteError(e));
|
|
2749
2293
|
}
|
|
2750
2294
|
})();
|
|
2751
2295
|
}
|
|
@@ -2754,9 +2298,9 @@ function createDb(options) {
|
|
|
2754
2298
|
try {
|
|
2755
2299
|
const entry = resolveModel(models, name);
|
|
2756
2300
|
const result = await deleteMany(queryFn, entry.table, opts, dialectObj);
|
|
2757
|
-
return
|
|
2301
|
+
return ok(result);
|
|
2758
2302
|
} catch (e) {
|
|
2759
|
-
return
|
|
2303
|
+
return err2(toWriteError(e));
|
|
2760
2304
|
}
|
|
2761
2305
|
})();
|
|
2762
2306
|
}
|
|
@@ -2765,9 +2309,9 @@ function createDb(options) {
|
|
|
2765
2309
|
try {
|
|
2766
2310
|
const entry = resolveModel(models, name);
|
|
2767
2311
|
const result = await count(queryFn, entry.table, opts);
|
|
2768
|
-
return
|
|
2312
|
+
return ok(result);
|
|
2769
2313
|
} catch (e) {
|
|
2770
|
-
return
|
|
2314
|
+
return err2(toReadError(e));
|
|
2771
2315
|
}
|
|
2772
2316
|
})();
|
|
2773
2317
|
}
|
|
@@ -2776,9 +2320,9 @@ function createDb(options) {
|
|
|
2776
2320
|
try {
|
|
2777
2321
|
const entry = resolveModel(models, name);
|
|
2778
2322
|
const result = await aggregate(queryFn, entry.table, opts);
|
|
2779
|
-
return
|
|
2323
|
+
return ok(result);
|
|
2780
2324
|
} catch (e) {
|
|
2781
|
-
return
|
|
2325
|
+
return err2(toReadError(e));
|
|
2782
2326
|
}
|
|
2783
2327
|
})();
|
|
2784
2328
|
}
|
|
@@ -2787,9 +2331,9 @@ function createDb(options) {
|
|
|
2787
2331
|
try {
|
|
2788
2332
|
const entry = resolveModel(models, name);
|
|
2789
2333
|
const result = await groupBy(queryFn, entry.table, opts);
|
|
2790
|
-
return
|
|
2334
|
+
return ok(result);
|
|
2791
2335
|
} catch (e) {
|
|
2792
|
-
return
|
|
2336
|
+
return err2(toReadError(e));
|
|
2793
2337
|
}
|
|
2794
2338
|
})();
|
|
2795
2339
|
}
|
|
@@ -2816,9 +2360,9 @@ function createDb(options) {
|
|
|
2816
2360
|
client.query = async (fragment) => {
|
|
2817
2361
|
try {
|
|
2818
2362
|
const result = await executeQuery(queryFn, fragment.sql, fragment.params);
|
|
2819
|
-
return
|
|
2363
|
+
return ok(result);
|
|
2820
2364
|
} catch (e) {
|
|
2821
|
-
return
|
|
2365
|
+
return err2(toReadError(e, fragment.sql));
|
|
2822
2366
|
}
|
|
2823
2367
|
};
|
|
2824
2368
|
client.close = async () => {
|
|
@@ -2883,11 +2427,6 @@ function createColumnWithMeta(meta) {
|
|
|
2883
2427
|
},
|
|
2884
2428
|
check(sql) {
|
|
2885
2429
|
return cloneWith(this, { check: sql });
|
|
2886
|
-
},
|
|
2887
|
-
references(table, column) {
|
|
2888
|
-
return cloneWith(this, {
|
|
2889
|
-
references: { table, column: column ?? "id" }
|
|
2890
|
-
});
|
|
2891
2430
|
}
|
|
2892
2431
|
};
|
|
2893
2432
|
return col;
|
|
@@ -2902,8 +2441,6 @@ function defaultMeta(sqlType) {
|
|
|
2902
2441
|
_annotations: {},
|
|
2903
2442
|
isReadOnly: false,
|
|
2904
2443
|
isAutoUpdate: false,
|
|
2905
|
-
isTenant: false,
|
|
2906
|
-
references: null,
|
|
2907
2444
|
check: null
|
|
2908
2445
|
};
|
|
2909
2446
|
}
|
|
@@ -2923,23 +2460,6 @@ function createSerialColumn() {
|
|
|
2923
2460
|
_annotations: {},
|
|
2924
2461
|
isReadOnly: false,
|
|
2925
2462
|
isAutoUpdate: false,
|
|
2926
|
-
isTenant: false,
|
|
2927
|
-
references: null,
|
|
2928
|
-
check: null
|
|
2929
|
-
});
|
|
2930
|
-
}
|
|
2931
|
-
function createTenantColumn(targetTableName) {
|
|
2932
|
-
return createColumnWithMeta({
|
|
2933
|
-
sqlType: "uuid",
|
|
2934
|
-
primary: false,
|
|
2935
|
-
unique: false,
|
|
2936
|
-
nullable: false,
|
|
2937
|
-
hasDefault: false,
|
|
2938
|
-
_annotations: {},
|
|
2939
|
-
isReadOnly: false,
|
|
2940
|
-
isAutoUpdate: false,
|
|
2941
|
-
isTenant: true,
|
|
2942
|
-
references: { table: targetTableName, column: "id" },
|
|
2943
2463
|
check: null
|
|
2944
2464
|
});
|
|
2945
2465
|
}
|
|
@@ -3022,11 +2542,12 @@ function getColumnNamesWithAnnotation(table, annotation) {
|
|
|
3022
2542
|
}
|
|
3023
2543
|
|
|
3024
2544
|
// src/schema/model.ts
|
|
3025
|
-
function createModel(table, relations) {
|
|
2545
|
+
function createModel(table, relations, options) {
|
|
3026
2546
|
return {
|
|
3027
2547
|
table,
|
|
3028
2548
|
relations: relations ?? {},
|
|
3029
|
-
schemas: deriveSchemas(table)
|
|
2549
|
+
schemas: deriveSchemas(table),
|
|
2550
|
+
_tenant: options?.tenant ?? null
|
|
3030
2551
|
};
|
|
3031
2552
|
}
|
|
3032
2553
|
|
|
@@ -3061,7 +2582,11 @@ function createManyRelation(target, foreignKey) {
|
|
|
3061
2582
|
}
|
|
3062
2583
|
|
|
3063
2584
|
// src/schema/table.ts
|
|
2585
|
+
var DANGEROUS_SQL_PATTERN = /;|--|\b(DROP|DELETE|INSERT|UPDATE|ALTER|CREATE|EXEC)\b/i;
|
|
3064
2586
|
function createIndex(columns, options) {
|
|
2587
|
+
if (options?.where && DANGEROUS_SQL_PATTERN.test(options.where)) {
|
|
2588
|
+
throw new Error(`Unsafe WHERE clause expression in index: "${options.where}"`);
|
|
2589
|
+
}
|
|
3065
2590
|
return {
|
|
3066
2591
|
columns: Array.isArray(columns) ? columns : [columns],
|
|
3067
2592
|
...options
|
|
@@ -3136,9 +2661,8 @@ var d = {
|
|
|
3136
2661
|
enumValues: values
|
|
3137
2662
|
});
|
|
3138
2663
|
},
|
|
3139
|
-
tenant: (targetTable) => createTenantColumn(targetTable._name),
|
|
3140
2664
|
table: (name, columns, options) => createTable(name, columns, options),
|
|
3141
|
-
index: (columns) => createIndex(columns),
|
|
2665
|
+
index: (columns, options) => createIndex(columns, options),
|
|
3142
2666
|
ref: {
|
|
3143
2667
|
one: (target, foreignKey) => createOneRelation(target, foreignKey),
|
|
3144
2668
|
many: (target, foreignKey) => createManyRelation(target, foreignKey)
|
|
@@ -3147,7 +2671,7 @@ var d = {
|
|
|
3147
2671
|
table,
|
|
3148
2672
|
relations
|
|
3149
2673
|
}),
|
|
3150
|
-
model: (table, relations = {}) => createModel(table, relations)
|
|
2674
|
+
model: (table, relations = {}, options) => createModel(table, relations, options)
|
|
3151
2675
|
};
|
|
3152
2676
|
// src/schema/define-annotations.ts
|
|
3153
2677
|
function defineAnnotations(...annotations) {
|
|
@@ -3192,6 +2716,7 @@ function createRegistry(tables, relationsCallback) {
|
|
|
3192
2716
|
return result;
|
|
3193
2717
|
}
|
|
3194
2718
|
export {
|
|
2719
|
+
validateIndexes,
|
|
3195
2720
|
toWriteError,
|
|
3196
2721
|
toReadError,
|
|
3197
2722
|
resolveErrorCode,
|