@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
@@ -1,124 +1,4 @@
1
- import assert from "node:assert";
2
- import { after, before, describe, test } from "node:test";
3
- import { createTestDb, randomId, type TestDb } from "../test-db.js";
4
- import { AccountRepo } from "./i4-account.js";
5
- import { AccountConfigRepo } from "./i4-account-config.js";
6
- import { AddressRepo } from "./i4-address.js";
1
+ import { openSqliteTestStores } from "../test-stores.js";
2
+ import { accountConfigRepoSuite } from "./i4-account-config.suite.js";
7
3
 
8
- describe("AccountConfigRepo", () => {
9
- let db: TestDb;
10
- let close: () => Promise<void>;
11
- let repo: AccountConfigRepo;
12
-
13
- before(async () => {
14
- ({ db, close } = await createTestDb());
15
- repo = new AccountConfigRepo(db as never, db as never);
16
- });
17
-
18
- after(async () => {
19
- await close();
20
- });
21
-
22
- test("create and get", async () => {
23
- const userId = randomId();
24
- const cfg = await repo.create({ userId, name: "My Config" });
25
-
26
- assert.ok(cfg.accountConfigId);
27
- assert.equal(cfg.userId, userId);
28
- assert.equal(cfg.name, "My Config");
29
- assert.equal(cfg.state, "active");
30
-
31
- const fetched = await repo.get(cfg.accountConfigId);
32
- assert.equal(fetched.accountConfigId, cfg.accountConfigId);
33
-
34
- await repo.delete(cfg.accountConfigId);
35
- });
36
-
37
- test("batchGet: WHERE id = ANY($1)", async () => {
38
- const userId = randomId();
39
- const c1 = await repo.create({ userId });
40
- const c2 = await repo.create({ userId });
41
-
42
- const results = await repo.get([c1.accountConfigId, c2.accountConfigId]);
43
- assert.equal(results.length, 2);
44
-
45
- await repo.deleteMany([c1.accountConfigId, c2.accountConfigId]);
46
- });
47
-
48
- test("batchGet empty array returns []", async () => {
49
- const results = await repo.get([]);
50
- assert.deepEqual(results, []);
51
- });
52
-
53
- test("get throws for missing config", async () => {
54
- await assert.rejects(repo.get(randomId()), /not found/i);
55
- });
56
-
57
- test("describe assembles collection (accountConfig + account + address)", async () => {
58
- const userId = randomId();
59
- const cfg = await repo.create({ userId });
60
-
61
- const accountRepo = new AccountRepo(db as never, db as never);
62
- const addrRepo = new AddressRepo(db as never);
63
-
64
- await accountRepo.create({
65
- accountConfigId: cfg.accountConfigId,
66
- username: "u",
67
- email: "u@test.com",
68
- isActive: true,
69
- imapHost: "imap.test.com",
70
- imapPort: 993,
71
- imapTls: true,
72
- imapStartTls: false,
73
- connectionState: "not_authenticated",
74
- });
75
-
76
- await addrRepo.createAddress({
77
- addressId: randomId(),
78
- accountConfigId: cfg.accountConfigId,
79
- localPart: "u",
80
- domain: "test.com",
81
- normalizedEmail: "u@test.com",
82
- normalizedCompound: "u@test.com:u",
83
- });
84
-
85
- const desc = await repo.describe(cfg.accountConfigId);
86
- assert.equal(desc.accountConfig.length, 1);
87
- assert.equal(desc.account.length, 1);
88
- assert.equal(desc.address.length, 1);
89
-
90
- await repo.delete(cfg.accountConfigId);
91
- });
92
-
93
- test("listAll returns all configs", async () => {
94
- const userId = randomId();
95
- const c1 = await repo.create({ userId });
96
- const c2 = await repo.create({ userId });
97
-
98
- const all = await repo.listAll();
99
- const ids = new Set(all.map((c) => c.accountConfigId));
100
- assert.ok(ids.has(c1.accountConfigId));
101
- assert.ok(ids.has(c2.accountConfigId));
102
-
103
- await repo.deleteMany([c1.accountConfigId, c2.accountConfigId]);
104
- });
105
-
106
- describe("continuation token rejection (#172)", () => {
107
- for (const [label, token] of [
108
- ["an unparseable", "not-a-cursor"],
109
- ["a bare number", Buffer.from("123").toString("base64url")],
110
- ["a JSON array", Buffer.from("[1,2]").toString("base64url")],
111
- ] as const) {
112
- test(`${label} token is rejected as a 400`, async () => {
113
- await assert.rejects(
114
- () => repo.listByUser(randomId(), { continuationToken: token }),
115
- (error: unknown) => {
116
- assert.equal((error as { statusCode?: number }).statusCode, 400);
117
- assert.equal((error as Error).name, "BadRequestError");
118
- return true;
119
- },
120
- );
121
- });
122
- }
123
- });
124
- });
4
+ accountConfigRepoSuite(openSqliteTestStores);
@@ -3,11 +3,12 @@ import type {
3
3
  AccountConfigItem,
4
4
  CreateAccountConfigInput,
5
5
  IAccountConfigRepository,
6
+ IAddressRepository,
6
7
  ResultList,
7
8
  UpdateAccountConfigInput,
8
9
  } from "@remit/data-ports";
