@remit/drizzle-service 0.0.101 → 0.0.103

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.
Files changed (62) hide show
  1. package/package.json +1 -1
  2. package/src/batch.sqlite.test.ts +4 -4
  3. package/src/batch.ts +47 -21
  4. package/src/databases.ts +24 -6
  5. package/src/db.ts +20 -5
  6. package/src/index.ts +12 -0
  7. package/src/repair/junk-only-address.sqlite.test.ts +7 -2
  8. package/src/repos/account.conformance.sqlite.test.ts +6 -2
  9. package/src/repos/calendar-collection.ts +2 -2
  10. package/src/repos/calendar-default.sqlite.test.ts +6 -2
  11. package/src/repos/calendar-event-index.ts +2 -2
  12. package/src/repos/calendar-feed-token.ts +2 -2
  13. package/src/repos/calendar-object.ts +2 -2
  14. package/src/repos/calendar-put.sqlite.test.ts +6 -2
  15. package/src/repos/calendar-store.ts +36 -40
  16. package/src/repos/calendar-suggestion.ts +5 -4
  17. package/src/repos/cascade-delete.test.ts +3 -2
  18. package/src/repos/cascade-delete.ts +38 -35
  19. package/src/repos/envelope.sqlite.test.ts +3 -2
  20. package/src/repos/envelope.test.ts +3 -2
  21. package/src/repos/envelope.ts +19 -13
  22. package/src/repos/filter.ts +2 -2
  23. package/src/repos/i4-account-config.test.ts +10 -6
  24. package/src/repos/i4-account-config.ts +12 -13
  25. package/src/repos/i4-account-export-request.ts +2 -2
  26. package/src/repos/i4-account-setting.ts +6 -5
  27. package/src/repos/i4-account.test.ts +12 -8
  28. package/src/repos/i4-account.ts +10 -13
  29. package/src/repos/i4-address-junk-move.sqlite.test.ts +7 -2
  30. package/src/repos/i4-address.ts +17 -7
  31. package/src/repos/i4-config-import.ts +2 -2
  32. package/src/repos/i4-mailbox-lock.ts +2 -2
  33. package/src/repos/i4-mailbox-special-use.sqlite.test.ts +12 -10
  34. package/src/repos/i4-mailbox-special-use.ts +34 -44
  35. package/src/repos/i4-mailbox.sqlite.test.ts +12 -10
  36. package/src/repos/i4-mailbox.test.ts +15 -14
  37. package/src/repos/i4-mailbox.ts +19 -12
  38. package/src/repos/i4-message-flag-push.ts +2 -2
  39. package/src/repos/i4-message-placement-move.ts +2 -2
  40. package/src/repos/i4-organize-job-request.ts +2 -2
  41. package/src/repos/i4-outbox-attachment.test.ts +7 -2
  42. package/src/repos/i4-outbox-attachment.ts +14 -10
  43. package/src/repos/i4-outbox-message.ts +7 -6
  44. package/src/repos/i4-sender-signer-standing.ts +2 -2
  45. package/src/repos/in-list.sqlite.test.ts +61 -0
  46. package/src/repos/in-list.ts +14 -0
  47. package/src/repos/label.ts +2 -2
  48. package/src/repos/message-flag.ts +8 -10
  49. package/src/repos/message-label.ts +5 -4
  50. package/src/repos/message.sqlite.test.ts +3 -3
  51. package/src/repos/message.test.ts +6 -5
  52. package/src/repos/message.ts +30 -24
  53. package/src/repos/quarantine.ts +2 -2
  54. package/src/repos/sync-warning.ts +5 -4
  55. package/src/repos/thread-message-body-muted.sqlite.test.ts +7 -3
  56. package/src/repos/thread-message-field-terms.sqlite.test.ts +7 -6
  57. package/src/repos/thread-message.sqlite.test.ts +7 -3
  58. package/src/repos/thread-message.test.ts +22 -18
  59. package/src/repos/thread-message.ts +16 -7
  60. package/src/search/thread-message-search.sqlite.test.ts +8 -3
  61. package/src/search/thread-message-search.ts +13 -8
  62. package/src/sqlite-repositories.ts +48 -32
@@ -23,8 +23,8 @@ import {
23
23
  type SQLWrapper,
24
24
  sql,
25
25
  } from "drizzle-orm";
