@remit/drizzle-service 0.0.102 → 0.0.104

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 (88) hide show
  1. package/package.json +4 -3
  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.d1.test.ts +20 -0
  9. package/src/repos/account.conformance.sqlite.test.ts +6 -2
  10. package/src/repos/calendar-collection.conformance.d1.test.ts +20 -0
  11. package/src/repos/calendar-collection.ts +2 -2
  12. package/src/repos/calendar-default.d1.test.ts +4 -0
  13. package/src/repos/calendar-default.sqlite.test.ts +3 -72
  14. package/src/repos/calendar-default.suite.ts +68 -0
  15. package/src/repos/calendar-event-index.conformance.d1.test.ts +20 -0
  16. package/src/repos/calendar-event-index.ts +2 -2
  17. package/src/repos/calendar-feed-token.conformance.d1.test.ts +20 -0
  18. package/src/repos/calendar-feed-token.ts +2 -2
  19. package/src/repos/calendar-object.conformance.d1.test.ts +20 -0
  20. package/src/repos/calendar-object.ts +2 -2
  21. package/src/repos/calendar-put.d1.test.ts +4 -0
  22. package/src/repos/calendar-put.sqlite.test.ts +3 -205
  23. package/src/repos/calendar-put.suite.ts +210 -0
  24. package/src/repos/calendar-store.ts +36 -40
  25. package/src/repos/calendar-suggestion.ts +2 -2
  26. package/src/repos/cascade-delete.test.ts +3 -2
  27. package/src/repos/cascade-delete.ts +12 -8
  28. package/src/repos/envelope.sqlite.test.ts +3 -2
  29. package/src/repos/envelope.test.ts +3 -2
  30. package/src/repos/envelope.ts +14 -9
  31. package/src/repos/filter.d1.test.ts +4 -0
  32. package/src/repos/filter.suite.ts +476 -0
  33. package/src/repos/filter.test.ts +3 -472
  34. package/src/repos/filter.ts +2 -2
  35. package/src/repos/i4-account-config.d1.test.ts +4 -0
  36. package/src/repos/i4-account-config.suite.ts +131 -0
  37. package/src/repos/i4-account-config.test.ts +3 -123
  38. package/src/repos/i4-account-config.ts +8 -10
  39. package/src/repos/i4-account-export-request.d1.test.ts +4 -0
  40. package/src/repos/i4-account-export-request.suite.ts +44 -0
  41. package/src/repos/i4-account-export-request.test.ts +3 -40
  42. package/src/repos/i4-account-export-request.ts +2 -2
  43. package/src/repos/i4-account-setting.d1.test.ts +4 -0
  44. package/src/repos/i4-account-setting.suite.ts +100 -0
  45. package/src/repos/i4-account-setting.test.ts +3 -93
  46. package/src/repos/i4-account-setting.ts +2 -2
  47. package/src/repos/i4-account.test.ts +12 -8
  48. package/src/repos/i4-account.ts +6 -10
  49. package/src/repos/i4-address-junk-move.sqlite.test.ts +7 -2
  50. package/src/repos/i4-address.ts +12 -2
  51. package/src/repos/i4-config-import.ts +2 -2
  52. package/src/repos/i4-mailbox-lock.ts +2 -2
  53. package/src/repos/i4-mailbox-special-use.sqlite.test.ts +12 -10
  54. package/src/repos/i4-mailbox-special-use.ts +29 -40
  55. package/src/repos/i4-mailbox.sqlite.test.ts +12 -10
  56. package/src/repos/i4-mailbox.test.ts +15 -14
  57. package/src/repos/i4-mailbox.ts +13 -7
  58. package/src/repos/i4-message-flag-push.ts +2 -2
  59. package/src/repos/i4-message-placement-move.ts +2 -2
  60. package/src/repos/i4-organize-job-request.ts +2 -2
  61. package/src/repos/i4-outbox-attachment.test.ts +7 -2
  62. package/src/repos/i4-outbox-attachment.ts +11 -5
  63. package/src/repos/i4-outbox-message.ts +2 -2
  64. package/src/repos/i4-sender-signer-standing.ts +2 -2
  65. package/src/repos/label.conformance.d1.test.ts +20 -0
  66. package/src/repos/label.ts +2 -2
  67. package/src/repos/message-flag.ts +3 -6
  68. package/src/repos/message-label.ts +2 -2
  69. package/src/repos/message.sqlite.test.ts +57 -3
  70. package/src/repos/message.test.ts +6 -5
  71. package/src/repos/message.ts +81 -10
  72. package/src/repos/quarantine.ts +2 -2
  73. package/src/repos/sync-warning.ts +2 -2
  74. package/src/repos/thread-message-body-muted.sqlite.test.ts +30 -22
  75. package/src/repos/thread-message-field-terms.sqlite.test.ts +16 -11
  76. package/src/repos/thread-message.sqlite.test.ts +13 -7
  77. package/src/repos/thread-message.test.ts +43 -27
  78. package/src/repos/thread-message.ts +26 -14
  79. package/src/repos/thread-search-predicates.ts +36 -63
  80. package/src/schema/message-data.ts +3 -0
  81. package/src/schema-sets/mail.ts +1 -0
  82. package/src/search/thread-message-search.sqlite.test.ts +57 -11
  83. package/src/search/thread-message-search.ts +26 -12
  84. package/src/sqlite-repositories.ts +48 -32
  85. package/src/test-d1.ts +40 -0
  86. package/src/test-db-sqlite.ts +1 -1
  87. package/src/test-shipped-sqlite-schema.ts +8 -6
  88. package/src/test-stores.ts +30 -0
