@remit/imap-worker 0.0.89 → 0.0.91
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/dist/account-check.d.ts +27 -0
- package/dist/account-check.js +70 -0
- package/dist/body-sync-gate.d.ts +12 -0
- package/dist/body-sync-gate.js +53 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +193 -0
- package/dist/config-env.d.ts +5 -0
- package/dist/config-env.js +37 -0
- package/dist/connection-pool.d.ts +22 -0
- package/dist/connection-pool.js +97 -0
- package/dist/connection-scope.d.ts +8 -0
- package/dist/connection-scope.js +10 -0
- package/dist/context.d.ts +46 -0
- package/dist/context.js +15 -0
- package/dist/e2e-processor-shim.d.ts +2 -0
- package/dist/e2e-processor-shim.js +181 -0
- package/dist/emit.d.ts +15 -0
- package/dist/emit.js +76 -0
- package/dist/events.d.ts +203 -0
- package/dist/events.js +1 -0
- package/dist/failure-kind.d.ts +10 -0
- package/{src/failure-kind.ts → dist/failure-kind.js} +6 -6
- package/dist/handlers/append-sent-message.d.ts +3 -0
- package/dist/handlers/append-sent-message.js +152 -0
- package/dist/handlers/delete-account-objects.d.ts +3 -0
- package/dist/handlers/delete-account-objects.js +35 -0
- package/dist/handlers/empty-trash.d.ts +14 -0
- package/dist/handlers/empty-trash.js +217 -0
- package/dist/handlers/flag-push.d.ts +38 -0
- package/dist/handlers/flag-push.js +195 -0
- package/dist/handlers/imported-folder.d.ts +4 -0
- package/dist/handlers/imported-folder.js +23 -0
- package/dist/handlers/mailbox-management.d.ts +6 -0
- package/dist/handlers/mailbox-management.js +294 -0
- package/dist/handlers/mailbox-sync-order.d.ts +21 -0
- package/dist/handlers/mailbox-sync-order.js +50 -0
- package/dist/handlers/message-copy.d.ts +40 -0
- package/dist/handlers/message-copy.js +371 -0
- package/dist/handlers/message-delete-terminal.d.ts +60 -0
- package/dist/handlers/message-delete-terminal.js +77 -0
- package/dist/handlers/message-delete.d.ts +57 -0
- package/dist/handlers/message-delete.js +520 -0
- package/{src/handlers/message-move-terminal.ts → dist/handlers/message-move-terminal.d.ts} +18 -105
- package/dist/handlers/message-move-terminal.js +94 -0
- package/dist/handlers/message-move.d.ts +146 -0
- package/dist/handlers/message-move.js +490 -0
- package/dist/handlers/placement-move-push.d.ts +62 -0
- package/dist/handlers/placement-move-push.js +307 -0
- package/dist/handlers/probe-message-id.d.ts +2 -0
- package/dist/handlers/probe-message-id.js +6 -0
- package/dist/handlers/sync-mailboxes.d.ts +45 -0
- package/dist/handlers/sync-mailboxes.js +233 -0
- package/dist/handlers/sync-message-body.d.ts +23 -0
- package/dist/handlers/sync-message-body.js +182 -0
- package/dist/handlers/sync-messages.d.ts +29 -0
- package/dist/handlers/sync-messages.js +285 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +67 -0
- package/dist/lambda-context.d.ts +8 -0
- package/dist/lambda-context.js +33 -0
- package/dist/mailbox-row.d.ts +9 -0
- package/dist/mailbox-row.js +6 -0
- package/dist/poller.d.ts +1 -0
- package/dist/poller.js +51 -0
- package/dist/process.d.ts +6 -0
- package/dist/process.js +3 -0
- package/dist/processor.d.ts +3 -0
- package/dist/processor.js +44 -0
- package/dist/queues.d.ts +10 -0
- package/dist/queues.js +1 -0
- package/dist/s3-account-objects.d.ts +3 -0
- package/dist/s3-account-objects.js +26 -0
- package/dist/scheduler/attachment-sweep.d.ts +11 -0
- package/dist/scheduler/attachment-sweep.js +23 -0
- package/dist/scheduler/calendar-subscriptions.d.ts +26 -0
- package/dist/scheduler/calendar-subscriptions.js +35 -0
- package/dist/scheduler/config.d.ts +27 -0
- package/{src/scheduler/config.ts → dist/scheduler/config.js} +7 -26
- package/{src/scheduler/decide-due.ts → dist/scheduler/decide-due.d.ts} +3 -12
- package/dist/scheduler/decide-due.js +17 -0
- package/dist/scheduler/handler.d.ts +21 -0
- package/{src/scheduler/handler.ts → dist/scheduler/handler.js} +24 -36
- package/{src/scheduler/loop.ts → dist/scheduler/loop.d.ts} +7 -23
- package/dist/scheduler/loop.js +8 -0
- package/dist/scheduler/run-tick.d.ts +62 -0
- package/dist/scheduler/run-tick.js +164 -0
- package/dist/scheduler/runner.d.ts +2 -0
- package/{src/scheduler/runner.ts → dist/scheduler/runner.js} +58 -84
- package/dist/scheduler/stranded-placement-redrive.d.ts +16 -0
- package/dist/scheduler/stranded-placement-redrive.js +172 -0
- package/dist/sqs-queues.d.ts +4 -0
- package/dist/sqs-queues.js +22 -0
- package/dist/with-oauth-lifecycle-deps.d.ts +5 -0
- package/dist/with-oauth-lifecycle-deps.js +17 -0
- package/dist/with-oauth-lifecycle.d.ts +56 -0
- package/dist/with-oauth-lifecycle.js +92 -0
- package/{src/worker-version.ts → dist/worker-version.d.ts} +1 -2
- package/dist/worker-version.js +12 -0
- package/package.json +75 -69
- package/src/account-check.test.ts +0 -149
- package/src/account-check.ts +0 -101
- package/src/body-sync-gate.test.ts +0 -185
- package/src/body-sync-gate.ts +0 -86
- package/src/cli.ts +0 -224
- package/src/connection-scope.test.ts +0 -266
- package/src/connection-scope.ts +0 -314
- package/src/credential-less-sync-fence.test.ts +0 -152
- package/src/e2e-processor-shim.ts +0 -232
- package/src/emit.test.ts +0 -127
- package/src/emit.ts +0 -117
- package/src/events.ts +0 -252
- package/src/failure-kind.test.ts +0 -42
- package/src/handlers/append-sent-message.sqlite.test.ts +0 -176
- package/src/handlers/append-sent-message.ts +0 -264
- package/src/handlers/delete-account-objects.test.ts +0 -128
- package/src/handlers/delete-account-objects.ts +0 -94
- package/src/handlers/empty-trash.sqlite.test.ts +0 -209
- package/src/handlers/empty-trash.ts +0 -352
- package/src/handlers/flag-push.ts +0 -285
- package/src/handlers/imported-folder.test.ts +0 -63
- package/src/handlers/imported-folder.ts +0 -38
- package/src/handlers/mailbox-management.sqlite.test.ts +0 -265
- package/src/handlers/mailbox-management.ts +0 -486
- package/src/handlers/mailbox-sync-order.test.ts +0 -93
- package/src/handlers/mailbox-sync-order.ts +0 -65
- package/src/handlers/message-copy.sqlite.test.ts +0 -298
- package/src/handlers/message-copy.ts +0 -566
- package/src/handlers/message-delete-terminal.ts +0 -147
- package/src/handlers/message-delete.sqlite.test.ts +0 -291
- package/src/handlers/message-delete.ts +0 -885
- package/src/handlers/message-move.sqlite.test.ts +0 -271
- package/src/handlers/message-move.test.ts +0 -216
- package/src/handlers/message-move.ts +0 -752
- package/src/handlers/placement-move-push.test.ts +0 -137
- package/src/handlers/placement-move-push.ts +0 -475
- package/src/handlers/placement-push.sqlite.test.ts +0 -298
- package/src/handlers/probe-message-id.sqlite.test.ts +0 -192
- package/src/handlers/probe-message-id.ts +0 -15
- package/src/handlers/sync-mailboxes.test.ts +0 -183
- package/src/handlers/sync-mailboxes.ts +0 -421
- package/src/handlers/sync-message-body.sqlite.test.ts +0 -314
- package/src/handlers/sync-message-body.test.ts +0 -150
- package/src/handlers/sync-message-body.ts +0 -365
- package/src/handlers/sync-messages.sqlite.test.ts +0 -183
- package/src/handlers/sync-messages.ts +0 -525
- package/src/index.test.ts +0 -22
- package/src/index.ts +0 -86
- package/src/mail-sync-selection.sqlite.test.ts +0 -234
- package/src/mailbox-row.ts +0 -20
- package/src/pending-sync.sqlite.test.ts +0 -166
- package/src/poller.ts +0 -55
- package/src/processor.ts +0 -60
- package/src/queues.ts +0 -49
- package/src/scheduler/attachment-sweep.sqlite.test.ts +0 -75
- package/src/scheduler/attachment-sweep.ts +0 -46
- package/src/scheduler/calendar-subscriptions.sqlite.test.ts +0 -147
- package/src/scheduler/calendar-subscriptions.ts +0 -103
- package/src/scheduler/config.test.ts +0 -40
- package/src/scheduler/decide-due.test.ts +0 -44
- package/src/scheduler/half-deleted-accounts.sqlite.test.ts +0 -196
- package/src/scheduler/loop.test.ts +0 -143
- package/src/scheduler/run-tick.sqlite.test.ts +0 -191
- package/src/scheduler/run-tick.ts +0 -281
- package/src/scheduler/stranded-placement-redrive.ts +0 -265
- package/src/stranded-placement.sqlite.test.ts +0 -794
- package/src/test-helpers/test-world.ts +0 -427
- package/src/thread-join.sqlite.test.ts +0 -106
- package/src/with-oauth-lifecycle-deps.sqlite.test.ts +0 -269
- package/src/with-oauth-lifecycle-deps.ts +0 -71
- package/src/with-oauth-lifecycle.ts +0 -158
- package/tsconfig.json +0 -8
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
4
|
-
import {
|
|
5
|
-
_resetForTest,
|
|
6
|
-
type RemitClient,
|
|
7
|
-
setClient,
|
|
8
|
-
} from "@remit/backend/client";
|
|
9
|
-
import type { AccountItem } from "@remit/data-ports";
|
|
10
|
-
import { recordingQueue } from "@remit/data-ports/recording-queue";
|
|
11
|
-
import {
|
|
12
|
-
AccountAuthType,
|
|
13
|
-
AccountService,
|
|
14
|
-
ConnectionState,
|
|
15
|
-
MailboxCursorState,
|
|
16
|
-
} from "@remit/domain-enums";
|
|
17
|
-
import {
|
|
18
|
-
type AccountRepo,
|
|
19
|
-
createSqliteRepositories,
|
|
20
|
-
type DrizzleMessageRepository,
|
|
21
|
-
MailboxLockRepo,
|
|
22
|
-
type MailboxRepo,
|
|
23
|
-
} from "@remit/drizzle-service";
|
|
24
|
-
import { createShippedSqliteDb } from "@remit/drizzle-service/test-sqlite";
|
|
25
|
-
import type { Logger } from "@remit/logger";
|
|
26
|
-
import { syncMailboxes } from "./handlers/sync-mailboxes.js";
|
|
27
|
-
import { syncMessages } from "./handlers/sync-messages.js";
|
|
28
|
-
import { setWorkerQueues } from "./queues.js";
|
|
29
|
-
import { runSchedulerTick } from "./scheduler/run-tick.js";
|
|
30
|
-
|
|
31
|
-
const NOW = 1_700_000_000_000;
|
|
32
|
-
|
|
33
|
-
const silentLogger: Logger = (() => {
|
|
34
|
-
const noop = () => {};
|
|
35
|
-
const stub = {
|
|
36
|
-
info: noop,
|
|
37
|
-
warn: noop,
|
|
38
|
-
error: noop,
|
|
39
|
-
debug: noop,
|
|
40
|
-
fatal: noop,
|
|
41
|
-
trace: noop,
|
|
42
|
-
child: () => stub,
|
|
43
|
-
};
|
|
44
|
-
return stub as unknown as Logger;
|
|
45
|
-
})();
|
|
46
|
-
|
|
47
|
-
let accounts: AccountRepo;
|
|
48
|
-
let mailboxes: MailboxRepo;
|
|
49
|
-
let messages: DrizzleMessageRepository;
|
|
50
|
-
let locks: MailboxLockRepo;
|
|
51
|
-
let client: RemitClient;
|
|
52
|
-
let close: () => void;
|
|
53
|
-
|
|
54
|
-
beforeEach(() => {
|
|
55
|
-
const store = createShippedSqliteDb();
|
|
56
|
-
close = store.close;
|
|
57
|
-
const repos = createSqliteRepositories({
|
|
58
|
-
app: store.db,
|
|
59
|
-
mail: store.db,
|
|
60
|
-
search: store.db,
|
|
61
|
-
});
|
|
62
|
-
accounts = repos.account;
|
|
63
|
-
mailboxes = repos.mailbox;
|
|
64
|
-
messages = repos.message;
|
|
65
|
-
locks = new MailboxLockRepo(store.db);
|
|
66
|
-
client = {
|
|
67
|
-
account: accounts,
|
|
68
|
-
mailbox: mailboxes,
|
|
69
|
-
message: messages,
|
|
70
|
-
mailboxLock: locks,
|
|
71
|
-
} as unknown as RemitClient;
|
|
72
|
-
setClient(client);
|
|
73
|
-
const queue = recordingQueue();
|
|
74
|
-
setWorkerQueues({
|
|
75
|
-
mailboxes: queue,
|
|
76
|
-
messages: queue,
|
|
77
|
-
flags: queue,
|
|
78
|
-
body: queue,
|
|
79
|
-
mailboxManagement: queue,
|
|
80
|
-
messageManagement: queue,
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
afterEach(() => {
|
|
85
|
-
_resetForTest();
|
|
86
|
-
close();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const seedSyncedAccount = async (): Promise<{
|
|
90
|
-
account: AccountItem;
|
|
91
|
-
mailboxId: string;
|
|
92
|
-
messageId: string;
|
|
93
|
-
}> => {
|
|
94
|
-
const account = await accounts.create({
|
|
95
|
-
accountConfigId: "cfg-1181",
|
|
96
|
-
username: "person@example.com",
|
|
97
|
-
email: "person@example.com",
|
|
98
|
-
authType: AccountAuthType.Password,
|
|
99
|
-
passwordHash: '{"ciphertext":"x"}',
|
|
100
|
-
imapHost: "imap.example.com",
|
|
101
|
-
imapPort: 993,
|
|
102
|
-
imapTls: true,
|
|
103
|
-
imapStartTls: false,
|
|
104
|
-
isActive: true,
|
|
105
|
-
connectionState: ConnectionState.Authenticated,
|
|
106
|
-
});
|
|
107
|
-
const created = await mailboxes.create({
|
|
108
|
-
accountId: account.accountId,
|
|
109
|
-
namespacePrefix: "",
|
|
110
|
-
hierarchyDelimiter: "/",
|
|
111
|
-
fullPath: "INBOX",
|
|
112
|
-
uidValidity: 7,
|
|
113
|
-
uidNext: 4,
|
|
114
|
-
highestModseq: "42",
|
|
115
|
-
messageCount: 1,
|
|
116
|
-
unseenCount: 0,
|
|
117
|
-
deletedCount: 0,
|
|
118
|
-
totalSize: 10,
|
|
119
|
-
lastSyncUid: 3,
|
|
120
|
-
highWaterMarkUid: 3,
|
|
121
|
-
lastMessageSyncAt: NOW,
|
|
122
|
-
cursorState: MailboxCursorState.normal,
|
|
123
|
-
} as Parameters<MailboxRepo["create"]>[0]);
|
|
124
|
-
if (created.outcome !== "Created") throw new Error("INBOX path taken");
|
|
125
|
-
const mailbox = created.mailbox;
|
|
126
|
-
const messageId = randomUUID();
|
|
127
|
-
await messages.create({
|
|
128
|
-
messageId,
|
|
129
|
-
mailboxId: mailbox.mailboxId,
|
|
130
|
-
uid: 3,
|
|
131
|
-
sequenceNumber: 1,
|
|
132
|
-
rfc822Size: 10,
|
|
133
|
-
internalDate: NOW,
|
|
134
|
-
envelopeId: randomUUID(),
|
|
135
|
-
rootBodyPartId: randomUUID(),
|
|
136
|
-
status: "active",
|
|
137
|
-
syncStatus: "synced",
|
|
138
|
-
});
|
|
139
|
-
return { account, mailboxId: mailbox.mailboxId, messageId };
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const tick = async (): Promise<{ enqueued: number; accountIds: string[] }> => {
|
|
143
|
-
const queue = recordingQueue();
|
|
144
|
-
const result = await runSchedulerTick({
|
|
145
|
-
redriveStrandedPlacements: async () => 0,
|
|
146
|
-
accountService: accounts,
|
|
147
|
-
queue,
|
|
148
|
-
log: silentLogger,
|
|
149
|
-
tickIntervalMs: 60_000,
|
|
150
|
-
offlineIntervalMs: 0,
|
|
151
|
-
now: NOW,
|
|
152
|
-
});
|
|
153
|
-
return {
|
|
154
|
-
enqueued: result.enqueued,
|
|
155
|
-
accountIds: queue.sent.map((message) => String(message.body.accountId)),
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const setServices = (
|
|
160
|
-
accountId: string,
|
|
161
|
-
syncedServices: AccountItem["syncedServices"],
|
|
162
|
-
) => accounts.update(accountId, { syncedServices });
|
|
163
|
-
|
|
164
|
-
describe("turning mail sync off and on again", () => {
|
|
165
|
-
it("stops every mail sync entry point and keeps the stored mail", async () => {
|
|
166
|
-
const { account, mailboxId, messageId } = await seedSyncedAccount();
|
|
167
|
-
const storedMailboxes = await mailboxes.listAllByAccount(account.accountId);
|
|
168
|
-
const storedMessage = await messages.get(messageId);
|
|
169
|
-
|
|
170
|
-
await setServices(account.accountId, [AccountService.Calendar]);
|
|
171
|
-
|
|
172
|
-
assert.deepEqual(await tick(), { enqueued: 0, accountIds: [] });
|
|
173
|
-
|
|
174
|
-
await syncMailboxes(
|
|
175
|
-
{
|
|
176
|
-
type: "SYNC_MAILBOXES",
|
|
177
|
-
eventId: "evt-mailboxes",
|
|
178
|
-
timestamp: NOW,
|
|
179
|
-
accountId: account.accountId,
|
|
180
|
-
},
|
|
181
|
-
silentLogger,
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
let connections = 0;
|
|
185
|
-
await syncMessages(
|
|
186
|
-
{
|
|
187
|
-
type: "SYNC_MESSAGES",
|
|
188
|
-
eventId: "evt-messages",
|
|
189
|
-
timestamp: NOW,
|
|
190
|
-
accountId: account.accountId,
|
|
191
|
-
mailboxId,
|
|
192
|
-
},
|
|
193
|
-
silentLogger,
|
|
194
|
-
() => {
|
|
195
|
-
connections += 1;
|
|
196
|
-
throw new Error("a sync that is off must not connect");
|
|
197
|
-
},
|
|
198
|
-
);
|
|
199
|
-
assert.equal(connections, 0);
|
|
200
|
-
|
|
201
|
-
assert.deepEqual(
|
|
202
|
-
await mailboxes.listAllByAccount(account.accountId),
|
|
203
|
-
storedMailboxes,
|
|
204
|
-
);
|
|
205
|
-
assert.deepEqual(await messages.get(messageId), storedMessage);
|
|
206
|
-
assert.equal(
|
|
207
|
-
(await accounts.get(account.accountId)).connectionState,
|
|
208
|
-
ConnectionState.Authenticated,
|
|
209
|
-
);
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
it("resumes from the stored markers when mail is turned back on", async () => {
|
|
213
|
-
const { account, messageId } = await seedSyncedAccount();
|
|
214
|
-
const storedMailboxes = await mailboxes.listAllByAccount(account.accountId);
|
|
215
|
-
const storedMessage = await messages.get(messageId);
|
|
216
|
-
|
|
217
|
-
await setServices(account.accountId, [AccountService.Calendar]);
|
|
218
|
-
await tick();
|
|
219
|
-
await setServices(account.accountId, [
|
|
220
|
-
AccountService.Mail,
|
|
221
|
-
AccountService.Calendar,
|
|
222
|
-
]);
|
|
223
|
-
|
|
224
|
-
assert.deepEqual(await tick(), {
|
|
225
|
-
enqueued: 1,
|
|
226
|
-
accountIds: [account.accountId],
|
|
227
|
-
});
|
|
228
|
-
assert.deepEqual(
|
|
229
|
-
await mailboxes.listAllByAccount(account.accountId),
|
|
230
|
-
storedMailboxes,
|
|
231
|
-
);
|
|
232
|
-
assert.deepEqual(await messages.get(messageId), storedMessage);
|
|
233
|
-
});
|
|
234
|
-
});
|
package/src/mailbox-row.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { IMailboxRepository, MailboxItem } from "@remit/data-ports";
|
|
2
|
-
import { isNotFoundError } from "@remit/data-ports/errors";
|
|
3
|
-
|
|
4
|
-
export type MailboxRow =
|
|
5
|
-
| { kind: "present"; mailbox: MailboxItem }
|
|
6
|
-
| { kind: "gone"; mailboxId: string };
|
|
7
|
-
|
|
8
|
-
export const findMailboxRow = (
|
|
9
|
-
mailboxService: Pick<IMailboxRepository, "get">,
|
|
10
|
-
accountId: string,
|
|
11
|
-
mailboxId: string,
|
|
12
|
-
): Promise<MailboxRow> =>
|
|
13
|
-
mailboxService.get(accountId, mailboxId).then(
|
|
14
|
-
(mailbox): MailboxRow =>
|
|
15
|
-
mailbox ? { kind: "present", mailbox } : { kind: "gone", mailboxId },
|
|
16
|
-
(error: unknown): MailboxRow => {
|
|
17
|
-
if (isNotFoundError(error)) return { kind: "gone", mailboxId };
|
|
18
|
-
throw error;
|
|
19
|
-
},
|
|
20
|
-
);
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
_resetForTest,
|
|
5
|
-
type RemitClient,
|
|
6
|
-
setClient,
|
|
7
|
-
} from "@remit/backend/client";
|
|
8
|
-
import {
|
|
9
|
-
type RecordingQueue,
|
|
10
|
-
recordingQueue,
|
|
11
|
-
} from "@remit/data-ports/recording-queue";
|
|
12
|
-
import { MailboxLockRepo } from "@remit/drizzle-service";
|
|
13
|
-
import { createShippedSqliteDb } from "@remit/drizzle-service/test-sqlite";
|
|
14
|
-
import { emitEvent, SYNC_MESSAGES_PENDING } from "./emit.js";
|
|
15
|
-
import type { SyncMessagesEvent } from "./events.js";
|
|
16
|
-
import { emitMailboxResync, emitMoveResync } from "./handlers/message-move.js";
|
|
17
|
-
import { emitSyncMessagesEvents } from "./handlers/sync-mailboxes.js";
|
|
18
|
-
import { setWorkerQueues } from "./queues.js";
|
|
19
|
-
|
|
20
|
-
const ACCOUNT = "account-1366";
|
|
21
|
-
const FOLDERS = [
|
|
22
|
-
{ mailboxId: "mbx-inbox", fullPath: "INBOX" },
|
|
23
|
-
{ mailboxId: "mbx-sent", fullPath: "Sent" },
|
|
24
|
-
{ mailboxId: "mbx-archive", fullPath: "Archive" },
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
let queue: RecordingQueue;
|
|
28
|
-
let locks: MailboxLockRepo;
|
|
29
|
-
let closeStore: () => void;
|
|
30
|
-
|
|
31
|
-
type Queued = { mailboxId: string; eventId: string };
|
|
32
|
-
|
|
33
|
-
const queued = (): Queued[] =>
|
|
34
|
-
queue.sent.map(({ body }) => ({
|
|
35
|
-
mailboxId: String(body.mailboxId),
|
|
36
|
-
eventId: String(body.eventId),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
const inboxSync = (): Omit<SyncMessagesEvent, "eventId" | "timestamp"> => ({
|
|
40
|
-
type: "SYNC_MESSAGES",
|
|
41
|
-
accountId: ACCOUNT,
|
|
42
|
-
mailboxId: "mbx-inbox",
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const fanOut = (): Promise<void> =>
|
|
46
|
-
emitSyncMessagesEvents(ACCOUNT, FOLDERS, emitEvent);
|
|
47
|
-
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
queue = recordingQueue();
|
|
50
|
-
setWorkerQueues({
|
|
51
|
-
mailboxes: queue,
|
|
52
|
-
messages: queue,
|
|
53
|
-
flags: queue,
|
|
54
|
-
body: queue,
|
|
55
|
-
mailboxManagement: queue,
|
|
56
|
-
messageManagement: queue,
|
|
57
|
-
});
|
|
58
|
-
const store = createShippedSqliteDb();
|
|
59
|
-
closeStore = store.close;
|
|
60
|
-
locks = new MailboxLockRepo(store.db);
|
|
61
|
-
setClient({ mailboxLock: locks } as unknown as RemitClient);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
afterEach(() => {
|
|
65
|
-
_resetForTest();
|
|
66
|
-
closeStore();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe("SYNC_MESSAGES under explicit refreshes (#1366)", () => {
|
|
70
|
-
it("queues one event per folder however many rounds arrive while none has started", async () => {
|
|
71
|
-
for (let round = 0; round < 25; round += 1) await fanOut();
|
|
72
|
-
|
|
73
|
-
assert.deepEqual(
|
|
74
|
-
queued()
|
|
75
|
-
.map((event) => event.mailboxId)
|
|
76
|
-
.sort(),
|
|
77
|
-
["mbx-archive", "mbx-inbox", "mbx-sent"],
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("queues a folder again once its pending sync has started", async () => {
|
|
82
|
-
await fanOut();
|
|
83
|
-
const inbox = queued().find((event) => event.mailboxId === "mbx-inbox");
|
|
84
|
-
if (!inbox) throw new Error("INBOX was not queued");
|
|
85
|
-
|
|
86
|
-
await locks.releaseLock(
|
|
87
|
-
ACCOUNT,
|
|
88
|
-
inbox.mailboxId,
|
|
89
|
-
SYNC_MESSAGES_PENDING,
|
|
90
|
-
inbox.eventId,
|
|
91
|
-
);
|
|
92
|
-
await fanOut();
|
|
93
|
-
|
|
94
|
-
assert.equal(queued().length, FOLDERS.length + 1);
|
|
95
|
-
assert.equal(queued().at(-1)?.mailboxId, "mbx-inbox");
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("keeps a folder's pending marker when a stale event starts", async () => {
|
|
99
|
-
await fanOut();
|
|
100
|
-
await locks.releaseLock(
|
|
101
|
-
ACCOUNT,
|
|
102
|
-
"mbx-inbox",
|
|
103
|
-
SYNC_MESSAGES_PENDING,
|
|
104
|
-
"an-event-from-before-the-marker",
|
|
105
|
-
);
|
|
106
|
-
await fanOut();
|
|
107
|
-
|
|
108
|
-
assert.equal(queued().length, FOLDERS.length);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("takes the same marker for a move's resync and a delete's resync", async () => {
|
|
112
|
-
await emitMoveResync(emitEvent, {
|
|
113
|
-
accountId: ACCOUNT,
|
|
114
|
-
sourceMailboxId: "mbx-inbox",
|
|
115
|
-
destinationMailboxId: "mbx-sent",
|
|
116
|
-
});
|
|
117
|
-
await emitMailboxResync(emitEvent, {
|
|
118
|
-
accountId: ACCOUNT,
|
|
119
|
-
mailboxId: "mbx-inbox",
|
|
120
|
-
});
|
|
121
|
-
await fanOut();
|
|
122
|
-
|
|
123
|
-
assert.equal(queued().length, FOLDERS.length);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("takes over a marker whose event was lost once it has expired", async () => {
|
|
127
|
-
await locks.tryAcquireLock(
|
|
128
|
-
"mbx-inbox",
|
|
129
|
-
"SYNC_MESSAGES_PENDING",
|
|
130
|
-
ACCOUNT,
|
|
131
|
-
"lost-event",
|
|
132
|
-
-1,
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
await emitEvent(inboxSync());
|
|
136
|
-
|
|
137
|
-
assert.equal(queued().length, 1);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("releases the marker when the send fails, so the next round is not swallowed", async () => {
|
|
141
|
-
let sends = 0;
|
|
142
|
-
queue.answer(async () => {
|
|
143
|
-
sends += 1;
|
|
144
|
-
if (sends === 1) throw new Error("queue unreachable");
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await assert.rejects(emitEvent(inboxSync()), /queue unreachable/);
|
|
148
|
-
await emitEvent(inboxSync());
|
|
149
|
-
|
|
150
|
-
assert.equal(queued().length, 2);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("fails the operation when the marker store fails", async () => {
|
|
154
|
-
setClient({
|
|
155
|
-
mailboxLock: {
|
|
156
|
-
get: async () => null,
|
|
157
|
-
tryAcquireLock: async () => {
|
|
158
|
-
throw new Error("marker store down");
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
} as unknown as RemitClient);
|
|
162
|
-
|
|
163
|
-
await assert.rejects(emitEvent(inboxSync()), /marker store down/);
|
|
164
|
-
assert.equal(queue.sent.length, 0);
|
|
165
|
-
});
|
|
166
|
-
});
|
package/src/poller.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "@remit/logger";
|
|
2
|
-
import { startMetricsServer } from "@remit/logger-lambda/metrics";
|
|
3
|
-
import { installNodeLogger } from "@remit/logger-lambda/node";
|
|
4
|
-
import { runQueuePoller } from "@remit/sqs-client/poller";
|
|
5
|
-
import { env } from "expect-env";
|
|
6
|
-
import { handler } from "./index.js";
|
|
7
|
-
|
|
8
|
-
installNodeLogger();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Production queue poller — the deployed form of `e2e-processor-shim.ts`.
|
|
12
|
-
* Polls every imap-worker queue and invokes the production Lambda handler.
|
|
13
|
-
*/
|
|
14
|
-
const log = createLogger();
|
|
15
|
-
|
|
16
|
-
// /metrics and nothing else, on the compose network (standalone-observability
|
|
17
|
-
// D2). No health route on it: worker liveness is a heartbeat file, which keeps
|
|
18
|
-
// answering when this server does not.
|
|
19
|
-
startMetricsServer();
|
|
20
|
-
|
|
21
|
-
await runQueuePoller({
|
|
22
|
-
log,
|
|
23
|
-
targets: [
|
|
24
|
-
{
|
|
25
|
-
queueUrl: env.SQS_QUEUE_URL_MAILBOXES,
|
|
26
|
-
handler,
|
|
27
|
-
functionName: "imap-worker-mailboxes",
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
queueUrl: env.SQS_QUEUE_URL_MESSAGES,
|
|
31
|
-
handler,
|
|
32
|
-
functionName: "imap-worker-messages",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
queueUrl: env.SQS_QUEUE_URL_FLAGS,
|
|
36
|
-
handler,
|
|
37
|
-
functionName: "imap-worker-flags",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
queueUrl: env.SQS_QUEUE_URL_BODY,
|
|
41
|
-
handler,
|
|
42
|
-
functionName: "imap-worker-body",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
queueUrl: env.SQS_QUEUE_URL_MAILBOX_MGMT,
|
|
46
|
-
handler,
|
|
47
|
-
functionName: "imap-worker-mailbox-mgmt",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
queueUrl: env.SQS_QUEUE_URL_MESSAGE_MGMT,
|
|
51
|
-
handler,
|
|
52
|
-
functionName: "imap-worker-message-mgmt",
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
});
|
package/src/processor.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import type { Logger } from "@remit/logger";
|
|
2
|
-
import type { WorkerEvent } from "./events.js";
|
|
3
|
-
import { handleAppendSentMessage } from "./handlers/append-sent-message.js";
|
|
4
|
-
import { handleDeleteAccountObjects } from "./handlers/delete-account-objects.js";
|
|
5
|
-
import { handleEmptyTrash } from "./handlers/empty-trash.js";
|
|
6
|
-
import { handleFlagPush } from "./handlers/flag-push.js";
|
|
7
|
-
import { processMailboxManagement } from "./handlers/mailbox-management.js";
|
|
8
|
-
import { handleMessageCopy } from "./handlers/message-copy.js";
|
|
9
|
-
import { handleMessageDelete } from "./handlers/message-delete.js";
|
|
10
|
-
import { handleMessageMove } from "./handlers/message-move.js";
|
|
11
|
-
import { handlePlacementMovePush } from "./handlers/placement-move-push.js";
|
|
12
|
-
import { syncMailboxes } from "./handlers/sync-mailboxes.js";
|
|
13
|
-
import { syncMessageBody } from "./handlers/sync-message-body.js";
|
|
14
|
-
import { syncMessages } from "./handlers/sync-messages.js";
|
|
15
|
-
|
|
16
|
-
export const processEvent = async (
|
|
17
|
-
event: WorkerEvent,
|
|
18
|
-
log: Logger,
|
|
19
|
-
/**
|
|
20
|
-
* SQS's own delivery count for the record carrying this event (1 on first
|
|
21
|
-
* delivery). Read by SYNC_MESSAGE_BODY, PLACEMENT_MOVE_PUSH, FLAG_PUSH,
|
|
22
|
-
* APPEND_SENT_MESSAGE, MESSAGE_MOVE, MESSAGE_DELETE and MESSAGE_COPY — each
|
|
23
|
-
* knows from it when this is
|
|
24
|
-
* the last attempt before the queue's own redrive policy would DLQ the
|
|
25
|
-
* record, so it can resolve retry exhaustion into a terminal outcome
|
|
26
|
-
* (issue #1270) instead of dead-lettering blindly.
|
|
27
|
-
*/
|
|
28
|
-
receiveCount = 1,
|
|
29
|
-
): Promise<void> => {
|
|
30
|
-
switch (event.type) {
|
|
31
|
-
case "SYNC_MAILBOXES":
|
|
32
|
-
return syncMailboxes(event, log);
|
|
33
|
-
case "SYNC_MESSAGES":
|
|
34
|
-
return syncMessages(event, log);
|
|
35
|
-
case "SYNC_MESSAGE_BODY":
|
|
36
|
-
return syncMessageBody(event, log, receiveCount);
|
|
37
|
-
case "MAILBOX_CREATE":
|
|
38
|
-
case "MAILBOX_RENAME":
|
|
39
|
-
case "MAILBOX_DELETE":
|
|
40
|
-
return processMailboxManagement(event, log);
|
|
41
|
-
case "MESSAGE_DELETE":
|
|
42
|
-
return handleMessageDelete(event, log, receiveCount);
|
|
43
|
-
case "MESSAGE_MOVE":
|
|
44
|
-
return handleMessageMove(event, log, receiveCount);
|
|
45
|
-
case "PLACEMENT_MOVE_PUSH":
|
|
46
|
-
return handlePlacementMovePush(event, log, receiveCount);
|
|
47
|
-
case "FLAG_PUSH":
|
|
48
|
-
return handleFlagPush(event, log, receiveCount);
|
|
49
|
-
case "MESSAGE_COPY":
|
|
50
|
-
return handleMessageCopy(event, log, receiveCount);
|
|
51
|
-
case "EMPTY_TRASH":
|
|
52
|
-
return handleEmptyTrash(event, log);
|
|
53
|
-
case "APPEND_SENT_MESSAGE":
|
|
54
|
-
return handleAppendSentMessage(event, log, receiveCount);
|
|
55
|
-
case "DELETE_ACCOUNT_OBJECTS":
|
|
56
|
-
return handleDeleteAccountObjects(event, log);
|
|
57
|
-
default:
|
|
58
|
-
throw new Error(`Unknown event type: ${(event as WorkerEvent).type}`);
|
|
59
|
-
}
|
|
60
|
-
};
|
package/src/queues.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import type { FifoQueueProducer, QueueProducer } from "@remit/data-ports";
|
|
2
|
-
import { createQueueProducer } from "@remit/sqs-client/producer";
|
|
3
|
-
import { env } from "expect-env";
|
|
4
|
-
import type { ImapEvent, WorkerEvent } from "./events.js";
|
|
5
|
-
|
|
6
|
-
export interface WorkerQueues {
|
|
7
|
-
mailboxes: FifoQueueProducer<ImapEvent>;
|
|
8
|
-
messages: FifoQueueProducer<ImapEvent>;
|
|
9
|
-
flags: FifoQueueProducer<ImapEvent>;
|
|
10
|
-
body: QueueProducer<ImapEvent>;
|
|
11
|
-
mailboxManagement: QueueProducer<ImapEvent>;
|
|
12
|
-
messageManagement: QueueProducer<WorkerEvent>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export type WorkerQueueUrls = Record<keyof WorkerQueues, string>;
|
|
16
|
-
|
|
17
|
-
export const workerQueueUrlsFromEnv = (): WorkerQueueUrls => ({
|
|
18
|
-
mailboxes: env.SQS_QUEUE_URL_MAILBOXES,
|
|
19
|
-
messages: env.SQS_QUEUE_URL_MESSAGES,
|
|
20
|
-
flags: env.SQS_QUEUE_URL_FLAGS,
|
|
21
|
-
body: env.SQS_QUEUE_URL_BODY,
|
|
22
|
-
mailboxManagement: env.SQS_QUEUE_URL_MAILBOX_MGMT,
|
|
23
|
-
messageManagement: env.SQS_QUEUE_URL_MESSAGE_MGMT,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
export const sqsWorkerQueues = (urls: WorkerQueueUrls): WorkerQueues => ({
|
|
27
|
-
mailboxes: createQueueProducer({ queueUrl: urls.mailboxes }),
|
|
28
|
-
messages: createQueueProducer({ queueUrl: urls.messages }),
|
|
29
|
-
flags: createQueueProducer({ queueUrl: urls.flags }),
|
|
30
|
-
body: createQueueProducer({ queueUrl: urls.body }),
|
|
31
|
-
mailboxManagement: createQueueProducer({ queueUrl: urls.mailboxManagement }),
|
|
32
|
-
messageManagement: createQueueProducer({ queueUrl: urls.messageManagement }),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
let composed: WorkerQueues | null = null;
|
|
36
|
-
|
|
37
|
-
/** Register the worker's queues from its entry, before the first event. */
|
|
38
|
-
export const setWorkerQueues = (queues: WorkerQueues): void => {
|
|
39
|
-
composed = queues;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export const workerQueues = (): WorkerQueues => {
|
|
43
|
-
if (!composed) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
"no worker queues registered — register them with setWorkerQueues() from the entry",
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
return composed;
|
|
49
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { after, before, describe, it } from "node:test";
|
|
4
|
-
import { openStore, type World } from "../test-helpers/test-world.js";
|
|
5
|
-
import { buildAttachmentSweep } from "./attachment-sweep.js";
|
|
6
|
-
|
|
7
|
-
let store: Awaited<ReturnType<typeof openStore>>;
|
|
8
|
-
|
|
9
|
-
before(async () => {
|
|
10
|
-
store = await openStore();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
after(() => store.close());
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* A draft holding one uploaded file, and one object under the same draft that
|
|
17
|
-
* no attachment row names — what a browser leaves behind when it uploads and
|
|
18
|
-
* the reservation lapses before it is completed.
|
|
19
|
-
*/
|
|
20
|
-
const arrange = async (w: World) => {
|
|
21
|
-
const { outbox, attachmentId } = await w.draft(Buffer.from("q1,q2\n1,2\n"));
|
|
22
|
-
await w.client.storage.storeOutboxAttachment({
|
|
23
|
-
accountConfigId: w.account.accountConfigId,
|
|
24
|
-
accountId: w.account.accountId,
|
|
25
|
-
outboxMessageId: outbox.outboxMessageId,
|
|
26
|
-
outboxAttachmentId: `orphan-${randomUUID()}`,
|
|
27
|
-
content: Buffer.from("left behind"),
|
|
28
|
-
});
|
|
29
|
-
return { outboxMessageId: outbox.outboxMessageId, attachmentId };
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const storedIds = async (w: World, outboxMessageId: string) =>
|
|
33
|
-
(
|
|
34
|
-
await w.client.storage.listOutboxAttachments(
|
|
35
|
-
w.account.accountConfigId,
|
|
36
|
-
w.account.accountId,
|
|
37
|
-
outboxMessageId,
|
|
38
|
-
)
|
|
39
|
-
).map((item) => item.outboxAttachmentId);
|
|
40
|
-
|
|
41
|
-
describe("the outbox attachment sweep", () => {
|
|
42
|
-
it("removes the objects no attachment row names and keeps the ones that are", async () => {
|
|
43
|
-
const w = await store.world();
|
|
44
|
-
const { outboxMessageId, attachmentId } = await arrange(w);
|
|
45
|
-
|
|
46
|
-
await buildAttachmentSweep(w.client, w.log)(w.account);
|
|
47
|
-
|
|
48
|
-
assert.deepEqual(await storedIds(w, outboxMessageId), [attachmentId]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("runs for every account on the hosted tick, due a sync or not", async () => {
|
|
52
|
-
const first = await store.world();
|
|
53
|
-
const firstDraft = await arrange(first);
|
|
54
|
-
await first.client.account.update(first.account.accountId, {
|
|
55
|
-
lastSyncAt: Date.now(),
|
|
56
|
-
});
|
|
57
|
-
const second = await store.world();
|
|
58
|
-
const secondDraft = await arrange(second);
|
|
59
|
-
|
|
60
|
-
const { handler } = await import("./handler.js");
|
|
61
|
-
store.bindWorkerQueues();
|
|
62
|
-
await handler(
|
|
63
|
-
{ time: new Date().toISOString() } as never,
|
|
64
|
-
{} as never,
|
|
65
|
-
() => {},
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
assert.deepEqual(await storedIds(first, firstDraft.outboxMessageId), [
|
|
69
|
-
firstDraft.attachmentId,
|
|
70
|
-
]);
|
|
71
|
-
assert.deepEqual(await storedIds(second, secondDraft.outboxMessageId), [
|
|
72
|
-
secondDraft.attachmentId,
|
|
73
|
-
]);
|
|
74
|
-
});
|
|
75
|
-
});
|