@coffre/db 0.1.13 → 0.1.14

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/migrate.d.ts CHANGED
@@ -8,15 +8,30 @@ export declare function restrictDatabase(client: Pick<PoolClient, 'query'>): Pro
8
8
  * in the built package, where the build copies them.
9
9
  */
10
10
  export declare function migrationsFolder(engine: Engine): string;
11
- /** A migration run's plan: `applied` of the `total` migrations there before it, the rest applied by it. */
11
+ /**
12
+ * A migration run's plan: `applied` of the `total` migrations there before
13
+ * it, and the tags of the rest, which it applies.
14
+ */
12
15
  export type MigrationPlan = {
13
16
  applied: number;
14
17
  total: number;
18
+ pending: string[];
15
19
  };
20
+ /**
21
+ * Which of this version's migrations a database has applied, and which it
22
+ * has not, without changing it or waiting on a migration under way. A
23
+ * history that is not a prefix of this version's is refused, as
24
+ * `migrateDatabase` refuses it.
25
+ */
26
+ export declare function migrationStatus(url: string): Promise<{
27
+ applied: string[];
28
+ pending: string[];
29
+ }>;
16
30
  /**
17
31
  * Apply missing migrations, check their history and reassert database
18
- * privileges. `onPlan` hears how many there are to apply before they are:
19
- * all of them go in one call, with no word of each.
32
+ * privileges. `onPlan` hears what there is to apply before it is, under
33
+ * the migration lock, and may stop the run by throwing: all of it then goes
34
+ * in one call, with no word of each.
20
35
  */
21
- export declare function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan) => void): Promise<void>;
36
+ export declare function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan) => void | Promise<void>): Promise<void>;
22
37
  //#endregion
package/dist/migrate.js CHANGED
@@ -105,9 +105,35 @@ async function sqliteMigrator(url) {
105
105
  };
106
106
  }
107
107
  /**
108
+ * Which of this version's migrations a database has applied, and which it
109
+ * has not, without changing it or waiting on a migration under way. A
110
+ * history that is not a prefix of this version's is refused, as
111
+ * `migrateDatabase` refuses it.
112
+ */
113
+ async function migrationStatus(url) {
114
+ const engine = engineOfUrl(url);
115
+ const expected = await expectedMigrations(engine);
116
+ const migrator = await {
117
+ postgres: postgresMigrator,
118
+ sqlite: sqliteMigrator
119
+ }[engine](url);
120
+ try {
121
+ const history = await migrator.applied();
122
+ verifyHistory(expected, history, false);
123
+ const applied = history?.length ?? 0;
124
+ return {
125
+ applied: expected.slice(0, applied).map((entry) => entry.tag),
126
+ pending: expected.slice(applied).map((entry) => entry.tag)
127
+ };
128
+ } finally {
129
+ await migrator.close();
130
+ }
131
+ }
132
+ /**
108
133
  * Apply missing migrations, check their history and reassert database
109
- * privileges. `onPlan` hears how many there are to apply before they are:
110
- * all of them go in one call, with no word of each.
134
+ * privileges. `onPlan` hears what there is to apply before it is, under
135
+ * the migration lock, and may stop the run by throwing: all of it then goes
136
+ * in one call, with no word of each.
111
137
  */
