@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.
Files changed (83) hide show
  1. package/README.md +217 -78
  2. package/dist/cli.d.ts +76 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +78 -0
  5. package/dist/index.d.ts +5 -4
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +7 -7
  8. package/dist/lib/column.d.ts +1 -1
  9. package/dist/lib/column.d.ts.map +1 -1
  10. package/dist/lib/database/execution-context.d.ts +5 -5
  11. package/dist/lib/database/execution-context.d.ts.map +1 -1
  12. package/dist/lib/database/execution-context.js +1 -0
  13. package/dist/lib/database/helpers.js +6 -6
  14. package/dist/lib/database/query-execution.d.ts.map +1 -1
  15. package/dist/lib/database/query-execution.js +17 -17
  16. package/dist/lib/database/relations.js +5 -5
  17. package/dist/lib/database/write-lifecycle.d.ts +2 -2
  18. package/dist/lib/database/write-lifecycle.d.ts.map +1 -1
  19. package/dist/lib/database/write-lifecycle.js +5 -5
  20. package/dist/lib/database.d.ts +74 -59
  21. package/dist/lib/database.d.ts.map +1 -1
  22. package/dist/lib/database.js +176 -83
  23. package/dist/lib/{adapter.d.ts → driver.d.ts} +46 -48
  24. package/dist/lib/driver.d.ts.map +1 -0
  25. package/dist/lib/errors.d.ts +2 -2
  26. package/dist/lib/errors.d.ts.map +1 -1
  27. package/dist/lib/errors.js +4 -4
  28. package/dist/lib/migrations/journal-store.d.ts +6 -6
  29. package/dist/lib/migrations/journal-store.d.ts.map +1 -1
  30. package/dist/lib/migrations/journal-store.js +20 -11
  31. package/dist/lib/migrations/runner.d.ts +12 -19
  32. package/dist/lib/migrations/runner.d.ts.map +1 -1
  33. package/dist/lib/migrations/runner.js +152 -130
  34. package/dist/lib/migrations-node.d.ts +19 -1
  35. package/dist/lib/migrations-node.d.ts.map +1 -1
  36. package/dist/lib/migrations-node.js +22 -1
  37. package/dist/lib/migrations.d.ts +55 -20
  38. package/dist/lib/migrations.d.ts.map +1 -1
  39. package/dist/lib/operators.d.ts +8 -7
  40. package/dist/lib/operators.d.ts.map +1 -1
  41. package/dist/lib/operators.js +15 -11
  42. package/dist/lib/query.d.ts +1 -1
  43. package/dist/lib/query.d.ts.map +1 -1
  44. package/dist/lib/query.js +4 -4
  45. package/dist/lib/sql-helpers.d.ts +1 -1
  46. package/dist/lib/sql-helpers.d.ts.map +1 -1
  47. package/dist/lib/sql.d.ts +1 -1
  48. package/dist/lib/sql.js +1 -1
  49. package/dist/lib/table.d.ts +1 -1
  50. package/dist/lib/table.d.ts.map +1 -1
  51. package/dist/lib/table.js +5 -5
  52. package/dist/migrations/node.d.ts +1 -1
  53. package/dist/migrations/node.d.ts.map +1 -1
  54. package/dist/migrations/node.js +1 -1
  55. package/dist/migrations.d.ts +1 -2
  56. package/dist/migrations.d.ts.map +1 -1
  57. package/dist/migrations.js +2 -3
  58. package/dist/operators.js +1 -1
  59. package/dist/sql-helpers.js +1 -1
  60. package/package.json +13 -9
  61. package/src/cli.ts +179 -0
  62. package/src/index.ts +19 -20
  63. package/src/lib/column.ts +1 -1
  64. package/src/lib/database/execution-context.ts +10 -6
  65. package/src/lib/database/helpers.ts +1 -1
  66. package/src/lib/database/query-execution.ts +15 -11
  67. package/src/lib/database/write-lifecycle.ts +4 -4
  68. package/src/lib/database.ts +223 -96
  69. package/src/lib/{adapter.ts → driver.ts} +48 -48
  70. package/src/lib/errors.ts +4 -4
  71. package/src/lib/migrations/journal-store.ts +21 -11
  72. package/src/lib/migrations/runner.ts +201 -148
  73. package/src/lib/migrations-node.ts +23 -1
  74. package/src/lib/migrations.ts +58 -19
  75. package/src/lib/operators.ts +24 -11
  76. package/src/lib/query.ts +1 -1
  77. package/src/lib/sql-helpers.ts +1 -1
  78. package/src/lib/sql.ts +1 -1
  79. package/src/lib/table.ts +1 -1
  80. package/src/migrations/node.ts +1 -1
  81. package/src/migrations.ts +3 -4
  82. package/dist/lib/adapter.d.ts.map +0 -1
  83. /package/dist/lib/{adapter.js → driver.js} +0 -0
