@remit/drizzle-service 1.0.1 → 1.0.2

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/drizzle-service",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Drizzle ORM service over SQLite",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -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(threadMessageSearchTable, rows, this.maxBoundParameters).map(
272
- (slice) =>
273
- this.search
274
- .insert(threadMessageSearchTable)
275
- .values(slice.map(toDocument)),
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;