9
10
  import { and, asc, eq, gt, or } from "drizzle-orm";
10
- import type { Db } from "../db.js";
11
+ import type { RepositoryDb } from "../db.js";
11
12
  import { NotFoundError } from "../error.js";
12
13
  import { randomId } from "../id.js";
13
14
  import { decodeToken, resultList } from "../pagination.js";
@@ -15,12 +16,12 @@ import {
15
16
  accountConfigTable,
16
17
  accountTable,
17
18
  } from "../schema/i4-account-config.js";
18
- import { addressTable } from "../schema/i4-address.js";
19
19
  import { rowToAccount } from "./i4-account.js";
20
- import { rowToAddress } from "./i4-address.js";
21
20
  import { inList } from "./in-list.js";
22
21
 
23
- type DB = Db<Record<string, unknown>>;
22
+ type DB = RepositoryDb<Record<string, unknown>>;
23
+
24
+ type AddressReader = Pick<IAddressRepository, "listAllByAccountConfig">;
24
25
 
25
26
  function rowToAccountConfig(
26
27
  row: typeof accountConfigTable.$inferSelect,
@@ -40,7 +41,7 @@ function rowToAccountConfig(
40
41
  export class AccountConfigRepo implements IAccountConfigRepository {
41
42
  constructor(
42
43
  private db: DB,
43
- private mail: DB,
44
+ private addresses: AddressReader,
44
45
  ) {}
45
46
 
46
47
  async create(input: CreateAccountConfigInput): Promise<AccountConfigItem> {
@@ -183,10 +184,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
183
184
  .select()
184
185
  .from(accountTable)
185
186
  .where(eq(accountTable.accountConfigId, accountConfigId)),
186
- this.mail
187
- .select()
188
- .from(addressTable)
189
- .where(eq(addressTable.accountConfigId, accountConfigId)),
187
+ this.addresses.listAllByAccountConfig(accountConfigId),
190
188
  ]);
191
189
  if (configs.length === 0) {
192
190
  throw new NotFoundError(`AccountConfig not found: ${accountConfigId}`);
@@ -194,7 +192,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
194
192
  return {
195
193
  accountConfig: configs.map(rowToAccountConfig),
196
194
  account: accounts.map(rowToAccount),
197
- address: addresses.map(rowToAddress),
195
+ address: addresses,
198
196
  };
199
197
  }
200
198
 
@@ -0,0 +1,4 @@
1
+ import { openD1TestStores } from "../test-d1.js";
2
+ import { accountExportRequestRepoSuite } from "./i4-account-export-request.suite.js";
3
+
4
+ accountExportRequestRepoSuite(openD1TestStores);
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { after, before, describe, test } from "node:test";
3
+ import { randomId } from "../id.js";
4
+ import type { OpenTestStores } from "../test-stores.js";
5
+ import { AccountExportRequestRepo } from "./i4-account-export-request.js";
6
+
7
+ export const accountExportRequestRepoSuite = (open: OpenTestStores): void => {
8
+ describe("AccountExportRequestRepo", () => {
9
+ let close: () => Promise<void>;
10
+ let repo: AccountExportRequestRepo;
11
+
12
+ before(async () => {
13
+ const opened = await open();
14
+ close = opened.close;
15
+ repo = new AccountExportRequestRepo(opened.stores.app.db);
16
+ });
17
+
18
+ after(async () => {
19
+ await close();
20
+ });
21
+
22
+ describe("continuation token rejection (#172)", () => {
23
+ for (const [label, token] of [
24
+ ["an unparseable", "not-a-cursor"],
25
+ ["a bare number", Buffer.from("123").toString("base64url")],
26
+ ["a JSON array", Buffer.from("[1,2]").toString("base64url")],
27
+ ] as const) {
28
+ test(`${label} token is rejected as a 400`, async () => {
29
+ await assert.rejects(
30
+ () =>
31
+ repo.listByAccountConfig(randomId(), {
32
+ continuationToken: token,
33
+ }),
34
+ (error: unknown) => {
35
+ assert.equal((error as { statusCode?: number }).statusCode, 400);
36
+ assert.equal((error as Error).name, "BadRequestError");
37
+ return true;
38
+ },
39
+ );
40
+ });
41
+ }
42
+ });
43
+ });
44
+ };
@@ -1,41 +1,4 @@
1
- import assert from "node:assert/strict";
2
- import { after, before, describe, test } from "node:test";
3
- import { createTestDb, randomId, type TestDb } from "../test-db.js";
4
- import { AccountExportRequestRepo } from "./i4-account-export-request.js";
1
+ import { openSqliteTestStores } from "../test-stores.js";
2
+ import { accountExportRequestRepoSuite } from "./i4-account-export-request.suite.js";
5
3
 
6
- describe("AccountExportRequestRepo", () => {
7
- let db: TestDb;
8
- let close: () => Promise<void>;
9
- let repo: AccountExportRequestRepo;
10
-
11
- before(async () => {
12
- ({ db, close } = await createTestDb());
13
- repo = new AccountExportRequestRepo(db as never);
14
- });
15
-
16
- after(async () => {
17
- await close();
18
- });
19
-
20
- describe("continuation token rejection (#172)", () => {
21
- for (const [label, token] of [
22
- ["an unparseable", "not-a-cursor"],
23
- ["a bare number", Buffer.from("123").toString("base64url")],
24
- ["a JSON array", Buffer.from("[1,2]").toString("base64url")],
25
- ] as const) {
26
- test(`${label} token is rejected as a 400`, async () => {
27
- await assert.rejects(
28
- () =>
29
- repo.listByAccountConfig(randomId(), {
30
- continuationToken: token,
31
- }),
32
- (error: unknown) => {
33
- assert.equal((error as { statusCode?: number }).statusCode, 400);
34
- assert.equal((error as Error).name, "BadRequestError");
35
- return true;
36
- },
37
- );
38
- });
39
- }
40
- });
41
- });
4
+ accountExportRequestRepoSuite(openSqliteTestStores);
@@ -6,13 +6,13 @@ import type {
6
6
  UpdateAccountExportRequestInput,
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 { accountExportRequestTable } from "../schema/i4-account-export-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 accountExportRequestTable.$inferSelect,
@@ -0,0 +1,4 @@
1
+ import { openD1TestStores } from "../test-d1.js";
2
+ import { accountSettingRepoSuite } from "./i4-account-setting.suite.js";
3
+
4
+ accountSettingRepoSuite(openD1TestStores);
@@ -0,0 +1,100 @@
1
+ import assert from "node:assert";
2
+ import { after, before, describe, test } from "node:test";
3
+ import { randomId } from "../id.js";
4
+ import type { OpenTestStores } from "../test-stores.js";
5
+ import { AccountSettingRepo } from "./i4-account-setting.js";
6
+
7
+ export const accountSettingRepoSuite = (open: OpenTestStores): void => {
8
+ describe("AccountSettingRepo", () => {
9
+ let close: () => Promise<void>;
10
+ let repo: AccountSettingRepo;
11
+
12
+ before(async () => {
13
+ const opened = await open();
14
+ close = opened.close;
15
+ repo = new AccountSettingRepo(opened.stores.app.db);
16
+ });
17
+
18
+ after(async () => {
19
+ await close();
20
+ });
21
+
22
+ test("upsert and get", async () => {
23
+ const accountConfigId = randomId();
24
+ await repo.upsert({
25
+ accountConfigId,
26
+ name: "Theme",
27
+ value: { kind: "String", value: "dark" },
28
+ });
29
+
30
+ const setting = await repo.get(accountConfigId, "Theme");
31
+ assert.ok(setting);
32
+ assert.equal(setting.name, "Theme");
33
+ assert.deepEqual(setting.value, { kind: "String", value: "dark" });
34
+
35
+ await repo.delete(accountConfigId, "Theme");
36
+ });
37
+
38
+ test("upsert is idempotent (overwrites)", async () => {
39
+ const accountConfigId = randomId();
40
+ await repo.upsert({
41
+ accountConfigId,
42
+ name: "Density",
43
+ value: { kind: "String", value: "compact" },
44
+ });
45
+ await repo.upsert({
46
+ accountConfigId,
47
+ name: "Density",
48
+ value: { kind: "String", value: "comfortable" },
49
+ });
50
+
51
+ const setting = await repo.get(accountConfigId, "Density");
52
+ assert.deepEqual(setting?.value, {
53
+ kind: "String",
54
+ value: "comfortable",
55
+ });
56
+
57
+ await repo.delete(accountConfigId, "Density");
58
+ });
59
+
60
+ test("get returns null when setting absent", async () => {
61
+ const setting = await repo.get(randomId(), "Theme");
62
+ assert.equal(setting, null);
63
+ });
64
+
65
+ test("listByAccountConfig returns all settings ordered by name", async () => {
66
+ const accountConfigId = randomId();
67
+ await repo.upsert({
68
+ accountConfigId,
69
+ name: "Theme",
70
+ value: { kind: "String", value: "dark" },
71
+ });
72
+ await repo.upsert({
73
+ accountConfigId,
74
+ name: "Density",
75
+ value: { kind: "String", value: "compact" },
76
+ });
77
+
78
+ const settings = await repo.listByAccountConfig(accountConfigId);
79
+ assert.equal(settings.length, 2);
80
+ assert.equal(settings[0].name, "Density");
81
+ assert.equal(settings[1].name, "Theme");
82
+
83
+ await repo.delete(accountConfigId, "Theme");
84
+ await repo.delete(accountConfigId, "Density");
85
+ });
86
+
87
+ test("delete removes the setting", async () => {
88
+ const accountConfigId = randomId();
89
+ await repo.upsert({
90
+ accountConfigId,
91
+ name: "Theme",
92
+ value: { kind: "Boolean", value: true },
93
+ });
94
+ await repo.delete(accountConfigId, "Theme");
95
+
96
+ const setting = await repo.get(accountConfigId, "Theme");
97
+ assert.equal(setting, null);
98
+ });
99
+ });
100
+ };
@@ -1,94 +1,4 @@
1
- import assert from "node:assert";
2
- import { after, before, describe, test } from "node:test";
3
- import { createTestDb, randomId, type TestDb } from "../test-db.js";
4
- import { AccountSettingRepo } from "./i4-account-setting.js";
1
+ import { openSqliteTestStores } from "../test-stores.js";
2
+ import { accountSettingRepoSuite } from "./i4-account-setting.suite.js";
5
3
 
6
- describe("AccountSettingRepo", () => {
7
- let db: TestDb;
8
- let close: () => Promise<void>;
9
- let repo: AccountSettingRepo;
10
-
11
- before(async () => {
12
- ({ db, close } = await createTestDb());
13
- repo = new AccountSettingRepo(db as never);
14
- });
15
-
16
- after(async () => {
17
- await close();
18
- });
19
-
20
- test("upsert and get", async () => {
21
- const accountConfigId = randomId();
22
- await repo.upsert({
23
- accountConfigId,
24
- name: "Theme",
25
- value: { kind: "String", value: "dark" },
26
- });
27
-
28
- const setting = await repo.get(accountConfigId, "Theme");
29
- assert.ok(setting);
30
- assert.equal(setting.name, "Theme");
31
- assert.deepEqual(setting.value, { kind: "String", value: "dark" });
32
-
33
- await repo.delete(accountConfigId, "Theme");
34
- });
35
-
36
- test("upsert is idempotent (overwrites)", async () => {
37
- const accountConfigId = randomId();
38
- await repo.upsert({
39
- accountConfigId,
40
- name: "Density",
41
- value: { kind: "String", value: "compact" },
42
- });
43
- await repo.upsert({
44
- accountConfigId,
45
- name: "Density",
46
- value: { kind: "String", value: "comfortable" },
47
- });
48
-
49
- const setting = await repo.get(accountConfigId, "Density");
50
- assert.deepEqual(setting?.value, { kind: "String", value: "comfortable" });
51
-
52
- await repo.delete(accountConfigId, "Density");
53
- });
54
-
55
- test("get returns null when setting absent", async () => {
56
- const setting = await repo.get(randomId(), "Theme");
57
- assert.equal(setting, null);
58
- });
59
-
60
- test("listByAccountConfig returns all settings ordered by name", async () => {
61
- const accountConfigId = randomId();
62
- await repo.upsert({
63
- accountConfigId,
64
- name: "Theme",
65
- value: { kind: "String", value: "dark" },
66
- });
67
- await repo.upsert({
68
- accountConfigId,
69
- name: "Density",
70
- value: { kind: "String", value: "compact" },
71
- });
72
-
73
- const settings = await repo.listByAccountConfig(accountConfigId);
74
- assert.equal(settings.length, 2);
75
- assert.equal(settings[0].name, "Density");
76
- assert.equal(settings[1].name, "Theme");
77
-
78
- await repo.delete(accountConfigId, "Theme");
79
- await repo.delete(accountConfigId, "Density");
80
- });
81
-
82
- test("delete removes the setting", async () => {
83
- const accountConfigId = randomId();
84
- await repo.upsert({
85
- accountConfigId,
86
- name: "Theme",
87
- value: { kind: "Boolean", value: true },
88
- });
89
- await repo.delete(accountConfigId, "Theme");
90
-
91
- const setting = await repo.get(accountConfigId, "Theme");
92
- assert.equal(setting, null);
93
- });
94
- });
4
+ accountSettingRepoSuite(openSqliteTestStores);
@@ -6,11 +6,11 @@ import type {
6
6
  UpsertAccountSettingInput,
7
7
  } from "@remit/data-ports";
8
8
  import { and, eq } from "drizzle-orm";
9
- import type { Db } from "../db.js";
9
+ import type { RepositoryDb } from "../db.js";
10
10
  import { accountSettingTable } from "../schema/i4-account-setting.js";
11
11
  import { inList } from "./in-list.js";
12
12
 
13
- type DB = Db<Record<string, unknown>>;
13
+ type DB = RepositoryDb<Record<string, unknown>>;
14
14
 
15
15
  // The PG store derives its own deterministic id from the composite key. It is
16
16
  // stable within this backend but not byte-identical to the DDB base36 UUIDv5
@@ -1,9 +1,8 @@
1
1
  import assert from "node:assert";
2
2
  import { after, before, describe, test } from "node:test";
3
- import { SearchIndex } from "../search/thread-message-search.js";
3
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
4
4
  import { createTestDb, randomId, type TestDb } from "../test-db.js";
5
- import { AccountRepo } from "./i4-account.js";
6
- import { MailboxRepo } from "./i4-mailbox.js";
5
+ import type { AccountRepo } from "./i4-account.js";
7
6
  import { mailboxCreated } from "./test-helpers.js";
8
7
 
9
8
  function makeAccountInput(accountConfigId: string) {
@@ -27,7 +26,11 @@ describe("AccountRepo", () => {
27
26
 
28
27
  before(async () => {
29
28
  ({ db, close } = await createTestDb());
30
- repo = new AccountRepo(db as never, db as never);
29
+ repo = createSqliteRepositories({
30
+ app: db,
31
+ mail: db,
32
+ search: db,
33
+ }).account;
31
34
  });
32
35
 
33
36
  after(async () => {
@@ -161,10 +164,11 @@ describe("AccountRepo", () => {
161
164
  const accountConfigId = randomId();
162
165
  const account = await repo.create(makeAccountInput(accountConfigId));
163
166
 
164
- const mailboxRepo = new MailboxRepo(
165
- db as never,
166
- new SearchIndex(db as never, db as never),
167
- );
167
+ const mailboxRepo = createSqliteRepositories({
168
+ app: db,
169
+ mail: db,
170
+ search: db,
171
+ }).mailbox;
168
172
  mailboxCreated(
169
173
  await mailboxRepo.create({
170
174
  accountId: account.accountId,
@@ -4,22 +4,21 @@ import type {
4
4
  AccountSchedulerPage,
5
5
  CreateAccountInput,
6
6
  IAccountRepository,
7
+ IMailboxRepository,
7
8
  RemovableAccountField,
8
9
  ResultList,
9
10
  UpdateAccountInput,
10
11
  } from "@remit/data-ports";
11
12
  import { AccountService, SyncPhase } from "@remit/domain-enums";
12
13
  import { and, asc, eq, gt, like, or, sql } from "drizzle-orm";
13
- import type { Db } from "../db.js";
14
+ import type { RepositoryDb } from "../db.js";
14
15
  import { NotFoundError } from "../error.js";
15
16
  import { randomId } from "../id.js";
16
17
  import { decodeToken, encodeToken, resultList } from "../pagination.js";
17
18
  import { accountTable } from "../schema/i4-account-config.js";
18
- import { mailboxTable } from "../schema/i4-mailbox.js";
19
- import { rowToMailbox } from "./i4-mailbox.js";
20
19
  import { inList } from "./in-list.js";
21
20
 
22
- type DB = Db<Record<string, unknown>>;
21
+ type DB = RepositoryDb<Record<string, unknown>>;
23
22
 
24
23
  /**
25
24
  * A stored failure is a sentence the account card shows, not a transcript.
@@ -74,7 +73,7 @@ export function rowToAccount(
74
73
  export class AccountRepo implements IAccountRepository {
75
74
  constructor(
76
75
  private db: DB,
77
- private mail: DB,
76
+ private mailboxes: Pick<IMailboxRepository, "listAllByAccount">,
78
77
  ) {}
79
78
 
80
79
  async create(input: CreateAccountInput): Promise<AccountItem> {
@@ -269,17 +268,14 @@ export class AccountRepo implements IAccountRepository {
269
268
  .select()
270
269
  .from(accountTable)
271
270
  .where(eq(accountTable.accountId, accountId)),
272
- this.mail
273
- .select()
274
- .from(mailboxTable)
275
- .where(eq(mailboxTable.accountId, accountId)),
271
+ this.mailboxes.listAllByAccount(accountId),
276
272
  ]);
277
273
  if (accounts.length === 0) {
278
274
  throw new NotFoundError(`Account not found: ${accountId}`);
279
275
  }
280
276
  return {
281
277
  account: accounts.map(rowToAccount),
282
- mailbox: mailboxes.map(rowToMailbox),
278
+ mailbox: mailboxes,
283
279
  };
284
280
  }
285
281
 
@@ -1,9 +1,10 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { after, before, beforeEach, describe, test } from "node:test";
3
3
  import type Database from "better-sqlite3";
4
+ import { createSqliteRepositories } from "../sqlite-repositories.js";
4
5
  import { createTestDb, type TestDb } from "../test-db.js";
5
6
  import { AddressRepo } from "./i4-address.js";
6
- import { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
7
+ import type { MailboxSpecialUseRepo } from "./i4-mailbox-special-use.js";
7
8
 
8
9
  const CONFIG = "cfg-1";
9
10
 
@@ -124,7 +125,11 @@ describe("reconciling one message's addresses at the moment it moves", () => {
124
125
  before(async () => {
125
126
  ({ db, sqlite, close } = await createTestDb());
126
127
  repo = new AddressRepo(db as never);
127
- specialUse = new MailboxSpecialUseRepo(db as never, db as never);
128
+ specialUse = createSqliteRepositories({
129
+ app: db,
130
+ mail: db,
131
+ search: db,
132
+ }).mailboxSpecialUse;
128
133
  });
129
134
 
130
135
  /**
@@ -22,7 +22,7 @@ import {
22
22
  type SQL,
23
23
  sql,
24
24
  } from "drizzle-orm";
25
- import type { Db } from "../db.js";
25
+ import type { RepositoryDb } from "../db.js";
26
26
  import { NotFoundError } from "../error.js";
27
27
  import { envelopeAddressId } from "../id.js";
28
28
  import { decodeToken, resultList } from "../pagination.js";
@@ -44,7 +44,7 @@ import {
44
44
  } from "./address-search-predicates.js";
45
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]:
@@ -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> {