@remix-run/data-table 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -78
- package/dist/cli.d.ts +76 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +78 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -7
- package/dist/lib/column.d.ts +1 -1
- package/dist/lib/column.d.ts.map +1 -1
- package/dist/lib/database/execution-context.d.ts +5 -5
- package/dist/lib/database/execution-context.d.ts.map +1 -1
- package/dist/lib/database/execution-context.js +1 -0
- package/dist/lib/database/helpers.js +6 -6
- package/dist/lib/database/query-execution.d.ts.map +1 -1
- package/dist/lib/database/query-execution.js +17 -17
- package/dist/lib/database/relations.js +5 -5
- package/dist/lib/database/write-lifecycle.d.ts +2 -2
- package/dist/lib/database/write-lifecycle.d.ts.map +1 -1
- package/dist/lib/database/write-lifecycle.js +5 -5
- package/dist/lib/database.d.ts +74 -59
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +176 -83
- package/dist/lib/{adapter.d.ts → driver.d.ts} +46 -48
- package/dist/lib/driver.d.ts.map +1 -0
- package/dist/lib/errors.d.ts +2 -2
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +4 -4
- package/dist/lib/migrations/journal-store.d.ts +6 -6
- package/dist/lib/migrations/journal-store.d.ts.map +1 -1
- package/dist/lib/migrations/journal-store.js +20 -11
- package/dist/lib/migrations/runner.d.ts +12 -19
- package/dist/lib/migrations/runner.d.ts.map +1 -1
- package/dist/lib/migrations/runner.js +152 -130
- package/dist/lib/migrations-node.d.ts +19 -1
- package/dist/lib/migrations-node.d.ts.map +1 -1
- package/dist/lib/migrations-node.js +22 -1
- package/dist/lib/migrations.d.ts +55 -20
- package/dist/lib/migrations.d.ts.map +1 -1
- package/dist/lib/operators.d.ts +8 -7
- package/dist/lib/operators.d.ts.map +1 -1
- package/dist/lib/operators.js +15 -11
- package/dist/lib/query.d.ts +1 -1
- package/dist/lib/query.d.ts.map +1 -1
- package/dist/lib/query.js +4 -4
- package/dist/lib/sql-helpers.d.ts +1 -1
- package/dist/lib/sql-helpers.d.ts.map +1 -1
- package/dist/lib/sql.d.ts +1 -1
- package/dist/lib/sql.js +1 -1
- package/dist/lib/table.d.ts +1 -1
- package/dist/lib/table.d.ts.map +1 -1
- package/dist/lib/table.js +5 -5
- package/dist/migrations/node.d.ts +1 -1
- package/dist/migrations/node.d.ts.map +1 -1
- package/dist/migrations/node.js +1 -1
- package/dist/migrations.d.ts +1 -2
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +2 -3
- package/dist/operators.js +1 -1
- package/dist/sql-helpers.js +1 -1
- package/package.json +13 -9
- package/src/cli.ts +179 -0
- package/src/index.ts +19 -20
- package/src/lib/column.ts +1 -1
- package/src/lib/database/execution-context.ts +10 -6
- package/src/lib/database/helpers.ts +1 -1
- package/src/lib/database/query-execution.ts +15 -11
- package/src/lib/database/write-lifecycle.ts +4 -4
- package/src/lib/database.ts +223 -96
- package/src/lib/{adapter.ts → driver.ts} +48 -48
- package/src/lib/errors.ts +4 -4
- package/src/lib/migrations/journal-store.ts +21 -11
- package/src/lib/migrations/runner.ts +201 -148
- package/src/lib/migrations-node.ts +23 -1
- package/src/lib/migrations.ts +58 -19
- package/src/lib/operators.ts +24 -11
- package/src/lib/query.ts +1 -1
- package/src/lib/sql-helpers.ts +1 -1
- package/src/lib/sql.ts +1 -1
- package/src/lib/table.ts +1 -1
- package/src/migrations/node.ts +1 -1
- package/src/migrations.ts +3 -4
- package/dist/lib/adapter.d.ts.map +0 -1
- /package/dist/lib/{adapter.js → driver.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rawSql } from
|
|
1
|
+
import { rawSql } from '../sql.js';
|
|
2
2
|
export async function computeChecksum(migration) {
|
|
3
3
|
let digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(migration.up));
|
|
4
4
|
return bytesToHex(new Uint8Array(digest));
|
|
@@ -10,8 +10,8 @@ function bytesToHex(bytes) {
|
|
|
10
10
|
}
|
|
11
11
|
return hex;
|
|
12
12
|
}
|
|
13
|
-
export async function ensureMigrationJournal(
|
|
14
|
-
await
|
|
13
|
+
export async function ensureMigrationJournal(driver, tableName) {
|
|
14
|
+
await driver.executeScript('create table if not exists ' +
|
|
15
15
|
tableName +
|
|
16
16
|
' (' +
|
|
17
17
|
'id varchar(64) not null primary key, ' +
|
|
@@ -21,9 +21,9 @@ export async function ensureMigrationJournal(adapter, tableName) {
|
|
|
21
21
|
'applied_at timestamp not null default current_timestamp' +
|
|
22
22
|
')');
|
|
23
23
|
}
|
|
24
|
-
export async function hasMigrationJournal(
|
|
24
|
+
export async function hasMigrationJournal(driver, tableName) {
|
|
25
25
|
try {
|
|
26
|
-
await
|
|
26
|
+
await driver.execute({
|
|
27
27
|
operation: {
|
|
28
28
|
kind: 'raw',
|
|
29
29
|
sql: rawSql('select 1 from ' + tableName + ' limit 1'),
|
|
@@ -32,11 +32,20 @@ export async function hasMigrationJournal(adapter, tableName) {
|
|
|
32
32
|
return true;
|
|
33
33
|
}
|
|
34
34
|
catch {
|
|
35
|
+
// The probe fails both when the journal table is missing and when the
|
|
36
|
+
// database itself is unreachable. Only the former means "no journal yet";
|
|
37
|
+
// rethrow connection/auth failures via a table-independent query.
|
|
38
|
+
await driver.execute({
|
|
39
|
+
operation: {
|
|
40
|
+
kind: 'raw',
|
|
41
|
+
sql: rawSql('select 1'),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
35
44
|
return false;
|
|
36
45
|
}
|
|
37
46
|
}
|
|
38
|
-
export async function loadJournalRows(
|
|
39
|
-
let result = await
|
|
47
|
+
export async function loadJournalRows(driver, tableName) {
|
|
48
|
+
let result = await driver.execute({
|
|
40
49
|
operation: {
|
|
41
50
|
kind: 'raw',
|
|
42
51
|
sql: rawSql('select id, name, checksum, batch, applied_at from ' + tableName + ' order by id asc'),
|
|
@@ -51,8 +60,8 @@ export async function loadJournalRows(adapter, tableName) {
|
|
|
51
60
|
appliedAt: new Date(String(row.applied_at)),
|
|
52
61
|
}));
|
|
53
62
|
}
|
|
54
|
-
export async function insertJournalRow(
|
|
55
|
-
await
|
|
63
|
+
export async function insertJournalRow(driver, tableName, row, transaction) {
|
|
64
|
+
await driver.execute({
|
|
56
65
|
operation: {
|
|
57
66
|
kind: 'raw',
|
|
58
67
|
sql: rawSql('insert into ' + tableName + ' (id, name, checksum, batch) values (?, ?, ?, ?)', [
|
|
@@ -65,8 +74,8 @@ export async function insertJournalRow(adapter, tableName, row, transaction) {
|
|
|
65
74
|
transaction,
|
|
66
75
|
});
|
|
67
76
|
}
|
|
68
|
-
export async function deleteJournalRow(
|
|
69
|
-
await
|
|
77
|
+
export async function deleteJournalRow(driver, tableName, id, transaction) {
|
|
78
|
+
await driver.execute({
|
|
70
79
|
operation: {
|
|
71
80
|
kind: 'raw',
|
|
72
81
|
sql: rawSql('delete from ' + tableName + ' where id = ?', [id]),
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { MigrationDescriptor, MigrationRegistry,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* let runner = createMigrationRunner(adapter, migrations, {
|
|
14
|
-
* journalTable: 'app_migrations',
|
|
15
|
-
* })
|
|
16
|
-
* await runner.up()
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
export declare function createMigrationRunner(adapter: DatabaseAdapter, migrations: MigrationDescriptor[] | MigrationRegistry, options?: MigrationRunnerOptions): MigrationRunner;
|
|
1
|
+
import type { DatabaseDriver } from '../driver.ts';
|
|
2
|
+
import type { MigrationOperationOptions, MigrateResult, MigrationDescriptor, MigrationRegistry, MigrationStatusEntry } from '../migrations.ts';
|
|
3
|
+
interface MigrationRunnerOptions {
|
|
4
|
+
journalTable?: string;
|
|
5
|
+
}
|
|
6
|
+
interface MigrationRunner {
|
|
7
|
+
up(options?: MigrationOperationOptions): Promise<MigrateResult>;
|
|
8
|
+
down(options?: MigrationOperationOptions): Promise<MigrateResult>;
|
|
9
|
+
status(): Promise<MigrationStatusEntry[]>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createMigrationRunner(driver: DatabaseDriver, migrations: MigrationDescriptor[] | MigrationRegistry, options?: MigrationRunnerOptions): MigrationRunner;
|
|
12
|
+
export {};
|
|
20
13
|
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/lib/migrations/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/lib/migrations/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAoB,MAAM,cAAc,CAAA;AACpE,OAAO,KAAK,EACV,yBAAyB,EACzB,aAAa,EACb,mBAAmB,EAGnB,iBAAiB,EAEjB,oBAAoB,EAErB,MAAM,kBAAkB,CAAA;AAEzB,UAAU,sBAAsB;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,UAAU,eAAe;IACvB,EAAE,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC/D,IAAI,CAAC,OAAO,CAAC,EAAE,yBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IACjE,MAAM,IAAI,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAA;CAC1C;AAkSD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,mBAAmB,EAAE,GAAG,iBAAiB,EACrD,OAAO,GAAE,sBAA2B,GACnC,eAAe,CA6EjB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { parseTransactionDirective } from
|
|
2
|
-
import { deleteJournalRow, ensureMigrationJournal, getBatch, hasMigrationJournal, insertJournalRow, loadJournalRows, computeChecksum, } from
|
|
3
|
-
import { resolveMigrations } from
|
|
1
|
+
import { parseTransactionDirective } from './directive.js';
|
|
2
|
+
import { deleteJournalRow, ensureMigrationJournal, getBatch, hasMigrationJournal, insertJournalRow, loadJournalRows, computeChecksum, } from './journal-store.js';
|
|
3
|
+
import { resolveMigrations } from './registry.js';
|
|
4
4
|
function assertStepOption(step) {
|
|
5
5
|
if (step === undefined) {
|
|
6
6
|
return;
|
|
@@ -9,26 +9,40 @@ function assertStepOption(step) {
|
|
|
9
9
|
throw new Error('Invalid migration step option. Expected a positive integer.');
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function assertMigrationOperationOptions(options) {
|
|
13
13
|
if (options.to !== undefined && options.step !== undefined) {
|
|
14
14
|
throw new Error('Cannot combine "to" and "step" migration options in the same run');
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
function
|
|
18
|
-
if (
|
|
19
|
-
return;
|
|
17
|
+
function resolveTargetOption(migrations, to) {
|
|
18
|
+
if (to === undefined) {
|
|
19
|
+
return undefined;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// Accept either a bare migration id or the full `id_name` directory form and
|
|
22
|
+
// normalize to the bare id so range filtering compares ids consistently.
|
|
23
|
+
let matches = migrations.filter((migration) => migration.id === to || migration.id + '_' + migration.name === to);
|
|
24
|
+
if (matches.length === 0) {
|
|
23
25
|
throw new Error('Unknown migration target: ' + to);
|
|
24
26
|
}
|
|
27
|
+
if (matches.length > 1) {
|
|
28
|
+
throw new Error('Ambiguous migration target "' +
|
|
29
|
+
to +
|
|
30
|
+
'". Matches: ' +
|
|
31
|
+
matches.map((migration) => migration.id + '_' + migration.name).join(', '));
|
|
32
|
+
}
|
|
33
|
+
return matches[0].id;
|
|
25
34
|
}
|
|
26
|
-
async function
|
|
35
|
+
async function assertMigrationIntegrity(migrations, journal, direction) {
|
|
27
36
|
let migrationMap = new Map(migrations.map((migration) => [migration.id, migration]));
|
|
28
37
|
for (let row of journal) {
|
|
29
38
|
let migration = migrationMap.get(row.id);
|
|
30
39
|
if (!migration) {
|
|
31
|
-
|
|
40
|
+
// Rolling back must stay possible when journal rows have no matching
|
|
41
|
+
// migration files, so only forward runs hard-error on orphaned entries.
|
|
42
|
+
if (direction === 'down') {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
throw new Error('Applied migration "' + row.id + '_' + row.name + '" is missing from current migrations');
|
|
32
46
|
}
|
|
33
47
|
let expected = await computeChecksum(migration);
|
|
34
48
|
if (expected !== row.checksum) {
|
|
@@ -53,150 +67,143 @@ function resolveTransactionMode(migration) {
|
|
|
53
67
|
return 'auto';
|
|
54
68
|
}
|
|
55
69
|
async function runMigrations(input) {
|
|
56
|
-
|
|
70
|
+
if (input.driver.capabilities.migrationLock && !input.driver.withMigrationLock) {
|
|
71
|
+
throw new Error('Database driver reports migration lock support but does not implement withMigrationLock()');
|
|
72
|
+
}
|
|
73
|
+
if (input.driver.withMigrationLock) {
|
|
74
|
+
return input.driver.withMigrationLock(input.journalTable, (driver) => runMigrationsUnlocked({ ...input, driver }));
|
|
75
|
+
}
|
|
76
|
+
return runMigrationsUnlocked(input);
|
|
77
|
+
}
|
|
78
|
+
async function runMigrationsUnlocked(input) {
|
|
79
|
+
let driver = input.driver;
|
|
57
80
|
let migrations = input.migrations;
|
|
58
81
|
let journalTable = input.journalTable;
|
|
59
82
|
let dryRun = Boolean(input.options.dryRun);
|
|
60
|
-
let target = input.options.to;
|
|
61
83
|
let step = input.options.step;
|
|
62
|
-
|
|
84
|
+
assertMigrationOperationOptions(input.options);
|
|
63
85
|
assertStepOption(step);
|
|
64
|
-
|
|
86
|
+
let target = resolveTargetOption(migrations, input.options.to);
|
|
65
87
|
let sql = [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
88
|
+
let journal = [];
|
|
89
|
+
if (dryRun) {
|
|
90
|
+
let canReadJournal = await hasMigrationJournal(driver, journalTable);
|
|
91
|
+
if (canReadJournal) {
|
|
92
|
+
journal = await loadJournalRows(driver, journalTable);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
await ensureMigrationJournal(driver, journalTable);
|
|
97
|
+
journal = await loadJournalRows(driver, journalTable);
|
|
98
|
+
}
|
|
99
|
+
let appliedMap = new Map(journal.map((row) => [row.id, row]));
|
|
100
|
+
await assertMigrationIntegrity(migrations, journal, input.direction);
|
|
101
|
+
let toRun = [];
|
|
102
|
+
if (input.direction === 'up') {
|
|
103
|
+
for (let migration of migrations) {
|
|
104
|
+
if (!appliedMap.has(migration.id)) {
|
|
105
|
+
toRun.push(migration);
|
|
73
106
|
}
|
|
74
107
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
journal = await loadJournalRows(adapter, journalTable);
|
|
108
|
+
if (target) {
|
|
109
|
+
toRun = toRun.filter((migration) => migration.id <= target);
|
|
78
110
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
let toRun = [];
|
|
82
|
-
if (input.direction === 'up') {
|
|
83
|
-
for (let migration of migrations) {
|
|
84
|
-
if (!appliedMap.has(migration.id)) {
|
|
85
|
-
toRun.push(migration);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (target) {
|
|
89
|
-
toRun = toRun.filter((migration) => migration.id <= target);
|
|
90
|
-
}
|
|
91
|
-
if (step !== undefined) {
|
|
92
|
-
toRun = toRun.slice(0, step);
|
|
93
|
-
}
|
|
111
|
+
if (step !== undefined) {
|
|
112
|
+
toRun = toRun.slice(0, step);
|
|
94
113
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
for (let migration of toRun) {
|
|
104
|
-
if (migration.down === undefined) {
|
|
105
|
-
throw new Error('Migration "' + migration.id + '" has no down script');
|
|
106
|
-
}
|
|
107
|
-
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
toRun = migrations.filter((migration) => appliedMap.has(migration.id)).reverse();
|
|
117
|
+
if (target) {
|
|
118
|
+
toRun = toRun.filter((migration) => migration.id >= target);
|
|
119
|
+
}
|
|
120
|
+
if (step !== undefined) {
|
|
121
|
+
toRun = toRun.slice(0, step);
|
|
108
122
|
}
|
|
109
|
-
let applied = [];
|
|
110
|
-
let reverted = [];
|
|
111
|
-
let batch = getBatch(journal);
|
|
112
123
|
for (let migration of toRun) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (mode === 'required' && !adapter.capabilities.transactionalDdl) {
|
|
116
|
-
throw new Error('Migration "' +
|
|
117
|
-
migration.id +
|
|
118
|
-
'" requires transactional DDL, but adapter does not support it');
|
|
124
|
+
if (migration.down === undefined) {
|
|
125
|
+
throw new Error('Migration "' + migration.id + '" has no down script');
|
|
119
126
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let applied = [];
|
|
130
|
+
let reverted = [];
|
|
131
|
+
let batch = getBatch(journal);
|
|
132
|
+
for (let migration of toRun) {
|
|
133
|
+
let script = (input.direction === 'up' ? migration.up : migration.down);
|
|
134
|
+
let mode = resolveTransactionMode(migration);
|
|
135
|
+
if (mode === 'required' && !driver.capabilities.transactionalDdl) {
|
|
136
|
+
throw new Error('Migration "' +
|
|
137
|
+
migration.id +
|
|
138
|
+
'" requires transactional DDL, but the database does not support it');
|
|
139
|
+
}
|
|
140
|
+
let shouldUseTransaction = !dryRun && mode !== 'none' && driver.capabilities.transactionalDdl;
|
|
141
|
+
let token;
|
|
142
|
+
if (shouldUseTransaction) {
|
|
143
|
+
token = await driver.beginTransaction();
|
|
144
|
+
}
|
|
145
|
+
sql.push(script);
|
|
146
|
+
try {
|
|
147
|
+
if (!dryRun) {
|
|
148
|
+
if (script.trim().length > 0) {
|
|
149
|
+
await driver.executeScript(script, token);
|
|
150
|
+
}
|
|
124
151
|
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
152
|
+
if (input.direction === 'up') {
|
|
127
153
|
if (!dryRun) {
|
|
128
|
-
|
|
129
|
-
await adapter.executeScript(script, token);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (input.direction === 'up') {
|
|
133
|
-
if (!dryRun) {
|
|
134
|
-
await insertJournalRow(adapter, journalTable, {
|
|
135
|
-
id: migration.id,
|
|
136
|
-
name: migration.name,
|
|
137
|
-
checksum: await computeChecksum(migration),
|
|
138
|
-
batch,
|
|
139
|
-
}, token);
|
|
140
|
-
}
|
|
141
|
-
applied.push({
|
|
142
|
-
id: migration.id,
|
|
143
|
-
name: migration.name,
|
|
144
|
-
status: 'applied',
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
if (!dryRun) {
|
|
149
|
-
await deleteJournalRow(adapter, journalTable, migration.id, token);
|
|
150
|
-
}
|
|
151
|
-
reverted.push({
|
|
154
|
+
await insertJournalRow(driver, journalTable, {
|
|
152
155
|
id: migration.id,
|
|
153
156
|
name: migration.name,
|
|
154
|
-
|
|
155
|
-
|
|
157
|
+
checksum: await computeChecksum(migration),
|
|
158
|
+
batch,
|
|
159
|
+
}, token);
|
|
156
160
|
}
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
applied.push({
|
|
162
|
+
id: migration.id,
|
|
163
|
+
name: migration.name,
|
|
164
|
+
status: 'applied',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
if (!dryRun) {
|
|
169
|
+
await deleteJournalRow(driver, journalTable, migration.id, token);
|
|
159
170
|
}
|
|
171
|
+
reverted.push({
|
|
172
|
+
id: migration.id,
|
|
173
|
+
name: migration.name,
|
|
174
|
+
status: 'pending',
|
|
175
|
+
});
|
|
160
176
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (token) {
|
|
180
|
+
try {
|
|
181
|
+
await driver.rollbackTransaction(token);
|
|
182
|
+
}
|
|
183
|
+
catch (rollbackError) {
|
|
184
|
+
throw new AggregateError([error, rollbackError], 'Migration and rollback both failed', {
|
|
185
|
+
cause: error,
|
|
186
|
+
});
|
|
164
187
|
}
|
|
165
|
-
throw error;
|
|
166
188
|
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (token) {
|
|
192
|
+
await driver.commitTransaction(token);
|
|
167
193
|
}
|
|
168
|
-
return {
|
|
169
|
-
applied,
|
|
170
|
-
reverted,
|
|
171
|
-
sql,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
finally {
|
|
175
|
-
await adapter.releaseMigrationLock?.();
|
|
176
194
|
}
|
|
195
|
+
return {
|
|
196
|
+
applied,
|
|
197
|
+
reverted,
|
|
198
|
+
sql,
|
|
199
|
+
};
|
|
177
200
|
}
|
|
178
|
-
|
|
179
|
-
* Creates a migration runner for applying/reverting SQL migrations against an adapter.
|
|
180
|
-
* @param adapter Database adapter used to execute migration scripts.
|
|
181
|
-
* @param migrations Migration descriptors or registry.
|
|
182
|
-
* @param options Optional runner configuration.
|
|
183
|
-
* @returns A migration runner instance.
|
|
184
|
-
* @example
|
|
185
|
-
* ```ts
|
|
186
|
-
* import { createMigrationRunner } from 'remix/data-table/migrations'
|
|
187
|
-
*
|
|
188
|
-
* let runner = createMigrationRunner(adapter, migrations, {
|
|
189
|
-
* journalTable: 'app_migrations',
|
|
190
|
-
* })
|
|
191
|
-
* await runner.up()
|
|
192
|
-
* ```
|
|
193
|
-
*/
|
|
194
|
-
export function createMigrationRunner(adapter, migrations, options = {}) {
|
|
201
|
+
export function createMigrationRunner(driver, migrations, options = {}) {
|
|
195
202
|
let journalTable = options.journalTable ?? 'data_table_migrations';
|
|
196
203
|
return {
|
|
197
204
|
async up(runOptions = {}) {
|
|
198
205
|
return runMigrations({
|
|
199
|
-
|
|
206
|
+
driver,
|
|
200
207
|
migrations: resolveMigrations(migrations),
|
|
201
208
|
journalTable,
|
|
202
209
|
direction: 'up',
|
|
@@ -205,7 +212,7 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
|
|
|
205
212
|
},
|
|
206
213
|
async down(runOptions = {}) {
|
|
207
214
|
return runMigrations({
|
|
208
|
-
|
|
215
|
+
driver,
|
|
209
216
|
migrations: resolveMigrations(migrations),
|
|
210
217
|
journalTable,
|
|
211
218
|
direction: 'down',
|
|
@@ -213,10 +220,12 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
|
|
|
213
220
|
});
|
|
214
221
|
},
|
|
215
222
|
async status() {
|
|
216
|
-
await
|
|
217
|
-
|
|
223
|
+
let journal = (await hasMigrationJournal(driver, journalTable))
|
|
224
|
+
? await loadJournalRows(driver, journalTable)
|
|
225
|
+
: [];
|
|
218
226
|
let journalMap = new Map(journal.map((row) => [row.id, row]));
|
|
219
227
|
let sortedMigrations = resolveMigrations(migrations);
|
|
228
|
+
let migrationIds = new Set(sortedMigrations.map((migration) => migration.id));
|
|
220
229
|
let statuses = [];
|
|
221
230
|
for (let migration of sortedMigrations) {
|
|
222
231
|
let journalRow = journalMap.get(migration.id);
|
|
@@ -240,7 +249,20 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
|
|
|
240
249
|
checksum: journalRow.checksum,
|
|
241
250
|
});
|
|
242
251
|
}
|
|
243
|
-
|
|
252
|
+
for (let journalRow of journal) {
|
|
253
|
+
if (migrationIds.has(journalRow.id)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
statuses.push({
|
|
257
|
+
id: journalRow.id,
|
|
258
|
+
name: journalRow.name,
|
|
259
|
+
status: 'missing',
|
|
260
|
+
appliedAt: journalRow.appliedAt,
|
|
261
|
+
batch: journalRow.batch,
|
|
262
|
+
checksum: journalRow.checksum,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return statuses.sort((left, right) => left.id.localeCompare(right.id));
|
|
244
266
|
},
|
|
245
267
|
};
|
|
246
268
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MigrationDescriptor } from './migrations.ts';
|
|
1
|
+
import type { MigrationDescriptor, Seed } from './migrations.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Loads SQL-file migrations from a directory on Node.js.
|
|
4
4
|
*
|
|
@@ -17,4 +17,22 @@ import type { MigrationDescriptor } from './migrations.ts';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export declare function loadMigrations(directory: string): Promise<MigrationDescriptor[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Loads a SQL seed file on Node.js.
|
|
22
|
+
*
|
|
23
|
+
* The file may contain multiple SQL statements. Seeds that must be safe to
|
|
24
|
+
* run against an already-seeded database should use idempotent statements
|
|
25
|
+
* (for example, `insert or ignore` on SQLite).
|
|
26
|
+
*
|
|
27
|
+
* @param filename Absolute or relative path to a SQL seed file.
|
|
28
|
+
* @returns A seed function that executes the file's SQL script.
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { loadSeed } from 'remix/data-table/migrations/node'
|
|
32
|
+
*
|
|
33
|
+
* let seed = await loadSeed('./app/data/seed.sql')
|
|
34
|
+
* await db.reset({ migrations, seed })
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadSeed(filename: string): Promise<Seed>;
|
|
20
38
|
//# sourceMappingURL=migrations-node.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrations-node.d.ts","sourceRoot":"","sources":["../../src/lib/migrations-node.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"migrations-node.d.ts","sourceRoot":"","sources":["../../src/lib/migrations-node.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAGhE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA0DtF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { parseMigrationDirectoryName } from
|
|
3
|
+
import { parseMigrationDirectoryName } from './migrations/directory-name.js';
|
|
4
4
|
/**
|
|
5
5
|
* Loads SQL-file migrations from a directory on Node.js.
|
|
6
6
|
*
|
|
@@ -68,6 +68,27 @@ export async function loadMigrations(directory) {
|
|
|
68
68
|
}
|
|
69
69
|
return migrations;
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Loads a SQL seed file on Node.js.
|
|
73
|
+
*
|
|
74
|
+
* The file may contain multiple SQL statements. Seeds that must be safe to
|
|
75
|
+
* run against an already-seeded database should use idempotent statements
|
|
76
|
+
* (for example, `insert or ignore` on SQLite).
|
|
77
|
+
*
|
|
78
|
+
* @param filename Absolute or relative path to a SQL seed file.
|
|
79
|
+
* @returns A seed function that executes the file's SQL script.
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { loadSeed } from 'remix/data-table/migrations/node'
|
|
83
|
+
*
|
|
84
|
+
* let seed = await loadSeed('./app/data/seed.sql')
|
|
85
|
+
* await db.reset({ migrations, seed })
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export async function loadSeed(filename) {
|
|
89
|
+
let sql = await fs.readFile(filename, 'utf8');
|
|
90
|
+
return (db) => db.executeScript(sql);
|
|
91
|
+
}
|
|
71
92
|
function isNodeFileNotFoundError(error) {
|
|
72
93
|
return (typeof error === 'object' &&
|
|
73
94
|
error !== null &&
|