@remit/drizzle-service 0.0.99 → 0.0.101
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 +78 -0
- package/src/batch.ts +46 -0
- package/src/databases.ts +70 -0
- package/src/db.ts +10 -9
- package/src/generated-enum.test.ts +8 -0
- package/src/generated-id.test.ts +19 -0
- package/src/id.test.ts +0 -8
- package/src/id.ts +0 -6
- package/src/index.ts +16 -6
- package/src/repair/address-display-name.sqlite.test.ts +3 -5
- package/src/repair/address-display-name.ts +8 -3
- package/src/repair/junk-only-address.sqlite.test.ts +8 -36
- package/src/repair/stranded-sent-outbox.sqlite.test.ts +2 -9
- package/src/repos/account.conformance.sqlite.test.ts +1 -1
- package/src/repos/calendar-default.sqlite.test.ts +6 -6
- package/src/repos/calendar-put.sqlite.test.ts +40 -39
- package/src/repos/calendar-store.ts +273 -0
- package/src/repos/cascade-delete.sqlite.test.ts +7 -9
- package/src/repos/cascade-delete.test.ts +6 -13
- package/src/repos/cascade-delete.ts +127 -97
- package/src/repos/envelope.test.ts +1 -17
- package/src/repos/envelope.ts +83 -67
- package/src/repos/filter.test.ts +22 -2
- package/src/repos/filter.ts +1 -5
- package/src/repos/i4-account-config.test.ts +2 -2
- package/src/repos/i4-account-config.ts +5 -2
- package/src/repos/i4-account.test.ts +6 -2
- package/src/repos/i4-account.ts +12 -56
- package/src/repos/i4-address-junk-move.sqlite.test.ts +1 -1
- package/src/repos/i4-address-merge-flags.sqlite.test.ts +1 -2
- package/src/repos/i4-address.ts +58 -90
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +6 -2
- package/src/repos/i4-mailbox-special-use.ts +9 -6
- package/src/repos/i4-mailbox.sqlite.test.ts +12 -14
- package/src/repos/i4-mailbox.test.ts +17 -62
- package/src/repos/i4-mailbox.ts +121 -94
- package/src/repos/i4-organize-job-request.ts +0 -4
- package/src/repos/i4-outbox-attachment.ts +62 -33
- package/src/repos/mappers.ts +0 -16
- package/src/repos/message.sqlite.test.ts +32 -106
- package/src/repos/message.test.ts +6 -167
- package/src/repos/message.ts +216 -228
- package/src/repos/sync-warning.ts +76 -0
- package/src/repos/thread-message-body-muted.sqlite.test.ts +5 -10
- package/src/repos/thread-message-field-terms.sqlite.test.ts +5 -1
- package/src/repos/thread-message.sqlite.test.ts +2 -1
- package/src/repos/thread-message.test.ts +17 -4
- package/src/repos/thread-message.ts +502 -285
- package/src/repos/thread-search-predicates.ts +24 -26
- package/src/schema/message-data.ts +4 -8
- package/src/schema/sync-warning.ts +3 -0
- package/src/schema-sets/app.ts +17 -0
- package/src/schema-sets/mail.ts +32 -0
- package/src/schema-sets/search.ts +5 -0
- package/src/schema-version-accounting.sqlite.test.ts +56 -43
- package/src/schema.ts +3 -4
- package/src/search/thread-message-search.sqlite.test.ts +131 -0
- package/src/search/thread-message-search.ts +271 -0
- package/src/sqlite-client.ts +5 -7
- package/src/sqlite-repositories.ts +75 -0
- package/src/test-db-sqlite.ts +5 -13
- package/src/test-shipped-sqlite-schema.ts +46 -45
- package/src/test-sqlite.ts +35 -11
- package/src/vps-migrations-drift.sqlite.test.ts +81 -15
- package/src/account-granted-scopes-migration.sqlite.test.ts +0 -66
- package/src/account-synced-services-migration.sqlite.test.ts +0 -56
- package/src/mailbox-path-dedupe.sqlite.test.ts +0 -462
- package/src/mailbox-sync-status-backfill.sqlite.test.ts +0 -118
- package/src/outbox-created-cleanup.sqlite.test.ts +0 -83
- package/src/repair/duplicate-mailbox-mail.ts +0 -98
- package/src/repair/thread-message-category-contract.ts +0 -505
- package/src/repair/thread-message-category.sqlite.test.ts +0 -100
- package/src/repair/thread-message-category.ts +0 -415
- package/src/repos/calendar-migrations.sqlite.test.ts +0 -345
- package/src/repos/calendar-unit-of-work.ts +0 -31
- package/src/repos/config-import-atomicity.sqlite.test.ts +0 -255
- package/src/repos/filter-anchor-transaction.test.ts +0 -136
- package/src/repos/filter-anchor-transaction.ts +0 -45
- package/src/repos/filter-anchor.test.ts +0 -131
- package/src/repos/filter-anchor.ts +0 -105
- package/src/repos/i4-mailbox-special-use.structure.test.ts +0 -27
- package/src/repos/message-placement-backfill.sqlite.test.ts +0 -190
- package/src/repos/search-index-shape.test.ts +0 -82
- package/src/repos/search-index-shape.ts +0 -56
- package/src/repos/serialized-writes.sqlite.test.ts +0 -199
- package/src/repos/unit-of-work.sqlite.test.ts +0 -138
- package/src/repos/unit-of-work.test.ts +0 -109
- package/src/repos/unit-of-work.ts +0 -31
- package/src/schema/outbox.ts +0 -46
- package/src/schema-full-sqlite.ts +0 -13
- package/src/tx.sqlite.test.ts +0 -83
- package/src/tx.ts +0 -233
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { and, eq, inArray, or, type SQL } from "drizzle-orm";
|
|
2
|
-
import type
|
|
2
|
+
import { runBatch, type SqliteBatchItem } from "../batch.js";
|
|
3
|
+
import {
|
|
4
|
+
openSqliteDatabases,
|
|
5
|
+
type SqliteDatabasePaths,
|
|
6
|
+
type SqliteDatabases,
|
|
7
|
+
} from "../databases.js";
|
|
3
8
|
import { accountTable } from "../schema/i4-account-config.js";
|
|
4
9
|
import { accountSettingTable } from "../schema/i4-account-setting.js";
|
|
5
10
|
import { addressTable } from "../schema/i4-address.js";
|
|
@@ -8,12 +13,11 @@ import { mailboxLockTable } from "../schema/i4-mailbox-lock.js";
|
|
|
8
13
|
import { messageFlagPushTable } from "../schema/i4-message-flag-push.js";
|
|
9
14
|
import { messagePlacementMoveTable } from "../schema/i4-message-placement-move.js";
|
|
10
15
|
import { outboxMessageTable } from "../schema/i4-outbox-message.js";
|
|
11
|
-
import { messageDataSchema } from "../schema/message-data.js";
|
|
12
16
|
import { quarantineTable } from "../schema/quarantine.js";
|
|
17
|
+
import { syncWarningTable } from "../schema/sync-warning.js";
|
|
13
18
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { deleteMessageSubtree, type SubtreeDb } from "./message.js";
|
|
19
|
+
import { SearchIndex } from "../search/thread-message-search.js";
|
|
20
|
+
import { deleteMessageSubtree } from "./message.js";
|
|
17
21
|
|
|
18
22
|
export interface CascadeEntity {
|
|
19
23
|
entityType: string;
|
|
@@ -24,8 +28,6 @@ export interface CascadeDeleteLogger {
|
|
|
24
28
|
info(obj: Record<string, unknown>, msg: string): void;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
type CascadeDb = Db<Record<string, unknown>>;
|
|
28
|
-
|
|
29
31
|
/**
|
|
30
32
|
* Per-message entity types. Their rows are removed with the message subtree by
|
|
31
33
|
* message id, so their individually-enumerated keys are not deleted one by one —
|
|
@@ -40,7 +42,6 @@ const MESSAGE_CHILD_TYPES = new Set([
|
|
|
40
42
|
"BodyPart",
|
|
41
43
|
"BodyPartParameter",
|
|
42
44
|
"BodyPartStorage",
|
|
43
|
-
"BodyPartContent",
|
|
44
45
|
"RawMessageStorage",
|
|
45
46
|
]);
|
|
46
47
|
|
|
@@ -71,15 +72,17 @@ const groupByType = (
|
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Relational equivalent of the DynamoDB BatchWriteItem cascade: delete every
|
|
74
|
-
* enumerated row for an account/tenant
|
|
75
|
-
*
|
|
76
|
-
*
|
|
75
|
+
* enumerated row for an account/tenant, one batch per database — the mail rows
|
|
76
|
+
* first, then the app rows, then the search documents of the deleted thread
|
|
77
|
+
* messages. Every statement is keyed, so a rerun after a failure between two
|
|
78
|
+
* batches deletes what is left. Message subtrees
|
|
79
|
+
* (message + nine child tables) are deleted set-wise by message id; the remaining
|
|
77
80
|
* entity types are deleted by their enumerated primary keys. AccountConfig is
|
|
78
81
|
* never deleted here — the caller removes it last through its repository, the
|
|
79
82
|
* cascade-in-progress marker.
|
|
80
83
|
*/
|
|
81
84
|
export const runDrizzleCascadeDelete = async (
|
|
82
|
-
|
|
85
|
+
{ app, mail, search }: SqliteDatabases,
|
|
83
86
|
entities: CascadeEntity[],
|
|
84
87
|
log: CascadeDeleteLogger,
|
|
85
88
|
): Promise<void> => {
|
|
@@ -92,103 +95,132 @@ export const runDrizzleCascadeDelete = async (
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const items: SqliteBatchItem[] = [];
|
|
99
|
+
const appItems: SqliteBatchItem[] = [];
|
|
100
|
+
const messageIds = (grouped.get("Message") ?? []).map((k) => k.messageId);
|
|
101
|
+
items.push(...deleteMessageSubtree(mail, messageIds));
|
|
102
|
+
|
|
103
|
+
const threadMessageIds = (grouped.get("ThreadMessage") ?? []).map(
|
|
104
|
+
(k) => k.threadMessageId,
|
|
105
|
+
);
|
|
106
|
+
if (threadMessageIds.length > 0) {
|
|
107
|
+
items.push(
|
|
108
|
+
mail
|
|
104
109
|
.delete(threadMessageTable)
|
|
105
|
-
.where(inArray(threadMessageTable.threadMessageId, threadMessageIds))
|
|
106
|
-
|
|
110
|
+
.where(inArray(threadMessageTable.threadMessageId, threadMessageIds)),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
107
113
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
const mailboxIds = (grouped.get("Mailbox") ?? []).map((k) => k.mailboxId);
|
|
115
|
+
if (mailboxIds.length > 0) {
|
|
116
|
+
items.push(
|
|
117
|
+
mail
|
|
118
|
+
.delete(syncWarningTable)
|
|
119
|
+
.where(inArray(syncWarningTable.mailboxId, mailboxIds)),
|
|
120
|
+
);
|
|
121
|
+
items.push(
|
|
122
|
+
mail
|
|
111
123
|
.delete(mailboxTable)
|
|
112
|
-
.where(inArray(mailboxTable.mailboxId, mailboxIds))
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const outboxMessageIds = (grouped.get("OutboxMessage") ?? []).map(
|
|
116
|
-
(k) => k.outboxMessageId,
|
|
124
|
+
.where(inArray(mailboxTable.mailboxId, mailboxIds)),
|
|
117
125
|
);
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const outboxMessageIds = (grouped.get("OutboxMessage") ?? []).map(
|
|
129
|
+
(k) => k.outboxMessageId,
|
|
130
|
+
);
|
|
131
|
+
if (outboxMessageIds.length > 0) {
|
|
132
|
+
items.push(
|
|
133
|
+
mail
|
|
120
134
|
.delete(outboxMessageTable)
|
|
121
|
-
.where(inArray(outboxMessageTable.outboxMessageId, outboxMessageIds))
|
|
122
|
-
|
|
135
|
+
.where(inArray(outboxMessageTable.outboxMessageId, outboxMessageIds)),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
123
138
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
139
|
+
const lockKeys = grouped.get("MailboxLock") ?? [];
|
|
140
|
+
if (lockKeys.length > 0) {
|
|
141
|
+
const conditions: SQL[] = lockKeys.map((k) =>
|
|
142
|
+
and(
|
|
143
|
+
eq(mailboxLockTable.mailboxId, k.mailboxId),
|
|
144
|
+
eq(mailboxLockTable.eventName, k.eventName),
|
|
145
|
+
),
|
|
146
|
+
) as SQL[];
|
|
147
|
+
items.push(mail.delete(mailboxLockTable).where(or(...conditions)));
|
|
148
|
+
}
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
150
|
+
const accountSettingIds = (grouped.get("AccountSetting") ?? []).map(
|
|
151
|
+
(k) => k.accountSettingId,
|
|
152
|
+
);
|
|
153
|
+
if (accountSettingIds.length > 0) {
|
|
154
|
+
appItems.push(
|
|
155
|
+
app
|
|
140
156
|
.delete(accountSettingTable)
|
|
141
157
|
.where(
|
|
142
158
|
inArray(accountSettingTable.accountSettingId, accountSettingIds),
|
|
143
|
-
)
|
|
144
|
-
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
145
162
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
const addressIds = (grouped.get("Address") ?? []).map((k) => k.addressId);
|
|
164
|
+
if (addressIds.length > 0) {
|
|
165
|
+
items.push(
|
|
166
|
+
mail
|
|
149
167
|
.delete(addressTable)
|
|
150
|
-
.where(inArray(addressTable.addressId, addressIds))
|
|
151
|
-
|
|
168
|
+
.where(inArray(addressTable.addressId, addressIds)),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
const accountIds = (grouped.get("Account") ?? []).map((k) => k.accountId);
|
|
173
|
+
if (accountIds.length > 0) {
|
|
174
|
+
// Quarantine rows are deleted set-wise by account rather than from an
|
|
175
|
+
// enumerated key, the way message subtrees are. They carry the user's
|
|
176
|
+
// own folder names and parser output quoting the message, so account
|
|
177
|
+
// deletion has to take them even though nothing enumerates them.
|
|
178
|
+
items.push(
|
|
179
|
+
mail
|
|
160
180
|
.delete(quarantineTable)
|
|
161
|
-
.where(inArray(quarantineTable.accountId, accountIds))
|
|
162
|
-
|
|
181
|
+
.where(inArray(quarantineTable.accountId, accountIds)),
|
|
182
|
+
);
|
|
183
|
+
items.push(
|
|
184
|
+
mail
|
|
185
|
+
.delete(syncWarningTable)
|
|
186
|
+
.where(inArray(syncWarningTable.accountId, accountIds)),
|
|
187
|
+
);
|
|
188
|
+
appItems.push(
|
|
189
|
+
app
|
|
163
190
|
.delete(accountTable)
|
|
164
|
-
.where(inArray(accountTable.accountId, accountIds))
|
|
165
|
-
|
|
191
|
+
.where(inArray(accountTable.accountId, accountIds)),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
166
194
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
195
|
+
const placementMoveMessageIds = (
|
|
196
|
+
grouped.get("MessagePlacementMove") ?? []
|
|
197
|
+
).map((k) => k.messageId);
|
|
198
|
+
if (placementMoveMessageIds.length > 0) {
|
|
199
|
+
items.push(
|
|
200
|
+
mail
|
|
172
201
|
.delete(messagePlacementMoveTable)
|
|
173
202
|
.where(
|
|
174
203
|
inArray(messagePlacementMoveTable.messageId, placementMoveMessageIds),
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Composite key (messageId, flagName) — a message can carry an
|
|
179
|
-
// independent pending read-state marker and pending star marker at once
|
|
180
|
-
// (issue #1273), so a single-column inArray cannot target one row.
|
|
181
|
-
const flagPushKeys = grouped.get("MessageFlagPush") ?? [];
|
|
182
|
-
if (flagPushKeys.length > 0) {
|
|
183
|
-
const conditions: SQL[] = flagPushKeys.map((k) =>
|
|
184
|
-
and(
|
|
185
|
-
eq(messageFlagPushTable.messageId, k.messageId),
|
|
186
|
-
eq(messageFlagPushTable.flagName, k.flagName),
|
|
187
204
|
),
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
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
|
+
const flagPushKeys = grouped.get("MessageFlagPush") ?? [];
|
|
212
|
+
if (flagPushKeys.length > 0) {
|
|
213
|
+
const conditions: SQL[] = flagPushKeys.map((k) =>
|
|
214
|
+
and(
|
|
215
|
+
eq(messageFlagPushTable.messageId, k.messageId),
|
|
216
|
+
eq(messageFlagPushTable.flagName, k.flagName),
|
|
217
|
+
),
|
|
218
|
+
) as SQL[];
|
|
219
|
+
items.push(mail.delete(messageFlagPushTable).where(or(...conditions)));
|
|
220
|
+
}
|
|
221
|
+
await runBatch(mail, items);
|
|
222
|
+
await runBatch(app, appItems);
|
|
223
|
+
await new SearchIndex(mail, search).sync(threadMessageIds);
|
|
192
224
|
|
|
193
225
|
log.info({ entityCount: entities.length }, "Drizzle cascade delete complete");
|
|
194
226
|
};
|
|
@@ -199,15 +231,13 @@ export type CascadeDeleter = (
|
|
|
199
231
|
) => Promise<void>;
|
|
200
232
|
|
|
201
233
|
/**
|
|
202
|
-
* Open the
|
|
203
|
-
*
|
|
204
|
-
*
|
|
234
|
+
* Open the SQLite databases through the same connection settings every writer
|
|
235
|
+
* uses and bind a cascade deleter to them. Async because opening a SQLite handle
|
|
236
|
+
* loads the native binding lazily.
|
|
205
237
|
*/
|
|
206
238
|
export const createSqliteCascadeDeleter = async (
|
|
207
|
-
|
|
239
|
+
paths: SqliteDatabasePaths,
|
|
208
240
|
): Promise<CascadeDeleter> => {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
});
|
|
212
|
-
return (entities, log) => runDrizzleCascadeDelete(db, entities, log);
|
|
241
|
+
const databases = await openSqliteDatabases(paths);
|
|
242
|
+
return (entities, log) => runDrizzleCascadeDelete(databases, entities, log);
|
|
213
243
|
};
|
|
@@ -34,22 +34,7 @@ describe("DrizzleEnvelopeRepository", () => {
|
|
|
34
34
|
await stop();
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
describe("getMessageData — MessageData shape
|
|
38
|
-
test("returns all 8 collection members", async () => {
|
|
39
|
-
const data = await repo.getMessageData(MESSAGE_ID);
|
|
40
|
-
const keys = Object.keys(data).sort();
|
|
41
|
-
assert.deepStrictEqual(keys, [
|
|
42
|
-
"bodyPart",
|
|
43
|
-
"bodyPartContent",
|
|
44
|
-
"bodyPartParameter",
|
|
45
|
-
"bodyPartStorage",
|
|
46
|
-
"envelope",
|
|
47
|
-
"envelopeAddress",
|
|
48
|
-
"messageReference",
|
|
49
|
-
"rawMessageStorage",
|
|
50
|
-
]);
|
|
51
|
-
});
|
|
52
|
-
|
|
37
|
+
describe("getMessageData — MessageData shape", () => {
|
|
53
38
|
test("envelope has 1 row with correct fields", async () => {
|
|
54
39
|
const data = await repo.getMessageData(MESSAGE_ID);
|
|
55
40
|
assert.equal(data.envelope.length, 1);
|
|
@@ -70,7 +55,6 @@ describe("DrizzleEnvelopeRepository", () => {
|
|
|
70
55
|
assert.equal(data.bodyPartParameter.length, 0);
|
|
71
56
|
assert.equal(data.rawMessageStorage.length, 0);
|
|
72
57
|
assert.equal(data.bodyPartStorage.length, 0);
|
|
73
|
-
assert.equal(data.bodyPartContent.length, 0);
|
|
74
58
|
});
|
|
75
59
|
|
|
76
60
|
test("throws NotFoundError for unknown messageId", async () => {
|
package/src/repos/envelope.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
BodyPartContentUpsertInput,
|
|
3
2
|
BodyPartItem,
|
|
4
3
|
BodyPartUpsertInput,
|
|
5
4
|
CreateEnvelopeInput,
|
|
6
5
|
EnvelopeItem,
|
|
7
6
|
IEnvelopeRepository,
|
|
8
7
|
MessageData,
|
|
8
|
+
MessageReferenceInput,
|
|
9
9
|
UpdateEnvelopeInput,
|
|
10
10
|
} from "@remit/data-ports";
|
|
11
11
|
import { eq, inArray } from "drizzle-orm";
|
|
12
|
+
import { runBatch } from "../batch.js";
|
|
12
13
|
import type { Db } from "../db.js";
|
|
13
14
|
import {
|
|
14
15
|
CreateFailedConflictError,
|
|
@@ -16,25 +17,24 @@ import {
|
|
|
16
17
|
NotFoundError,
|
|
17
18
|
} from "../error.js";
|
|
18
19
|
import {
|
|
19
|
-
bodyPartContentId as deriveBodyPartContentId,
|
|
20
20
|
bodyPartId as deriveBodyPartId,
|
|
21
21
|
bodyPartParameterId as deriveBodyPartParameterId,
|
|
22
22
|
envelopeId as deriveEnvelopeId,
|
|
23
|
+
deterministicBase36Id,
|
|
23
24
|
} from "../id.js";
|
|
24
25
|
import {
|
|
25
|
-
bodyPartContentTable,
|
|
26
26
|
bodyPartParameterTable,
|
|
27
27
|
bodyPartStorageTable,
|
|
28
28
|
bodyPartTable,
|
|
29
|
+
bodyStructureRefetchTable,
|
|
29
30
|
envelopeAddressTable,
|
|
30
31
|
envelopeTable,
|
|
31
32
|
type MessageDataSchema,
|
|
32
33
|
messageReferenceTable,
|
|
34
|
+
messageTable,
|
|
33
35
|
rawMessageStorageTable,
|
|
34
36
|
} from "../schema/message-data.js";
|
|
35
|
-
import { runInTransaction } from "../tx.js";
|
|
36
37
|
import {
|
|
37
|
-
toBodyPartContentItem,
|
|
38
38
|
toBodyPartItem,
|
|
39
39
|
toBodyPartParameterItem,
|
|
40
40
|
toBodyPartStorageItem,
|
|
@@ -66,7 +66,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
66
66
|
bodyPartParameters,
|
|
67
67
|
rawMessageStorages,
|
|
68
68
|
bodyPartStorages,
|
|
69
|
-
bodyPartContents,
|
|
70
69
|
] = await Promise.all([
|
|
71
70
|
this.db
|
|
72
71
|
.select()
|
|
@@ -96,10 +95,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
96
95
|
.select()
|
|
97
96
|
.from(bodyPartStorageTable)
|
|
98
97
|
.where(eq(bodyPartStorageTable.messageId, messageId)),
|
|
99
|
-
this.db
|
|
100
|
-
.select()
|
|
101
|
-
.from(bodyPartContentTable)
|
|
102
|
-
.where(eq(bodyPartContentTable.messageId, messageId)),
|
|
103
98
|
]);
|
|
104
99
|
|
|
105
100
|
if (envelopes.length === 0) {
|
|
@@ -114,7 +109,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
114
109
|
bodyPartParameter: bodyPartParameters.map(toBodyPartParameterItem),
|
|
115
110
|
rawMessageStorage: rawMessageStorages.map(toRawMessageStorageItem),
|
|
116
111
|
bodyPartStorage: bodyPartStorages.map(toBodyPartStorageItem),
|
|
117
|
-
bodyPartContent: bodyPartContents.map(toBodyPartContentItem),
|
|
118
112
|
};
|
|
119
113
|
}
|
|
120
114
|
|
|
@@ -180,38 +174,68 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
180
174
|
(r) => r.bodyPartParameterId,
|
|
181
175
|
);
|
|
182
176
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
177
|
+
const insertParts = this.db
|
|
178
|
+
.insert(bodyPartTable)
|
|
179
|
+
.values(dedupedBodyPartRows)
|
|
180
|
+
.onConflictDoUpdate({
|
|
181
|
+
target: bodyPartTable.bodyPartId,
|
|
182
|
+
set: { updatedAt: now },
|
|
183
|
+
});
|
|
184
|
+
if (dedupedParameterRows.length === 0) {
|
|
185
|
+
await runBatch(this.db, [insertParts]);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await runBatch(this.db, [
|
|
189
|
+
insertParts,
|
|
190
|
+
this.db
|
|
191
|
+
.insert(bodyPartParameterTable)
|
|
192
|
+
.values(dedupedParameterRows)
|
|
187
193
|
.onConflictDoUpdate({
|
|
188
|
-
target:
|
|
189
|
-
set: {
|
|
190
|
-
|
|
194
|
+
target: bodyPartParameterTable.bodyPartParameterId,
|
|
195
|
+
set: {
|
|
196
|
+
parameterValue: bodyPartParameterTable.parameterValue,
|
|
197
|
+
updatedAt: now,
|
|
198
|
+
},
|
|
199
|
+
}),
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
191
202
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
203
|
+
async replaceMessageReferences(
|
|
204
|
+
messageId: string,
|
|
205
|
+
references: MessageReferenceInput[],
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
const now = Date.now();
|
|
208
|
+
const envelopeId = deriveEnvelopeId(messageId);
|
|
209
|
+
const clear = this.db
|
|
210
|
+
.delete(messageReferenceTable)
|
|
211
|
+
.where(eq(messageReferenceTable.messageId, messageId));
|
|
212
|
+
if (references.length === 0) {
|
|
213
|
+
await runBatch(this.db, [clear]);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
await runBatch(this.db, [
|
|
217
|
+
clear,
|
|
218
|
+
this.db.insert(messageReferenceTable).values(
|
|
219
|
+
references.map((reference) => ({
|
|
220
|
+
...reference,
|
|
221
|
+
messageReferenceId: deterministicBase36Id(
|
|
222
|
+
`msgref:${messageId}:${reference.referenceType}:${reference.referenceOrder}`,
|
|
223
|
+
),
|
|
224
|
+
messageId,
|
|
225
|
+
envelopeId,
|
|
226
|
+
createdAt: now,
|
|
227
|
+
updatedAt: now,
|
|
228
|
+
})),
|
|
229
|
+
),
|
|
230
|
+
]);
|
|
205
231
|
}
|
|
206
232
|
|
|
207
233
|
async deleteManyEnvelopes(envelopeIds: string[]): Promise<void> {
|
|
208
234
|
if (envelopeIds.length === 0) return;
|
|
209
235
|
|
|
210
|
-
await
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.where(inArray(envelopeTable.envelopeId, envelopeIds));
|
|
214
|
-
});
|
|
236
|
+
await this.db
|
|
237
|
+
.delete(envelopeTable)
|
|
238
|
+
.where(inArray(envelopeTable.envelopeId, envelopeIds));
|
|
215
239
|
}
|
|
216
240
|
|
|
217
241
|
async createEnvelope(input: CreateEnvelopeInput): Promise<EnvelopeItem> {
|
|
@@ -301,37 +325,6 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
301
325
|
.where(eq(envelopeTable.envelopeId, envelopeId));
|
|
302
326
|
}
|
|
303
327
|
|
|
304
|
-
async upsertBodyPartContents(
|
|
305
|
-
messageId: string,
|
|
306
|
-
contents: BodyPartContentUpsertInput[],
|
|
307
|
-
): Promise<void> {
|
|
308
|
-
if (contents.length === 0) return;
|
|
309
|
-
|
|
310
|
-
const now = Date.now();
|
|
311
|
-
|
|
312
|
-
const rows = contents.map((c) => ({
|
|
313
|
-
bodyPartContentId: deriveBodyPartContentId(messageId, c.bodyPartId),
|
|
314
|
-
messageId,
|
|
315
|
-
bodyPartId: c.bodyPartId,
|
|
316
|
-
content: c.content,
|
|
317
|
-
contentLength: c.content.length,
|
|
318
|
-
createdAt: now,
|
|
319
|
-
updatedAt: now,
|
|
320
|
-
}));
|
|
321
|
-
|
|
322
|
-
await this.db
|
|
323
|
-
.insert(bodyPartContentTable)
|
|
324
|
-
.values(rows)
|
|
325
|
-
.onConflictDoUpdate({
|
|
326
|
-
target: bodyPartContentTable.bodyPartContentId,
|
|
327
|
-
set: {
|
|
328
|
-
content: bodyPartContentTable.content,
|
|
329
|
-
contentLength: bodyPartContentTable.contentLength,
|
|
330
|
-
updatedAt: now,
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
328
|
async listBodyParts(messageId: string): Promise<BodyPartItem[]> {
|
|
336
329
|
const rows = await this.db
|
|
337
330
|
.select()
|
|
@@ -339,4 +332,27 @@ export class DrizzleEnvelopeRepository implements IEnvelopeRepository {
|
|
|
339
332
|
.where(eq(bodyPartTable.messageId, messageId));
|
|
340
333
|
return rows.map(toBodyPartItem);
|
|
341
334
|
}
|
|
335
|
+
|
|
336
|
+
async listQueuedStructureRefetches(
|
|
337
|
+
mailboxId: string,
|
|
338
|
+
limit: number,
|
|
339
|
+
): Promise<Array<{ messageId: string; uid: number }>> {
|
|
340
|
+
return this.db
|
|
341
|
+
.select({ messageId: messageTable.messageId, uid: messageTable.uid })
|
|
342
|
+
.from(bodyStructureRefetchTable)
|
|
343
|
+
.innerJoin(
|
|
344
|
+
messageTable,
|
|
345
|
+
eq(messageTable.messageId, bodyStructureRefetchTable.messageId),
|
|
346
|
+
)
|
|
347
|
+
.where(eq(messageTable.mailboxId, mailboxId))
|
|
348
|
+
.orderBy(messageTable.uid)
|
|
349
|
+
.limit(limit);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async clearStructureRefetches(messageIds: string[]): Promise<void> {
|
|
353
|
+
if (messageIds.length === 0) return;
|
|
354
|
+
await this.db
|
|
355
|
+
.delete(bodyStructureRefetchTable)
|
|
356
|
+
.where(inArray(bodyStructureRefetchTable.messageId, messageIds));
|
|
357
|
+
}
|
|
342
358
|
}
|