@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.
- package/package.json +4 -3
- package/src/batch.sqlite.test.ts +4 -4
- package/src/batch.ts +47 -21
- package/src/databases.ts +24 -6
- package/src/db.ts +20 -5
- package/src/index.ts +12 -0
- package/src/repair/junk-only-address.sqlite.test.ts +7 -2
- package/src/repos/account.conformance.d1.test.ts +20 -0
- package/src/repos/account.conformance.sqlite.test.ts +6 -2
- package/src/repos/calendar-collection.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-collection.ts +2 -2
- package/src/repos/calendar-default.d1.test.ts +4 -0
- package/src/repos/calendar-default.sqlite.test.ts +3 -72
- package/src/repos/calendar-default.suite.ts +68 -0
- package/src/repos/calendar-event-index.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-event-index.ts +2 -2
- package/src/repos/calendar-feed-token.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-feed-token.ts +2 -2
- package/src/repos/calendar-object.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-object.ts +2 -2
- package/src/repos/calendar-put.d1.test.ts +4 -0
- package/src/repos/calendar-put.sqlite.test.ts +3 -205
- package/src/repos/calendar-put.suite.ts +210 -0
- package/src/repos/calendar-store.ts +36 -40
- package/src/repos/calendar-suggestion.ts +2 -2
- package/src/repos/cascade-delete.test.ts +3 -2
- package/src/repos/cascade-delete.ts +12 -8
- package/src/repos/envelope.sqlite.test.ts +3 -2
- package/src/repos/envelope.test.ts +3 -2
- package/src/repos/envelope.ts +14 -9
- package/src/repos/filter.d1.test.ts +4 -0
- package/src/repos/filter.suite.ts +476 -0
- package/src/repos/filter.test.ts +3 -472
- package/src/repos/filter.ts +2 -2
- package/src/repos/i4-account-config.d1.test.ts +4 -0
- package/src/repos/i4-account-config.suite.ts +131 -0
- package/src/repos/i4-account-config.test.ts +3 -123
- package/src/repos/i4-account-config.ts +8 -10
- package/src/repos/i4-account-export-request.d1.test.ts +4 -0
- package/src/repos/i4-account-export-request.suite.ts +44 -0
- package/src/repos/i4-account-export-request.test.ts +3 -40
- package/src/repos/i4-account-export-request.ts +2 -2
- package/src/repos/i4-account-setting.d1.test.ts +4 -0
- package/src/repos/i4-account-setting.suite.ts +100 -0
- package/src/repos/i4-account-setting.test.ts +3 -93
- package/src/repos/i4-account-setting.ts +2 -2
- package/src/repos/i4-account.test.ts +12 -8
- package/src/repos/i4-account.ts +6 -10
- package/src/repos/i4-address-junk-move.sqlite.test.ts +7 -2
- package/src/repos/i4-address.ts +12 -2
- package/src/repos/i4-config-import.ts +2 -2
- package/src/repos/i4-mailbox-lock.ts +2 -2
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +12 -10
- package/src/repos/i4-mailbox-special-use.ts +29 -40
- package/src/repos/i4-mailbox.sqlite.test.ts +12 -10
- package/src/repos/i4-mailbox.test.ts +15 -14
- package/src/repos/i4-mailbox.ts +13 -7
- package/src/repos/i4-message-flag-push.ts +2 -2
- package/src/repos/i4-message-placement-move.ts +2 -2
- package/src/repos/i4-organize-job-request.ts +2 -2
- package/src/repos/i4-outbox-attachment.test.ts +7 -2
- package/src/repos/i4-outbox-attachment.ts +11 -5
- package/src/repos/i4-outbox-message.ts +2 -2
- package/src/repos/i4-sender-signer-standing.ts +2 -2
- package/src/repos/label.conformance.d1.test.ts +20 -0
- package/src/repos/label.ts +2 -2
- package/src/repos/message-flag.ts +3 -6
- package/src/repos/message-label.ts +2 -2
- package/src/repos/message.sqlite.test.ts +57 -3
- package/src/repos/message.test.ts +6 -5
- package/src/repos/message.ts +81 -10
- package/src/repos/quarantine.ts +2 -2
- package/src/repos/sync-warning.ts +2 -2
- package/src/repos/thread-message-body-muted.sqlite.test.ts +30 -22
- package/src/repos/thread-message-field-terms.sqlite.test.ts +16 -11
- package/src/repos/thread-message.sqlite.test.ts +13 -7
- package/src/repos/thread-message.test.ts +43 -27
- package/src/repos/thread-message.ts +26 -14
- package/src/repos/thread-search-predicates.ts +36 -63
- package/src/schema/message-data.ts +3 -0
- package/src/schema-sets/mail.ts +1 -0
- package/src/search/thread-message-search.sqlite.test.ts +57 -11
- package/src/search/thread-message-search.ts +26 -12
- package/src/sqlite-repositories.ts +48 -32
- package/src/test-d1.ts +40 -0
- package/src/test-db-sqlite.ts +1 -1
- package/src/test-shipped-sqlite-schema.ts +8 -6
- package/src/test-stores.ts +30 -0
|
@@ -1,124 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
195
|
+
address: addresses,
|
|
198
196
|
};
|
|
199
197
|
}
|
|
200
198
|
|
|
@@ -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
|
|
2
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
15
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
16
16
|
|
|
17
17
|
function rowToItem(
|
|
18
18
|
row: typeof accountExportRequestTable.$inferSelect,
|
|
@@ -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
|
|
2
|
-
import {
|
|
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
|
-
|
|
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
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 =
|
|
165
|
-
db
|
|
166
|
-
|
|
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,
|
package/src/repos/i4-account.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
128
|
+
specialUse = createSqliteRepositories({
|
|
129
|
+
app: db,
|
|
130
|
+
mail: db,
|
|
131
|
+
search: db,
|
|
132
|
+
}).mailboxSpecialUse;
|
|
128
133
|
});
|
|
129
134
|
|
|
130
135
|
/**
|
package/src/repos/i4-address.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type SQL,
|
|
23
23
|
sql,
|
|
24
24
|
} from "drizzle-orm";
|
|
25
|
-
import type {
|
|
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 =
|
|
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> {
|