kern-migrations 0.1.0 → 0.2.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/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { i as MigrationHistory, t as MigrationTarget } from "./target-GAW46krF.mjs";
1
+ import { i as MigrationHistory, t as MigrationTarget } from "./target-iyJah0hO.mjs";
2
2
  /** Options of the list module that the command `new` writes. */
3
3
  interface MigrationListModuleOptions {
4
4
  /** Path of the module, absolute or relative to the working directory, or its `file:` URL. */
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { d as moduleExtensions, i as isMigrationTargetDb, l as getMigrationNumber, s as compareMigrationNumbers, w as getErrorMessage } from "./target-Bxf6BZub.mjs";
2
- import { i as rollback, n as migrate, r as redo, t as getMigrationStatus } from "./migrate-Bd9skEp-.mjs";
1
+ import { d as moduleExtensions, i as isMigrationTargetDb, l as getMigrationNumber, s as compareMigrationNumbers, w as getErrorMessage } from "./target-BHaRgZJG.mjs";
2
+ import { i as rollback, n as migrate, r as redo, t as getMigrationStatus } from "./migrate-CxWPJJmr.mjs";
3
3
  import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { parseArgs } from "node:util";
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { n as MigrationError, t as MigrationDirection } from "./error-BOZbdvKf.mjs";
2
- import { a as MigrationHistoryOptions, c as Migration, d as MigrationStep, f as defineMigration, i as MigrationHistory, l as MigrationChange, n as MigrationTargetDb, o as MigrationLoader, r as MigrationTargetOptions, s as defineMigrations, t as MigrationTarget, u as MigrationDatabase } from "./target-GAW46krF.mjs";
2
+ import { a as MigrationHistoryOptions, c as Migration, d as MigrationStep, f as defineMigration, i as MigrationHistory, l as MigrationChange, n as MigrationTargetDb, o as MigrationLoader, r as MigrationTargetOptions, s as defineMigrations, t as MigrationTarget, u as MigrationDatabase } from "./target-iyJah0hO.mjs";
3
3
  /** Builds a step that adds a column of a SQL definition, such as `"text NOT NULL DEFAULT ''"`, to a table, and drops it on rollback. */
4
4
  export declare function addColumn(table: string, column: string, definition: string): MigrationStep;
5
5
  /**
@@ -149,6 +149,7 @@ interface MigrationStatus {
149
149
  * Inside a transaction, it runs in a nested transaction, so that the migrations commit or roll back with the enclosing one, and every migration runs in it.
150
150
  * A target that is up to date costs two statements outside a transaction; otherwise, it locks the target, so that processes that migrate it at once apply each migration once.
151
151
  * It rejects when the target has applied a migration that the history lacks before its last migration, or a migration after a pending one; applied migrations after the last one, such as of a newer version of the application, are allowed.
152
+ * In a transaction at `repeatable read` or `serializable`, it rejects when it has migrations to apply, before it locks the target, since the snapshot of the transaction misses the migrations that another process applies meanwhile.
152
153
  */
153
154
  export declare function migrate(target: MigrationTarget, history: MigrationHistory, options?: MigrateOptions): Promise<string[]>;
154
155
  /**
@@ -156,6 +157,7 @@ export declare function migrate(target: MigrationTarget, history: MigrationHisto
156
157
  *
157
158
  * It rolls them back in one transaction, unless a migration runs outside a transaction, as `migrate` applies them.
158
159
  * It rejects when the target has applied a migration that the history lacks, since it cannot roll it back.
160
+ * In a transaction at `repeatable read` or `serializable`, it rejects when the target has applied migrations, before it locks the target.
159
161
  */
160
162
  export declare function rollback(target: MigrationTarget, history: MigrationHistory, options?: RollbackOptions): Promise<string[]>;
161
163
  /**
@@ -163,6 +165,7 @@ export declare function rollback(target: MigrationTarget, history: MigrationHist
163
165
  *
164
166
  * It runs them in one transaction, unless a migration runs outside a transaction, as `migrate` applies them.
165
167
  * It rejects when the target has applied a migration that the history lacks, since it cannot roll it back.
168
+ * In a transaction at `repeatable read` or `serializable`, it rejects when the target has applied migrations, before it locks the target.
166
169
  */
167
170
  export declare function redo(target: MigrationTarget, history: MigrationHistory, options?: RedoOptions): Promise<string[]>;
168
171
  /** Lists the applied and the pending migrations of `history` on `target`, and the applied migrations that the history lacks. */
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { C as MigrationError, S as quoteLiteral, b as quoteDollar, c as defineMigrations, f as defineMigration, g as runChange, m as findStepProblem, p as findSharedStepProblem, x as quoteIdentifier } from "./target-Bxf6BZub.mjs";
2
- import { i as rollback, n as migrate, r as redo, t as getMigrationStatus } from "./migrate-Bd9skEp-.mjs";
1
+ import { C as MigrationError, S as quoteLiteral, b as quoteDollar, c as defineMigrations, f as defineMigration, g as runChange, m as findStepProblem, p as findSharedStepProblem, x as quoteIdentifier } from "./target-BHaRgZJG.mjs";
2
+ import { i as rollback, n as migrate, r as redo, t as getMigrationStatus } from "./migrate-CxWPJJmr.mjs";
3
3
  // src/helpers/step.ts
4
4
  /**
5
5
  * Inverts a step: its `down` becomes `up`, and its `up` becomes `down`, also of its changes outside a transaction, such as to build a helper that drops what another helper creates.
@@ -1,4 +1,4 @@
1
- import { _ as runMigrationSteps, a as openTarget, h as getOutsideTransactionChange, l as getMigrationNumber, n as deleteVersion, o as readVersions, r as insertVersion, t as createVersionsTable, v as runOutsideTransaction, y as toMigrationDatabase } from "./target-Bxf6BZub.mjs";
1
+ import { _ as runMigrationSteps, a as openTarget, h as getOutsideTransactionChange, l as getMigrationNumber, n as deleteVersion, o as readVersions, r as insertVersion, t as createVersionsTable, v as runOutsideTransaction, y as toMigrationDatabase } from "./target-BHaRgZJG.mjs";
2
2
  // src/migrate.ts
3
3
  /**
4
4
  * Applies the pending migrations of `history` to `target`, or the first `count` of them, and resolves to their keys.
@@ -7,12 +7,13 @@ import { _ as runMigrationSteps, a as openTarget, h as getOutsideTransactionChan
7
7
  * Inside a transaction, it runs in a nested transaction, so that the migrations commit or roll back with the enclosing one, and every migration runs in it.
8
8
  * A target that is up to date costs two statements outside a transaction; otherwise, it locks the target, so that processes that migrate it at once apply each migration once.
9
9
  * It rejects when the target has applied a migration that the history lacks before its last migration, or a migration after a pending one; applied migrations after the last one, such as of a newer version of the application, are allowed.
10
+ * In a transaction at `repeatable read` or `serializable`, it rejects when it has migrations to apply, before it locks the target, since the snapshot of the transaction misses the migrations that another process applies meanwhile.
10
11
  */
11
12
  async function migrate(target, history, options = {}) {
12
13
  const count = options.count === void 0 ? Number.POSITIVE_INFINITY : getCount(options.count, false);
13
14
  const opened = await openTarget(target, history, options);
14
15
  if (getPendingKeys(history, opened.target, opened.versions).length === 0) return [];
15
- return await runLocked(opened.target, history, (versions) => {
16
+ return await runLocked(opened, history, "migrate", (versions) => {
16
17
  const keys = getPendingKeys(history, opened.target, versions).slice(0, count);
17
18
  return {
18
19
  keys,
@@ -28,13 +29,14 @@ async function migrate(target, history, options = {}) {
28
29
  *
29
30
  * It rolls them back in one transaction, unless a migration runs outside a transaction, as `migrate` applies them.
30
31
  * It rejects when the target has applied a migration that the history lacks, since it cannot roll it back.
32
+ * In a transaction at `repeatable read` or `serializable`, it rejects when the target has applied migrations, before it locks the target.
31
33
  */
32
34
  async function rollback(target, history, options = {}) {
33
35
  const count = getCount(options.count, true);
34
36
  const opened = await openTarget(target, history, options);
35
37
  checkAppliedKeys(history, opened.target, opened.versions, "roll back the migrations of");
36
38
  if (opened.versions.applied.length === 0) return [];
37
- return await runLocked(opened.target, history, (versions) => {
39
+ return await runLocked(opened, history, "roll back the migrations of", (versions) => {
38
40
  checkAppliedKeys(history, opened.target, versions, "roll back the migrations of");
39
41
  const keys = getLastKeys(versions, count).reverse();
40
42
  return {
@@ -51,13 +53,14 @@ async function rollback(target, history, options = {}) {
51
53
  *
52
54
  * It runs them in one transaction, unless a migration runs outside a transaction, as `migrate` applies them.
53
55
  * It rejects when the target has applied a migration that the history lacks, since it cannot roll it back.
56
+ * In a transaction at `repeatable read` or `serializable`, it rejects when the target has applied migrations, before it locks the target.
54
57
  */
55
58
  async function redo(target, history, options = {}) {
56
59
  const count = getCount(options.count, false);
57
60
  const opened = await openTarget(target, history, options);
58
61
  checkAppliedKeys(history, opened.target, opened.versions, "redo the migrations of");
59
62
  if (opened.versions.applied.length === 0) return [];
60
- return await runLocked(opened.target, history, (versions) => {
63
+ return await runLocked(opened, history, "redo the migrations of", (versions) => {
61
64
  checkAppliedKeys(history, opened.target, versions, "redo the migrations of");
62
65
  const keys = getLastKeys(versions, count);
63
66
  const runs = [];
@@ -119,16 +122,19 @@ function checkAppliedKeys(history, target, versions, action) {
119
122
  if (unknown !== void 0) throw new Error(`Cannot ${action} schema "${target.schemaName}": migration ${unknown} is applied, but the history lacks it`);
120
123
  }
121
124
  /**
122
- * Locks `target`, reads its versions again, loads the migrations that `plan` chooses from them, runs them, and resolves to the keys of the plan.
125
+ * Locks the target of `opened`, reads its versions again, loads the migrations that `plan` chooses from them, runs them, and resolves to the keys of the plan.
123
126
  *
124
- * The lock belongs to the qualified name of the table of versions, so that different targets migrate at once.
127
+ * The lock belongs to the qualified name of the table of versions, so that different targets and histories of one target migrate at once.
125
128
  * In a transaction block, such as of an enclosing transaction or a test, it runs every migration in a nested transaction that locks the target, with the search path of the migrations.
126
129
  * Outside one, it runs them in a session with that search path, which polls for the lock, since a transaction that waits for it would keep a snapshot, which `CREATE INDEX CONCURRENTLY` of the session that holds the lock waits for.
127
130
  * It decides which migrations run outside a transaction only from the steps that it loads under the lock, since another process may change the applied migrations before the lock.
131
+ * `action` names the call in its errors, such as `roll back the migrations of`.
128
132
  */
129
- async function runLocked(target, history, plan) {
133
+ async function runLocked(opened, history, action, plan) {
134
+ const { target } = opened;
130
135
  const { database } = target;
131
136
  const inTransaction = database.isInTransactionBlock();
137
+ if (inTransaction && snapshotIsolationLevels.has(opened.isolation)) throw new Error(`Cannot ${action} schema "${target.schemaName}" in a transaction at isolation level ${opened.isolation}, whose snapshot misses the migrations that another process applies or rolls back meanwhile; run the call outside a transaction or in a transaction at read committed`);
132
138
  const run = async () => {
133
139
  await (inTransaction ? database.lock(target.table) : database.pollLock(target.table));
134
140
  const versions = await readVersions(target);
@@ -140,6 +146,18 @@ async function runLocked(target, history, plan) {
140
146
  const options = { searchPath: target.searchPath };
141
147
  return inTransaction ? await database.transaction(run, options) : await database.session(run, options);
142
148
  }
149
+ /**
150
+ * Isolation levels whose transaction reads from one snapshot, which misses what other transactions commit after it.
151
+ *
152
+ * A call in a transaction block reads the versions under the lock from that snapshot, so it would apply again the migrations that another process has applied meanwhile, or roll back others than the last ones.
153
+ */
154
+ const snapshotIsolationLevels = /* @__PURE__ */ new Set(["repeatable read", "serializable"]);
155
+ /**
156
+ * Options of the transactions of a call in a session, whatever the default isolation level of the database.
157
+ *
158
+ * Creating the target schema and `createExtension` check again under a lock what another transaction has committed meanwhile, which a transaction at `read committed` sees, since each statement takes a new snapshot there.
159
+ */
160
+ const sessionTransactionOptions = { isolation: "read committed" };
143
161
  /** Runs `runs` in order in the transaction of a call, creating the schema and the table of versions that `versions` lacks before them. */
144
162
  async function runInTransaction(target, versions, runs) {
145
163
  if (runs.length === 0) return;
@@ -149,6 +167,8 @@ async function runInTransaction(target, versions, runs) {
149
167
  /**
150
168
  * Runs `runs` in order in the session of a call: a migration that has a change of its direction outside a transaction runs it alone and is recorded after it in a statement of its own, and each sequence of other migrations between such migrations runs in one transaction.
151
169
  *
170
+ * Before a first migration outside a transaction, it creates the schema and the table of versions in a transaction of their own.
171
+ *
152
172
  * The migrations before a failure stay committed, and a migration whose change fails outside a transaction keeps its record, so that `migrate` runs its `up` again, and `rollback` its `down`.
153
173
  */
154
174
  async function runInSession(target, versions, runs) {
@@ -157,7 +177,7 @@ async function runInSession(target, versions, runs) {
157
177
  const run = runs[start];
158
178
  const change = getOutsideTransactionChange(run.steps, run.direction);
159
179
  if (change !== void 0) {
160
- if (start === 0) await createVersionsTable(target, versions);
180
+ if (start === 0) await target.database.transaction(() => createVersionsTable(target, versions), sessionTransactionOptions);
161
181
  await runOutsideTransaction(toMigrationDatabase(target.database), run.key, run.direction, change);
162
182
  await recordRun(target, run);
163
183
  start++;
@@ -170,7 +190,7 @@ async function runInSession(target, versions, runs) {
170
190
  await target.database.transaction(async () => {
171
191
  if (first) await createVersionsTable(target, versions);
172
192
  await runMigrations(target, batch);
173
- });
193
+ }, sessionTransactionOptions);
174
194
  start = end;
175
195
  }
176
196
  }
@@ -108,10 +108,14 @@ async function runOutsideTransaction(database, key, direction, change) {
108
108
  });
109
109
  }
110
110
  }
111
- /** Returns the steps of the default export of the module of migration `key`, rejecting a value that is not a migration. */
112
- function toMigrationSteps(key, value) {
111
+ /**
112
+ * Returns the steps of migration `key`, rejecting a value that is not a migration.
113
+ *
114
+ * `requirement` completes the message that rejects a value that is neither a step nor a non-empty array of steps, such as `be a step or a non-empty array of steps`.
115
+ */
116
+ function toMigrationSteps(key, value, requirement) {
113
117
  const steps = Array.isArray(value) ? value : [value];
114
- if (typeof value !== "object" || value === null || steps.length === 0) throw new MigrationError(`Migration ${key} must export a step or a non-empty array of steps as its default export`, { key });
118
+ if (typeof value !== "object" || value === null || steps.length === 0) throw new MigrationError(`Migration ${key} must ${requirement}`, { key });
115
119
  for (let index = 0; index < steps.length; index++) {
116
120
  const step = steps[index];
117
121
  const problem = findStepProblem(step) ?? (steps.length > 1 ? findSharedStepProblem(step) : void 0);
@@ -198,14 +202,14 @@ function compareMigrationNumbers(left, right) {
198
202
  return left < right ? -1 : left > right ? 1 : 0;
199
203
  }
200
204
  /**
201
- * Defines a history of migrations from the loaders of their modules, by key or by path, as `import.meta.glob` returns them.
205
+ * Defines a history of migrations from the loaders of their modules or the migrations themselves, by key or by path as `import.meta.glob` returns them.
202
206
  *
203
207
  * A key is the file name without directories and extension, of the form `<number>_<name>`, such as `0001_create_form`, and the numbers order the history.
204
- * It rejects a key of another form, a path of a file of migration tests, and two keys of one number, such as migrations of two branches that took one number.
208
+ * It rejects a key of another form, a path of a file of migration tests, two keys of one number, such as migrations of two branches that took one number, and a value that is neither a loader nor a migration.
205
209
  */
206
- function defineMigrations(loaders, options = {}) {
210
+ function defineMigrations(migrations, options = {}) {
207
211
  const entries = [];
208
- for (const [name, load] of Object.entries(loaders)) {
212
+ for (const [name, value] of Object.entries(migrations)) {
209
213
  const isPath = name.includes("/");
210
214
  if (isPath && getMigrationTestKey(name.slice(name.lastIndexOf("/") + 1)) !== void 0) throw new Error(`${name} is a file of migration tests rather than a migration: exclude migration tests from the modules of the history, such as with "!./migrations/*.migration-test.ts" in the patterns of import.meta.glob`);
211
215
  const key = isPath ? getFileKey(name) : name;
@@ -214,7 +218,7 @@ function defineMigrations(loaders, options = {}) {
214
218
  entries.push({
215
219
  key,
216
220
  number,
217
- load
221
+ load: toEntryLoader(key, value)
218
222
  });
219
223
  }
220
224
  entries.sort((left, right) => compareMigrationNumbers(left.number, right.number));
@@ -232,19 +236,29 @@ function defineMigrations(loaders, options = {}) {
232
236
  async load(key) {
233
237
  const entry = byKey.get(key);
234
238
  if (entry === void 0) throw new Error(`The history has no migration ${key}`);
235
- let module;
236
- try {
237
- module = await entry.load();
238
- } catch (error) {
239
- throw new MigrationError(`Cannot load migration ${key}: ${getErrorMessage(error)}`, {
240
- key,
241
- cause: error
242
- });
243
- }
244
- return toMigrationSteps(key, typeof module === "object" && module !== null ? module.default : void 0);
239
+ return await entry.load();
245
240
  }
246
241
  };
247
242
  }
243
+ /** Returns the function that gives the steps of migration `key`: it checks a migration given as a value at once, and loads the module of a loader only when the function is called. */
244
+ function toEntryLoader(key, value) {
245
+ if (typeof value === "function") return () => loadModule(key, value);
246
+ const steps = toMigrationSteps(key, value, "be the loader of its module, a step, or a non-empty array of steps");
247
+ return async () => steps;
248
+ }
249
+ /** Loads the module of migration `key` with `loader` and returns the steps of its default export. */
250
+ async function loadModule(key, loader) {
251
+ let module;
252
+ try {
253
+ module = await loader();
254
+ } catch (error) {
255
+ throw new MigrationError(`Cannot load migration ${key}: ${getErrorMessage(error)}`, {
256
+ key,
257
+ cause: error
258
+ });
259
+ }
260
+ return toMigrationSteps(key, typeof module === "object" && module !== null ? module.default : void 0, "export a step or a non-empty array of steps as its default export");
261
+ }
248
262
  /** Returns the key of a migration module at `path`: its file name without directories and extension. */
249
263
  function getFileKey(path) {
250
264
  const fileName = path.slice(path.lastIndexOf("/") + 1);
@@ -274,7 +288,8 @@ async function openTarget(target, history, options) {
274
288
  };
275
289
  return {
276
290
  target: resolved,
277
- versions: await readApplied(resolved, state)
291
+ versions: await readApplied(resolved, state),
292
+ isolation: state.isolation
278
293
  };
279
294
  }
280
295
  /** Reads the versions of a resolved target again, such as after locking it. */
@@ -288,9 +303,20 @@ async function readVersions(target) {
288
303
  * Checking `versions` first keeps PostgreSQL from sending a notice of an existing schema, which postgres.js logs by default.
289
304
  */
290
305
  async function createVersionsTable(target, versions) {
291
- if (!versions.schemaExists) await execute(target.database, `CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(target.schemaName)}`);
306
+ if (!versions.schemaExists) await execute(target.database, buildCreateSchema(target.schemaName));
292
307
  if (!versions.tableExists) await execute(target.database, `CREATE TABLE IF NOT EXISTS ${target.table} (key text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())`);
293
308
  }
309
+ /**
310
+ * Builds a statement that creates schema `schemaName` unless it exists, checking it again under a transaction-level advisory lock of the schema.
311
+ *
312
+ * Histories of one target do not share the lock of the target, and `IF NOT EXISTS` misses a schema that another transaction has created and not committed, so without this lock a history that creates the schema while another one does fails on the unique index of `pg_namespace` once the other commits.
313
+ * The lock lasts until the transaction of the statement ends, and the check under it sees a schema that another history committed meanwhile under `READ COMMITTED`, since each statement of the block takes a new snapshot.
314
+ * The key of the lock includes the schema, so that targets of different schemas, such as new tenants, do not wait for each other.
315
+ */
316
+ function buildCreateSchema(schemaName) {
317
+ const name = quoteLiteral(schemaName);
318
+ return `DO ${quoteDollar(`BEGIN PERFORM pg_advisory_xact_lock(hashtextextended(${quoteLiteral(`kern-migrations schema ${schemaName}`)}, 0)); IF NOT EXISTS (SELECT FROM pg_catalog.pg_namespace WHERE nspname = ${name}) THEN CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schemaName)}; END IF; END`)}`;
319
+ }
294
320
  /** Records migration `key` as applied. */
295
321
  async function insertVersion(target, key) {
296
322
  await execute(target.database, `INSERT INTO ${target.table} (key) VALUES ($1)`, [key]);
@@ -300,14 +326,14 @@ async function deleteVersion(target, key) {
300
326
  await execute(target.database, `DELETE FROM ${target.table} WHERE key = $1`, [key]);
301
327
  }
302
328
  /**
303
- * Resolves `schemaName`, or `current_schema()` when it is `null`, and checks that the schema and its table of versions exist, in one statement.
329
+ * Resolves `schemaName`, or `current_schema()` when it is `null`, checks that the schema and its table of versions exist, and reads the isolation level of the transaction, in one statement.
304
330
  *
305
331
  * It reads `pg_catalog` rather than calling `to_regclass`, whose lookups in the catalog cache of the connection miss a schema that another transaction committed while this one waited for the lock of the target.
306
332
  * It rejects a schema or table name longer than `max_identifier_length` bytes, which PostgreSQL would truncate, so that the check never misses a schema or a table that the migrator created.
307
333
  */
308
334
  async function readState(database, schemaName, tableName) {
309
- const { rows } = await execute(database, `SELECT target.name, namespace.oid IS NOT NULL, EXISTS (SELECT FROM pg_catalog.pg_class WHERE relnamespace = namespace.oid AND relname = $2::text), current_setting('max_identifier_length'), octet_length(target.name), octet_length($2::text) FROM (SELECT coalesce($1::text, current_schema()) AS name) AS target LEFT JOIN pg_catalog.pg_namespace AS namespace ON namespace.nspname = target.name`, [schemaName, tableName]);
310
- const [name, schemaExists, tableExists, maxLength, schemaLength, tableLength] = rows[0];
335
+ const { rows } = await execute(database, `SELECT target.name, namespace.oid IS NOT NULL, EXISTS (SELECT FROM pg_catalog.pg_class WHERE relnamespace = namespace.oid AND relname = $2::text), current_setting('max_identifier_length'), octet_length(target.name), octet_length($2::text), current_setting('transaction_isolation') FROM (SELECT coalesce($1::text, current_schema()) AS name) AS target LEFT JOIN pg_catalog.pg_namespace AS namespace ON namespace.nspname = target.name`, [schemaName, tableName]);
336
+ const [name, schemaExists, tableExists, maxLength, schemaLength, tableLength, isolation] = rows[0];
311
337
  if (name === null || name === void 0) throw new Error("Cannot resolve the target schema: current_schema() is NULL, since no schema of the search path exists; pass schemaName");
312
338
  const limit = Number(maxLength);
313
339
  for (const [kind, value, length] of [[
@@ -322,7 +348,8 @@ async function readState(database, schemaName, tableName) {
322
348
  return {
323
349
  schemaName: name,
324
350
  schemaExists: schemaExists === "t",
325
- tableExists: tableExists === "t"
351
+ tableExists: tableExists === "t",
352
+ isolation
326
353
  };
327
354
  }
328
355
  /** Reads the applied keys of a target whose table of versions `state` found. */
@@ -51,19 +51,19 @@ interface MigrationHistory {
51
51
  /** PostgreSQL schemas that the search path of the migrations lists after the target schema. */
52
52
  readonly searchPath: readonly string[];
53
53
  /**
54
- * Loads the module of the migration of `key` and returns its steps.
54
+ * Returns the steps of the migration of `key`, loading its module when the history has its loader.
55
55
  *
56
56
  * It rejects with a `MigrationError` when the module fails to load or does not export a migration.
57
57
  */
58
58
  load(key: string): Promise<readonly MigrationStep[]>;
59
59
  }
60
60
  /**
61
- * Defines a history of migrations from the loaders of their modules, by key or by path, as `import.meta.glob` returns them.
61
+ * Defines a history of migrations from the loaders of their modules or the migrations themselves, by key or by path as `import.meta.glob` returns them.
62
62
  *
63
63
  * A key is the file name without directories and extension, of the form `<number>_<name>`, such as `0001_create_form`, and the numbers order the history.
64
- * It rejects a key of another form, a path of a file of migration tests, and two keys of one number, such as migrations of two branches that took one number.
64
+ * It rejects a key of another form, a path of a file of migration tests, two keys of one number, such as migrations of two branches that took one number, and a value that is neither a loader nor a migration.
65
65
  */
66
- declare function defineMigrations(loaders: Readonly<Record<string, MigrationLoader>>, options?: MigrationHistoryOptions): MigrationHistory;
66
+ declare function defineMigrations(migrations: Readonly<Record<string, MigrationLoader | Migration>>, options?: MigrationHistoryOptions): MigrationHistory;
67
67
  /**
68
68
  * Target of migrations: a database and a PostgreSQL schema, the target schema.
69
69
  *
@@ -1,5 +1,5 @@
1
1
  import { n as MigrationError } from "./error-BOZbdvKf.mjs";
2
- import { i as MigrationHistory, t as MigrationTarget, u as MigrationDatabase } from "./target-GAW46krF.mjs";
2
+ import { i as MigrationHistory, t as MigrationTarget, u as MigrationDatabase } from "./target-iyJah0hO.mjs";
3
3
  /** Callback of a migration test that prepares or checks the data of the schema through the database of the migration. */
4
4
  type MigrationTestCallback = (database: MigrationDatabase) => unknown;
5
5
  /** Type that rejects `assertUpError` of a migration test together with `assertUp` or `assertDown`. */
package/dist/testing.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as MigrationError, _ as runMigrationSteps, i as isMigrationTargetDb, u as getMigrationTestKey, w as getErrorMessage, x as quoteIdentifier, y as toMigrationDatabase } from "./target-Bxf6BZub.mjs";
1
+ import { C as MigrationError, _ as runMigrationSteps, i as isMigrationTargetDb, u as getMigrationTestKey, w as getErrorMessage, x as quoteIdentifier, y as toMigrationDatabase } from "./target-BHaRgZJG.mjs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { readdir } from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kern-migrations",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Migrations of PostgreSQL schemas for kern-db and kern-orm",
5
5
  "keywords": [
6
6
  "kern-db",