@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
@@ -19,11 +19,10 @@ import {
19
19
  eq,
20
20
  getTableColumns,
21
21
  gt,
22
- inArray,
23
22
  type SQL,
24
23
  sql,
25
24
  } from "drizzle-orm";
26
- import type { Db } from "../db.js";
25
+ import type { RepositoryDb } from "../db.js";
27
26
  import { NotFoundError } from "../error.js";
28
27
  import { envelopeAddressId } from "../id.js";
29
28
  import { decodeToken, resultList } from "../pagination.js";
@@ -43,8 +42,9 @@ import {
43
42
  addressRecency,
44
43
  addressSearchMatch,
45
44
  } from "./address-search-predicates.js";
45
+ import { inList } from "./in-list.js";
46
46
 
47
- type DB = Db<Record<string, unknown>>;
47
+ type DB = RepositoryDb<Record<string, unknown>>;
48
48
 
49
49
  type AddressUpdate = Partial<{
50
50
  [K in keyof typeof addressTable.$inferInsert]:
@@ -400,7 +400,7 @@ export class AddressRepo implements IAddressRepository {
400
400
  .where(
401
401
  and(
402
402
  eq(addressTable.accountConfigId, accountConfigId),
403
- inArray(addressTable.addressId, addressId),
403
+ inList(addressTable.addressId, addressId),
404
404
  ),
405
405
  );
406
406
  return rows.map(rowToAddress);
@@ -666,7 +666,7 @@ export class AddressRepo implements IAddressRepository {
666
666
  .where(
667
667
  and(
668
668
  eq(addressTable.accountConfigId, accountConfigId),
669
- inArray(addressTable.addressId, addressIds),
669
+ inList(addressTable.addressId, addressIds),
670
670
  ),
671
671
  );
672
672
  }
@@ -786,6 +786,16 @@ export class AddressRepo implements IAddressRepository {
786
786
  );
787
787
  }
788
788
 
789
+ async listAllByAccountConfig(
790
+ accountConfigId: string,
791
+ ): Promise<AddressItem[]> {
792
+ const rows = await this.db
793
+ .select()
794
+ .from(addressTable)
795
+ .where(eq(addressTable.accountConfigId, accountConfigId));
796
+ return rows.map(rowToAddress);
797
+ }
798
+
789
799
  async createEnvelopeAddress(
790
800
  input: CreateEnvelopeAddressInput,
791
801
  ): Promise<EnvelopeAddressItem> {
@@ -856,7 +866,7 @@ export class AddressRepo implements IAddressRepository {
856
866
  .select()
857
867
  .from(envelopeAddressTable)
858
868
  .where(
859
- inArray(envelopeAddressTable.envelopeAddressId, envelopeAddressId),
869
+ inList(envelopeAddressTable.envelopeAddressId, envelopeAddressId),
860
870
  );
861
871
  return rows.map(rowToEnvelopeAddress);
862
872
  }
@@ -884,7 +894,7 @@ export class AddressRepo implements IAddressRepository {
884
894
  await this.db
885
895
  .delete(envelopeAddressTable)
886
896
  .where(
887
- inArray(envelopeAddressTable.envelopeAddressId, envelopeAddressIds),
897
+ inList(envelopeAddressTable.envelopeAddressId, envelopeAddressIds),
888
898
  );
889
899
  }
890
900
  }
@@ -7,12 +7,12 @@ import {
7
7
  type UpdateConfigImportInput,
8
8
  } from "@remit/data-ports";
9
9
  import { desc, eq } from "drizzle-orm";
10
- import type { Db } from "../db.js";
10
+ import type { RepositoryDb } from "../db.js";
11
11
  import { NotFoundError } from "../error.js";
12
12
  import { randomId } from "../id.js";
13
13
  import { configImportTable } from "../schema/i4-config-import.js";
14
14
 
15
- type DB = Db<Record<string, unknown>>;
15
+ type DB = RepositoryDb<Record<string, unknown>>;
16
16
 
17
17
  type StoredRef = Omit<ConfigImportUnresolvedRefItem, "mailboxId"> & {
18
18
  mailboxId?: string;
@@ -4,11 +4,11 @@ import type {
4
4
  WithMailboxLockResult,
5
5
  } from "@remit/data-ports";
6
6
  import { and, eq, lt } from "drizzle-orm";
7
- import type { Db } from "../db.js";
7
+ import type { RepositoryDb } from "../db.js";
8
8
  import { randomId } from "../id.js";
9
9
  import { mailboxLockTable } from "../schema/i4-mailbox-lock.js";
10
10
 
11
- type DB = Db<Record<string, unknown>>;
11
+ type DB = RepositoryDb<Record<string, unknown>>;
12
12
 
13
13
  const TTL_SECONDS = 3600;
14
14
 
@@ -12,11 +12,11 @@ import {
12
12
  mailboxSpecialUseTable,
13
13
  mailboxTable,
14
14
  } from "../schema.js";
15
- import { SearchIndex } from "../search/thread-message-search.js";
15
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
16
16
  import { createSqliteTestDb } from "../test-db-sqlite.js";
17
- import { AccountSettingRepo } from "./i4-account-setting.js";
18
- import { MailboxRepo } from "./i4-mailbox.js";
19
- import { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
17
+ import type { AccountSettingRepo } from "./i4-account-setting.js";
18
+ import type { MailboxRepo } from "./i4-mailbox.js";
19
+ import type { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
20
20
  import { mailboxCreated } from "./test-helpers.js";
21
21
 
22
22
  const makeMailboxInput = (
@@ -57,12 +57,14 @@ describe("MailboxSpecialUseRepo role lookups (sqlite)", () => {
57
57
  });
58
58
  close = testDb.close;
59
59
  db = testDb.db;
60
- repo = new MailboxSpecialUseRepo(testDb.db as never, testDb.db as never);
61
- mailboxes = new MailboxRepo(
62
- testDb.db as never,
63
- new SearchIndex(testDb.db as never, testDb.db as never),
64
- );
65
- accountSettings = new AccountSettingRepo(testDb.db as never);
60
+ const repos = createSqliteRepositories({
61
+ app: testDb.db,
62
+ mail: testDb.db,
63
+ search: testDb.db,
64
+ });
65
+ repo = repos.mailboxSpecialUse;
66
+ mailboxes = repos.mailbox;
67
+ accountSettings = repos.accountSetting;
66
68
  });
67
69
 
68
70
  after(async () => {
@@ -1,4 +1,7 @@
1
1
  import type {
2
+ AccountItem,
3
+ IAccountRepository,
4
+ IAccountSettingRepository,
2
5
  IMailboxSpecialUseRepository,
3
6
  MailboxSpecialUseItem,
4
7
  MailboxSpecialUseValue,
@@ -14,14 +17,22 @@ import {
14
17
  type UnappointedRoleResolution,
15
18
  } from "@remit/data-ports/folder-role";
16
19
  import { CanonicalMailboxRole } from "@remit/domain-enums";
17
- import { eq, inArray } from "drizzle-orm";
18
- import type { Db } from "../db.js";
20
+ import { eq } from "drizzle-orm";
21
+ import type { RepositoryDb } from "../db.js";
19
22
  import { randomId } from "../id.js";
20
- import { accountTable } from "../schema/i4-account-config.js";
21
23
  import { mailboxSpecialUseTable, mailboxTable } from "../schema/i4-mailbox.js";
22
- import { AccountSettingRepo } from "./i4-account-setting.js";
24
+ import { inList } from "./in-list.js";
23
25
 
24
- type DB = Db<Record<string, unknown>>;
26
+ type DB = RepositoryDb<Record<string, unknown>>;
27
+
28
+ type AccountReader = Pick<
29
+ IAccountRepository,
30
+ "get" | "listAllByAccountConfig" | "listAll"
31
+ >;
32
+
33
+ type AppointmentReader = Pick<IAccountSettingRepository, "get" | "getMany">;
34
+
35
+ type AppointedAccount = Pick<AccountItem, "accountId" | "accountConfigId">;
25
36
 
26
37
  const JUNK_ROLES: readonly CanonicalMailboxRoleValue[] = [
27
38
  CanonicalMailboxRole.Junk,
@@ -88,14 +99,11 @@ function rowToSpecialUse(
88
99
  }
89
100
 
90
101
  export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
91
- private readonly accountSetting: AccountSettingRepo;
92
-
93
102
  constructor(
94
- private app: DB,
95
103
  private db: DB,
96
- ) {
97
- this.accountSetting = new AccountSettingRepo(app);
98
- }
104
+ private accounts: AccountReader,
105
+ private accountSetting: AppointmentReader,
106
+ ) {}
99
107
 
100
108
  async create(
101
109
  mailboxId: string,
@@ -202,11 +210,9 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
202
210
  async resolveJunkRolesForConfig(
203
211
  accountConfigId: string,
204
212
  ): Promise<JunkRoleMailboxes> {
205
- const accounts = await this.app
206
- .select({ accountId: accountTable.accountId })
207
- .from(accountTable)
208
- .where(eq(accountTable.accountConfigId, accountConfigId));
209
- return this.resolveJunkRoles(accounts.map((row) => row.accountId));
213
+ return this.resolveJunkRoles(
214
+ await this.accounts.listAllByAccountConfig(accountConfigId),
215
+ );
210
216
  }
211
217
 
212
218
  /**
@@ -216,21 +222,19 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
216
222
  * run one pass over the address table instead of one per config.
217
223
  */
218
224
  async resolveJunkRolesForInstance(): Promise<JunkRoleMailboxes> {
219
- const accounts = await this.app
220
- .select({ accountId: accountTable.accountId })
221
- .from(accountTable);
222
- return this.resolveJunkRoles(accounts.map((row) => row.accountId));
225
+ return this.resolveJunkRoles(await this.accounts.listAll());
223
226
  }
224
227
 
225
228
  private async resolveJunkRoles(
226
- accountIds: readonly string[],
229
+ accounts: readonly AppointedAccount[],
227
230
  ): Promise<JunkRoleMailboxes> {
228
- if (accountIds.length === 0) {
231
+ if (accounts.length === 0) {
229
232
  return { junkMailboxIds: [], trashMailboxIds: [] };
230
233
  }
234
+ const accountIds = accounts.map((account) => account.accountId);
231
235
  const [candidates, appointments] = await Promise.all([
232
236
  this.roleCandidatesFor(accountIds),
233
- this.appointedMailboxIds(accountIds, JUNK_ROLES),
237
+ this.appointedMailboxIds(accounts, JUNK_ROLES),
234
238
  ]);
235
239
 
236
240
  const junkMailboxIds: string[] = [];
@@ -254,24 +258,13 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
254
258
  }
255
259
 
256
260
  /**
257
- * Each account's appointment for each role, in two reads. Same precedence
258
- * input as `appointedMailboxId`, batched: an account row missing is a caller
259
- * racing a delete, and leaves the account with no appointment rather than
260
- * failing the lookup.
261
+ * Each account's appointment for each role, in one read. Same precedence
262
+ * input as `appointedMailboxId`, batched.
261
263
  */
262
264
  private async appointedMailboxIds(
263
- accountIds: readonly string[],
265
+ accounts: readonly AppointedAccount[],
264
266
  roles: readonly CanonicalMailboxRoleValue[],
265
267
  ): Promise<Map<string, string>> {
266
- const accounts = await this.app
267
- .select({
268
- accountId: accountTable.accountId,
269
- accountConfigId: accountTable.accountConfigId,
270
- })
271
- .from(accountTable)
272
- .where(inArray(accountTable.accountId, [...accountIds]));
273
- if (accounts.length === 0) return new Map();
274
-
275
268
  const wanted = new Map<string, string>();
276
269
  for (const account of accounts) {
277
270
  for (const role of roles) {
@@ -303,14 +296,14 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
303
296
  const rows = await this.db
304
297
  .select()
305
298
  .from(mailboxTable)
306
- .where(inArray(mailboxTable.accountId, [...accountIds]));
299
+ .where(inList(mailboxTable.accountId, accountIds));
307
300
  if (rows.length === 0) return new Map();
308
301
 
309
302
  const entries = await this.db
310
303
  .select()
311
304
  .from(mailboxSpecialUseTable)
312
305
  .where(
313
- inArray(
306
+ inList(
314
307
  mailboxSpecialUseTable.mailboxId,
315
308
  rows.map((row) => row.mailboxId),
316
309
  ),
@@ -366,10 +359,7 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
366
359
  accountId: string,
367
360
  role: CanonicalMailboxRoleValue,
368
361
  ): Promise<string | undefined> {
369
- const [account] = await this.app
370
- .select({ accountConfigId: accountTable.accountConfigId })
371
- .from(accountTable)
372
- .where(eq(accountTable.accountId, accountId));
362
+ const [account] = await this.accounts.get([accountId]);
373
363
  if (!account) return undefined;
374
364
 
375
365
  const setting = await this.accountSetting.get(
@@ -397,7 +387,7 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
397
387
  .select()
398
388
  .from(mailboxSpecialUseTable)
399
389
  .where(
400
- inArray(
390
+ inList(
401
391
  mailboxSpecialUseTable.mailboxId,
402
392
  rows.map((row) => row.mailboxId),
403
393
  ),
@@ -5,13 +5,13 @@ import { MailboxSyncStatus } from "@remit/domain-enums";
5
5
  import Database from "better-sqlite3";
6
6
  import { drizzle } from "drizzle-orm/better-sqlite3";
7
7
  import { mailboxTable } from "../schema.js";
8
- import { SearchIndex } from "../search/thread-message-search.js";
8
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
9
9
  import { createSqliteTestDb } from "../test-db-sqlite.js";
10
10
  import {
11
11
  applyMigrationSet,
12
12
  shippedTableDdl,
13
13
  } from "../test-shipped-sqlite-schema.js";
14
- import { MailboxRepo } from "./i4-mailbox.js";
14
+ import type { MailboxRepo } from "./i4-mailbox.js";
15
15
  import { mailboxCreated } from "./test-helpers.js";
16
16
 
17
17
  function makeMailboxInput(accountId: string, fullPath = "INBOX") {
@@ -41,10 +41,11 @@ describe("MailboxRepo (sqlite)", () => {
41
41
  before(async () => {
42
42
  const testDb = await createSqliteTestDb({ mailbox: mailboxTable });
43
43
  close = testDb.close;
44
- repo = new MailboxRepo(
45
- testDb.db as never,
46
- new SearchIndex(testDb.db as never, testDb.db as never),
47
- );
44
+ repo = createSqliteRepositories({
45
+ app: testDb.db,
46
+ mail: testDb.db,
47
+ search: testDb.db,
48
+ }).mailbox;
48
49
  });
49
50
 
50
51
  after(async () => {
@@ -210,10 +211,11 @@ describe("MailboxRepo (sqlite, shipped migrations)", () => {
210
211
  const sqlite = new Database(":memory:");
211
212
  applyMigrationSet(sqlite, "mail");
212
213
  const db = drizzle(sqlite, { schema: { mailbox: mailboxTable } });
213
- repo = new MailboxRepo(
214
- db as never,
215
- new SearchIndex(db as never, db as never),
216
- );
214
+ repo = createSqliteRepositories({
215
+ app: db,
216
+ mail: db,
217
+ search: db,
218
+ }).mailbox;
217
219
  close = async () => {
218
220
  sqlite.close();
219
221
  };
@@ -2,7 +2,7 @@ import assert from "node:assert";
2
2
  import { after, before, describe, test } from "node:test";
3
3
  import { MailboxSyncStatus } from "@remit/domain-enums";
4
4
  import { eq } from "drizzle-orm";
5
- import { runBatch } from "../batch.js";
5
+ import { syncBatch } from "../batch.js";
6
6
  import {
7
7
  envelopeId as deriveEnvelopeId,
8
8
  rootBodyPartId as deriveRootBodyPartId,
@@ -10,14 +10,13 @@ import {
10
10
  import {
11
11
  filterTable,
12
12
  mailboxSpecialUseTable,
13
- mailboxTable,
14
13
  messageTable,
15
14
  threadMessageTable,
16
15
  } from "../schema.js";
17
- import { SearchIndex } from "../search/thread-message-search.js";
16
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
18
17
  import { createTestDb, randomId, type TestDb } from "../test-db.js";
19
- import { MailboxRepo } from "./i4-mailbox.js";
20
- import { DrizzleMessageRepository, deleteMessageSubtree } from "./message.js";
18
+ import type { MailboxRepo } from "./i4-mailbox.js";
19
+ import { deleteMessageSubtree } from "./message.js";
21
20
  import { mailboxCreated } from "./test-helpers.js";
22
21
 
23
22
  /** Every state a row can carry, for a transition that decides against none. */
@@ -55,10 +54,11 @@ describe("MailboxRepo", () => {
55
54
 
56
55
  before(async () => {
57
56
  ({ db, close } = await createTestDb());
58
- repo = new MailboxRepo(
59
- db as never,
60
- new SearchIndex(db as never, db as never),
61
- );
57
+ repo = createSqliteRepositories({
58
+ app: db,
59
+ mail: db,
60
+ search: db,
61
+ }).mailbox;
62
62
  });
63
63
 
64
64
  after(async () => {
@@ -543,10 +543,11 @@ describe("MailboxRepo", () => {
543
543
  describe("deleteMailboxWithMail — the folder's mail goes with it (D8)", () => {
544
544
  const seedMessage = async (mailboxId: string, uid: number) => {
545
545
  const messageId = randomId();
546
- await new DrizzleMessageRepository(
547
- db as never,
548
- new SearchIndex(db as never, db as never),
549
- ).create({
546
+ await createSqliteRepositories({
547
+ app: db,
548
+ mail: db,
549
+ search: db,
550
+ }).message.create({
550
551
  messageId,
551
552
  mailboxId,
552
553
  uid,
@@ -674,7 +675,7 @@ describe("MailboxRepo", () => {
674
675
 
675
676
  // The shape an interruption leaves: one message's rows already gone,
676
677
  // the mailbox row still there and still `deleting`.
677
- await runBatch(db, deleteMessageSubtree(db, [first]));
678
+ await syncBatch(db)(deleteMessageSubtree(db, [first]));
678
679
  await repo.transition(accountId, mailbox.mailboxId, {
679
680
  from: EVERY_STATE,
680
681
  to: MailboxSyncStatus.deleting,
@@ -27,8 +27,8 @@ import {
27
27
  sql,
28
28
  } from "drizzle-orm";
29
29
  import shortUuid from "short-uuid";
30
- import { runBatch } from "../batch.js";
31
- import type { Db } from "../db.js";
30
+ import type { WriteBatch } from "../batch.js";
31
+ import type { RepositoryDb, SqliteStore } from "../db.js";
32
32
  import { NotFoundError } from "../error.js";
33
33
  import { decodeToken, resultList } from "../pagination.js";
34
34
  import {
@@ -44,6 +44,7 @@ import { messageTable } from "../schema/message-data.js";
44
44
  import { syncWarningTable } from "../schema/sync-warning.js";
45
45
  import { threadMessageTable } from "../schema/thread-message.js";
46
46
  import type { SearchIndex } from "../search/thread-message-search.js";
47
+ import { inList } from "./in-list.js";
47
48
  import { deleteMessageSubtree } from "./message.js";
48
49
 
49
50
  const base36Translator = shortUuid.createTranslator(
@@ -51,7 +52,7 @@ const base36Translator = shortUuid.createTranslator(
51
52
  );
52
53
  const generateMailboxId = () => base36Translator.fromUUID(randomUUID());
53
54
 
54
- type DB = Db<Record<string, unknown>>;
55
+ type DB = RepositoryDb<Record<string, unknown>>;
55
56
 
56
57
  /**
57
58
  * Message subtrees removed per batch by {@link MailboxRepo.deleteMailboxWithMail},
@@ -148,10 +149,16 @@ export function rowToMailbox(
148
149
  }
149
150
 
150
151
  export class MailboxRepo implements IMailboxRepository {
152
+ private readonly db: DB;
153
+ private readonly batch: WriteBatch;
154
+
151
155
  constructor(
152
- private db: DB,
156
+ store: SqliteStore<Record<string, unknown>>,
153
157
  private search: SearchIndex,
154
- ) {}
158
+ ) {
159
+ this.db = store.db;
160
+ this.batch = store.batch;
161
+ }
155
162
 
156
163
  async create(input: CreateMailboxInput): Promise<CreateMailboxResult> {
157
164
  const now = Date.now();
@@ -207,7 +214,7 @@ export class MailboxRepo implements IMailboxRepository {
207
214
  .where(
208
215
  and(
209
216
  eq(mailboxTable.accountId, accountId),
210
- inArray(mailboxTable.mailboxId, mailboxId),
217
+ inList(mailboxTable.mailboxId, mailboxId),
211
218
  ),
212
219
  );
213
220
  return rows.map(rowToMailbox);
@@ -378,13 +385,13 @@ export class MailboxRepo implements IMailboxRepository {
378
385
 
379
386
  async deleteMany(accountId: string, mailboxIds: string[]): Promise<void> {
380
387
  if (mailboxIds.length === 0) return;
381
- await runBatch(this.db, [
388
+ await this.batch([
382
389
  this.db
383
390
  .delete(syncWarningTable)
384
391
  .where(
385
392
  and(
386
393
  eq(syncWarningTable.accountId, accountId),
387
- inArray(syncWarningTable.mailboxId, mailboxIds),
394
+ inList(syncWarningTable.mailboxId, mailboxIds),
388
395
  ),
389
396
  ),
390
397
  this.db
@@ -392,7 +399,7 @@ export class MailboxRepo implements IMailboxRepository {
392
399
  .where(
393
400
  and(
394
401
  eq(mailboxTable.accountId, accountId),
395
- inArray(mailboxTable.mailboxId, mailboxIds),
402
+ inList(mailboxTable.mailboxId, mailboxIds),
396
403
  ),
397
404
  ),
398
405
  ]);
@@ -551,15 +558,15 @@ export class MailboxRepo implements IMailboxRepository {
551
558
  const listings = await this.db
552
559
  .select({ threadMessageId: threadMessageTable.threadMessageId })
553
560
  .from(threadMessageTable)
554
- .where(inArray(threadMessageTable.messageId, messageIds));
561
+ .where(inList(threadMessageTable.messageId, messageIds));
555
562
 
556
563
  // The primitive the rest of the codebase deletes mail with: nine
557
564
  // per-message child tables. A bespoke table list would orphan them.
558
- await runBatch(this.db, [
565
+ await this.batch([
559
566
  ...deleteMessageSubtree(this.db, messageIds),
560
567
  this.db
561
568
  .delete(threadMessageTable)
562
- .where(inArray(threadMessageTable.messageId, messageIds)),
569
+ .where(inList(threadMessageTable.messageId, messageIds)),
563
570
  ]);
564
571
  await this.search.sync(listings.map((row) => row.threadMessageId));
565
572
  }
@@ -3,10 +3,10 @@ import type {
3
3
  PutMessageFlagPushInput,
4
4
  } from "@remit/data-ports";
5
5
  import { and, eq } from "drizzle-orm";
6
- import type { Db } from "../db.js";
6
+ import type { RepositoryDb } from "../db.js";
7
7
  import { messageFlagPushTable } from "../schema/i4-message-flag-push.js";
8
8
 
9
- type DB = Db<Record<string, unknown>>;
9
+ type DB = RepositoryDb<Record<string, unknown>>;
10
10
 
11
11
  export type { MessageFlagPushItem, PutMessageFlagPushInput };
12
12
  export type FlagPushOperation = MessageFlagPushItem["operation"];
@@ -3,10 +3,10 @@ import type {
3
3
  PutMessagePlacementMoveInput,
4
4
  } from "@remit/data-ports";
5
5
  import { eq } from "drizzle-orm";
6
- import type { Db } from "../db.js";
6
+ import type { RepositoryDb } from "../db.js";
7
7
  import { messagePlacementMoveTable } from "../schema/i4-message-placement-move.js";
8
8
 
9
- type DB = Db<Record<string, unknown>>;
9
+ type DB = RepositoryDb<Record<string, unknown>>;
10
10
 
11
11
  export type { MessagePlacementMoveItem, PutMessagePlacementMoveInput };
12
12
  export type MessagePlacementMoveState = MessagePlacementMoveItem["state"];
@@ -6,13 +6,13 @@ import type {
6
6
  UpdateOrganizeJobRequestInput,
7
7
  } from "@remit/data-ports";
8
8
  import { eq } from "drizzle-orm";
9
- import type { Db } from "../db.js";
9
+ import type { RepositoryDb } from "../db.js";
10
10
  import { NotFoundError } from "../error.js";
11
11
  import { randomId } from "../id.js";
12
12
  import { decodeToken, resultList } from "../pagination.js";
13
13
  import { organizeJobRequestTable } from "../schema/i4-organize-job-request.js";
14
14
 
15
- type DB = Db<Record<string, unknown>>;
15
+ type DB = RepositoryDb<Record<string, unknown>>;
16
16
 
17
17
  function rowToItem(
18
18
  row: typeof organizeJobRequestTable.$inferSelect,
@@ -1,7 +1,8 @@
1
1
  import assert from "node:assert";
2
2
  import { after, before, describe, test } from "node:test";
3
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
3
4
  import { createTestDb, randomId } from "../test-db.js";
4
- import { OutboxAttachmentRepo } from "./i4-outbox-attachment.js";
5
+ import type { OutboxAttachmentRepo } from "./i4-outbox-attachment.js";
5
6
 
6
7
  const CAP = { maxTotalBytes: 1000, maxCount: 3, nowSeconds: 1_000_000 };
7
8
 
@@ -27,7 +28,11 @@ const input = (
27
28
 
28
29
  before(async () => {
29
30
  harness = await createTestDb();
30
- repo = new OutboxAttachmentRepo(harness.db);
31
+ repo = createSqliteRepositories({
32
+ app: harness.db,
33
+ mail: harness.db,
34
+ search: harness.db,
35
+ }).outboxAttachment;
31
36
  });
32
37
 
33
38
  after(async () => {
@@ -6,13 +6,14 @@ import type {
6
6
  ReserveOutboxAttachmentResult,
7
7
  } from "@remit/data-ports";
8
8
  import { holdsRoom } from "@remit/data-ports";
9
- import { and, eq, gt, gte, inArray, lt, or, sum } from "drizzle-orm";
10
- import { runBatch } from "../batch.js";
11
- import type { Db } from "../db.js";
9
+ import { and, eq, gt, gte, lt, or, sum } from "drizzle-orm";
10
+ import type { WriteBatch } from "../batch.js";
11
+ import type { RepositoryDb, SqliteStore } from "../db.js";
12
12
  import { NotFoundError } from "../error.js";
13
13
  import { outboxAttachmentTable } from "../schema/i4-outbox-attachment.js";
14
+ import { inList } from "./in-list.js";
14
15
 
15
- type DB = Db<Record<string, unknown>>;
16
+ type DB = RepositoryDb<Record<string, unknown>>;
16
17
 
17
18
  function rowToOutboxAttachment(
18
19
  row: typeof outboxAttachmentTable.$inferSelect,
@@ -34,7 +35,13 @@ function rowToOutboxAttachment(
34
35
  }
35
36
 
36
37
  export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
37
- constructor(private db: DB) {}
38
+ private readonly db: DB;
39
+ private readonly batch: WriteBatch;
40
+
41
+ constructor(store: SqliteStore<Record<string, unknown>>) {
42
+ this.db = store.db;
43
+ this.batch = store.batch;
44
+ }
38
45
 
39
46
  /**
40
47
  * The per-message cap. The insert, the read of the draft's live rows and a
@@ -59,7 +66,7 @@ export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
59
66
  gte(outboxAttachmentTable.reservationExpiresAt, cap.nowSeconds),
60
67
  ),
61
68
  );
62
- const [inserted, rows, removed] = await runBatch(this.db, [
69
+ const [inserted, rows, removed] = await this.batch([
63
70
  this.db
64
71
  .insert(outboxAttachmentTable)
65
72
  .values({
@@ -218,10 +225,7 @@ export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
218
225
  .where(
219
226
  and(
220
227
  eq(outboxAttachmentTable.accountConfigId, accountConfigId),
221
- inArray(
222
- outboxAttachmentTable.outboxAttachmentId,
223
- outboxAttachmentIds,
224
- ),
228
+ inList(outboxAttachmentTable.outboxAttachmentId, outboxAttachmentIds),
225
229
  ),
226
230
  );
227
231
  }