@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
@@ -5,13 +5,14 @@ import type {
5
5
  ResultList,
6
6
  UpdateOutboxMessageInput,
7
7
  } from "@remit/data-ports";
8
- import { and, desc, eq, inArray, lt, or } from "drizzle-orm";
9
- import type { Db } from "../db.js";
8
+ import { and, desc, eq, lt, or } from "drizzle-orm";
9
+ import type { RepositoryDb } from "../db.js";
10
10
  import { ForbiddenError, NotFoundError } from "../error.js";
11
11
  import { decodeToken, resultList } from "../pagination.js";
12
12
  import { outboxMessageTable } from "../schema/i4-outbox-message.js";
13
+ import { inList } from "./in-list.js";
13
14
 
14
- type DB = Db<Record<string, unknown>>;
15
+ type DB = RepositoryDb<Record<string, unknown>>;
15
16
 
16
17
  function rowToOutboxMessage(
17
18
  row: typeof outboxMessageTable.$inferSelect,
@@ -99,7 +100,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
99
100
  .where(
100
101
  and(
101
102
  eq(outboxMessageTable.accountConfigId, accountConfigId),
102
- inArray(outboxMessageTable.outboxMessageId, outboxMessageId),
103
+ inList(outboxMessageTable.outboxMessageId, outboxMessageId),
103
104
  ),
104
105
  );
105
106
  return rows.map(rowToOutboxMessage);
@@ -264,7 +265,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
264
265
  .where(
265
266
  and(
266
267
  eq(outboxMessageTable.accountConfigId, accountConfigId),
267
- inArray(outboxMessageTable.outboxMessageId, outboxMessageIds),
268
+ inList(outboxMessageTable.outboxMessageId, outboxMessageIds),
268
269
  ),
269
270
  );
270
271
  }
