@remit/drizzle-service 1.0.1 → 2.0.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/package.json
CHANGED
package/src/databases.ts
CHANGED
|
@@ -4,11 +4,13 @@ import type { Db, SqliteStore } from "./db.js";
|
|
|
4
4
|
import { messageDataSchema } from "./schema/message-data.js";
|
|
5
5
|
import { createSqliteDatabase } from "./sqlite-client.js";
|
|
6
6
|
|
|
7
|
-
// The three databases
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
7
|
+
// The three databases an instance runs on (#1420), each on the file its
|
|
8
|
+
// SQLITE_*_DB_PATH names; a self-host points all three at one remit.db. The app
|
|
9
|
+
// database holds what lives in the client: accounts, config, rules, settings
|
|
10
|
+
// and auth. The tenant mail database holds what lives on the IMAP server, plus
|
|
11
|
+
// the local rows that refer to it. The tenant search database holds the
|
|
12
|
+
// full-text index, rebuilt from the mail database, so it never commits together
|
|
13
|
+
// with it.
|
|
12
14
|
export interface SqliteDatabases {
|
|
13
15
|
app: Db<Record<string, unknown>>;
|
|
14
16
|
mail: Db<Record<string, unknown>>;
|
|
@@ -68,15 +68,18 @@ const journalEntries = (set: string): unknown[] => {
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
* The `<
|
|
72
|
-
* live databases.
|
|
71
|
+
* The `<database>:<table>` pairs `read_schema_version` in the wrapper sums on
|
|
72
|
+
* the live databases, each database read from the file it is configured on.
|
|
73
73
|
*/
|
|
74
74
|
const wrapperCountedTables = (): string[] => {
|
|
75
75
|
const match = read("deploy/vps/remit").match(
|
|
76
|
-
/for p in ((
|
|
76
|
+
/for p in ((?:\$_sv_\w+:__drizzle_migrations_\w+ ?)+); do/,
|
|
77
77
|
);
|
|
78
78
|
assert.ok(match, "the wrapper no longer sums any __drizzle_migrations table");
|
|
79
|
-
return match[1]
|
|
79
|
+
return match[1]
|
|
80
|
+
.trim()
|
|
81
|
+
.split(/\s+/)
|
|
82
|
+
.map((pair) => pair.replace(/^\$_sv_/, ""));
|
|
80
83
|
};
|
|
81
84
|
|
|
82
85
|
describe("schema version accounting", () => {
|
|
@@ -147,7 +150,7 @@ describe("schema version accounting", () => {
|
|
|
147
150
|
test("the wrapper counts exactly the tables the migrator writes", () => {
|
|
148
151
|
assert.deepEqual(
|
|
149
152
|
wrapperCountedTables().sort(),
|
|
150
|
-
sets.map(({ database, table }) => `${database}
|
|
153
|
+
sets.map(({ database, table }) => `${database}:${table}`).sort(),
|
|
151
154
|
);
|
|
152
155
|
});
|
|
153
156
|
|
|
@@ -161,6 +161,29 @@ describe("the thread-message search index in its own database", () => {
|
|
|
161
161
|
assert.deepEqual(await found("receipt"), [created.threadMessageId]);
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
test("rebuilds while a live writer indexes a message the rebuild has yet to reach", async () => {
|
|
165
|
+
const created = await repo.create(input({ subject: "Raced reminder" }));
|
|
166
|
+
const store = syncStore(search.db, BETTER_SQLITE3_LIMITS);
|
|
167
|
+
let raced = false;
|
|
168
|
+
const migrator = new SearchIndex(mail.db, {
|
|
169
|
+
...store,
|
|
170
|
+
batch: async (items) => {
|
|
171
|
+
if (!raced) {
|
|
172
|
+
raced = true;
|
|
173
|
+
await index.sync([created.threadMessageId]);
|
|
174
|
+
}
|
|
175
|
+
return store.batch(items);
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await migrator.rebuild();
|
|
180
|
+
await migrator.rebuild();
|
|
181
|
+
|
|
182
|
+
assert.equal(raced, true);
|
|
183
|
+
assert.equal(await index.needsRebuild(), false);
|
|
184
|
+
assert.deepEqual(await found("reminder"), [created.threadMessageId]);
|
|
185
|
+
});
|
|
186
|
+
|
|
164
187
|
test("matches the body text past the snippet, through a rewrite and a rebuild", async () => {
|
|
165
188
|
const created = await repo.create(
|
|
166
189
|
input({ subject: "Long letter", snippet: "Dear reader, a preview" }),
|
|
@@ -267,13 +267,24 @@ export class SearchIndex {
|
|
|
267
267
|
.limit(DOCUMENTS_PER_REBUILD_PAGE);
|
|
268
268
|
const last = rows.at(-1);
|
|
269
269
|
if (!last) return indexed;
|
|
270
|
+
// A live writer may index one of these messages after the delete
|
|
271
|
+
// above, so each page replaces its documents the way sync does.
|
|
270
272
|
await this.batch(
|
|
271
|
-
chunkRows(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
273
|
+
chunkRows(
|
|
274
|
+
threadMessageSearchTable,
|
|
275
|
+
rows,
|
|
276
|
+
this.maxBoundParameters,
|
|
277
|
+
).flatMap((slice) => [
|
|
278
|
+
this.search.delete(threadMessageSearchTable).where(
|
|
279
|
+
inArray(
|
|
280
|
+
threadMessageSearchTable.rowid,
|
|
281
|
+
slice.map((row) => searchRowid(row.threadMessageId)),
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
this.search
|
|
285
|
+
.insert(threadMessageSearchTable)
|
|
286
|
+
.values(slice.map(toDocument)),
|
|
287
|
+
]),
|
|
277
288
|
);
|
|
278
289
|
indexed += rows.length;
|
|
279
290
|
after = last.threadMessageId;
|