@@ -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,
@@ -15,14 +18,21 @@ import {
15
18
  } from "@remit/data-ports/folder-role";
16
19
  import { CanonicalMailboxRole } from "@remit/domain-enums";
17
20
  import { eq } from "drizzle-orm";
18
- import type { Db } from "../db.js";
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";
23
24
  import { inList } from "./in-list.js";
24
25
 
25
- 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">;
26
36
 
27
37
  const JUNK_ROLES: readonly CanonicalMailboxRoleValue[] = [
28
38
  CanonicalMailboxRole.Junk,
@@ -89,14 +99,11 @@ function rowToSpecialUse(
89
99
  }
90
100
 
91
101
  export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
92
- private readonly accountSetting: AccountSettingRepo;
93
-
94
102
  constructor(
95
- private app: DB,
96
103
  private db: DB,
97
- ) {
98
- this.accountSetting = new AccountSettingRepo(app);
99
- }
104
+ private accounts: AccountReader,
105
+ private accountSetting: AppointmentReader,
106
+ ) {}
100
107
 
101
108
  async create(
102
109
  mailboxId: string,
@@ -203,11 +210,9 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
203
210
  async resolveJunkRolesForConfig(
204
211
  accountConfigId: string,
205
212
  ): Promise<JunkRoleMailboxes> {
206
- const accounts = await this.app
207
- .select({ accountId: accountTable.accountId })
208
- .from(accountTable)
209
- .where(eq(accountTable.accountConfigId, accountConfigId));
210
- return this.resolveJunkRoles(accounts.map((row) => row.accountId));
213
+ return this.resolveJunkRoles(
214
+ await this.accounts.listAllByAccountConfig(accountConfigId),
215
+ );
211
216
  }
212
217
 
213
218
  /**
@@ -217,21 +222,19 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
217
222
  * run one pass over the address table instead of one per config.
218
223
  */
219
224
  async resolveJunkRolesForInstance(): Promise<JunkRoleMailboxes> {
220
- const accounts = await this.app
221
- .select({ accountId: accountTable.accountId })
222
- .from(accountTable);
223
- return this.resolveJunkRoles(accounts.map((row) => row.accountId));
225
+ return this.resolveJunkRoles(await this.accounts.listAll());
224
226
  }
225
227
 
226
228
  private async resolveJunkRoles(
227
- accountIds: readonly string[],
229
+ accounts: readonly AppointedAccount[],
228
230
  ): Promise<JunkRoleMailboxes> {
229
- if (accountIds.length === 0) {
231
+ if (accounts.length === 0) {
230
232
  return { junkMailboxIds: [], trashMailboxIds: [] };
231
233
  }
234
+ const accountIds = accounts.map((account) => account.accountId);
232
235
  const [candidates, appointments] = await Promise.all([
233
236
  this.roleCandidatesFor(accountIds),
234
- this.appointedMailboxIds(accountIds, JUNK_ROLES),
237
+ this.appointedMailboxIds(accounts, JUNK_ROLES),
235
238
  ]);
236
239
 
237
240
  const junkMailboxIds: string[] = [];
@@ -255,24 +258,13 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
255
258
  }
256
259
 
257
260
  /**
258
- * Each account's appointment for each role, in two reads. Same precedence
259
- * input as `appointedMailboxId`, batched: an account row missing is a caller
260
- * racing a delete, and leaves the account with no appointment rather than
261
- * failing the lookup.
261
+ * Each account's appointment for each role, in one read. Same precedence
262
+ * input as `appointedMailboxId`, batched.
262
263
  */
263
264
  private async appointedMailboxIds(
264
- accountIds: readonly string[],
265
+ accounts: readonly AppointedAccount[],
265
266
  roles: readonly CanonicalMailboxRoleValue[],
266
267
  ): Promise<Map<string, string>> {
267
- const accounts = await this.app
268
- .select({
269
- accountId: accountTable.accountId,
270
- accountConfigId: accountTable.accountConfigId,
271
- })
272
- .from(accountTable)
273
- .where(inList(accountTable.accountId, accountIds));
274
- if (accounts.length === 0) return new Map();
275
-
276
268
  const wanted = new Map<string, string>();
277
269
  for (const account of accounts) {
278
270
  for (const role of roles) {
@@ -367,10 +359,7 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
367
359
  accountId: string,
368
360
  role: CanonicalMailboxRoleValue,
369
361
  ): Promise<string | undefined> {
370
- const [account] = await this.app
371
- .select({ accountConfigId: accountTable.accountConfigId })
372
- .from(accountTable)
373
- .where(eq(accountTable.accountId, accountId));
362
+ const [account] = await this.accounts.get([accountId]);
374
363
  if (!account) return undefined;
375
364
 
376
365
  const setting = await this.accountSetting.get(
@@ -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 {
@@ -52,7 +52,7 @@ const base36Translator = shortUuid.createTranslator(
52
52
  );
53
53
  const generateMailboxId = () => base36Translator.fromUUID(randomUUID());
54
54
 
55
- type DB = Db<Record<string, unknown>>;
55
+ type DB = RepositoryDb<Record<string, unknown>>;
56
56
 
57
57
  /**
58
58
  * Message subtrees removed per batch by {@link MailboxRepo.deleteMailboxWithMail},
@@ -149,10 +149,16 @@ export function rowToMailbox(
149
149
  }
150
150
 
151
151
  export class MailboxRepo implements IMailboxRepository {
152
+ private readonly db: DB;
153
+ private readonly batch: WriteBatch;
154
+
152
155
  constructor(
153
- private db: DB,
156
+ store: SqliteStore<Record<string, unknown>>,
154
157
  private search: SearchIndex,
155
- ) {}
158
+ ) {
159
+ this.db = store.db;
160
+ this.batch = store.batch;
161
+ }
156
162
 
157
163
  async create(input: CreateMailboxInput): Promise<CreateMailboxResult> {
158
164
  const now = Date.now();
@@ -379,7 +385,7 @@ export class MailboxRepo implements IMailboxRepository {
379
385
 
380
386
  async deleteMany(accountId: string, mailboxIds: string[]): Promise<void> {
381
387
  if (mailboxIds.length === 0) return;
382
- await runBatch(this.db, [
388
+ await this.batch([
383
389
  this.db
384
390
  .delete(syncWarningTable)
385
391
  .where(
@@ -556,7 +562,7 @@ export class MailboxRepo implements IMailboxRepository {
556
562
 
557
563
  // The primitive the rest of the codebase deletes mail with: nine
558
564
  // per-message child tables. A bespoke table list would orphan them.
559
- await runBatch(this.db, [
565
+ await this.batch([
560
566
  ...deleteMessageSubtree(this.db, messageIds),
561
567
  this.db
562
568
  .delete(threadMessageTable)
@@ -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 () => {
@@ -7,13 +7,13 @@ import type {
7
7
  } from "@remit/data-ports";
8
8
  import { holdsRoom } from "@remit/data-ports";
9
9
  import { and, eq, gt, gte, lt, or, sum } from "drizzle-orm";
10
- import { runBatch } from "../batch.js";
11
- import type { Db } from "../db.js";
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
14
  import { inList } from "./in-list.js";
15
15
 
16
- type DB = Db<Record<string, unknown>>;
16
+ type DB = RepositoryDb<Record<string, unknown>>;
17
17
 
18
18
  function rowToOutboxAttachment(
19
19
  row: typeof outboxAttachmentTable.$inferSelect,
@@ -35,7 +35,13 @@ function rowToOutboxAttachment(
35
35
  }
36
36
 
37
37
  export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
38
- 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
+ }
39
45
 
40
46
  /**
41
47
  * The per-message cap. The insert, the read of the draft's live rows and a
@@ -60,7 +66,7 @@ export class OutboxAttachmentRepo implements IOutboxAttachmentRepository {
60
66
  gte(outboxAttachmentTable.reservationExpiresAt, cap.nowSeconds),
61
67
  ),
62
68
  );
63
- const [inserted, rows, removed] = await runBatch(this.db, [
69
+ const [inserted, rows, removed] = await this.batch([
64
70
  this.db
65
71
  .insert(outboxAttachmentTable)
66
72
  .values({
@@ -6,13 +6,13 @@ import type {
6
6
  UpdateOutboxMessageInput,
7
7
  } from "@remit/data-ports";
8
8
  import { and, desc, eq, lt, or } from "drizzle-orm";
9
- import type { Db } from "../db.js";
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
13
  import { inList } from "./in-list.js";
14
14
 
15
- type DB = Db<Record<string, unknown>>;
15
+ type DB = RepositoryDb<Record<string, unknown>>;
16
16
 
17
17
  function rowToOutboxMessage(
18
18
  row: typeof outboxMessageTable.$inferSelect,
@@ -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,20 @@
1
+ import { labelRepositoryConformance } from "@remit/data-ports/conformance";
2
+ import { NotFoundError } from "../error.js";
3
+ import { randomId } from "../id.js";
4
+ import { openD1TestStores } from "../test-d1.js";
5
+ import { LabelRepo } from "./label.js";
6
+
7
+ let close: (() => Promise<void>) | undefined;
8
+
9
+ labelRepositoryConformance({
10
+ async createRepository() {
11
+ const opened = await openD1TestStores();
12
+ close = opened.close;
13
+ return new LabelRepo(opened.stores.app.db);
14
+ },
15
+ teardown: async () => {
16
+ await close?.();
17
+ },
18
+ makeId: () => randomId(),
19
+ isNotFoundError: (error) => error instanceof NotFoundError,
20
+ });
@@ -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
 
@@ -5,15 +5,12 @@ import type {
5
5
  MessageFlagItem,
6
6
  } from "@remit/data-ports";
7
7
  import { and, eq } from "drizzle-orm";
8
- import type { Db } from "../db.js";
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";
14
11
  import { inList } from "./in-list.js";
15
12
 
16
- type DB = Db<MessageDataSchema>;
13
+ type DB = RepositoryDb<Record<string, unknown>>;
17
14
 
18
15
  function rowToMessageFlag(
19
16
  row: typeof messageFlagTable.$inferSelect,
@@ -4,12 +4,12 @@ import type {
4
4
  MessageLabelItem,
5
5
  } from "@remit/data-ports";
6
6
  import { and, desc, eq } from "drizzle-orm";
7
- import type { Db } from "../db.js";
7
+ import type { RepositoryDb } from "../db.js";
8
8
  import { deterministicBase36Id } from "../id.js";
9
9
  import { messageLabelTable } from "../schema.js";
10
10
  import { inList } from "./in-list.js";
11
11
 
12
- type DB = Db<Record<string, unknown>>;
12
+ type DB = RepositoryDb<Record<string, unknown>>;
13
13
 
14
14
  function rowToMessageLabel(
15
15
  row: typeof messageLabelTable.$inferSelect,