@@ -1,4 +1,4 @@
1
- import { rawSql } from "../sql.js";
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(adapter, tableName) {
14
- await adapter.executeScript('create table if not exists ' +
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(adapter, tableName) {
24
+ export async function hasMigrationJournal(driver, tableName) {
25
25
  try {
26
- await adapter.execute({
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(adapter, tableName) {
39
- let result = await adapter.execute({
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(adapter, tableName, row, transaction) {
55
- await adapter.execute({
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(adapter, tableName, id, transaction) {
69
- await adapter.execute({
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 { DatabaseAdapter } from '../adapter.ts';
2
- import type { MigrationDescriptor, MigrationRegistry, MigrationRunner, MigrationRunnerOptions } from '../migrations.ts';
3
- /**
4
- * Creates a migration runner for applying/reverting SQL migrations against an adapter.
5
- * @param adapter Database adapter used to execute migration scripts.
6
- * @param migrations Migration descriptors or registry.
7
- * @param options Optional runner configuration.
8
- * @returns A migration runner instance.
9
- * @example
10
- * ```ts
11
- * import { createMigrationRunner } from 'remix/data-table/migrations'
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,eAAe,EAAoB,MAAM,eAAe,CAAA;AACtE,OAAO,KAAK,EAGV,mBAAmB,EAGnB,iBAAiB,EACjB,eAAe,EACf,sBAAsB,EAIvB,MAAM,kBAAkB,CAAA;AAqPzB;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,mBAAmB,EAAE,GAAG,iBAAiB,EACrD,OAAO,GAAE,sBAA2B,GACnC,eAAe,CA6DjB"}
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 "./directive.js";
2
- import { deleteJournalRow, ensureMigrationJournal, getBatch, hasMigrationJournal, insertJournalRow, loadJournalRows, computeChecksum, } from "./journal-store.js";
3
- import { resolveMigrations } from "./registry.js";
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 assertMigrateOptions(options) {
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 assertTargetOption(migrations, to) {
18
- if (!to) {
19
- return;
17
+ function resolveTargetOption(migrations, to) {
18
+ if (to === undefined) {
19
+ return undefined;
20
20
  }
21
- let target = migrations.find((migration) => migration.id === to);
22
- if (!target) {
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 assertNoMigrationDrift(migrations, journal) {
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
- continue;
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
- let adapter = input.adapter;
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
- assertMigrateOptions(input.options);
84
+ assertMigrationOperationOptions(input.options);
63
85
  assertStepOption(step);
64
- assertTargetOption(migrations, target);
86
+ let target = resolveTargetOption(migrations, input.options.to);
65
87
  let sql = [];
66
- await adapter.acquireMigrationLock?.();
67
- try {
68
- let journal = [];
69
- if (dryRun) {
70
- let canReadJournal = await hasMigrationJournal(adapter, journalTable);
71
- if (canReadJournal) {
72
- journal = await loadJournalRows(adapter, journalTable);
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
- else {
76
- await ensureMigrationJournal(adapter, journalTable);
77
- journal = await loadJournalRows(adapter, journalTable);
108
+ if (target) {
109
+ toRun = toRun.filter((migration) => migration.id <= target);
78
110
  }
79
- let appliedMap = new Map(journal.map((row) => [row.id, row]));
80
- await assertNoMigrationDrift(migrations, journal);
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
- else {
96
- toRun = migrations.filter((migration) => appliedMap.has(migration.id)).reverse();
97
- if (target) {
98
- toRun = toRun.filter((migration) => migration.id >= target);
99
- }
100
- if (step !== undefined) {
101
- toRun = toRun.slice(0, step);
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
- let script = (input.direction === 'up' ? migration.up : migration.down);
114
- let mode = resolveTransactionMode(migration);
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
- let shouldUseTransaction = !dryRun && mode !== 'none' && adapter.capabilities.transactionalDdl;
121
- let token;
122
- if (shouldUseTransaction) {
123
- token = await adapter.beginTransaction();
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
- sql.push(script);
126
- try {
152
+ if (input.direction === 'up') {
127
153
  if (!dryRun) {
128
- if (script.trim().length > 0) {
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
- status: 'pending',
155
- });
157
+ checksum: await computeChecksum(migration),
158
+ batch,
159
+ }, token);
156
160
  }
157
- if (token) {
158
- await adapter.commitTransaction(token);
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
- catch (error) {
162
- if (token) {
163
- await adapter.rollbackTransaction(token);
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
- adapter,
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
- adapter,
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 ensureMigrationJournal(adapter, journalTable);
217
- let journal = await loadJournalRows(adapter, journalTable);
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
- return statuses;
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;AAG1D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA0DtF"}
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 "./migrations/directory-name.js";
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 &&