26
- import { runBatch } from "../batch.js";
27
- import type { Db } from "../db.js";
26
+ import type { WriteBatch } from "../batch.js";
27
+ import type { RepositoryDb, SqliteStore } from "../db.js";
28
28
  import { NotFoundError } from "../error.js";
29
29
  import { deterministicBase36Id } from "../id.js";
30
30
  import { decodeToken } from "../pagination.js";
@@ -34,6 +34,7 @@ import type {
34
34
  SearchIndex,
35
35
  SearchPosition,
36
36
  } from "../search/thread-message-search.js";
37
+ import { inList } from "./in-list.js";
37
38
  import {
38
39
  bodyMatch,
39
40
  fromMatch,
@@ -43,6 +44,8 @@ import {
43
44
  subjectMatch,
44
45
  } from "./thread-search-predicates.js";
45
46
 
47
+ type DB = RepositoryDb<Record<string, unknown>>;
48
+
46
49
  export const deriveThreadMessageId = (
47
50
  threadId: string,
48
51
  messageId: string,
@@ -325,10 +328,16 @@ function sentDateCursorCond(
325
328
  export class DrizzleThreadMessageRepository
326
329
  implements IThreadMessageRepository
327
330
  {
331
+ private readonly db: DB;
332
+ private readonly batch: WriteBatch;
333
+
328
334
  constructor(
329
- private readonly db: Db<Record<string, unknown>>,
335
+ store: SqliteStore<Record<string, unknown>>,
330
336
  private readonly search: SearchIndex,
331
- ) {}
337
+ ) {
338
+ this.db = store.db;
339
+ this.batch = store.batch;
340
+ }
332
341
 
333
342
  /**
334
343
  * One page of rows in date order (ranked first when asked). Without a text
@@ -579,7 +588,7 @@ export class DrizzleThreadMessageRepository
579
588
  .where(
580
589
  and(
581
590
  eq(threadMessageTable.accountConfigId, accountConfigId),
582
- inArray(threadMessageTable.threadMessageId, threadMessageId),
591
+ inList(threadMessageTable.threadMessageId, threadMessageId),
583
592
  ),
584
593
  );
585
594
  return rows.map(toItem);
@@ -678,7 +687,7 @@ export class DrizzleThreadMessageRepository
678
687
  .where(
679
688
  and(
680
689
  eq(threadMessageTable.accountConfigId, accountConfigId),
681
- inArray(threadMessageTable.threadMessageId, ids),
690
+ inList(threadMessageTable.threadMessageId, ids),
682
691
  ),
683
692
  );
684
693
  await this.search.sync(ids);
@@ -1081,7 +1090,7 @@ export class DrizzleThreadMessageRepository
1081
1090
  .returning({ threadMessageId: threadMessageTable.threadMessageId }),
1082
1091
  ];
1083
1092
  });
1084
- const results = await runBatch(this.db, items);
1093
+ const results = await this.batch(items);
1085
1094
  await this.search.sync(
1086
1095
  rows.flatMap((row) => [
1087
1096
  row.threadMessageId,
@@ -3,8 +3,9 @@ import { after, before, describe, test } from "node:test";
3
3
  import type { CreateThreadMessageInput } from "@remit/data-ports";
4
4
  import Database from "better-sqlite3";
5
5
  import { drizzle } from "drizzle-orm/better-sqlite3";
6
+ import { syncStore } from "../databases.js";
6
7
  import type { Db } from "../db.js";
7
- import { DrizzleThreadMessageRepository } from "../repos/thread-message.js";
8
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
8
9
  import { applyMigrationSet } from "../test-shipped-sqlite-schema.js";
9
10
  import {
10
11
  SearchIndex,
@@ -48,8 +49,12 @@ const open = (set: "mail" | "search") => {
48
49
  describe("the thread-message search index in its own database", () => {
49
50
  const mail = open("mail");
50
51
  const search = open("search");
51
- const index = new SearchIndex(mail.db, search.db);
52
- const repo = new DrizzleThreadMessageRepository(mail.db, index);
52
+ const index = new SearchIndex(mail.db, syncStore(search.db));
53
+ const repo = createSqliteRepositories({
54
+ app: mail.db,
55
+ mail: mail.db,
56
+ search: search.db,
57
+ }).threadMessage;
53
58
 
54
59
  const found = async (query: string): Promise<string[]> =>
55
60
  (await repo.searchByMailboxWindow(ACCOUNT, MAILBOX, { query })).items.map(
@@ -12,8 +12,8 @@ import {
12
12
  sql,
13
13
  } from "drizzle-orm";
14
14
  import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
15
- import { runBatch, type SqliteBatchItem } from "../batch.js";
16
- import type { Db } from "../db.js";
15
+ import type { SqliteBatchItem, WriteBatch } from "../batch.js";
16
+ import type { RepositoryDb, SqliteStore } from "../db.js";
17
17
  import { threadMessageTable } from "../schema/thread-message.js";
18
18
 
19
19
  // The FTS5 trigram table the search migration set creates
@@ -32,7 +32,7 @@ export const threadMessageSearchTable = sqliteTable("thread_message_fts", {
32
32
  body: text("body").notNull(),
33
33
  });
34
34
 
35
- type AnyDb = Db<Record<string, unknown>>;
35
+ type AnyDb = RepositoryDb<Record<string, unknown>>;
36
36
 
37
37
  type SearchDocument = typeof threadMessageSearchTable.$inferInsert;
38
38
 
@@ -138,10 +138,16 @@ export interface CandidateQuery {
138
138
  * only, so no statement carries more ids than a page holds.
139
139
  */
140
140
  export class SearchIndex {
141
+ private readonly search: AnyDb;
142
+ private readonly batch: WriteBatch;
143
+
141
144
  constructor(
142
145
  private readonly mail: AnyDb,
143
- private readonly search: AnyDb,
144
- ) {}
146
+ store: SqliteStore<Record<string, unknown>>,
147
+ ) {
148
+ this.search = store.db;
149
+ this.batch = store.batch;
150
+ }
145
151
 
146
152
  /** Make the index match the mail database for these thread messages. */
147
153
  async sync(threadMessageIds: readonly string[]): Promise<void> {
@@ -165,7 +171,7 @@ export class SearchIndex {
165
171
  .values(rows.map(toDocument)),
166
172
  );
167
173
  }
168
- await runBatch(this.search, items);
174
+ await this.batch(items);
169
175
  }
170
176
  }
171
177
 
@@ -256,8 +262,7 @@ export class SearchIndex {
256
262
  .limit(DOCUMENTS_PER_REBUILD_PAGE);
257
263
  const last = rows.at(-1);
258
264
  if (!last) return indexed;
259
- await runBatch(
260
- this.search,
265
+ await this.batch(
261
266
  chunk(rows, DOCUMENTS_PER_STATEMENT).map((slice) =>
262
267
  this.search
263
268
  .insert(threadMessageSearchTable)
@@ -1,4 +1,8 @@
1
- import type { SqliteDatabases } from "./databases.js";
1
+ import {
2
+ type SqliteDatabases,
3
+ type SqliteStores,
4
+ syncStores,
5
+ } from "./databases.js";
2
6
  import { CalendarCollectionRepo } from "./repos/calendar-collection.js";
3
7
  import { CalendarEventIndexRepo } from "./repos/calendar-event-index.js";
4
8
  import { CalendarFeedTokenRepo } from "./repos/calendar-feed-token.js";
@@ -31,45 +35,57 @@ import { SyncWarningRepo } from "./repos/sync-warning.js";
31
35
  import { DrizzleThreadMessageRepository } from "./repos/thread-message.js";
32
36
  import { SearchIndex } from "./search/thread-message-search.js";
33
37
 
34
- // Every repo over the database that holds its tables (#1420).
35
- export const createSqliteRepositories = ({
38
+ export const composeSqliteRepositories = ({
36
39
  app,
37
40
  mail,
38
41
  search,
39
- }: SqliteDatabases) => {
40
- const searchIndex = new SearchIndex(mail, search);
42
+ }: SqliteStores) => {
43
+ const searchIndex = new SearchIndex(mail.db, search);
44
+ const accountSetting = new AccountSettingRepo(app.db);
45
+ const address = new AddressRepo(mail.db);
46
+ const mailbox = new MailboxRepo(mail, searchIndex);
47
+ const account = new AccountRepo(app.db, mailbox);
48
+ const calendarSuggestion = new CalendarSuggestionRepo(mail.db);
41
49
  return {
42
- accountConfig: new AccountConfigRepo(app, mail),
43
- account: new AccountRepo(app, mail),
44
- accountSetting: new AccountSettingRepo(app),
45
- address: new AddressRepo(mail),
46
- mailbox: new MailboxRepo(mail, searchIndex),
47
- mailboxSpecialUse: new MailboxSpecialUseRepo(app, mail),
48
- mailboxLock: new MailboxLockRepo(mail),
50
+ accountConfig: new AccountConfigRepo(app.db, address),
51
+ account,
52
+ accountSetting,
53
+ address,
54
+ mailbox,
55
+ mailboxSpecialUse: new MailboxSpecialUseRepo(
56
+ mail.db,
57
+ account,
58
+ accountSetting,
59
+ ),
60
+ mailboxLock: new MailboxLockRepo(mail.db),
49
61
  message: new DrizzleMessageRepository(mail, searchIndex),
50
- messageFlag: new DrizzleMessageFlagRepository(mail),
51
- outboxMessage: new OutboxMessageRepo(mail),
62
+ messageFlag: new DrizzleMessageFlagRepository(mail.db),
63
+ outboxMessage: new OutboxMessageRepo(mail.db),
52
64
  outboxAttachment: new OutboxAttachmentRepo(mail),
53
65
  threadMessage: new DrizzleThreadMessageRepository(mail, searchIndex),
54
66
  envelope: new DrizzleEnvelopeRepository(mail),
55
- accountExportRequest: new AccountExportRequestRepo(app),
56
- configImport: new ConfigImportRepo(app),
57
- quarantine: new QuarantineRepo(mail),
58
- syncWarning: new SyncWarningRepo(mail),
59
- organizeJobRequest: new OrganizeJobRequestRepo(mail),
60
- placementMove: new MessagePlacementMoveRepo(mail),
61
- flagPush: new MessageFlagPushRepo(mail),
62
- filter: new FilterRepo(app),
63
- label: new LabelRepo(app),
64
- messageLabel: new MessageLabelRepo(mail),
65
- senderSignerStanding: new SenderSignerStandingRepo(mail),
66
- calendarCollection: new CalendarCollectionRepo(app),
67
- calendarObject: new CalendarObjectRepo(app),
68
- calendarEventIndex: new CalendarEventIndexRepo(app),
69
- calendarFeedToken: new CalendarFeedTokenRepo(app),
70
- calendarSuggestion: new CalendarSuggestionRepo(mail),
71
- calendarStore: new DrizzleCalendarStore(app, mail),
67
+ accountExportRequest: new AccountExportRequestRepo(app.db),
68
+ configImport: new ConfigImportRepo(app.db),
69
+ quarantine: new QuarantineRepo(mail.db),
70
+ syncWarning: new SyncWarningRepo(mail.db),
71
+ organizeJobRequest: new OrganizeJobRequestRepo(mail.db),
72
+ placementMove: new MessagePlacementMoveRepo(mail.db),
73
+ flagPush: new MessageFlagPushRepo(mail.db),
74
+ filter: new FilterRepo(app.db),
75
+ label: new LabelRepo(app.db),
76
+ messageLabel: new MessageLabelRepo(mail.db),
77
+ senderSignerStanding: new SenderSignerStandingRepo(mail.db),
78
+ calendarCollection: new CalendarCollectionRepo(app.db),
79
+ calendarObject: new CalendarObjectRepo(app.db),
80
+ calendarEventIndex: new CalendarEventIndexRepo(app.db),
81
+ calendarFeedToken: new CalendarFeedTokenRepo(app.db),
82
+ calendarSuggestion,
83
+ calendarStore: new DrizzleCalendarStore(app, calendarSuggestion),
72
84
  };
73
85
  };
74
86
 
75
- export type SqliteRepositories = ReturnType<typeof createSqliteRepositories>;
87
+ export type SqliteRepositories = ReturnType<typeof composeSqliteRepositories>;
88
+
89
+ export const createSqliteRepositories = (
90
+ databases: SqliteDatabases,
91
+ ): SqliteRepositories => composeSqliteRepositories(syncStores(databases));