@remix-run/data-table 0.2.1 → 0.4.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 (113) hide show
  1. package/README.md +251 -123
  2. package/dist/cli.d.ts +54 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +53 -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/column.js +10 -1
  11. package/dist/lib/database/execution-context.d.ts +5 -5
  12. package/dist/lib/database/execution-context.d.ts.map +1 -1
  13. package/dist/lib/database/execution-context.js +1 -0
  14. package/dist/lib/database/helpers.js +6 -6
  15. package/dist/lib/database/query-execution.d.ts.map +1 -1
  16. package/dist/lib/database/query-execution.js +17 -17
  17. package/dist/lib/database/relations.js +5 -5
  18. package/dist/lib/database/write-lifecycle.d.ts +2 -2
  19. package/dist/lib/database/write-lifecycle.d.ts.map +1 -1
  20. package/dist/lib/database/write-lifecycle.js +5 -5
  21. package/dist/lib/database.d.ts +74 -59
  22. package/dist/lib/database.d.ts.map +1 -1
  23. package/dist/lib/database.js +176 -83
  24. package/dist/lib/driver.d.ts +287 -0
  25. package/dist/lib/driver.d.ts.map +1 -0
  26. package/dist/lib/errors.d.ts +2 -2
  27. package/dist/lib/errors.d.ts.map +1 -1
  28. package/dist/lib/errors.js +4 -4
  29. package/dist/lib/migrations/directive.d.ts +12 -0
  30. package/dist/lib/migrations/directive.d.ts.map +1 -0
  31. package/dist/lib/migrations/directive.js +28 -0
  32. package/dist/lib/migrations/directory-name.d.ts +12 -0
  33. package/dist/lib/migrations/directory-name.d.ts.map +1 -0
  34. package/dist/lib/migrations/directory-name.js +18 -0
  35. package/dist/lib/migrations/journal-store.d.ts +7 -7
  36. package/dist/lib/migrations/journal-store.d.ts.map +1 -1
  37. package/dist/lib/migrations/journal-store.js +37 -28
  38. package/dist/lib/migrations/registry.d.ts +1 -1
  39. package/dist/lib/migrations/registry.js +1 -1
  40. package/dist/lib/migrations/runner.d.ts +12 -19
  41. package/dist/lib/migrations/runner.d.ts.map +1 -1
  42. package/dist/lib/migrations/runner.js +170 -175
  43. package/dist/lib/migrations-node.d.ts +26 -5
  44. package/dist/lib/migrations-node.d.ts.map +1 -1
  45. package/dist/lib/migrations-node.js +73 -41
  46. package/dist/lib/migrations.d.ts +63 -209
  47. package/dist/lib/migrations.d.ts.map +1 -1
  48. package/dist/lib/migrations.js +1 -38
  49. package/dist/lib/operators.d.ts +1 -1
  50. package/dist/lib/operators.js +1 -1
  51. package/dist/lib/query.d.ts +34 -1
  52. package/dist/lib/query.d.ts.map +1 -1
  53. package/dist/lib/query.js +32 -4
  54. package/dist/lib/sql-helpers.d.ts +1 -7
  55. package/dist/lib/sql-helpers.d.ts.map +1 -1
  56. package/dist/lib/sql-helpers.js +0 -16
  57. package/dist/lib/sql.d.ts +1 -1
  58. package/dist/lib/sql.js +1 -1
  59. package/dist/lib/table.d.ts +1 -1
  60. package/dist/lib/table.d.ts.map +1 -1
  61. package/dist/lib/table.js +5 -5
  62. package/dist/migrations/node.d.ts +1 -1
  63. package/dist/migrations/node.d.ts.map +1 -1
  64. package/dist/migrations/node.js +1 -1
  65. package/dist/migrations.d.ts +2 -6
  66. package/dist/migrations.d.ts.map +1 -1
  67. package/dist/migrations.js +2 -5
  68. package/dist/operators.js +1 -1
  69. package/dist/sql-helpers.d.ts +1 -1
  70. package/dist/sql-helpers.d.ts.map +1 -1
  71. package/dist/sql-helpers.js +1 -1
  72. package/package.json +13 -9
  73. package/src/cli.ts +120 -0
  74. package/src/index.ts +20 -54
  75. package/src/lib/column.ts +14 -2
  76. package/src/lib/database/execution-context.ts +10 -6
  77. package/src/lib/database/helpers.ts +1 -1
  78. package/src/lib/database/query-execution.ts +15 -11
  79. package/src/lib/database/write-lifecycle.ts +4 -4
  80. package/src/lib/database.ts +223 -96
  81. package/src/lib/driver.ts +333 -0
  82. package/src/lib/errors.ts +4 -4
  83. package/src/lib/migrations/directive.ts +38 -0
  84. package/src/lib/migrations/directory-name.ts +23 -0
  85. package/src/lib/migrations/journal-store.ts +42 -28
  86. package/src/lib/migrations/registry.ts +1 -1
  87. package/src/lib/migrations/runner.ts +224 -205
  88. package/src/lib/migrations-node.ts +80 -38
  89. package/src/lib/migrations.ts +62 -228
  90. package/src/lib/operators.ts +1 -1
  91. package/src/lib/query.ts +34 -1
  92. package/src/lib/sql-helpers.ts +1 -22
  93. package/src/lib/sql.ts +1 -1
  94. package/src/lib/table.ts +1 -1
  95. package/src/migrations/node.ts +1 -1
  96. package/src/migrations.ts +5 -16
  97. package/src/sql-helpers.ts +0 -1
  98. package/dist/lib/adapter.d.ts +0 -550
  99. package/dist/lib/adapter.d.ts.map +0 -1
  100. package/dist/lib/migrations/filename.d.ts +0 -12
  101. package/dist/lib/migrations/filename.d.ts.map +0 -1
  102. package/dist/lib/migrations/filename.js +0 -20
  103. package/dist/lib/migrations/helpers.d.ts +0 -11
  104. package/dist/lib/migrations/helpers.d.ts.map +0 -1
  105. package/dist/lib/migrations/helpers.js +0 -77
  106. package/dist/lib/migrations/schema-api.d.ts +0 -7
  107. package/dist/lib/migrations/schema-api.d.ts.map +0 -1
  108. package/dist/lib/migrations/schema-api.js +0 -326
  109. package/src/lib/adapter.ts +0 -653
  110. package/src/lib/migrations/filename.ts +0 -25
  111. package/src/lib/migrations/helpers.ts +0 -108
  112. package/src/lib/migrations/schema-api.ts +0 -417
  113. /package/dist/lib/{adapter.js → driver.js} +0 -0
