@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.
- package/package.json +1 -1
- 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.sqlite.test.ts +6 -2
- package/src/repos/calendar-collection.ts +2 -2
- package/src/repos/calendar-default.sqlite.test.ts +6 -2
- package/src/repos/calendar-event-index.ts +2 -2
- package/src/repos/calendar-feed-token.ts +2 -2
- package/src/repos/calendar-object.ts +2 -2
- package/src/repos/calendar-put.sqlite.test.ts +6 -2
- package/src/repos/calendar-store.ts +36 -40
- package/src/repos/calendar-suggestion.ts +5 -4
- package/src/repos/cascade-delete.test.ts +3 -2
- package/src/repos/cascade-delete.ts +38 -35
- package/src/repos/envelope.sqlite.test.ts +3 -2
- package/src/repos/envelope.test.ts +3 -2
- package/src/repos/envelope.ts +19 -13
- package/src/repos/filter.ts +2 -2
- package/src/repos/i4-account-config.test.ts +10 -6
- package/src/repos/i4-account-config.ts +12 -13
- package/src/repos/i4-account-export-request.ts +2 -2
- package/src/repos/i4-account-setting.ts +6 -5
- package/src/repos/i4-account.test.ts +12 -8
- package/src/repos/i4-account.ts +10 -13
- package/src/repos/i4-address-junk-move.sqlite.test.ts +7 -2
- package/src/repos/i4-address.ts +17 -7
- 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 +34 -44
- 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 +19 -12
- 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 +14 -10
- package/src/repos/i4-outbox-message.ts +7 -6
- package/src/repos/i4-sender-signer-standing.ts +2 -2
- package/src/repos/in-list.sqlite.test.ts +61 -0
- package/src/repos/in-list.ts +14 -0
- package/src/repos/label.ts +2 -2
- package/src/repos/message-flag.ts +8 -10
- package/src/repos/message-label.ts +5 -4
- package/src/repos/message.sqlite.test.ts +3 -3
- package/src/repos/message.test.ts +6 -5
- package/src/repos/message.ts +30 -24
- package/src/repos/quarantine.ts +2 -2
- package/src/repos/sync-warning.ts +5 -4
- package/src/repos/thread-message-body-muted.sqlite.test.ts +7 -3
- package/src/repos/thread-message-field-terms.sqlite.test.ts +7 -6
- package/src/repos/thread-message.sqlite.test.ts +7 -3
- package/src/repos/thread-message.test.ts +22 -18
- package/src/repos/thread-message.ts +16 -7
- package/src/search/thread-message-search.sqlite.test.ts +8 -3
- package/src/search/thread-message-search.ts +13 -8
- package/src/sqlite-repositories.ts +48 -32
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { runBatch, type SqliteBatchItem } from "../batch.js";
|
|
1
|
+
import type { SqliteBatchItem } from "../batch.js";
|
|
3
2
|
import {
|
|
4
3
|
openSqliteDatabases,
|
|
5
4
|
type SqliteDatabasePaths,
|
|
6
|
-
type
|
|
5
|
+
type SqliteStores,
|
|
6
|
+
syncStores,
|
|
7
7
|
} from "../databases.js";
|
|
8
8
|
import { accountTable } from "../schema/i4-account-config.js";
|
|
9
9
|
import { accountSettingTable } from "../schema/i4-account-setting.js";
|
|
@@ -17,6 +17,7 @@ import { quarantineTable } from "../schema/quarantine.js";
|
|
|
17
17
|
import { syncWarningTable } from "../schema/sync-warning.js";
|
|
18
18
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
19
19
|
import { SearchIndex } from "../search/thread-message-search.js";
|
|
20
|
+
import { inList, pairInList } from "./in-list.js";
|
|
20
21
|
import { deleteMessageSubtree } from "./message.js";
|
|
21
22
|
|
|
22
23
|
export interface CascadeEntity {
|
|
@@ -82,10 +83,12 @@ const groupByType = (
|
|
|
82
83
|
* cascade-in-progress marker.
|
|
83
84
|
*/
|
|
84
85
|
export const runDrizzleCascadeDelete = async (
|
|
85
|
-
|
|
86
|
+
stores: SqliteStores,
|
|
86
87
|
entities: CascadeEntity[],
|
|
87
88
|
log: CascadeDeleteLogger,
|
|
88
89
|
): Promise<void> => {
|
|
90
|
+
const app = stores.app.db;
|
|
91
|
+
const mail = stores.mail.db;
|
|
89
92
|
const grouped = groupByType(entities);
|
|
90
93
|
|
|
91
94
|
for (const entityType of grouped.keys()) {
|
|
@@ -107,7 +110,7 @@ export const runDrizzleCascadeDelete = async (
|
|
|
107
110
|
items.push(
|
|
108
111
|
mail
|
|
109
112
|
.delete(threadMessageTable)
|
|
110
|
-
.where(
|
|
113
|
+
.where(inList(threadMessageTable.threadMessageId, threadMessageIds)),
|
|
111
114
|
);
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -116,12 +119,12 @@ export const runDrizzleCascadeDelete = async (
|
|
|
116
119
|
items.push(
|
|
117
120
|
mail
|
|
118
121
|
.delete(syncWarningTable)
|
|
119
|
-
.where(
|
|
122
|
+
.where(inList(syncWarningTable.mailboxId, mailboxIds)),
|
|
120
123
|
);
|
|
121
124
|
items.push(
|
|
122
125
|
mail
|
|
123
126
|
.delete(mailboxTable)
|
|
124
|
-
.where(
|
|
127
|
+
.where(inList(mailboxTable.mailboxId, mailboxIds)),
|
|
125
128
|
);
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -132,19 +135,21 @@ export const runDrizzleCascadeDelete = async (
|
|
|
132
135
|
items.push(
|
|
133
136
|
mail
|
|
134
137
|
.delete(outboxMessageTable)
|
|
135
|
-
.where(
|
|
138
|
+
.where(inList(outboxMessageTable.outboxMessageId, outboxMessageIds)),
|
|
136
139
|
);
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
const lockKeys = grouped.get("MailboxLock") ?? [];
|
|
140
143
|
if (lockKeys.length > 0) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
items.push(
|
|
145
|
+
mail.delete(mailboxLockTable).where(
|
|
146
|
+
pairInList(
|
|
147
|
+
mailboxLockTable.mailboxId,
|
|
148
|
+
mailboxLockTable.eventName,
|
|
149
|
+
lockKeys.map((k) => [k.mailboxId, k.eventName]),
|
|
150
|
+
),
|
|
145
151
|
),
|
|
146
|
-
)
|
|
147
|
-
items.push(mail.delete(mailboxLockTable).where(or(...conditions)));
|
|
152
|
+
);
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
const accountSettingIds = (grouped.get("AccountSetting") ?? []).map(
|
|
@@ -154,9 +159,7 @@ export const runDrizzleCascadeDelete = async (
|
|
|
154
159
|
appItems.push(
|
|
155
160
|
app
|
|
156
161
|
.delete(accountSettingTable)
|
|
157
|
-
.where(
|
|
158
|
-
inArray(accountSettingTable.accountSettingId, accountSettingIds),
|
|
159
|
-
),
|
|
162
|
+
.where(inList(accountSettingTable.accountSettingId, accountSettingIds)),
|
|
160
163
|
);
|
|
161
164
|
}
|
|
162
165
|
|
|
@@ -165,7 +168,7 @@ export const runDrizzleCascadeDelete = async (
|
|
|
165
168
|
items.push(
|
|
166
169
|
mail
|
|
167
170
|
.delete(addressTable)
|
|
168
|
-
.where(
|
|
171
|
+
.where(inList(addressTable.addressId, addressIds)),
|
|
169
172
|
);
|
|
170
173
|
}
|
|
171
174
|
|
|
@@ -178,17 +181,17 @@ export const runDrizzleCascadeDelete = async (
|
|
|
178
181
|
items.push(
|
|
179
182
|
mail
|
|
180
183
|
.delete(quarantineTable)
|
|
181
|
-
.where(
|
|
184
|
+
.where(inList(quarantineTable.accountId, accountIds)),
|
|
182
185
|
);
|
|
183
186
|
items.push(
|
|
184
187
|
mail
|
|
185
188
|
.delete(syncWarningTable)
|
|
186
|
-
.where(
|
|
189
|
+
.where(inList(syncWarningTable.accountId, accountIds)),
|
|
187
190
|
);
|
|
188
191
|
appItems.push(
|
|
189
192
|
app
|
|
190
193
|
.delete(accountTable)
|
|
191
|
-
.where(
|
|
194
|
+
.where(inList(accountTable.accountId, accountIds)),
|
|
192
195
|
);
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -200,27 +203,26 @@ export const runDrizzleCascadeDelete = async (
|
|
|
200
203
|
mail
|
|
201
204
|
.delete(messagePlacementMoveTable)
|
|
202
205
|
.where(
|
|
203
|
-
|
|
206
|
+
inList(messagePlacementMoveTable.messageId, placementMoveMessageIds),
|
|
204
207
|
),
|
|
205
208
|
);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
// Composite key (messageId, flagName) — a message can carry an
|
|
209
|
-
// independent pending read-state marker and pending star marker at once
|
|
210
|
-
// (issue #1273), so a single-column inArray cannot target one row.
|
|
211
211
|
const flagPushKeys = grouped.get("MessageFlagPush") ?? [];
|
|
212
212
|
if (flagPushKeys.length > 0) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
items.push(
|
|
214
|
+
mail.delete(messageFlagPushTable).where(
|
|
215
|
+
pairInList(
|
|
216
|
+
messageFlagPushTable.messageId,
|
|
217
|
+
messageFlagPushTable.flagName,
|
|
218
|
+
flagPushKeys.map((k) => [k.messageId, k.flagName]),
|
|
219
|
+
),
|
|
217
220
|
),
|
|
218
|
-
)
|
|
219
|
-
items.push(mail.delete(messageFlagPushTable).where(or(...conditions)));
|
|
221
|
+
);
|
|
220
222
|
}
|
|
221
|
-
await
|
|
222
|
-
await
|
|
223
|
-
await new SearchIndex(mail, search).sync(threadMessageIds);
|
|
223
|
+
await stores.mail.batch(items);
|
|
224
|
+
await stores.app.batch(appItems);
|
|
225
|
+
await new SearchIndex(mail, stores.search).sync(threadMessageIds);
|
|
224
226
|
|
|
225
227
|
log.info({ entityCount: entities.length }, "Drizzle cascade delete complete");
|
|
226
228
|
};
|
|
@@ -239,5 +241,6 @@ export const createSqliteCascadeDeleter = async (
|
|
|
239
241
|
paths: SqliteDatabasePaths,
|
|
240
242
|
): Promise<CascadeDeleter> => {
|
|
241
243
|
const databases = await openSqliteDatabases(paths);
|
|
242
|
-
|
|
244
|
+
const stores = syncStores(databases);
|
|
245
|
+
return (entities, log) => runDrizzleCascadeDelete(stores, entities, log);
|
|
243
246
|
};
|
|
@@ -4,8 +4,9 @@ import {
|
|
|
4
4
|
type MessageDataSchema,
|
|
5
5
|
messageDataSchema,
|
|
6
6
|
} from "../schema/message-data.js";
|
|
7
|
+
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
7
8
|
import { createSqliteTestDb, type SqliteTestDb } from "../test-db-sqlite.js";
|
|
8
|
-
import { DrizzleEnvelopeRepository } from "./envelope.js";
|
|
9
|
+
import type { DrizzleEnvelopeRepository } from "./envelope.js";
|
|
9
10
|
|
|
10
11
|
// Envelope repo on sqlite (RFC 036 D1): the onConflictDoUpdate upsert path and
|
|
11
12
|
// the transactional body-part upsert both run on better-sqlite3 unchanged.
|
|
@@ -19,7 +20,7 @@ describe("DrizzleEnvelopeRepository (sqlite)", () => {
|
|
|
19
20
|
|
|
20
21
|
before(async () => {
|
|
21
22
|
({ db, close } = await createSqliteTestDb(messageDataSchema));
|
|
22
|
-
repo =
|
|
23
|
+
repo = createSqliteRepositories({ app: db, mail: db, search: db }).envelope;
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
after(async () => {
|
|
@@ -2,7 +2,8 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
3
|
import type { BodyPartUpsertInput } from "@remit/data-ports";
|
|
4
4
|
import { envelopeId as deriveEnvelopeId } from "../id.js";
|
|
5
|
-
import {
|
|
5
|
+
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
6
|
+
import type { DrizzleEnvelopeRepository } from "./envelope.js";
|
|
6
7
|
import { createTestDb, type TestDb } from "./test-helpers.js";
|
|
7
8
|
|
|
8
9
|
describe("DrizzleEnvelopeRepository", () => {
|
|
@@ -18,7 +19,7 @@ describe("DrizzleEnvelopeRepository", () => {
|
|
|
18
19
|
|
|
19
20
|
before(async () => {
|
|
20
21
|
({ db, stop } = await createTestDb());
|
|
21
|
-
repo =
|
|
22
|
+
repo = createSqliteRepositories({ app: db, mail: db, search: db }).envelope;
|
|
22
23
|
|
|
23
24
|
await repo.upsertEnvelope({
|
|
24
25
|
envelopeId: ENVELOPE_ID,
|
package/src/repos/envelope.ts
CHANGED
|
@@ -8,9 +8,9 @@ import type {
|
|
|
8
8
|
MessageReferenceInput,
|
|
9
9
|
UpdateEnvelopeInput,
|
|
10
10
|
} from "@remit/data-ports";
|
|
11
|
-
import { eq
|
|
12
|
-
import {
|
|
13
|
-
import type {
|
|
11
|
+
import { eq } from "drizzle-orm";
|
|
12
|
+
import type { WriteBatch } from "../batch.js";
|
|
13
|
+
import type { RepositoryDb, SqliteStore } from "../db.js";
|
|
14
14
|
import {
|
|
15
15
|
CreateFailedConflictError,
|
|
16
16
|
isUniqueViolation,
|
|
@@ -29,11 +29,11 @@ import {
|
|
|
29
29
|
bodyStructureRefetchTable,
|
|
30
30
|
envelopeAddressTable,
|
|
31
31
|
envelopeTable,
|
|
32
|
-
type MessageDataSchema,
|
|
33
32
|
messageReferenceTable,
|
|
34
33
|
messageTable,
|
|
35
34
|
rawMessageStorageTable,
|
|
36
35
|
} from "../schema/message-data.js";
|
|
36
|
+
import { inList } from "./in-list.js";
|
|
37
37
|
import {
|
|
38
38
|
toBodyPartItem,
|
|
39
39
|
toBodyPartParameterItem,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
toRawMessageStorageItem,
|
|
45
45
|
} from "./mappers.js";
|
|
46
46
|
|
|
47
|
-
type DB =
|
|
47
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
48
48
|
|
|
49
49
|
function dedupeById<T>(rows: T[], getId: (row: T) => string): T[] {
|
|
50
50
|
const byId = new Map<string, T>();
|
|
@@ -55,7 +55,13 @@ function dedupeById<T>(rows: T[], getId: (row: T) => string): T[] {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
58
|
-
|
|
58
|
+
private readonly db: DB;
|
|
59
|
+
private readonly batch: WriteBatch;
|
|
60
|
+
|
|
61
|
+
constructor(store: SqliteStore<Record<string, unknown>>) {
|
|
62
|
+
this.db = store.db;
|
|
63
|
+
this.batch = store.batch;
|
|
64
|
+
}
|
|
59
65
|
|
|
60
66
|
async getMessageData(messageId: string): Promise<MessageData> {
|
|
61
67
|
const [
|
|
@@ -182,10 +188,10 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
182
188
|
set: { updatedAt: now },
|
|
183
189
|
});
|
|
184
190
|
if (dedupedParameterRows.length === 0) {
|
|
185
|
-
await
|
|
191
|
+
await this.batch([insertParts]);
|
|
186
192
|
return;
|
|
187
193
|
}
|
|
188
|
-
await
|
|
194
|
+
await this.batch([
|
|
189
195
|
insertParts,
|
|
190
196
|
this.db
|
|
191
197
|
.insert(bodyPartParameterTable)
|
|
@@ -210,10 +216,10 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
210
216
|
.delete(messageReferenceTable)
|
|
211
217
|
.where(eq(messageReferenceTable.messageId, messageId));
|
|
212
218
|
if (references.length === 0) {
|
|
213
|
-
await
|
|
219
|
+
await this.batch([clear]);
|
|
214
220
|
return;
|
|
215
221
|
}
|
|
216
|
-
await
|
|
222
|
+
await this.batch([
|
|
217
223
|
clear,
|
|
218
224
|
this.db.insert(messageReferenceTable).values(
|
|
219
225
|
references.map((reference) => ({
|
|
@@ -235,7 +241,7 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
235
241
|
|
|
236
242
|
await this.db
|
|
237
243
|
.delete(envelopeTable)
|
|
238
|
-
.where(
|
|
244
|
+
.where(inList(envelopeTable.envelopeId, envelopeIds));
|
|
239
245
|
}
|
|
240
246
|
|
|
241
247
|
async createEnvelope(input: CreateEnvelopeInput): Promise<EnvelopeItem> {
|
|
@@ -293,7 +299,7 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
293
299
|
const rows = await this.db
|
|
294
300
|
.select()
|
|
295
301
|
.from(envelopeTable)
|
|
296
|
-
.where(
|
|
302
|
+
.where(inList(envelopeTable.envelopeId, envelopeId));
|
|
297
303
|
return rows.map(toEnvelopeItem);
|
|
298
304
|
}
|
|
299
305
|
|
|
@@ -353,6 +359,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
353
359
|
if (messageIds.length === 0) return;
|
|
354
360
|
await this.db
|
|
355
361
|
.delete(bodyStructureRefetchTable)
|
|
356
|
-
.where(
|
|
362
|
+
.where(inList(bodyStructureRefetchTable.messageId, messageIds));
|
|
357
363
|
}
|
|
358
364
|
}
|
package/src/repos/filter.ts
CHANGED
|
@@ -12,13 +12,13 @@ import {
|
|
|
12
12
|
FilterState,
|
|
13
13
|
} from "@remit/domain-enums";
|
|
14
14
|
import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
15
|
-
import type {
|
|
15
|
+
import type { RepositoryDb } from "../db.js";
|
|
16
16
|
import { NotFoundError } from "../error.js";
|
|
17
17
|
import { randomId } from "../id.js";
|
|
18
18
|
import { decodeToken, resultList } from "../pagination.js";
|
|
19
19
|
import { filterTable } from "../schema.js";
|
|
20
20
|
|
|
21
|
-
type DB =
|
|
21
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
22
22
|
|
|
23
23
|
const RULE_ASSERTION_FIELDS = [
|
|
24
24
|
"matchOperator",
|
|
@@ -1,9 +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, type TestDb } from "../test-db.js";
|
|
4
|
-
import {
|
|
5
|
-
import { AccountConfigRepo } from "./i4-account-config.js";
|
|
6
|
-
import { AddressRepo } from "./i4-address.js";
|
|
5
|
+
import type { AccountConfigRepo } from "./i4-account-config.js";
|
|
7
6
|
|
|
8
7
|
describe("AccountConfigRepo", () => {
|
|
9
8
|
let db: TestDb;
|
|
@@ -12,7 +11,11 @@ describe("AccountConfigRepo", () => {
|
|
|
12
11
|
|
|
13
12
|
before(async () => {
|
|
14
13
|
({ db, close } = await createTestDb());
|
|
15
|
-
repo =
|
|
14
|
+
repo = createSqliteRepositories({
|
|
15
|
+
app: db,
|
|
16
|
+
mail: db,
|
|
17
|
+
search: db,
|
|
18
|
+
}).accountConfig;
|
|
16
19
|
});
|
|
17
20
|
|
|
18
21
|
after(async () => {
|
|
@@ -58,8 +61,9 @@ describe("AccountConfigRepo", () => {
|
|
|
58
61
|
const userId = randomId();
|
|
59
62
|
const cfg = await repo.create({ userId });
|
|
60
63
|
|
|
61
|
-
const
|
|
62
|
-
const
|
|
64
|
+
const repos = createSqliteRepositories({ app: db, mail: db, search: db });
|
|
65
|
+
const accountRepo = repos.account;
|
|
66
|
+
const addrRepo = repos.address;
|
|
63
67
|
|
|
64
68
|
await accountRepo.create({
|
|
65
69
|
accountConfigId: cfg.accountConfigId,
|
|
@@ -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
|
-
import { and, asc, eq, gt,
|
|
10
|
-
import type {
|
|
10
|
+
import { and, asc, eq, gt, or } from "drizzle-orm";
|
|
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,11 +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 {
|
|
20
|
+
import { inList } from "./in-list.js";
|
|
21
21
|
|
|
22
|
-
type DB =
|
|
22
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
23
|
+
|
|
24
|
+
type AddressReader = Pick<IAddressRepository, "listAllByAccountConfig">;
|
|
23
25
|
|
|
24
26
|
function rowToAccountConfig(
|
|
25
27
|
row: typeof accountConfigTable.$inferSelect,
|
|
@@ -39,7 +41,7 @@ function rowToAccountConfig(
|
|
|
39
41
|
export class AccountConfigRepo implements IAccountConfigRepository {
|
|
40
42
|
constructor(
|
|
41
43
|
private db: DB,
|
|
42
|
-
private
|
|
44
|
+
private addresses: AddressReader,
|
|
43
45
|
) {}
|
|
44
46
|
|
|
45
47
|
async create(input: CreateAccountConfigInput): Promise<AccountConfigItem> {
|
|
@@ -71,7 +73,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
|
|
|
71
73
|
const rows = await this.db
|
|
72
74
|
.select()
|
|
73
75
|
.from(accountConfigTable)
|
|
74
|
-
.where(
|
|
76
|
+
.where(inList(accountConfigTable.accountConfigId, accountConfigId));
|
|
75
77
|
return rows.map(rowToAccountConfig);
|
|
76
78
|
}
|
|
77
79
|
const [row] = await this.db
|
|
@@ -116,7 +118,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
|
|
|
116
118
|
if (accountConfigIds.length === 0) return;
|
|
117
119
|
await this.db
|
|
118
120
|
.delete(accountConfigTable)
|
|
119
|
-
.where(
|
|
121
|
+
.where(inList(accountConfigTable.accountConfigId, accountConfigIds));
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
async listByUser(
|
|
@@ -182,10 +184,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
|
|
|
182
184
|
.select()
|
|
183
185
|
.from(accountTable)
|
|
184
186
|
.where(eq(accountTable.accountConfigId, accountConfigId)),
|
|
185
|
-
this.
|
|
186
|
-
.select()
|
|
187
|
-
.from(addressTable)
|
|
188
|
-
.where(eq(addressTable.accountConfigId, accountConfigId)),
|
|
187
|
+
this.addresses.listAllByAccountConfig(accountConfigId),
|
|
189
188
|
]);
|
|
190
189
|
if (configs.length === 0) {
|
|
191
190
|
throw new NotFoundError(`AccountConfig not found: ${accountConfigId}`);
|
|
@@ -193,7 +192,7 @@ export class AccountConfigRepo implements IAccountConfigRepository {
|
|
|
193
192
|
return {
|
|
194
193
|
accountConfig: configs.map(rowToAccountConfig),
|
|
195
194
|
account: accounts.map(rowToAccount),
|
|
196
|
-
address: addresses
|
|
195
|
+
address: addresses,
|
|
197
196
|
};
|
|
198
197
|
}
|
|
199
198
|
|
|
@@ -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,
|
|
@@ -5,11 +5,12 @@ import type {
|
|
|
5
5
|
IAccountSettingRepository,
|
|
6
6
|
UpsertAccountSettingInput,
|
|
7
7
|
} from "@remit/data-ports";
|
|
8
|
-
import { and, eq
|
|
9
|
-
import type {
|
|
8
|
+
import { and, eq } from "drizzle-orm";
|
|
9
|
+
import type { RepositoryDb } from "../db.js";
|
|
10
10
|
import { accountSettingTable } from "../schema/i4-account-setting.js";
|
|
11
|
+
import { inList } from "./in-list.js";
|
|
11
12
|
|
|
12
|
-
type DB =
|
|
13
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
13
14
|
|
|
14
15
|
// The PG store derives its own deterministic id from the composite key. It is
|
|
15
16
|
// stable within this backend but not byte-identical to the DDB base36 UUIDv5
|
|
@@ -63,8 +64,8 @@ export class AccountSettingRepo implements IAccountSettingRepository {
|
|
|
63
64
|
.from(accountSettingTable)
|
|
64
65
|
.where(
|
|
65
66
|
and(
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
inList(accountSettingTable.accountConfigId, accountConfigIds),
|
|
68
|
+
inList(accountSettingTable.name, names),
|
|
68
69
|
),
|
|
69
70
|
);
|
|
70
71
|
return rows.map(rowToAccountSetting);
|
|
@@ -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,21 +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
|
-
import { and, asc, eq, gt,
|
|
13
|
-
import type {
|
|
13
|
+
import { and, asc, eq, gt, like, or, sql } from "drizzle-orm";
|
|
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 {
|
|
19
|
-
import { rowToMailbox } from "./i4-mailbox.js";
|
|
19
|
+
import { inList } from "./in-list.js";
|
|
20
20
|
|
|
21
|
-
type DB =
|
|
21
|
+
type DB = RepositoryDb<Record<string, unknown>>;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* A stored failure is a sentence the account card shows, not a transcript.
|
|
@@ -73,7 +73,7 @@ export function rowToAccount(
|
|
|
73
73
|
export class AccountRepo implements IAccountRepository {
|
|
74
74
|
constructor(
|
|
75
75
|
private db: DB,
|
|
76
|
-
private
|
|
76
|
+
private mailboxes: Pick<IMailboxRepository, "listAllByAccount">,
|
|
77
77
|
) {}
|
|
78
78
|
|
|
79
79
|
async create(input: CreateAccountInput): Promise<AccountItem> {
|
|
@@ -129,7 +129,7 @@ export class AccountRepo implements IAccountRepository {
|
|
|
129
129
|
const rows = await this.db
|
|
130
130
|
.select()
|
|
131
131
|
.from(accountTable)
|
|
132
|
-
.where(
|
|
132
|
+
.where(inList(accountTable.accountId, accountId));
|
|
133
133
|
return rows.map(rowToAccount);
|
|
134
134
|
}
|
|
135
135
|
const [row] = await this.db
|
|
@@ -187,7 +187,7 @@ export class AccountRepo implements IAccountRepository {
|
|
|
187
187
|
if (accountIds.length === 0) return;
|
|
188
188
|
await this.db
|
|
189
189
|
.delete(accountTable)
|
|
190
|
-
.where(
|
|
190
|
+
.where(inList(accountTable.accountId, accountIds));
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
async list(
|
|
@@ -268,17 +268,14 @@ export class AccountRepo implements IAccountRepository {
|
|
|
268
268
|
.select()
|
|
269
269
|
.from(accountTable)
|
|
270
270
|
.where(eq(accountTable.accountId, accountId)),
|
|
271
|
-
this.
|
|
272
|
-
.select()
|
|
273
|
-
.from(mailboxTable)
|
|
274
|
-
.where(eq(mailboxTable.accountId, accountId)),
|
|
271
|
+
this.mailboxes.listAllByAccount(accountId),
|
|
275
272
|
]);
|
|
276
273
|
if (accounts.length === 0) {
|
|
277
274
|
throw new NotFoundError(`Account not found: ${accountId}`);
|
|
278
275
|
}
|
|
279
276
|
return {
|
|
280
277
|
account: accounts.map(rowToAccount),
|
|
281
|
-
mailbox: mailboxes
|
|
278
|
+
mailbox: mailboxes,
|
|
282
279
|
};
|
|
283
280
|
}
|
|
284
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
|
/**
|