@@ -298,7 +299,7 @@ export class OutboxMessageRepo implements IOutboxMessageRepository {
298
299
  .from(outboxMessageTable)
299
300
  .where(
300
301
  and(
301
- inArray(outboxMessageTable.accountId, accountIds),
302
+ inList(outboxMessageTable.accountId, accountIds),
302
303
  after
303
304
  ? or(
304
305
  lt(outboxMessageTable.createdAt, after.createdAt),
@@ -4,11 +4,11 @@ import type {
4
4
  SenderSignerStandingItem,
5
5
  } from "@remit/data-ports";
6
6
  import { and, eq, sql } from "drizzle-orm";
7
- import type { Db } from "../db.js";
7
+ import type { RepositoryDb } from "../db.js";
8
8
  import { NotFoundError } from "../error.js";
9
9
  import { senderSignerStandingTable } from "../schema.js";
10
10
 
11
- type DB = Db<Record<string, unknown>>;
11
+ type DB = RepositoryDb<Record<string, unknown>>;
12
12
 
13
13
  function rowToStanding(
14
14
  row: typeof senderSignerStandingTable.$inferSelect,
@@ -0,0 +1,61 @@
1
+ import assert from "node:assert/strict";
2
+ import { after, before, describe, test } from "node:test";
3
+ import { calendarTable } from "../schema.js";
4
+ import { createSqliteTestDb, type SqliteTestDb } from "../test-db-sqlite.js";
5
+ import { inList } from "./in-list.js";
6
+
7
+ const SCHEMA = { calendars: calendarTable };
8
+
9
+ const calendar = (calendarId: string): typeof calendarTable.$inferInsert => ({
10
+ calendarId,
11
+ accountConfigId: "account-config-in-list",
12
+ urlSegment: calendarId,
13
+ displayName: calendarId,
14
+ color: "Cal1",
15
+ componentSet: "VeventOnly",
16
+ source: "UserCreated",
17
+ timezone: "",
18
+ subscriptionUrl: "",
19
+ subscriptionEnabled: false,
20
+ syncSequence: 0,
21
+ createdAt: 0,
22
+ updatedAt: 0,
23
+ });
24
+
25
+ describe("inList on better-sqlite3", () => {
26
+ let db: SqliteTestDb<typeof SCHEMA>;
27
+ let close: () => Promise<void>;
28
+ const allIds = Array.from({ length: 150 }, (_, index) => `calendar-${index}`);
29
+
30
+ before(async () => {
31
+ ({ db, close } = await createSqliteTestDb(SCHEMA));
32
+ await db.insert(calendarTable).values(allIds.map(calendar));
33
+ });
34
+
35
+ after(() => close());
36
+
37
+ test("binds a single parameter for 150 ids", () => {
38
+ const query = db
39
+ .select({ calendarId: calendarTable.calendarId })
40
+ .from(calendarTable)
41
+ .where(inList(calendarTable.calendarId, allIds))
42
+ .toSQL();
43
+ assert.equal(query.params.length, 1);
44
+ });
45
+
46
+ test("matches only the listed ids", async () => {
47
+ const rows = await db
48
+ .select({ calendarId: calendarTable.calendarId })
49
+ .from(calendarTable)
50
+ .where(inList(calendarTable.calendarId, ["calendar-3", "missing"]));
51
+ assert.deepEqual(rows, [{ calendarId: "calendar-3" }]);
52
+ });
53
+
54
+ test("matches nothing for an empty list", async () => {
55
+ const rows = await db
56
+ .select({ calendarId: calendarTable.calendarId })
57
+ .from(calendarTable)
58
+ .where(inList(calendarTable.calendarId, []));
59
+ assert.deepEqual(rows, []);
60
+ });
61
+ });
@@ -0,0 +1,14 @@
1
+ import { type Column, type SQL, sql } from "drizzle-orm";
2
+
3
+ export const inList = <T extends Column>(
4
+ column: T,
5
+ values: readonly T["_"]["data"][],
6
+ ): SQL =>
7
+ sql`${column} in (select value from json_each(${JSON.stringify(values)}))`;
8
+
9
+ export const pairInList = <A extends Column, B extends Column>(
10
+ first: A,
11
+ second: B,
12
+ pairs: readonly (readonly [A["_"]["data"], B["_"]["data"]])[],
13
+ ): SQL =>
14
+ sql`(${first}, ${second}) in (select value->>0, value->>1 from json_each(${JSON.stringify(pairs)}))`;
@@ -6,13 +6,13 @@ import type {
6
6
  UpdateLabelInput,
7
7
  } from "@remit/data-ports";
8
8
  import { and, asc, eq, gt, or } 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 { labelTable } from "../schema.js";
14
14
 
15
- type DB = Db<Record<string, unknown>>;
15
+ type DB = RepositoryDb<Record<string, unknown>>;
16
16
 
17
17
  const normalize = (name: string): string => name.trim().toLowerCase();
18
18
 
@@ -4,15 +4,13 @@ import type {
4
4
  IMessageFlagRepository,
5
5
  MessageFlagItem,
6
6
  } from "@remit/data-ports";
7
- import { and, eq, inArray } from "drizzle-orm";
8
- import type { Db } from "../db.js";
7
+ import { and, eq } from "drizzle-orm";
8
+ import type { RepositoryDb } from "../db.js";
9
9
  import { NotFoundError } from "../error.js";
10
- import {
11
- type MessageDataSchema,
12
- messageFlagTable,
13
- } from "../schema/message-data.js";
10
+ import { messageFlagTable } from "../schema/message-data.js";
11
+ import { inList } from "./in-list.js";
14
12
 
15
- type DB = Db<MessageDataSchema>;
13
+ type DB = RepositoryDb<Record<string, unknown>>;
16
14
 
17
15
  function rowToMessageFlag(
18
16
  row: typeof messageFlagTable.$inferSelect,
@@ -58,7 +56,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
58
56
  const rows = await this.db
59
57
  .select()
60
58
  .from(messageFlagTable)
61
- .where(inArray(messageFlagTable.messageFlagId, idOrIds));
59
+ .where(inList(messageFlagTable.messageFlagId, idOrIds));
62
60
  return rows.map(rowToMessageFlag);
63
61
  }
64
62
  const [row] = await this.db
@@ -79,7 +77,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
79
77
  if (messageFlagIds.length === 0) return;
80
78
  await this.db
81
79
  .delete(messageFlagTable)
82
- .where(inArray(messageFlagTable.messageFlagId, messageFlagIds));
80
+ .where(inList(messageFlagTable.messageFlagId, messageFlagIds));
83
81
  }
84
82
 
85
83
  async getFlags(messageId: string): Promise<MessageFlagItem[]> {
@@ -155,7 +153,7 @@ export class DrizzleMessageFlagRepository implements IMessageFlagRepository {
155
153
  .where(
156
154
  and(
157
155
  eq(messageFlagTable.messageId, messageId),
158
- inArray(messageFlagTable.flagName, flagNames),
156
+ inList(messageFlagTable.flagName, flagNames),
159
157
  ),
160
158
  );
161
159
  }
@@ -3,12 +3,13 @@ import type {
3
3
  IMessageLabelRepository,
4
4
  MessageLabelItem,
5
5
  } from "@remit/data-ports";
6
- import { and, desc, eq, inArray } from "drizzle-orm";
7
- import type { Db } from "../db.js";
6
+ import { and, desc, eq } from "drizzle-orm";
7
+ import type { RepositoryDb } from "../db.js";
8
8
  import { deterministicBase36Id } from "../id.js";
9
9
  import { messageLabelTable } from "../schema.js";
10
+ import { inList } from "./in-list.js";
10
11
 
11
- type DB = Db<Record<string, unknown>>;
12
+ type DB = RepositoryDb<Record<string, unknown>>;
12
13
 
13
14
  function rowToMessageLabel(
14
15
  row: typeof messageLabelTable.$inferSelect,
@@ -82,7 +83,7 @@ export class MessageLabelRepo implements IMessageLabelRepository {
82
83
  const rows = await this.db
83
84
  .select()
84
85
  .from(messageLabelTable)
85
- .where(inArray(messageLabelTable.messageId, messageIds));
86
+ .where(inList(messageLabelTable.messageId, messageIds));
86
87
  return rows.map(rowToMessageLabel);
87
88
  }
88
89
 
@@ -6,9 +6,9 @@ import {
6
6
  } from "../id.js";
7
7
  import { messageDataSchema } from "../schema/message-data.js";
8
8
  import { threadMessageTable } from "../schema/thread-message.js";
9
- import { SearchIndex } from "../search/thread-message-search.js";
9
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
10
10
  import { createSqliteTestDb, type SqliteTestDb } from "../test-db-sqlite.js";
11
- import { DrizzleMessageRepository } from "./message.js";
11
+ import type { DrizzleMessageRepository } from "./message.js";
12
12
 
13
13
  const SCHEMA = { ...messageDataSchema, threadMessages: threadMessageTable };
14
14
 
@@ -43,7 +43,7 @@ describe("DrizzleMessageRepository (sqlite)", () => {
43
43
 
44
44
  before(async () => {
45
45
  ({ db, close } = await createSqliteTestDb(SCHEMA));
46
- repo = new DrizzleMessageRepository(db, new SearchIndex(db, db));
46
+ repo = createSqliteRepositories({ app: db, mail: db, search: db }).message;
47
47
  });
48
48
 
49
49
  after(async () => {
@@ -4,9 +4,9 @@ import {
4
4
  envelopeId as deriveEnvelopeId,
5
5
  rootBodyPartId as deriveRootBodyPartId,
6
6
  } from "../id.js";
7
- import { SearchIndex } from "../search/thread-message-search.js";
8
- import { DrizzleEnvelopeRepository } from "./envelope.js";
9
- import { DrizzleMessageRepository } from "./message.js";
7
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
8
+ import type { DrizzleEnvelopeRepository } from "./envelope.js";
9
+ import type { DrizzleMessageRepository } from "./message.js";
10
10
  import { createTestDb, type TestDb } from "./test-helpers.js";
11
11
 
12
12
  describe("DrizzleMessageRepository", () => {
@@ -37,8 +37,9 @@ describe("DrizzleMessageRepository", () => {
37
37
 
38
38
  before(async () => {
39
39
  ({ db, stop } = await createTestDb());
40
- messageRepo = new DrizzleMessageRepository(db, new SearchIndex(db, db));
41
- envelopeRepo = new DrizzleEnvelopeRepository(db);
40
+ const repos = createSqliteRepositories({ app: db, mail: db, search: db });
41
+ messageRepo = repos.message;
42
+ envelopeRepo = repos.envelope;
42
43
  });
43
44
 
44
45
  after(async () => {
@@ -18,8 +18,8 @@ import {
18
18
  or,
19
19
  type SQL,
20
20
  } from "drizzle-orm";
21
- import { runBatch, type SqliteBatchItem } from "../batch.js";
22
- import type { Db } from "../db.js";
21
+ import type { SqliteBatchItem, WriteBatch } from "../batch.js";
22
+ import type { RepositoryDb, SqliteStore } from "../db.js";
23
23
  import {
24
24
  CreateFailedConflictError,
25
25
  isUniqueViolation,
@@ -37,7 +37,6 @@ import {
37
37
  bodyStructureRefetchTable,
38
38
  envelopeAddressTable,
39
39
  envelopeTable,
40
- type MessageDataSchema,
41
40
  messageFlagTable,
42
41
  messageReferenceTable,
43
42
  messageTable,
@@ -45,6 +44,7 @@ import {
45
44
  } from "../schema/message-data.js";
46
45
  import { threadMessageTable } from "../schema/thread-message.js";
47
46
  import type { SearchIndex } from "../search/thread-message-search.js";
47
+ import { inList } from "./in-list.js";
48
48
  import {
49
49
  toBodyPartItem,
50
50
  toBodyPartParameterItem,
@@ -55,7 +55,7 @@ import {
55
55
  toRawMessageStorageItem,
56
56
  } from "./mappers.js";
57
57
 
58
- type DB = Db<MessageDataSchema>;
58
+ type DB = RepositoryDb<Record<string, unknown>>;
59
59
 
60
60
  function toMessageItem(row: typeof messageTable.$inferSelect): MessageItem {
61
61
  return {
@@ -111,7 +111,7 @@ function toMessageItem(row: typeof messageTable.$inferSelect): MessageItem {
111
111
  };
112
112
  }
113
113
 
114
- export type SubtreeDb = Pick<Db<Record<string, unknown>>, "delete">;
114
+ export type SubtreeDb = Pick<RepositoryDb<Record<string, unknown>>, "delete">;
115
115
 
116
116
  /**
117
117
  * The statements that delete a message and its whole per-message subtree by
@@ -129,36 +129,36 @@ export function deleteMessageSubtree(
129
129
  return [
130
130
  db
131
131
  .delete(messageFlagTable)
132
- .where(and(inArray(messageFlagTable.messageId, messageIds), guard)),
132
+ .where(and(inList(messageFlagTable.messageId, messageIds), guard)),
133
133
  db
134
134
  .delete(messageReferenceTable)
135
- .where(and(inArray(messageReferenceTable.messageId, messageIds), guard)),
135
+ .where(and(inList(messageReferenceTable.messageId, messageIds), guard)),
136
136
  db
137
137
  .delete(envelopeAddressTable)
138
- .where(and(inArray(envelopeAddressTable.messageId, messageIds), guard)),
138
+ .where(and(inList(envelopeAddressTable.messageId, messageIds), guard)),
139
139
  db
140
140
  .delete(bodyPartParameterTable)
141
- .where(and(inArray(bodyPartParameterTable.messageId, messageIds), guard)),
141
+ .where(and(inList(bodyPartParameterTable.messageId, messageIds), guard)),
142
142
  db
143
143
  .delete(bodyPartStorageTable)
144
- .where(and(inArray(bodyPartStorageTable.messageId, messageIds), guard)),
144
+ .where(and(inList(bodyPartStorageTable.messageId, messageIds), guard)),
145
145
  db
146
146
  .delete(rawMessageStorageTable)
147
- .where(and(inArray(rawMessageStorageTable.messageId, messageIds), guard)),
147
+ .where(and(inList(rawMessageStorageTable.messageId, messageIds), guard)),
148
148
  db
149
149
  .delete(bodyPartTable)
150
- .where(and(inArray(bodyPartTable.messageId, messageIds), guard)),
150
+ .where(and(inList(bodyPartTable.messageId, messageIds), guard)),
151
151
  db
152
152
  .delete(bodyStructureRefetchTable)
153
153
  .where(
154
- and(inArray(bodyStructureRefetchTable.messageId, messageIds), guard),
154
+ and(inList(bodyStructureRefetchTable.messageId, messageIds), guard),
155
155
  ),
156
156
  db
157
157
  .delete(envelopeTable)
158
- .where(and(inArray(envelopeTable.messageId, messageIds), guard)),
158
+ .where(and(inList(envelopeTable.messageId, messageIds), guard)),
159
159
  db
160
160
  .delete(messageTable)
161
- .where(and(inArray(messageTable.messageId, messageIds), guard))
161
+ .where(and(inList(messageTable.messageId, messageIds), guard))
162
162
  .returning({ messageId: messageTable.messageId }),
163
163
  ];
164
164
  }
@@ -192,10 +192,16 @@ const placementTerms = (expected: PlacementPredicate): SQL[] => {
192
192
  };
193
193
 
194
194
  export class DrizzleMessageRepository implements IMessageRepository {
195
+ private readonly db: DB;
196
+ private readonly batch: WriteBatch;
197
+
195
198
  constructor(
196
- private db: DB,
199
+ store: SqliteStore<Record<string, unknown>>,
197
200
  private search: SearchIndex,
198
- ) {}
201
+ ) {
202
+ this.db = store.db;
203
+ this.batch = store.batch;
204
+ }
199
205
 
200
206
  private rowOf(input: CreateMessageInput) {
201
207
  const now = Date.now();
@@ -275,7 +281,7 @@ export class DrizzleMessageRepository implements IMessageRepository {
275
281
  const rows = await this.db
276
282
  .select()
277
283
  .from(messageTable)
278
- .where(inArray(messageTable.messageId, messageId));
284
+ .where(inList(messageTable.messageId, messageId));
279
285
  return rows.map(toMessageItem);
280
286
  }
281
287
 
@@ -461,7 +467,7 @@ export class DrizzleMessageRepository implements IMessageRepository {
461
467
  .select({ threadMessageId: threadMessageTable.threadMessageId })
462
468
  .from(threadMessageTable)
463
469
  .where(eq(threadMessageTable.messageId, messageId));
464
- const results = await runBatch(this.db, [
470
+ const results = await this.batch([
465
471
  this.db
466
472
  .delete(threadMessageTable)
467
473
  .where(and(eq(threadMessageTable.messageId, messageId), placed)),
@@ -474,7 +480,7 @@ export class DrizzleMessageRepository implements IMessageRepository {
474
480
 
475
481
  async deleteMany(messageIds: string[]): Promise<void> {
476
482
  if (messageIds.length === 0) return;
477
- await runBatch(this.db, deleteMessageSubtree(this.db, messageIds));
483
+ await this.batch(deleteMessageSubtree(this.db, messageIds));
478
484
  }
479
485
 
480
486
  async listByMailbox(
@@ -547,18 +553,18 @@ export class DrizzleMessageRepository implements IMessageRepository {
547
553
  or(
548
554
  and(
549
555
  eq(messageTable.status, "moving"),
550
- inArray(messageTable.originalMailboxId, ids),
556
+ inList(messageTable.originalMailboxId, ids),
551
557
  isNotNull(messageTable.originalUid),
552
558
  ),
553
559
  and(
554
560
  eq(messageTable.status, "deleting"),
555
- inArray(messageTable.mailboxId, ids),
561
+ inList(messageTable.mailboxId, ids),
556
562
  ),
557
563
  and(
558
564
  eq(messageTable.status, "moving"),
559
565
  eq(messageTable.uid, 0),
560
566
  isNull(messageTable.originalUid),
561
- inArray(messageTable.mailboxId, ids),
567
+ inList(messageTable.mailboxId, ids),
562
568
  ),
563
569
  ),
564
570
  ),
@@ -622,7 +628,7 @@ export class DrizzleMessageRepository implements IMessageRepository {
622
628
  .select({ threadMessageId: threadMessageTable.threadMessageId })
623
629
  .from(threadMessageTable)
624
630
  .where(eq(threadMessageTable.messageId, messageId));
625
- const [moved] = await runBatch(this.db, [
631
+ const [moved] = await this.batch([
626
632
  this.db
627
633
  .update(messageTable)
628
634
  .set({
@@ -6,10 +6,10 @@ import type {
6
6
  } from "@remit/data-ports";
7
7
  import { deriveQuarantineId } from "@remit/data-ports/id";
8
8
  import { desc, eq } from "drizzle-orm";
9
- import type { Db } from "../db.js";
9
+ import type { RepositoryDb } from "../db.js";
10
10
  import { quarantineTable } from "../schema/quarantine.js";
11
11
 
12
- type DB = Db<Record<string, unknown>>;
12
+ type DB = RepositoryDb<Record<string, unknown>>;
13
13
 
14
14
  function rowToItem(row: typeof quarantineTable.$inferSelect): QuarantineItem {
15
15
  return {
@@ -5,11 +5,12 @@ import type {
5
5
  SyncWarningKey,
6
6
  } from "@remit/data-ports";
7
7
  import { deriveSyncWarningId } from "@remit/data-ports/id";
8
- import { and, count, desc, eq, gte, inArray, sql } from "drizzle-orm";
9
- import type { Db } from "../db.js";
8
+ import { and, count, desc, eq, gte, sql } from "drizzle-orm";
9
+ import type { RepositoryDb } from "../db.js";
10
10
  import { syncWarningTable } from "../schema/sync-warning.js";
11
+ import { inList } from "./in-list.js";
11
12
 
12
- type DB = Db<Record<string, unknown>>;
13
+ type DB = RepositoryDb<Record<string, unknown>>;
13
14
 
14
15
  export class SyncWarningRepo implements ISyncWarningRepository {
15
16
  constructor(private db: DB) {}
@@ -71,6 +72,6 @@ export class SyncWarningRepo implements ISyncWarningRepository {
71
72
  if (syncWarningIds.length === 0) return;
72
73
  await this.db
73
74
  .delete(syncWarningTable)
74
- .where(inArray(syncWarningTable.syncWarningId, syncWarningIds));
75
+ .where(inList(syncWarningTable.syncWarningId, syncWarningIds));
75
76
  };
76
77
  }
@@ -12,10 +12,10 @@ import { after, before, describe, test } from "node:test";
12
12
  import type { CreateThreadMessageInput } from "@remit/data-ports";
13
13
  import { addressTable } from "../schema/i4-address.js";
14
14
  import { threadMessageTable } from "../schema/thread-message.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
17
  import { shippedIndexDdl } from "../test-shipped-sqlite-schema.js";
18
- import { DrizzleThreadMessageRepository } from "./thread-message.js";
18
+ import type { DrizzleThreadMessageRepository } from "./thread-message.js";
19
19
 
20
20
  // The index the mail baseline creates for this predicate, read from the one
21
21
  // committed source so the plan assertion below is about the real object.
@@ -61,7 +61,11 @@ describe("thread-message body and muted-sender predicates (sqlite)", () => {
61
61
  { searchIndex: true },
62
62
  ));
63
63
  sqlite.exec(mutedIndexDdl);
64
- repo = new DrizzleThreadMessageRepository(db, new SearchIndex(db, db));
64
+ repo = createSqliteRepositories({
65
+ app: db,
66
+ mail: db,
67
+ search: db,
68
+ }).threadMessage;
65
69
  });
66
70
 
67
71
  after(async () => {
@@ -2,10 +2,10 @@ import assert from "node:assert/strict";
2
2
  import { after, before, describe, test } from "node:test";
3
3
  import type { CreateThreadMessageInput } from "@remit/data-ports";
4
4
  import { threadMessageTable } from "../schema/thread-message.js";
5
- import { SearchIndex } from "../search/thread-message-search.js";
5
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
6
6
  import { createSqliteTestDb } from "../test-db-sqlite.js";
7
7
  import {
8
- DrizzleThreadMessageRepository,
8
+ type DrizzleThreadMessageRepository,
9
9
  THREAD_SEARCH_MAX_LIMIT,
10
10
  } from "./thread-message.js";
11
11
 
@@ -55,10 +55,11 @@ describe("DrizzleThreadMessageRepository.listByFieldTerms (sqlite, #459)", () =>
55
55
  { searchIndex: true },
56
56
  );
57
57
  close = created.close;
58
- repo = new DrizzleThreadMessageRepository(
59
- created.db,
60
- new SearchIndex(created.db, created.db),
61
- );
58
+ repo = createSqliteRepositories({
59
+ app: created.db,
60
+ mail: created.db,
61
+ search: created.db,
62
+ }).threadMessage;
62
63
 
63
64
  await repo.create(
64
65
  makeInput({
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import { after, before, describe, test } from "node:test";
3
3
  import type { CreateThreadMessageInput } from "@remit/data-ports";
4
4
  import { threadMessageTable } from "../schema/thread-message.js";
5
- import { SearchIndex } from "../search/thread-message-search.js";
5
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
6
6
  import { createSqliteTestDb } from "../test-db-sqlite.js";
7
7
  import {
8
8
  BASE_DATE,
@@ -12,7 +12,7 @@ import {
12
12
  totalRows,
13
13
  } from "./category-fixture.js";
14
14
  import {
15
- DrizzleThreadMessageRepository,
15
+ type DrizzleThreadMessageRepository,
16
16
  THREAD_SEARCH_MAX_LIMIT,
17
17
  } from "./thread-message.js";
18
18
 
@@ -58,7 +58,11 @@ describe("DrizzleThreadMessageRepository (sqlite)", () => {
58
58
  },
59
59
  { searchIndex: true },
60
60
  ));
61
- repo = new DrizzleThreadMessageRepository(db, new SearchIndex(db, db));
61
+ repo = createSqliteRepositories({
62
+ app: db,
63
+ mail: db,
64
+ search: db,
65
+ }).threadMessage;
62
66
  });
63
67
 
64
68
  after(async () => {
@@ -3,11 +3,11 @@ import { after, before, describe, test } from "node:test";
3
3
  import type { CreateThreadMessageInput } from "@remit/data-ports";
4
4
  import shortUuid from "short-uuid";
5
5
  import { threadMessageTable } from "../schema/thread-message.js";
6
- import { SearchIndex } from "../search/thread-message-search.js";
6
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
7
7
  import { createSqliteTestDb } from "../test-db-sqlite.js";
8
8
  import {
9
9
  clampThreadSearchLimit,
10
- DrizzleThreadMessageRepository,
10
+ type DrizzleThreadMessageRepository,
11
11
  deriveThreadMessageId,
12
12
  THREAD_SEARCH_MAX_LIMIT,
13
13
  } from "./thread-message.js";
@@ -87,10 +87,11 @@ describe("DrizzleThreadMessageRepository.searchByMailboxWindow / countByMailbox"
87
87
  before(async () => {
88
88
  const harness = await setupDb();
89
89
  close = harness.close;
90
- repo = new DrizzleThreadMessageRepository(
91
- harness.db,
92
- new SearchIndex(harness.db, harness.db),
93
- );
90
+ repo = createSqliteRepositories({
91
+ app: harness.db,
92
+ mail: harness.db,
93
+ search: harness.db,
94
+ }).threadMessage;
94
95
  });
95
96
 
96
97
  after(async () => {
@@ -391,10 +392,11 @@ describe("DrizzleThreadMessageRepository.searchByDate", () => {
391
392
  before(async () => {
392
393
  const harness = await setupDb();
393
394
  close = harness.close;
394
- repo = new DrizzleThreadMessageRepository(
395
- harness.db,
396
- new SearchIndex(harness.db, harness.db),
397
- );
395
+ repo = createSqliteRepositories({
396
+ app: harness.db,
397
+ mail: harness.db,
398
+ search: harness.db,
399
+ }).threadMessage;
398
400
  });
399
401
 
400
402
  after(async () => {
@@ -921,10 +923,11 @@ describe("DrizzleThreadMessageRepository — native text search", () => {
921
923
  before(async () => {
922
924
  const harness = await setupDb();
923
925
  close = harness.close;
924
- repo = new DrizzleThreadMessageRepository(
925
- harness.db,
926
- new SearchIndex(harness.db, harness.db),
927
- );
926
+ repo = createSqliteRepositories({
927
+ app: harness.db,
928
+ mail: harness.db,
929
+ search: harness.db,
930
+ }).threadMessage;
928
931
  });
929
932
 
930
933
  after(async () => {
@@ -1096,10 +1099,11 @@ describe("DrizzleThreadMessageRepository — core CRUD", () => {
1096
1099
  before(async () => {
1097
1100
  const harness = await setupDb();
1098
1101
  close = harness.close;
1099
- repo = new DrizzleThreadMessageRepository(
1100
- harness.db,
1101
- new SearchIndex(harness.db, harness.db),
1102
- );
1102
+ repo = createSqliteRepositories({
1103
+ app: harness.db,
1104
+ mail: harness.db,
1105
+ search: harness.db,
1106
+ }).threadMessage;
1103
1107
  });
1104
1108
 
1105
1109
  after(async () => {