@@ -1,7 +1,6 @@
1
- import { createDatabase, createDatabaseWithTransaction } from "../database.js";
2
- import { deleteJournalRow, ensureMigrationJournal, getBatch, hasMigrationJournal, insertJournalRow, loadJournalRows, normalizeChecksum, } from "./journal-store.js";
3
- import { resolveMigrations } from "./registry.js";
4
- import { createMigrationSchema } from "./schema-api.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';
5
4
  function assertStepOption(step) {
6
5
  if (step === undefined) {
7
6
  return;
@@ -10,28 +9,42 @@ function assertStepOption(step) {
10
9
  throw new Error('Invalid migration step option. Expected a positive integer.');
11
10
  }
12
11
  }
13
- function assertMigrateOptions(options) {
12
+ function assertMigrationOperationOptions(options) {
14
13
  if (options.to !== undefined && options.step !== undefined) {
15
14
  throw new Error('Cannot combine "to" and "step" migration options in the same run');
16
15
  }
17
16
  }
18
- function assertTargetOption(migrations, to) {
17
+ function resolveTargetOption(migrations, to) {
19
18
  if (!to) {
20
- return;
19
+ return undefined;
21
20
  }
22
- let target = migrations.find((migration) => migration.id === to);
23
- 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) {
24
25
  throw new Error('Unknown migration target: ' + to);
25
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;
26
34
  }
27
- function assertNoMigrationDrift(migrations, journal) {
35
+ async function assertMigrationIntegrity(migrations, journal, direction) {
28
36
  let migrationMap = new Map(migrations.map((migration) => [migration.id, migration]));
29
37
  for (let row of journal) {
30
38
  let migration = migrationMap.get(row.id);
31
39
  if (!migration) {
32
- 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');
33
46
  }
34
- let expected = normalizeChecksum(migration);
47
+ let expected = await computeChecksum(migration);
35
48
  if (expected !== row.checksum) {
36
49
  throw new Error('Migration checksum drift detected for "' +
37
50
  row.id +
@@ -43,190 +56,154 @@ function assertNoMigrationDrift(migrations, journal) {
43
56
  }
44
57
  }
45
58
  }
46
- function createDryRunDatabase(adapter) {
47
- let error = new Error('Cannot execute data operations while running migrations with dryRun');
48
- let throwDryRunError = async () => {
49
- throw error;
50
- };
51
- let dryRunAdapter = {
52
- dialect: adapter.dialect,
53
- capabilities: adapter.capabilities,
54
- compileSql(operation) {
55
- return adapter.compileSql(operation);
56
- },
57
- async hasTable(table) {
58
- return adapter.hasTable(table);
59
- },
60
- async hasColumn(table, column) {
61
- return adapter.hasColumn(table, column);
62
- },
63
- execute: throwDryRunError,
64
- migrate: throwDryRunError,
65
- beginTransaction: throwDryRunError,
66
- commitTransaction: throwDryRunError,
67
- rollbackTransaction: throwDryRunError,
68
- createSavepoint: throwDryRunError,
69
- rollbackToSavepoint: throwDryRunError,
70
- releaseSavepoint: throwDryRunError,
71
- };
72
- return createDatabase(dryRunAdapter);
59
+ function resolveTransactionMode(migration) {
60
+ if (migration.transaction) {
61
+ return migration.transaction;
62
+ }
63
+ let directive = parseTransactionDirective(migration.up);
64
+ if (directive) {
65
+ return directive;
66
+ }
67
+ return 'auto';
73
68
  }
74
69
  async function runMigrations(input) {
75
- 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;
76
80
  let migrations = input.migrations;
77
81
  let journalTable = input.journalTable;
78
82
  let dryRun = Boolean(input.options.dryRun);
79
- let target = input.options.to;
80
83
  let step = input.options.step;
81
- assertMigrateOptions(input.options);
84
+ assertMigrationOperationOptions(input.options);
82
85
  assertStepOption(step);
83
- assertTargetOption(migrations, target);
86
+ let target = resolveTargetOption(migrations, input.options.to);
84
87
  let sql = [];
85
- await adapter.acquireMigrationLock?.();
86
- try {
87
- let journal = [];
88
- if (dryRun) {
89
- let canReadJournal = await hasMigrationJournal(adapter, journalTable);
90
- if (canReadJournal) {
91
- 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);
92
106
  }
93
107
  }
94
- else {
95
- await ensureMigrationJournal(adapter, journalTable);
96
- journal = await loadJournalRows(adapter, journalTable);
108
+ if (target) {
109
+ toRun = toRun.filter((migration) => migration.id <= target);
97
110
  }
98
- let appliedMap = new Map(journal.map((row) => [row.id, row]));
99
- assertNoMigrationDrift(migrations, journal);
100
- let toRun = [];
101
- if (input.direction === 'up') {
102
- for (let migration of migrations) {
103
- if (!appliedMap.has(migration.id)) {
104
- toRun.push(migration);
105
- }
106
- }
107
- if (target) {
108
- toRun = toRun.filter((migration) => migration.id <= target);
109
- }
110
- if (step !== undefined) {
111
- toRun = toRun.slice(0, step);
112
- }
111
+ if (step !== undefined) {
112
+ toRun = toRun.slice(0, step);
113
113
  }
114
- else {
115
- let appliedMigrations = migrations
116
- .filter((migration) => appliedMap.has(migration.id))
117
- .reverse();
118
- if (target) {
119
- appliedMigrations = appliedMigrations.filter((migration) => migration.id >= target);
120
- }
121
- if (step !== undefined) {
122
- appliedMigrations = appliedMigrations.slice(0, step);
123
- }
124
- toRun = appliedMigrations;
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);
125
122
  }
126
- let applied = [];
127
- let reverted = [];
128
- let batch = getBatch(journal);
129
123
  for (let migration of toRun) {
130
- if (migration.migration.transaction === 'required' &&
131
- !adapter.capabilities.transactionalDdl) {
132
- throw new Error('Migration "' +
133
- migration.id +
134
- '" 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');
135
126
  }
136
- let shouldUseTransaction = !dryRun &&
137
- migration.migration.transaction !== 'none' &&
138
- adapter.capabilities.transactionalDdl;
139
- let token;
140
- if (shouldUseTransaction) {
141
- 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
+ }
142
151
  }
143
- let db = dryRun
144
- ? createDryRunDatabase(adapter)
145
- : token
146
- ? createDatabaseWithTransaction(adapter, token)
147
- : createDatabase(adapter);
148
- let schema = createMigrationSchema(db, async (operation) => {
149
- let compiled = adapter.compileSql(operation);
150
- sql.push(...compiled);
152
+ if (input.direction === 'up') {
151
153
  if (!dryRun) {
152
- await adapter.migrate({ operation, transaction: token });
153
- }
154
- }, { transaction: token });
155
- let context = {
156
- db,
157
- schema,
158
- };
159
- try {
160
- if (input.direction === 'up') {
161
- await migration.migration.up(context);
162
- if (!dryRun) {
163
- await insertJournalRow(adapter, journalTable, {
164
- id: migration.id,
165
- name: migration.name,
166
- checksum: normalizeChecksum(migration),
167
- batch,
168
- }, token);
169
- }
170
- applied.push({
154
+ await insertJournalRow(driver, journalTable, {
171
155
  id: migration.id,
172
156
  name: migration.name,
173
- status: 'applied',
174
- });
175
- }
176
- else {
177
- await migration.migration.down(context);
178
- if (!dryRun) {
179
- await deleteJournalRow(adapter, journalTable, migration.id, token);
180
- }
181
- reverted.push({
182
- id: migration.id,
183
- name: migration.name,
184
- status: 'pending',
185
- });
157
+ checksum: await computeChecksum(migration),
158
+ batch,
159
+ }, token);
186
160
  }
187
- if (token) {
188
- 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);
189
170
  }
171
+ reverted.push({
172
+ id: migration.id,
173
+ name: migration.name,
174
+ status: 'pending',
175
+ });
190
176
  }
191
- catch (error) {
192
- if (token) {
193
- 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
+ });
194
187
  }
195
- throw error;
196
188
  }
189
+ throw error;
190
+ }
191
+ if (token) {
192
+ await driver.commitTransaction(token);
197
193
  }
198
- return {
199
- applied,
200
- reverted,
201
- sql,
202
- };
203
- }
204
- finally {
205
- await adapter.releaseMigrationLock?.();
206
194
  }
195
+ return {
196
+ applied,
197
+ reverted,
198
+ sql,
199
+ };
207
200
  }
208
- /**
209
- * Creates a migration runner for applying/reverting migrations against an adapter.
210
- * @param adapter Database adapter used to compile and execute migration operations.
211
- * @param migrations Migration descriptors or registry.
212
- * @param options Optional runner configuration.
213
- * @returns A migration runner instance.
214
- * @example
215
- * ```ts
216
- * import { createMigrationRunner } from 'remix/data-table/migrations'
217
- *
218
- * let runner = createMigrationRunner(adapter, migrations, {
219
- * journalTable: 'app_migrations',
220
- * })
221
- * await runner.up()
222
- * ```
223
- */
224
- export function createMigrationRunner(adapter, migrations, options = {}) {
201
+ export function createMigrationRunner(driver, migrations, options = {}) {
225
202
  let journalTable = options.journalTable ?? 'data_table_migrations';
226
203
  return {
227
204
  async up(runOptions = {}) {
228
205
  return runMigrations({
229
- adapter,
206
+ driver,
230
207
  migrations: resolveMigrations(migrations),
231
208
  journalTable,
232
209
  direction: 'up',
@@ -235,7 +212,7 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
235
212
  },
236
213
  async down(runOptions = {}) {
237
214
  return runMigrations({
238
- adapter,
215
+ driver,
239
216
  migrations: resolveMigrations(migrations),
240
217
  journalTable,
241
218
  direction: 'down',
@@ -243,21 +220,25 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
243
220
  });
244
221
  },
245
222
  async status() {
246
- await ensureMigrationJournal(adapter, journalTable);
247
- let journal = await loadJournalRows(adapter, journalTable);
223
+ let journal = (await hasMigrationJournal(driver, journalTable))
224
+ ? await loadJournalRows(driver, journalTable)
225
+ : [];
248
226
  let journalMap = new Map(journal.map((row) => [row.id, row]));
249
227
  let sortedMigrations = resolveMigrations(migrations);
250
- return sortedMigrations.map((migration) => {
228
+ let migrationIds = new Set(sortedMigrations.map((migration) => migration.id));
229
+ let statuses = [];
230
+ for (let migration of sortedMigrations) {
251
231
  let journalRow = journalMap.get(migration.id);
252
232
  if (!journalRow) {
253
- return {
233
+ statuses.push({
254
234
  id: migration.id,
255
235
  name: migration.name,
256
236
  status: 'pending',
257
- };
237
+ });
238
+ continue;
258
239
  }
259
- let checksum = normalizeChecksum(migration);
260
- return {
240
+ let checksum = await computeChecksum(migration);
241
+ statuses.push({
261
242
  id: migration.id,
262
243
  name: migration.name,
263
244
  status: checksum === journalRow.checksum
@@ -266,8 +247,22 @@ export function createMigrationRunner(adapter, migrations, options = {}) {
266
247
  appliedAt: journalRow.appliedAt,
267
248
  batch: journalRow.batch,
268
249
  checksum: journalRow.checksum,
269
- };
270
- });
250
+ });
251
+ }
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));
271
266
  },
272
267
  };
273
268
  }
@@ -1,10 +1,13 @@
1
- import type { MigrationDescriptor } from './migrations.ts';
1
+ import type { MigrationDescriptor, Seed } from './migrations.ts';
2
2
  /**
3
- * Loads migration modules from a directory on Node.js.
3
+ * Loads SQL-file migrations from a directory on Node.js.
4
4
  *
5
- * Filenames are used to infer migration `id` and `name`.
6
- * Each file must default-export `createMigration(...)`.
7
- * @param directory Absolute or relative directory containing migration files.
5
+ * Each migration is a directory named `YYYYMMDDHHmmss_<slug>` containing:
6
+ * - `up.sql` (required)
7
+ * - `down.sql` (optional; omit for irreversible migrations)
8
+ *
9
+ * `id` and `name` are inferred from the directory name.
10
+ * @param directory Absolute or relative directory containing migration directories.
8
11
  * @returns A sorted list of loaded migration descriptors.
9
12
  * @example
10
13
  * ```ts
@@ -14,4 +17,22 @@ import type { MigrationDescriptor } from './migrations.ts';
14
17
  * ```
15
18
  */
16
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>;
17
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":"AAIA,OAAO,KAAK,EAAa,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAGrE;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAiDtF"}
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,22 +1,15 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
9
- import { createHash } from 'node:crypto';
10
1
  import { promises as fs } from 'node:fs';
11
2
  import path from 'node:path';
12
- import { pathToFileURL } from 'node:url';
13
- import { parseMigrationFilename } from "./migrations/filename.js";
3
+ import { parseMigrationDirectoryName } from './migrations/directory-name.js';
14
4
  /**
15
- * Loads migration modules from a directory on Node.js.
5
+ * Loads SQL-file migrations from a directory on Node.js.
6
+ *
7
+ * Each migration is a directory named `YYYYMMDDHHmmss_<slug>` containing:
8
+ * - `up.sql` (required)
9
+ * - `down.sql` (optional; omit for irreversible migrations)
16
10
  *
17
- * Filenames are used to infer migration `id` and `name`.
18
- * Each file must default-export `createMigration(...)`.
19
- * @param directory Absolute or relative directory containing migration files.
11
+ * `id` and `name` are inferred from the directory name.
12
+ * @param directory Absolute or relative directory containing migration directories.
20
13
  * @returns A sorted list of loaded migration descriptors.
21
14
  * @example
22
15
  * ```ts
@@ -26,40 +19,79 @@ import { parseMigrationFilename } from "./migrations/filename.js";
26
19
  * ```
27
20
  */
28
21
  export async function loadMigrations(directory) {
29
- let allFiles = (await fs.readdir(directory, { withFileTypes: true }))
30
- .filter((entry) => entry.isFile())
22
+ let entries = await fs.readdir(directory, { withFileTypes: true });
23
+ let directories = entries
24
+ .filter((entry) => entry.isDirectory())
31
25
  .map((entry) => entry.name)
32
26
  .sort((left, right) => left.localeCompare(right));
33
- let files = [];
34
- for (let file of allFiles) {
35
- if (!/\.(?:m?ts|m?js|cts|cjs)$/.test(file)) {
36
- continue;
37
- }
38
- let parsed = parseMigrationFilename(file);
39
- files.push({ file, id: parsed.id, name: parsed.name });
40
- }
41
27
  let migrations = [];
42
28
  let seenIds = new Set();
43
- for (let entry of files) {
44
- if (seenIds.has(entry.id)) {
45
- throw new Error('Duplicate migration id "' + entry.id + '" inferred from filename "' + entry.file + '"');
29
+ for (let directoryName of directories) {
30
+ let parsed = parseMigrationDirectoryName(directoryName);
31
+ if (seenIds.has(parsed.id)) {
32
+ throw new Error('Duplicate migration id "' +
33
+ parsed.id +
34
+ '" inferred from directory "' +
35
+ directoryName +
36
+ '"');
46
37
  }
47
- seenIds.add(entry.id);
48
- let fullPath = path.join(directory, entry.file);
49
- let source = await fs.readFile(fullPath, 'utf8');
50
- let checksum = createHash('sha256').update(source).digest('hex');
51
- let module = (await import(__rewriteRelativeImportExtension(pathToFileURL(fullPath).href)));
52
- let migration = module.default;
53
- if (!migration || typeof migration.up !== 'function' || typeof migration.down !== 'function') {
54
- throw new Error('Migration file "' + entry.file + '" must default-export createMigration(...)');
38
+ seenIds.add(parsed.id);
39
+ let directoryPath = path.join(directory, directoryName);
40
+ let upPath = path.join(directoryPath, 'up.sql');
41
+ let downPath = path.join(directoryPath, 'down.sql');
42
+ let up;
43
+ try {
44
+ up = await fs.readFile(upPath, 'utf8');
45
+ }
46
+ catch (error) {
47
+ if (isNodeFileNotFoundError(error)) {
48
+ throw new Error('Migration directory "' + directoryName + '" is missing up.sql');
49
+ }
50
+ throw error;
51
+ }
52
+ let down;
53
+ try {
54
+ down = await fs.readFile(downPath, 'utf8');
55
+ }
56
+ catch (error) {
57
+ if (!isNodeFileNotFoundError(error)) {
58
+ throw error;
59
+ }
55
60
  }
56
61
  migrations.push({
57
- id: entry.id,
58
- name: entry.name,
59
- path: fullPath,
60
- checksum,
61
- migration,
62
+ id: parsed.id,
63
+ name: parsed.name,
64
+ up,
65
+ down,
66
+ path: directoryPath,
62
67
  });
63
68
  }
64
69
  return migrations;
65
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
+ }
92
+ function isNodeFileNotFoundError(error) {
93
+ return (typeof error === 'object' &&
94
+ error !== null &&
95
+ 'code' in error &&
96
+ error.code === 'ENOENT');
97
+ }