112
138
  async function migrateDatabase(url, onPlan) {
113
139
  const engine = engineOfUrl(url);
@@ -122,9 +148,11 @@ async function migrateDatabase(url, onPlan) {
122
148
  locked = true;
123
149
  const applied = await migrator.applied();
124
150
  verifyHistory(expected, applied, false);
125
- onPlan?.({
126
- applied: applied?.length ?? 0,
127
- total: expected.length
151
+ const done = applied?.length ?? 0;
152
+ await onPlan?.({
153
+ applied: done,
154
+ total: expected.length,
155
+ pending: expected.slice(done).map((entry) => entry.tag)
128
156
  });
129
157
  await migrator.migrate(migrationsFolder(engine));
130
158
  verifyHistory(expected, await migrator.applied(), true);
@@ -138,4 +166,4 @@ async function migrateDatabase(url, onPlan) {
138
166
  }
139
167
  }
140
168
  //#endregion
141
- export { migrateDatabase, migrationsFolder, restrictDatabase };
169
+ export { migrateDatabase, migrationStatus, migrationsFolder, restrictDatabase };
@@ -10,4 +10,12 @@ import { Engine } from "./dialect.js";
10
10
  */
11
11
  export declare const REQUIRED_MIGRATIONS: Record<Engine, number>;
12
12
  export declare function requiredMigrations(db: Queryable): number;
13
+ /**
14
+ * Every migration this version ships, oldest first, per engine: read from
15
+ * the journals at build time, so a Worker, which has no files, knows them
16
+ * too. A database that has applied fewer is behind this code, whatever
17
+ * `REQUIRED_MIGRATIONS` lets it run on.
18
+ */
19
+ export declare const KNOWN_MIGRATIONS: Record<Engine, readonly string[]>;
20
+ export declare function knownMigrations(db: Queryable): readonly string[];
13
21
  //#endregion
@@ -1,4 +1,34 @@
1
1
  import { engineOf } from "./dialect.js";
2
+ //#region src/migrations/postgres/meta/_journal.json
3
+ var entries$1 = [{
4
+ "idx": 0,
5
+ "version": "7",
6
+ "when": 1790930587507,
7
+ "tag": "0000_baseline",
8
+ "breakpoints": true
9
+ }, {
10
+ "idx": 1,
11
+ "version": "7",
12
+ "when": 1791062292678,
13
+ "tag": "0001_remove_syncs",
14
+ "breakpoints": true
15
+ }];
16
+ //#endregion
17
+ //#region src/migrations/sqlite/meta/_journal.json
18
+ var entries = [{
19
+ "idx": 0,
20
+ "version": "6",
21
+ "when": 1790874462505,
22
+ "tag": "0000_baseline",
23
+ "breakpoints": true
24
+ }, {
25
+ "idx": 1,
26
+ "version": "6",
27
+ "when": 1791062293531,
28
+ "tag": "0001_remove_syncs",
29
+ "breakpoints": true
30
+ }];
31
+ //#endregion
2
32
  //#region src/schema-version.ts
3
33
  /**
4
34
  * Minimum migration prefix required by this application image, per engine:
@@ -14,5 +44,18 @@ const REQUIRED_MIGRATIONS = {
14
44
  function requiredMigrations(db) {
15
45
  return REQUIRED_MIGRATIONS[engineOf(db)];
16
46
  }
47
+ /**
48
+ * Every migration this version ships, oldest first, per engine: read from
49
+ * the journals at build time, so a Worker, which has no files, knows them
50
+ * too. A database that has applied fewer is behind this code, whatever
51
+ * `REQUIRED_MIGRATIONS` lets it run on.
52
+ */
53
+ const KNOWN_MIGRATIONS = {
54
+ postgres: entries$1.map((entry) => entry.tag),
55
+ sqlite: entries.map((entry) => entry.tag)
56
+ };
57
+ function knownMigrations(db) {
58
+ return KNOWN_MIGRATIONS[engineOf(db)];
59
+ }
17
60
  //#endregion
18
- export { REQUIRED_MIGRATIONS, requiredMigrations };
61
+ export { KNOWN_MIGRATIONS, REQUIRED_MIGRATIONS, knownMigrations, requiredMigrations };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/db",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "coffre's database: the schema, its migrations and the connections, on Postgres or SQLite.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -74,7 +74,7 @@
74
74
  "drizzle-orm": "0.45.2",
75
75
  "pg": "8.16.3",
76
76
  "pg-connection-string": "2.14.0",
77
- "@coffre/core": "0.1.13"
77
+ "@coffre/core": "0.1.14"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/node": "26.1.1",
package/src/migrate.ts CHANGED
@@ -185,15 +185,45 @@ async function sqliteMigrator(url: string): Promise<Migrator> {
185
185
  };
186
186
  }
187
187
 
188
- /** A migration run's plan: `applied` of the `total` migrations there before it, the rest applied by it. */
189
- export type MigrationPlan = { applied: number; total: number };
188
+ /**
189
+ * A migration run's plan: `applied` of the `total` migrations there before
190
+ * it, and the tags of the rest, which it applies.
191
+ */
192
+ export type MigrationPlan = { applied: number; total: number; pending: string[] };
193
+
194
+ /**
195
+ * Which of this version's migrations a database has applied, and which it
196
+ * has not, without changing it or waiting on a migration under way. A
197
+ * history that is not a prefix of this version's is refused, as
198
+ * `migrateDatabase` refuses it.
199
+ */
200
+ export async function migrationStatus(url: string): Promise<{ applied: string[]; pending: string[] }> {
201
+ const engine = engineOfUrl(url);
202
+ const expected = await expectedMigrations(engine);
203
+ const migrator = await { postgres: postgresMigrator, sqlite: sqliteMigrator }[engine](url);
204
+ try {
205
+ const history = await migrator.applied();
206
+ verifyHistory(expected, history, false);
207
+ const applied = history?.length ?? 0;
208
+ return {
209
+ applied: expected.slice(0, applied).map((entry) => entry.tag),
210
+ pending: expected.slice(applied).map((entry) => entry.tag),
211
+ };
212
+ } finally {
213
+ await migrator.close();
214
+ }
215
+ }
190
216
 
191
217
  /**
192
218
  * Apply missing migrations, check their history and reassert database
193
- * privileges. `onPlan` hears how many there are to apply before they are:
194
- * all of them go in one call, with no word of each.
219
+ * privileges. `onPlan` hears what there is to apply before it is, under
220
+ * the migration lock, and may stop the run by throwing: all of it then goes
221
+ * in one call, with no word of each.
195
222
  */
196
- export async function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan) => void): Promise<void> {
223
+ export async function migrateDatabase(
224
+ url: string,
225
+ onPlan?: (plan: MigrationPlan) => void | Promise<void>,
226
+ ): Promise<void> {
197
227
  const engine = engineOfUrl(url);
198
228
  const expected = await expectedMigrations(engine);
199
229
  const migrator = await { postgres: postgresMigrator, sqlite: sqliteMigrator }[engine](url);
@@ -205,7 +235,8 @@ export async function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan
205
235
 
206
236
  const applied = await migrator.applied();
207
237
  verifyHistory(expected, applied, false);
208
- onPlan?.({ applied: applied?.length ?? 0, total: expected.length });
238
+ const done = applied?.length ?? 0;
239
+ await onPlan?.({ applied: done, total: expected.length, pending: expected.slice(done).map((entry) => entry.tag) });
209
240
  await migrator.migrate(migrationsFolder(engine));
210
241
  verifyHistory(expected, await migrator.applied(), true);
211
242
  await migrator.restrict();
@@ -1,5 +1,7 @@
1
1
  import type { Queryable } from './database.ts';
2
2
  import { engineOf, type Engine } from './dialect.ts';
3
+ import postgresJournal from './migrations/postgres/meta/_journal.json' with { type: 'json' };
4
+ import sqliteJournal from './migrations/sqlite/meta/_journal.json' with { type: 'json' };
3
5
 
4
6
  /**
5
7
  * Minimum migration prefix required by this application image, per engine:
@@ -13,3 +15,18 @@ export const REQUIRED_MIGRATIONS: Record<Engine, number> = { postgres: 1, sqlite
13
15
  export function requiredMigrations(db: Queryable): number {
14
16
  return REQUIRED_MIGRATIONS[engineOf(db)];
15
17
  }
18
+
19
+ /**
20
+ * Every migration this version ships, oldest first, per engine: read from
21
+ * the journals at build time, so a Worker, which has no files, knows them
22
+ * too. A database that has applied fewer is behind this code, whatever
23
+ * `REQUIRED_MIGRATIONS` lets it run on.
24
+ */
25
+ export const KNOWN_MIGRATIONS: Record<Engine, readonly string[]> = {
26
+ postgres: postgresJournal.entries.map((entry) => entry.tag),
27
+ sqlite: sqliteJournal.entries.map((entry) => entry.tag),
28
+ };
29
+
30
+ export function knownMigrations(db: Queryable): readonly string[] {
31
+ return KNOWN_MIGRATIONS[engineOf(db)];
32
+ }