@coffre/db 0.1.1 → 0.1.3

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/log.d.ts CHANGED
@@ -68,5 +68,11 @@ export declare function lockLogHead(tx: Transaction): Promise<{
68
68
  nextSeq: bigint;
69
69
  headHash: Buffer;
70
70
  }>;
71
- export declare function appendEntries(tx: Transaction, key: LogKey, entries: readonly NewEntry[]): Promise<Appended>;
71
+ /**
72
+ * Append `entries` under `key`. `check`, when given, runs once the head is
73
+ * locked and before anything is written: a writer's own condition on the
74
+ * log as it stands, which no other append can change until this one
75
+ * commits. It throws to refuse.
76
+ */
77
+ export declare function appendEntries(tx: Transaction, key: LogKey, entries: readonly NewEntry[], check?: (tx: Transaction) => Promise<void>): Promise<Appended>;
72
78
  //#endregion
package/dist/log.js CHANGED
@@ -60,10 +60,17 @@ async function lockLogHead(tx) {
60
60
  if (head === void 0) throw new Error("audit_chain_head is missing; refusing to write an unchained entry");
61
61
  return head;
62
62
  }
63
- async function appendEntries(tx, key, entries) {
63
+ /**
64
+ * Append `entries` under `key`. `check`, when given, runs once the head is
65
+ * locked and before anything is written: a writer's own condition on the
66
+ * log as it stands, which no other append can change until this one
67
+ * commits. It throws to refuse.
68
+ */
69
+ async function appendEntries(tx, key, entries, check) {
64
70
  if (entries.length === 0) throw new Error("appendEntries called with no entries");
65
71
  const { auditChainHead, auditLog } = tablesOf(tx);
66
72
  const head = await lockLogHead(tx);
73
+ await check?.(tx);
67
74
  const [{ now }] = await tx.select({ now: clockMillis(tx) }).from(auditChainHead);
68
75
  const [last] = await tx.select({
69
76
  seq: auditLog.seq,
package/dist/migrate.d.ts CHANGED
@@ -5,6 +5,15 @@ import { Engine } from "./dialect.js";
5
5
  * in the built package, where the build copies them.
6
6
  */
7
7
  export declare function migrationsFolder(engine: Engine): string;
8
- /** Apply missing migrations, check their history and reassert database privileges. */
9
- export declare function migrateDatabase(url: string): Promise<void>;
8
+ /** A migration run's plan: `applied` of the `total` migrations there before it, the rest applied by it. */
9
+ export type MigrationPlan = {
10
+ applied: number;
11
+ total: number;
12
+ };
13
+ /**
14
+ * Apply missing migrations, check their history and reassert database
15
+ * privileges. `onPlan` hears how many there are to apply before they are:
16
+ * all of them go in one call, with no word of each.
17
+ */
18
+ export declare function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan) => void): Promise<void>;
10
19
  //#endregion
package/dist/migrate.js CHANGED
@@ -102,8 +102,12 @@ async function sqliteMigrator(url) {
102
102
  close
103
103
  };
104
104
  }
105
- /** Apply missing migrations, check their history and reassert database privileges. */
106
- async function migrateDatabase(url) {
105
+ /**
106
+ * Apply missing migrations, check their history and reassert database
107
+ * privileges. `onPlan` hears how many there are to apply before they are:
108
+ * all of them go in one call, with no word of each.
109
+ */
110
+ async function migrateDatabase(url, onPlan) {
107
111
  const engine = engineOfUrl(url);
108
112
  const expected = await expectedMigrations(engine);
109
113
  const migrator = await {
@@ -114,7 +118,12 @@ async function migrateDatabase(url) {
114
118
  try {
115
119
  await migrator.lock();
116
120
  locked = true;
117
- verifyHistory(expected, await migrator.applied(), false);
121
+ const applied = await migrator.applied();
122
+ verifyHistory(expected, applied, false);
123
+ onPlan?.({
124
+ applied: applied?.length ?? 0,
125
+ total: expected.length
126
+ });
118
127
  await migrator.migrate(migrationsFolder(engine));
119
128
  verifyHistory(expected, await migrator.applied(), true);
120
129
  await migrator.restrict();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffre/db",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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.1"
77
+ "@coffre/core": "0.1.3"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@types/node": "26.1.1",
package/src/log.ts CHANGED
@@ -111,12 +111,24 @@ export async function lockLogHead(tx: Transaction): Promise<{ nextSeq: bigint; h
111
111
  return head;
112
112
  }
113
113
 
114
- export async function appendEntries(tx: Transaction, key: LogKey, entries: readonly NewEntry[]): Promise<Appended> {
114
+ /**
115
+ * Append `entries` under `key`. `check`, when given, runs once the head is
116
+ * locked and before anything is written: a writer's own condition on the
117
+ * log as it stands, which no other append can change until this one
118
+ * commits. It throws to refuse.
119
+ */
120
+ export async function appendEntries(
121
+ tx: Transaction,
122
+ key: LogKey,
123
+ entries: readonly NewEntry[],
124
+ check?: (tx: Transaction) => Promise<void>,
125
+ ): Promise<Appended> {
115
126
  if (entries.length === 0) throw new Error('appendEntries called with no entries');
116
127
  const { auditChainHead, auditLog } = tablesOf(tx);
117
128
 
118
129
  // Every append queues here, if its transaction has not already.
119
130
  const head = await lockLogHead(tx);
131
+ await check?.(tx);
120
132
  const [{ now }] = await tx.select({ now: clockMillis(tx) }).from(auditChainHead);
121
133
  const [last] = await tx
122
134
  .select({ seq: auditLog.seq, hash: auditLog.hash })
package/src/migrate.ts CHANGED
@@ -182,8 +182,15 @@ async function sqliteMigrator(url: string): Promise<Migrator> {
182
182
  };
183
183
  }
184
184
 
185
- /** Apply missing migrations, check their history and reassert database privileges. */
186
- export async function migrateDatabase(url: string): Promise<void> {
185
+ /** A migration run's plan: `applied` of the `total` migrations there before it, the rest applied by it. */
186
+ export type MigrationPlan = { applied: number; total: number };
187
+
188
+ /**
189
+ * Apply missing migrations, check their history and reassert database
190
+ * privileges. `onPlan` hears how many there are to apply before they are:
191
+ * all of them go in one call, with no word of each.
192
+ */
193
+ export async function migrateDatabase(url: string, onPlan?: (plan: MigrationPlan) => void): Promise<void> {
187
194
  const engine = engineOfUrl(url);
188
195
  const expected = await expectedMigrations(engine);
189
196
  const migrator = await { postgres: postgresMigrator, sqlite: sqliteMigrator }[engine](url);
@@ -193,7 +200,9 @@ export async function migrateDatabase(url: string): Promise<void> {
193
200
  await migrator.lock();
194
201
  locked = true;
195
202
 
196
- verifyHistory(expected, await migrator.applied(), false);
203
+ const applied = await migrator.applied();
204
+ verifyHistory(expected, applied, false);
205
+ onPlan?.({ applied: applied?.length ?? 0, total: expected.length });
197
206
  await migrator.migrate(migrationsFolder(engine));
198
207
  verifyHistory(expected, await migrator.applied(), true);
199
208
  await migrator.restrict();