@remit/drizzle-service 0.0.106 → 0.0.108
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 -2
- package/src/id.ts +9 -0
- package/src/index.ts +4 -0
- package/src/repair/junk-only-address.sqlite.test.ts +2 -0
- package/src/repos/account.conformance.d1.test.ts +3 -1
- package/src/repos/account.conformance.sqlite.test.ts +2 -0
- package/src/repos/calendar-default.suite.ts +5 -1
- package/src/repos/calendar-put.suite.ts +5 -1
- package/src/repos/cascade-delete.ts +1 -1
- package/src/repos/envelope.sqlite.test.ts +2 -0
- package/src/repos/envelope.test.ts +2 -0
- package/src/repos/envelope.ts +5 -3
- package/src/repos/i4-account-config.suite.ts +5 -1
- package/src/repos/i4-account.test.ts +3 -0
- package/src/repos/i4-address-junk-move.sqlite.test.ts +2 -0
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +2 -0
- package/src/repos/i4-mailbox.sqlite.test.ts +3 -0
- package/src/repos/i4-mailbox.test.ts +4 -3
- package/src/repos/i4-mailbox.ts +7 -28
- package/src/repos/i4-outbox-attachment.test.ts +2 -0
- package/src/repos/message-objects.sqlite.test.ts +271 -0
- package/src/repos/message-objects.ts +70 -0
- package/src/repos/message.sqlite.test.ts +11 -17
- package/src/repos/message.test.ts +4 -6
- package/src/repos/message.ts +341 -46
- package/src/repos/thread-message-body-muted.sqlite.test.ts +2 -0
- package/src/repos/thread-message-field-terms.sqlite.test.ts +2 -0
- package/src/repos/thread-message.sqlite.test.ts +2 -0
- package/src/repos/thread-message.test.ts +5 -0
- package/src/search/thread-message-search.sqlite.test.ts +2 -0
- package/src/sqlite-repositories.ts +18 -8
package/src/repos/message.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import {
|
|
9
9
|
and,
|
|
10
10
|
asc,
|
|
11
|
+
type Column,
|
|
11
12
|
eq,
|
|
12
13
|
exists,
|
|
13
14
|
gt,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
type SQL,
|
|
20
21
|
} from "drizzle-orm";
|
|
21
22
|
import type { SqliteBatchItem, WriteBatch } from "../batch.js";
|
|
23
|
+
import { chunk, chunkRows } from "../bound-parameters.js";
|
|
22
24
|
import type { RepositoryDb, SqliteStore } from "../db.js";
|
|
23
25
|
import {
|
|
24
26
|
CreateFailedConflictError,
|
|
@@ -26,7 +28,11 @@ import {
|
|
|
26
28
|
NotFoundError,
|
|
27
29
|
} from "../error.js";
|
|
28
30
|
import {
|
|
31
|
+
bodyPartId as deriveBodyPartId,
|
|
32
|
+
bodyPartParameterId as deriveBodyPartParameterId,
|
|
33
|
+
envelopeAddressId as deriveEnvelopeAddressId,
|
|
29
34
|
envelopeId as deriveEnvelopeId,
|
|
35
|
+
messageReferenceId as deriveMessageReferenceId,
|
|
30
36
|
rootBodyPartId as deriveRootBodyPartId,
|
|
31
37
|
} from "../id.js";
|
|
32
38
|
import { decodeToken, resultList } from "../pagination.js";
|
|
@@ -45,7 +51,7 @@ import {
|
|
|
45
51
|
} from "../schema/message-data.js";
|
|
46
52
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
47
53
|
import type { SearchIndex } from "../search/thread-message-search.js";
|
|
48
|
-
import { inList } from "./in-list.js";
|
|
54
|
+
import { inList, pairInList } from "./in-list.js";
|
|
49
55
|
import {
|
|
50
56
|
toBodyPartItem,
|
|
51
57
|
toBodyPartParameterItem,
|
|
@@ -55,6 +61,7 @@ import {
|
|
|
55
61
|
toMessageReferenceItem,
|
|
56
62
|
toRawMessageStorageItem,
|
|
57
63
|
} from "./mappers.js";
|
|
64
|
+
import type { MessageObjects } from "./message-objects.js";
|
|
58
65
|
|
|
59
66
|
type DB = RepositoryDb<Record<string, unknown>>;
|
|
60
67
|
|
|
@@ -112,59 +119,147 @@ function toMessageItem(row: typeof messageTable.$inferSelect): MessageItem {
|
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
export type SubtreeDb = Pick<
|
|
122
|
+
export type SubtreeDb = Pick<
|
|
123
|
+
RepositoryDb<Record<string, unknown>>,
|
|
124
|
+
"delete" | "select"
|
|
125
|
+
>;
|
|
126
|
+
|
|
127
|
+
export type SubtreeGuard = (messageId: Column) => SQL;
|
|
128
|
+
|
|
129
|
+
export const MAIL_DELETE_BATCH_SIZE = 100;
|
|
116
130
|
|
|
117
131
|
/**
|
|
118
132
|
* The statements that delete a message and its whole per-message subtree by
|
|
119
133
|
* message id: the child tables, then the message row. The caller runs them as
|
|
120
|
-
* one batch so the subtree goes atomically
|
|
121
|
-
* message
|
|
122
|
-
*
|
|
134
|
+
* one batch so the subtree goes atomically. `removed` and `removedParts` read
|
|
135
|
+
* the message and body-part rows the batch deletes; they go first, so they see
|
|
136
|
+
* them under the same guard. `guard` rides every statement on that
|
|
137
|
+
* statement's own `messageId` column, so a guard that reads the message row
|
|
138
|
+
* holds until the last statement removes it.
|
|
123
139
|
*/
|
|
124
140
|
export function deleteMessageSubtree(
|
|
125
141
|
db: SubtreeDb,
|
|
126
|
-
messageIds: string[],
|
|
127
|
-
guard?:
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
messageIds: readonly string[],
|
|
143
|
+
guard?: SubtreeGuard,
|
|
144
|
+
) {
|
|
145
|
+
const removed = db
|
|
146
|
+
.select({
|
|
147
|
+
messageId: messageTable.messageId,
|
|
148
|
+
bodyStorageKey: messageTable.bodyStorageKey,
|
|
149
|
+
})
|
|
150
|
+
.from(messageTable)
|
|
151
|
+
.where(
|
|
152
|
+
and(
|
|
153
|
+
inList(messageTable.messageId, messageIds),
|
|
154
|
+
guard?.(messageTable.messageId),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
const removedParts = db
|
|
158
|
+
.select({
|
|
159
|
+
messageId: bodyPartTable.messageId,
|
|
160
|
+
partPath: bodyPartTable.partPath,
|
|
161
|
+
})
|
|
162
|
+
.from(bodyPartTable)
|
|
163
|
+
.where(
|
|
164
|
+
and(
|
|
165
|
+
inList(bodyPartTable.messageId, messageIds),
|
|
166
|
+
guard?.(bodyPartTable.messageId),
|
|
167
|
+
),
|
|
168
|
+
);
|
|
169
|
+
if (messageIds.length === 0) {
|
|
170
|
+
return { removed, removedParts, statements: [] };
|
|
171
|
+
}
|
|
172
|
+
const statements: SqliteBatchItem[] = [
|
|
131
173
|
db
|
|
132
174
|
.delete(messageFlagTable)
|
|
133
|
-
.where(
|
|
175
|
+
.where(
|
|
176
|
+
and(
|
|
177
|
+
inList(messageFlagTable.messageId, messageIds),
|
|
178
|
+
guard?.(messageFlagTable.messageId),
|
|
179
|
+
),
|
|
180
|
+
),
|
|
134
181
|
db
|
|
135
182
|
.delete(messageReferenceTable)
|
|
136
|
-
.where(
|
|
183
|
+
.where(
|
|
184
|
+
and(
|
|
185
|
+
inList(messageReferenceTable.messageId, messageIds),
|
|
186
|
+
guard?.(messageReferenceTable.messageId),
|
|
187
|
+
),
|
|
188
|
+
),
|
|
137
189
|
db
|
|
138
190
|
.delete(envelopeAddressTable)
|
|
139
|
-
.where(
|
|
191
|
+
.where(
|
|
192
|
+
and(
|
|
193
|
+
inList(envelopeAddressTable.messageId, messageIds),
|
|
194
|
+
guard?.(envelopeAddressTable.messageId),
|
|
195
|
+
),
|
|
196
|
+
),
|
|
140
197
|
db
|
|
141
198
|
.delete(bodyPartParameterTable)
|
|
142
|
-
.where(
|
|
199
|
+
.where(
|
|
200
|
+
and(
|
|
201
|
+
inList(bodyPartParameterTable.messageId, messageIds),
|
|
202
|
+
guard?.(bodyPartParameterTable.messageId),
|
|
203
|
+
),
|
|
204
|
+
),
|
|
143
205
|
db
|
|
144
206
|
.delete(bodyPartStorageTable)
|
|
145
|
-
.where(
|
|
207
|
+
.where(
|
|
208
|
+
and(
|
|
209
|
+
inList(bodyPartStorageTable.messageId, messageIds),
|
|
210
|
+
guard?.(bodyPartStorageTable.messageId),
|
|
211
|
+
),
|
|
212
|
+
),
|
|
146
213
|
db
|
|
147
214
|
.delete(rawMessageStorageTable)
|
|
148
|
-
.where(
|
|
215
|
+
.where(
|
|
216
|
+
and(
|
|
217
|
+
inList(rawMessageStorageTable.messageId, messageIds),
|
|
218
|
+
guard?.(rawMessageStorageTable.messageId),
|
|
219
|
+
),
|
|
220
|
+
),
|
|
149
221
|
db
|
|
150
222
|
.delete(bodyPartTable)
|
|
151
|
-
.where(
|
|
223
|
+
.where(
|
|
224
|
+
and(
|
|
225
|
+
inList(bodyPartTable.messageId, messageIds),
|
|
226
|
+
guard?.(bodyPartTable.messageId),
|
|
227
|
+
),
|
|
228
|
+
),
|
|
152
229
|
db
|
|
153
230
|
.delete(bodyStructureRefetchTable)
|
|
154
231
|
.where(
|
|
155
|
-
and(
|
|
232
|
+
and(
|
|
233
|
+
inList(bodyStructureRefetchTable.messageId, messageIds),
|
|
234
|
+
guard?.(bodyStructureRefetchTable.messageId),
|
|
235
|
+
),
|
|
156
236
|
),
|
|
157
237
|
db
|
|
158
238
|
.delete(messageSearchTextTable)
|
|
159
|
-
.where(
|
|
239
|
+
.where(
|
|
240
|
+
and(
|
|
241
|
+
inList(messageSearchTextTable.messageId, messageIds),
|
|
242
|
+
guard?.(messageSearchTextTable.messageId),
|
|
243
|
+
),
|
|
244
|
+
),
|
|
160
245
|
db
|
|
161
246
|
.delete(envelopeTable)
|
|
162
|
-
.where(
|
|
247
|
+
.where(
|
|
248
|
+
and(
|
|
249
|
+
inList(envelopeTable.messageId, messageIds),
|
|
250
|
+
guard?.(envelopeTable.messageId),
|
|
251
|
+
),
|
|
252
|
+
),
|
|
163
253
|
db
|
|
164
254
|
.delete(messageTable)
|
|
165
|
-
.where(
|
|
166
|
-
|
|
255
|
+
.where(
|
|
256
|
+
and(
|
|
257
|
+
inList(messageTable.messageId, messageIds),
|
|
258
|
+
guard?.(messageTable.messageId),
|
|
259
|
+
),
|
|
260
|
+
),
|
|
167
261
|
];
|
|
262
|
+
return { removed, removedParts, statements };
|
|
168
263
|
}
|
|
169
264
|
|
|
170
265
|
/**
|
|
@@ -198,13 +293,16 @@ const placementTerms = (expected: PlacementPredicate): SQL[] => {
|
|
|
198
293
|
export class DrizzleMessageRepository implements IMessageRepository {
|
|
199
294
|
private readonly db: DB;
|
|
200
295
|
private readonly batch: WriteBatch;
|
|
296
|
+
private readonly maxBoundParameters: number;
|
|
201
297
|
|
|
202
298
|
constructor(
|
|
203
299
|
store: SqliteStore<Record<string, unknown>>,
|
|
204
300
|
private search: SearchIndex,
|
|
301
|
+
private messageObjects: MessageObjects,
|
|
205
302
|
) {
|
|
206
303
|
this.db = store.db;
|
|
207
304
|
this.batch = store.batch;
|
|
305
|
+
this.maxBoundParameters = store.maxBoundParameters;
|
|
208
306
|
}
|
|
209
307
|
|
|
210
308
|
private rowOf(input: CreateMessageInput) {
|
|
@@ -459,16 +557,172 @@ export class DrizzleMessageRepository implements IMessageRepository {
|
|
|
459
557
|
});
|
|
460
558
|
}
|
|
461
559
|
|
|
462
|
-
async
|
|
560
|
+
async copyMessage(
|
|
463
561
|
sourceMessageId: string,
|
|
464
|
-
|
|
465
|
-
): Promise<
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
.
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
562
|
+
input: CreateMessageInput,
|
|
563
|
+
): Promise<{ item: MessageItem; created: boolean }> {
|
|
564
|
+
const targetMessageId = input.messageId;
|
|
565
|
+
const [searchTexts, envelopes, references, addresses, parts, parameters] =
|
|
566
|
+
await Promise.all([
|
|
567
|
+
this.db
|
|
568
|
+
.select({ text: messageSearchTextTable.text })
|
|
569
|
+
.from(messageSearchTextTable)
|
|
570
|
+
.where(eq(messageSearchTextTable.messageId, sourceMessageId)),
|
|
571
|
+
this.db
|
|
572
|
+
.select()
|
|
573
|
+
.from(envelopeTable)
|
|
574
|
+
.where(eq(envelopeTable.messageId, sourceMessageId)),
|
|
575
|
+
this.db
|
|
576
|
+
.select()
|
|
577
|
+
.from(messageReferenceTable)
|
|
578
|
+
.where(eq(messageReferenceTable.messageId, sourceMessageId)),
|
|
579
|
+
this.db
|
|
580
|
+
.select()
|
|
581
|
+
.from(envelopeAddressTable)
|
|
582
|
+
.where(eq(envelopeAddressTable.messageId, sourceMessageId)),
|
|
583
|
+
this.db
|
|
584
|
+
.select()
|
|
585
|
+
.from(bodyPartTable)
|
|
586
|
+
.where(eq(bodyPartTable.messageId, sourceMessageId)),
|
|
587
|
+
this.db
|
|
588
|
+
.select()
|
|
589
|
+
.from(bodyPartParameterTable)
|
|
590
|
+
.where(eq(bodyPartParameterTable.messageId, sourceMessageId)),
|
|
591
|
+
]);
|
|
592
|
+
|
|
593
|
+
const now = Date.now();
|
|
594
|
+
const envelopeId = deriveEnvelopeId(targetMessageId);
|
|
595
|
+
const copiedPartIds = new Map(
|
|
596
|
+
parts.map((part) => [
|
|
597
|
+
part.bodyPartId,
|
|
598
|
+
deriveBodyPartId(targetMessageId, part.partPath),
|
|
599
|
+
]),
|
|
600
|
+
);
|
|
601
|
+
const partPaths = new Map(
|
|
602
|
+
parts.map((part) => [part.bodyPartId, part.partPath]),
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
const searchTextRows = searchTexts.map((row) => ({
|
|
606
|
+
messageId: targetMessageId,
|
|
607
|
+
text: row.text,
|
|
608
|
+
createdAt: now,
|
|
609
|
+
updatedAt: now,
|
|
610
|
+
}));
|
|
611
|
+
const envelopeRows = envelopes.map((row) => ({
|
|
612
|
+
...row,
|
|
613
|
+
envelopeId,
|
|
614
|
+
messageId: targetMessageId,
|
|
615
|
+
createdAt: now,
|
|
616
|
+
updatedAt: now,
|
|
617
|
+
}));
|
|
618
|
+
const referenceRows = references.map((row) => ({
|
|
619
|
+
...row,
|
|
620
|
+
messageReferenceId: deriveMessageReferenceId(
|
|
621
|
+
targetMessageId,
|
|
622
|
+
row.referenceType,
|
|
623
|
+
row.referenceOrder,
|
|
624
|
+
),
|
|
625
|
+
messageId: targetMessageId,
|
|
626
|
+
envelopeId,
|
|
627
|
+
createdAt: now,
|
|
628
|
+
updatedAt: now,
|
|
629
|
+
}));
|
|
630
|
+
const addressRows = addresses.map((row) => ({
|
|
631
|
+
...row,
|
|
632
|
+
envelopeAddressId: deriveEnvelopeAddressId(
|
|
633
|
+
targetMessageId,
|
|
634
|
+
row.addressRole,
|
|
635
|
+
row.addressOrder,
|
|
636
|
+
),
|
|
637
|
+
messageId: targetMessageId,
|
|
638
|
+
createdAt: now,
|
|
639
|
+
updatedAt: now,
|
|
640
|
+
}));
|
|
641
|
+
const partRows = parts.map((row) => ({
|
|
642
|
+
...row,
|
|
643
|
+
bodyPartId: deriveBodyPartId(targetMessageId, row.partPath),
|
|
644
|
+
messageId: targetMessageId,
|
|
645
|
+
parentBodyPartId:
|
|
646
|
+
row.parentBodyPartId === null
|
|
647
|
+
? null
|
|
648
|
+
: (copiedPartIds.get(row.parentBodyPartId) ?? null),
|
|
649
|
+
createdAt: now,
|
|
650
|
+
updatedAt: now,
|
|
651
|
+
}));
|
|
652
|
+
const parameterRows = parameters.flatMap((row) => {
|
|
653
|
+
const partPath = partPaths.get(row.bodyPartId);
|
|
654
|
+
if (partPath === undefined) return [];
|
|
655
|
+
return [
|
|
656
|
+
{
|
|
657
|
+
...row,
|
|
658
|
+
bodyPartParameterId: deriveBodyPartParameterId(
|
|
659
|
+
targetMessageId,
|
|
660
|
+
partPath,
|
|
661
|
+
row.parameterName,
|
|
662
|
+
),
|
|
663
|
+
messageId: targetMessageId,
|
|
664
|
+
bodyPartId: deriveBodyPartId(targetMessageId, partPath),
|
|
665
|
+
createdAt: now,
|
|
666
|
+
updatedAt: now,
|
|
667
|
+
},
|
|
668
|
+
];
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
const insertCopy = this.db
|
|
672
|
+
.insert(messageTable)
|
|
673
|
+
.values(this.rowOf(input))
|
|
674
|
+
.onConflictDoNothing({ target: messageTable.messageId })
|
|
675
|
+
.returning();
|
|
676
|
+
const clones: SqliteBatchItem[] = [
|
|
677
|
+
...searchTextRows.map((row) =>
|
|
678
|
+
this.db
|
|
679
|
+
.insert(messageSearchTextTable)
|
|
680
|
+
.values(row)
|
|
681
|
+
.onConflictDoNothing({ target: messageSearchTextTable.messageId }),
|
|
682
|
+
),
|
|
683
|
+
...chunkRows(envelopeTable, envelopeRows, this.maxBoundParameters).map(
|
|
684
|
+
(slice) =>
|
|
685
|
+
this.db.insert(envelopeTable).values(slice).onConflictDoNothing(),
|
|
686
|
+
),
|
|
687
|
+
...chunkRows(
|
|
688
|
+
messageReferenceTable,
|
|
689
|
+
referenceRows,
|
|
690
|
+
this.maxBoundParameters,
|
|
691
|
+
).map((slice) =>
|
|
692
|
+
this.db
|
|
693
|
+
.insert(messageReferenceTable)
|
|
694
|
+
.values(slice)
|
|
695
|
+
.onConflictDoNothing(),
|
|
696
|
+
),
|
|
697
|
+
...chunkRows(
|
|
698
|
+
envelopeAddressTable,
|
|
699
|
+
addressRows,
|
|
700
|
+
this.maxBoundParameters,
|
|
701
|
+
).map((slice) =>
|
|
702
|
+
this.db
|
|
703
|
+
.insert(envelopeAddressTable)
|
|
704
|
+
.values(slice)
|
|
705
|
+
.onConflictDoNothing(),
|
|
706
|
+
),
|
|
707
|
+
...chunkRows(bodyPartTable, partRows, this.maxBoundParameters).map(
|
|
708
|
+
(slice) =>
|
|
709
|
+
this.db.insert(bodyPartTable).values(slice).onConflictDoNothing(),
|
|
710
|
+
),
|
|
711
|
+
...chunkRows(
|
|
712
|
+
bodyPartParameterTable,
|
|
713
|
+
parameterRows,
|
|
714
|
+
this.maxBoundParameters,
|
|
715
|
+
).map((slice) =>
|
|
716
|
+
this.db
|
|
717
|
+
.insert(bodyPartParameterTable)
|
|
718
|
+
.values(slice)
|
|
719
|
+
.onConflictDoNothing(),
|
|
720
|
+
),
|
|
721
|
+
];
|
|
722
|
+
const [inserted] = await this.batch([insertCopy, ...clones]);
|
|
723
|
+
const [row] = inserted;
|
|
724
|
+
if (row) return { item: toMessageItem(row), created: true };
|
|
725
|
+
return { item: await this.get(targetMessageId), created: false };
|
|
472
726
|
}
|
|
473
727
|
|
|
474
728
|
async syncSearch(messageIds: string[]): Promise<void> {
|
|
@@ -529,24 +783,65 @@ export class DrizzleMessageRepository implements IMessageRepository {
|
|
|
529
783
|
),
|
|
530
784
|
),
|
|
531
785
|
);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
786
|
+
return (await this.removeMessages([messageId], () => placed)) > 0;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async deleteManyIfPlacement(
|
|
790
|
+
mailboxId: string,
|
|
791
|
+
placements: ReadonlyArray<{ messageId: string; uid: number }>,
|
|
792
|
+
): Promise<void> {
|
|
793
|
+
const placed = this.db
|
|
794
|
+
.select({ messageId: messageTable.messageId })
|
|
795
|
+
.from(messageTable)
|
|
796
|
+
.where(
|
|
797
|
+
and(
|
|
798
|
+
eq(messageTable.mailboxId, mailboxId),
|
|
799
|
+
pairInList(
|
|
800
|
+
messageTable.messageId,
|
|
801
|
+
messageTable.uid,
|
|
802
|
+
placements.map(({ messageId, uid }) => [messageId, uid] as const),
|
|
803
|
+
),
|
|
804
|
+
),
|
|
805
|
+
);
|
|
806
|
+
await this.removeMessages(
|
|
807
|
+
placements.map((placement) => placement.messageId),
|
|
808
|
+
(column) => inArray(column, placed),
|
|
809
|
+
);
|
|
545
810
|
}
|
|
546
811
|
|
|
547
812
|
async deleteMany(messageIds: string[]): Promise<void> {
|
|
548
|
-
|
|
549
|
-
|
|
813
|
+
await this.removeMessages(messageIds);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private async removeMessages(
|
|
817
|
+
messageIds: readonly string[],
|
|
818
|
+
guard?: SubtreeGuard,
|
|
819
|
+
): Promise<number> {
|
|
820
|
+
let removedCount = 0;
|
|
821
|
+
for (const batch of chunk(messageIds, MAIL_DELETE_BATCH_SIZE)) {
|
|
822
|
+
const listings = await this.db
|
|
823
|
+
.select({ threadMessageId: threadMessageTable.threadMessageId })
|
|
824
|
+
.from(threadMessageTable)
|
|
825
|
+
.where(inList(threadMessageTable.messageId, batch));
|
|
826
|
+
const subtree = deleteMessageSubtree(this.db, batch, guard);
|
|
827
|
+
const [removed, removedParts] = await this.batch([
|
|
828
|
+
subtree.removed,
|
|
829
|
+
subtree.removedParts,
|
|
830
|
+
this.db
|
|
831
|
+
.delete(threadMessageTable)
|
|
832
|
+
.where(
|
|
833
|
+
and(
|
|
834
|
+
inList(threadMessageTable.messageId, batch),
|
|
835
|
+
guard?.(threadMessageTable.messageId),
|
|
836
|
+
),
|
|
837
|
+
),
|
|
838
|
+
...subtree.statements,
|
|
839
|
+
]);
|
|
840
|
+
await this.search.sync(listings.map((row) => row.threadMessageId));
|
|
841
|
+
await this.messageObjects.reclaim(removed, removedParts);
|
|
842
|
+
removedCount += removed.length;
|
|
843
|
+
}
|
|
844
|
+
return removedCount;
|
|
550
845
|
}
|
|
551
846
|
|
|
552
847
|
async listByMailbox(
|
|
@@ -16,6 +16,7 @@ import { messageSearchTextTable } from "../schema/message-data.js";
|
|
|
16
16
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
17
17
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
18
18
|
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
19
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
19
20
|
import type { DrizzleThreadMessageRepository } from "./thread-message.js";
|
|
20
21
|
|
|
21
22
|
const ACCOUNT = "acct-seam";
|
|
@@ -68,6 +69,7 @@ describe("thread-message body and muted-sender predicates (sqlite)", () => {
|
|
|
68
69
|
search: db,
|
|
69
70
|
},
|
|
70
71
|
BETTER_SQLITE3_LIMITS,
|
|
72
|
+
messageObjectsInMemory(),
|
|
71
73
|
).threadMessage;
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -6,6 +6,7 @@ import { messageSearchTextTable } from "../schema/message-data.js";
|
|
|
6
6
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
7
7
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
8
8
|
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
9
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
9
10
|
import {
|
|
10
11
|
type DrizzleThreadMessageRepository,
|
|
11
12
|
THREAD_SEARCH_MAX_LIMIT,
|
|
@@ -67,6 +68,7 @@ describe("DrizzleThreadMessageRepository.listByFieldTerms (sqlite, #459)", () =>
|
|
|
67
68
|
search: created.db,
|
|
68
69
|
},
|
|
69
70
|
BETTER_SQLITE3_LIMITS,
|
|
71
|
+
messageObjectsInMemory(),
|
|
70
72
|
).threadMessage;
|
|
71
73
|
|
|
72
74
|
await repo.create(
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
LIVE_TOTALS,
|
|
14
14
|
totalRows,
|
|
15
15
|
} from "./category-fixture.js";
|
|
16
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
16
17
|
import {
|
|
17
18
|
type DrizzleThreadMessageRepository,
|
|
18
19
|
THREAD_SEARCH_MAX_LIMIT,
|
|
@@ -68,6 +69,7 @@ describe("DrizzleThreadMessageRepository (sqlite)", () => {
|
|
|
68
69
|
search: db,
|
|
69
70
|
},
|
|
70
71
|
BETTER_SQLITE3_LIMITS,
|
|
72
|
+
messageObjectsInMemory(),
|
|
71
73
|
).threadMessage;
|
|
72
74
|
});
|
|
73
75
|
|
|
@@ -10,6 +10,7 @@ import { messageSearchTextTable } from "../schema/message-data.js";
|
|
|
10
10
|
import { threadMessageTable } from "../schema/thread-message.js";
|
|
11
11
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
12
12
|
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
13
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
13
14
|
import {
|
|
14
15
|
clampThreadSearchLimit,
|
|
15
16
|
type DrizzleThreadMessageRepository,
|
|
@@ -102,6 +103,7 @@ describe("DrizzleThreadMessageRepository.searchByMailboxWindow / countByMailbox"
|
|
|
102
103
|
search: harness.db,
|
|
103
104
|
},
|
|
104
105
|
BETTER_SQLITE3_LIMITS,
|
|
106
|
+
messageObjectsInMemory(),
|
|
105
107
|
).threadMessage;
|
|
106
108
|
});
|
|
107
109
|
|
|
@@ -411,6 +413,7 @@ describe("DrizzleThreadMessageRepository.searchByDate", () => {
|
|
|
411
413
|
search: harness.db,
|
|
412
414
|
},
|
|
413
415
|
BETTER_SQLITE3_LIMITS,
|
|
416
|
+
messageObjectsInMemory(),
|
|
414
417
|
);
|
|
415
418
|
repo = repositories.threadMessage;
|
|
416
419
|
messages = repositories.message;
|
|
@@ -949,6 +952,7 @@ describe("DrizzleThreadMessageRepository — native text search", () => {
|
|
|
949
952
|
search: harness.db,
|
|
950
953
|
},
|
|
951
954
|
BETTER_SQLITE3_LIMITS,
|
|
955
|
+
messageObjectsInMemory(),
|
|
952
956
|
).threadMessage;
|
|
953
957
|
});
|
|
954
958
|
|
|
@@ -1128,6 +1132,7 @@ describe("DrizzleThreadMessageRepository — core CRUD", () => {
|
|
|
1128
1132
|
search: harness.db,
|
|
1129
1133
|
},
|
|
1130
1134
|
BETTER_SQLITE3_LIMITS,
|
|
1135
|
+
messageObjectsInMemory(),
|
|
1131
1136
|
).threadMessage;
|
|
1132
1137
|
});
|
|
1133
1138
|
|
|
@@ -6,6 +6,7 @@ import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
|
6
6
|
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
7
7
|
import { syncStore } from "../databases.js";
|
|
8
8
|
import type { Db } from "../db.js";
|
|
9
|
+
import { messageObjectsInMemory } from "../repos/message-objects.js";
|
|
9
10
|
import { messageSearchTextTable } from "../schema/message-data.js";
|
|
10
11
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
11
12
|
import { applyMigrationSet } from "../test-shipped-sqlite-schema.js";
|
|
@@ -62,6 +63,7 @@ describe("the thread-message search index in its own database", () => {
|
|
|
62
63
|
search: search.db,
|
|
63
64
|
},
|
|
64
65
|
BETTER_SQLITE3_LIMITS,
|
|
66
|
+
messageObjectsInMemory(),
|
|
65
67
|
).threadMessage;
|
|
66
68
|
|
|
67
69
|
const found = async (query: string): Promise<string[]> =>
|
|
@@ -31,20 +31,29 @@ import { LabelRepo } from "./repos/label.js";
|
|
|
31
31
|
import { DrizzleMessageRepository } from "./repos/message.js";
|
|
32
32
|
import { DrizzleMessageFlagRepository } from "./repos/message-flag.js";
|
|
33
33
|
import { MessageLabelRepo } from "./repos/message-label.js";
|
|
34
|
+
import {
|
|
35
|
+
type MessageObjectStore,
|
|
36
|
+
MessageObjects,
|
|
37
|
+
} from "./repos/message-objects.js";
|
|
34
38
|
import { QuarantineRepo } from "./repos/quarantine.js";
|
|
35
39
|
import { SyncWarningRepo } from "./repos/sync-warning.js";
|
|
36
40
|
import { DrizzleThreadMessageRepository } from "./repos/thread-message.js";
|
|
37
41
|
import { SearchIndex } from "./search/thread-message-search.js";
|
|
38
42
|
|
|
39
|
-
export const composeSqliteRepositories = (
|
|
40
|
-
app,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}: SqliteStores) => {
|
|
43
|
+
export const composeSqliteRepositories = (
|
|
44
|
+
{ app, mail, search }: SqliteStores,
|
|
45
|
+
objects: MessageObjectStore,
|
|
46
|
+
) => {
|
|
44
47
|
const searchIndex = new SearchIndex(mail.db, search);
|
|
48
|
+
const messageObjects = new MessageObjects(mail.db, objects);
|
|
45
49
|
const accountSetting = new AccountSettingRepo(app.db);
|
|
46
50
|
const address = new AddressRepo(mail.db);
|
|
47
|
-
const
|
|
51
|
+
const message = new DrizzleMessageRepository(
|
|
52
|
+
mail,
|
|
53
|
+
searchIndex,
|
|
54
|
+
messageObjects,
|
|
55
|
+
);
|
|
56
|
+
const mailbox = new MailboxRepo(mail, message);
|
|
48
57
|
const account = new AccountRepo(app.db, mailbox);
|
|
49
58
|
const calendarSuggestion = new CalendarSuggestionRepo(mail.db);
|
|
50
59
|
return {
|
|
@@ -59,7 +68,7 @@ export const composeSqliteRepositories = ({
|
|
|
59
68
|
accountSetting,
|
|
60
69
|
),
|
|
61
70
|
mailboxLock: new MailboxLockRepo(mail.db),
|
|
62
|
-
message
|
|
71
|
+
message,
|
|
63
72
|
messageFlag: new DrizzleMessageFlagRepository(mail.db),
|
|
64
73
|
outboxMessage: new OutboxMessageRepo(mail.db),
|
|
65
74
|
outboxAttachment: new OutboxAttachmentRepo(mail),
|
|
@@ -90,5 +99,6 @@ export type SqliteRepositories = ReturnType<typeof composeSqliteRepositories>;
|
|
|
90
99
|
export const createSqliteRepositories = (
|
|
91
100
|
databases: SqliteDatabases,
|
|
92
101
|
limits: SqliteLimits,
|
|
102
|
+
objects: MessageObjectStore,
|
|
93
103
|
): SqliteRepositories =>
|
|
94
|
-
composeSqliteRepositories(syncStores(databases, limits));
|
|
104
|
+
composeSqliteRepositories(syncStores(databases, limits), objects);
|