@remit/imap-worker 0.0.89 → 0.0.90
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/cli.ts +3 -9
- package/src/emit.test.ts +28 -10
- package/src/emit.ts +70 -72
- package/src/handlers/delete-account-objects.test.ts +11 -13
- package/src/handlers/delete-account-objects.ts +5 -3
- package/src/handlers/message-copy.sqlite.test.ts +3 -3
- package/src/handlers/message-copy.ts +5 -2
- package/src/handlers/message-delete.sqlite.test.ts +1 -1
- package/src/handlers/message-delete.ts +7 -4
- package/src/handlers/message-move.sqlite.test.ts +1 -1
- package/src/handlers/message-move.ts +9 -6
- package/src/handlers/placement-move-push.ts +9 -6
- package/src/handlers/placement-push.sqlite.test.ts +2 -1
- package/src/handlers/probe-message-id.sqlite.test.ts +1 -1
- package/src/handlers/sync-mailboxes.ts +8 -4
- package/src/handlers/sync-message-body.sqlite.test.ts +2 -2
- package/src/handlers/sync-message-body.ts +2 -1
- package/src/handlers/sync-messages.sqlite.test.ts +14 -2
- package/src/handlers/sync-messages.ts +20 -8
- package/src/index.ts +3 -7
- package/src/mail-sync-selection.sqlite.test.ts +4 -11
- package/src/pending-sync.sqlite.test.ts +9 -10
- package/src/processor.ts +10 -8
- package/src/queues.ts +14 -23
- package/src/scheduler/attachment-sweep.sqlite.test.ts +2 -3
- package/src/scheduler/handler.ts +31 -26
- package/src/scheduler/run-tick.sqlite.test.ts +1 -1
- package/src/scheduler/runner.ts +5 -10
- package/src/stranded-placement.sqlite.test.ts +5 -0
- package/src/test-helpers/test-world.ts +30 -32
- package/src/test-helpers/worker-queues.ts +11 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -6,12 +6,8 @@
|
|
|
6
6
|
// in npm-scripts/docker-bundle.mjs). Do not route it through the logger — usage
|
|
7
7
|
// text and argument errors are for the person who typed the command.
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
setWorkerQueues,
|
|
12
|
-
sqsWorkerQueues,
|
|
13
|
-
workerQueueUrlsFromEnv,
|
|
14
|
-
} from "./queues.js";
|
|
9
|
+
import { createEmitEvent } from "./emit.js";
|
|
10
|
+
import { sqsWorkerQueues, workerQueueUrlsFromEnv } from "./queues.js";
|
|
15
11
|
|
|
16
12
|
const HELP = `
|
|
17
13
|
remit-imap-worker - Process IMAP sync events
|
|
@@ -209,11 +205,9 @@ const buildEvent = () => {
|
|
|
209
205
|
|
|
210
206
|
const event = buildEvent();
|
|
211
207
|
|
|
212
|
-
setWorkerQueues(sqsWorkerQueues(workerQueueUrlsFromEnv()));
|
|
213
|
-
|
|
214
208
|
console.log(`Enqueueing ${event.type} event for account ${event.accountId}...`);
|
|
215
209
|
|
|
216
|
-
|
|
210
|
+
createEmitEvent(sqsWorkerQueues(workerQueueUrlsFromEnv()))(event)
|
|
217
211
|
.then(() => {
|
|
218
212
|
console.log("Event enqueued successfully");
|
|
219
213
|
process.exit(0);
|
package/src/emit.test.ts
CHANGED
|
@@ -11,13 +11,18 @@ import {
|
|
|
11
11
|
} from "@remit/data-ports/recording-queue";
|
|
12
12
|
import { MailboxLockRepo } from "@remit/drizzle-service";
|
|
13
13
|
import { createShippedSqliteDb } from "@remit/drizzle-service/test-sqlite";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
createEmitEvent,
|
|
16
|
+
type EmitEvent,
|
|
17
|
+
SYNC_MESSAGES_PENDING,
|
|
18
|
+
} from "./emit.js";
|
|
15
19
|
import type {
|
|
16
20
|
FlagPushEvent,
|
|
17
21
|
SyncMailboxesEvent,
|
|
22
|
+
SyncMessageBodyEvent,
|
|
18
23
|
SyncMessagesEvent,
|
|
19
24
|
} from "./events.js";
|
|
20
|
-
import {
|
|
25
|
+
import { workerQueuesOn } from "./test-helpers/worker-queues.js";
|
|
21
26
|
|
|
22
27
|
type Emitted<T> = Omit<T, "eventId" | "timestamp">;
|
|
23
28
|
|
|
@@ -27,16 +32,11 @@ let queue: RecordingQueue;
|
|
|
27
32
|
|
|
28
33
|
const sentCommands = () => queue.sent;
|
|
29
34
|
|
|
35
|
+
const emitEvent: EmitEvent = (event) =>
|
|
36
|
+
createEmitEvent(workerQueuesOn(queue))(event);
|
|
37
|
+
|
|
30
38
|
beforeEach(() => {
|
|
31
39
|
queue = recordingQueue();
|
|
32
|
-
setWorkerQueues({
|
|
33
|
-
mailboxes: queue,
|
|
34
|
-
messages: queue,
|
|
35
|
-
flags: queue,
|
|
36
|
-
body: queue,
|
|
37
|
-
mailboxManagement: queue,
|
|
38
|
-
messageManagement: queue,
|
|
39
|
-
});
|
|
40
40
|
const store = createShippedSqliteDb();
|
|
41
41
|
closeStore = store.close;
|
|
42
42
|
locks = new MailboxLockRepo(store.db);
|
|
@@ -125,3 +125,21 @@ describe("emitEvent on a FIFO queue", () => {
|
|
|
125
125
|
assert.equal(sent.deduplicationId, sent.body.eventId);
|
|
126
126
|
});
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
describe("emitEvent on a standard queue", () => {
|
|
130
|
+
it("sends a body sync with no group or deduplication id", async () => {
|
|
131
|
+
const event: Emitted<SyncMessageBodyEvent> = {
|
|
132
|
+
type: "SYNC_MESSAGE_BODY",
|
|
133
|
+
accountId: "acc-1",
|
|
134
|
+
mailboxId: "mbx-1",
|
|
135
|
+
messageIds: ["msg-1"],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
await emitEvent(event);
|
|
139
|
+
|
|
140
|
+
const [sent] = sentCommands();
|
|
141
|
+
if (!sent) throw new Error("expected a send");
|
|
142
|
+
assert.equal(sent.groupId, undefined);
|
|
143
|
+
assert.equal(sent.deduplicationId, undefined);
|
|
144
|
+
});
|
|
145
|
+
});
|
package/src/emit.ts
CHANGED
|
@@ -1,32 +1,53 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getClient } from "@remit/backend/client";
|
|
3
|
-
import type { FifoQueueProducer } from "@remit/data-ports";
|
|
3
|
+
import type { FifoQueueProducer, QueueProducer } from "@remit/data-ports";
|
|
4
4
|
import { recordImapFailure } from "@remit/logger-lambda/metrics";
|
|
5
5
|
import type { ImapEvent } from "./events.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { WorkerQueues } from "./queues.js";
|
|
7
7
|
|
|
8
8
|
type EventInput = Omit<ImapEvent, "eventId" | "timestamp">;
|
|
9
9
|
|
|
10
|
+
export type EmitEvent = (event: EventInput) => Promise<void>;
|
|
11
|
+
|
|
12
|
+
type Send = (event: ImapEvent) => Promise<void>;
|
|
13
|
+
|
|
14
|
+
// The deduplication id is the event's own id, so the queue suppresses a
|
|
15
|
+
// re-send of one event (the retry SQS's own idempotency guard is for) and
|
|
16
|
+
// nothing else. A shared id per account or per mailbox instead made the
|
|
17
|
+
// 5-minute window a rate limiter: the second sync of a mailbox within five
|
|
18
|
+
// minutes was discarded before any worker saw it, so mail that arrived after
|
|
19
|
+
// a sync could not be fetched until the window elapsed (issue #37).
|
|
20
|
+
const fifo =
|
|
21
|
+
(queue: FifoQueueProducer<ImapEvent>): Send =>
|
|
22
|
+
(event) =>
|
|
23
|
+
queue.send({
|
|
24
|
+
body: event,
|
|
25
|
+
groupId: event.accountId,
|
|
26
|
+
deduplicationId: event.eventId,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const standard =
|
|
30
|
+
(queue: QueueProducer<ImapEvent>): Send =>
|
|
31
|
+
(event) =>
|
|
32
|
+
queue.send({ body: event });
|
|
33
|
+
|
|
10
34
|
export const SYNC_MESSAGES_PENDING = "SYNC_MESSAGES_PENDING";
|
|
11
35
|
|
|
12
36
|
const SYNC_MESSAGES_PENDING_TTL_SECONDS = 300;
|
|
13
37
|
|
|
14
|
-
const
|
|
15
|
-
type: ImapEvent["type"],
|
|
16
|
-
queues: WorkerQueues,
|
|
17
|
-
): FifoQueueProducer<ImapEvent> => {
|
|
38
|
+
const senderFor = (type: ImapEvent["type"], queues: WorkerQueues): Send => {
|
|
18
39
|
switch (type) {
|
|
19
40
|
case "SYNC_MAILBOXES":
|
|
20
|
-
return queues.mailboxes;
|
|
41
|
+
return fifo(queues.mailboxes);
|
|
21
42
|
case "SYNC_MESSAGES":
|
|
22
|
-
return queues.messages;
|
|
43
|
+
return fifo(queues.messages);
|
|
23
44
|
// SYNC_MESSAGE_BODY routes to the single standard body queue (#612).
|
|
24
45
|
case "SYNC_MESSAGE_BODY":
|
|
25
|
-
return queues.body;
|
|
46
|
+
return standard(queues.body);
|
|
26
47
|
case "MAILBOX_CREATE":
|
|
27
48
|
case "MAILBOX_RENAME":
|
|
28
49
|
case "MAILBOX_DELETE":
|
|
29
|
-
return queues.mailboxManagement;
|
|
50
|
+
return standard(queues.mailboxManagement);
|
|
30
51
|
case "MESSAGE_DELETE":
|
|
31
52
|
case "MESSAGE_MOVE":
|
|
32
53
|
case "MESSAGE_COPY":
|
|
@@ -36,7 +57,7 @@ const queueFor = (
|
|
|
36
57
|
// payload carries only our message id (never a UID, unlike the legacy
|
|
37
58
|
// MESSAGE_MOVE event); per-event-type payload shape, same queue.
|
|
38
59
|
case "PLACEMENT_MOVE_PUSH":
|
|
39
|
-
return queues.messageManagement;
|
|
60
|
+
return standard(queues.messageManagement);
|
|
40
61
|
// Rides the existing flags queue (issue #1273) — this is imap-worker's OWN
|
|
41
62
|
// re-arm hint (the periodic per-mailbox sync tick catching up a marker
|
|
42
63
|
// stuck `pending`), distinct from `FlagPushService`'s user-facing hint
|
|
@@ -44,74 +65,51 @@ const queueFor = (
|
|
|
44
65
|
// already consumes; dispatch is by `type`, not by which queue delivered
|
|
45
66
|
// the message.
|
|
46
67
|
case "FLAG_PUSH":
|
|
47
|
-
return queues.flags;
|
|
68
|
+
return fifo(queues.flags);
|
|
48
69
|
}
|
|
49
70
|
};
|
|
50
71
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
export const emitEvent = async (
|
|
60
|
-
event: EventInput,
|
|
61
|
-
options?: EmitEventOptions,
|
|
62
|
-
) => {
|
|
63
|
-
const fullEvent: ImapEvent = {
|
|
64
|
-
...event,
|
|
65
|
-
eventId: randomUUID(),
|
|
66
|
-
timestamp: Date.now(),
|
|
67
|
-
} as ImapEvent;
|
|
68
|
-
|
|
69
|
-
const queue = queueFor(event.type, workerQueues());
|
|
70
|
-
|
|
71
|
-
// The deduplication id is the event's own id, so the queue suppresses a
|
|
72
|
-
// re-send of one event (the retry SQS's own idempotency guard is for) and
|
|
73
|
-
// nothing else. A shared id per account or per mailbox instead made the
|
|
74
|
-
// 5-minute window a rate limiter: the second sync of a mailbox within five
|
|
75
|
-
// minutes was discarded before any worker saw it, so mail that arrived after
|
|
76
|
-
// a sync could not be fetched until the window elapsed (issue #37).
|
|
77
|
-
const send = () =>
|
|
78
|
-
queue.send({
|
|
79
|
-
body: fullEvent,
|
|
80
|
-
groupId: event.accountId,
|
|
81
|
-
deduplicationId: fullEvent.eventId,
|
|
82
|
-
...(options?.delaySeconds && { delaySeconds: options.delaySeconds }),
|
|
83
|
-
});
|
|
84
|
-
if (fullEvent.type !== "SYNC_MESSAGES") {
|
|
85
|
-
await send();
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
72
|
+
export const createEmitEvent =
|
|
73
|
+
(queues: WorkerQueues): EmitEvent =>
|
|
74
|
+
async (event) => {
|
|
75
|
+
const fullEvent: ImapEvent = {
|
|
76
|
+
...event,
|
|
77
|
+
eventId: randomUUID(),
|
|
78
|
+
timestamp: Date.now(),
|
|
79
|
+
} as ImapEvent;
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
SYNC_MESSAGES_PENDING,
|
|
95
|
-
);
|
|
96
|
-
const acquired = await mailboxLock.tryAcquireLock(
|
|
97
|
-
mailboxId,
|
|
98
|
-
SYNC_MESSAGES_PENDING,
|
|
99
|
-
accountId,
|
|
100
|
-
eventId,
|
|
101
|
-
SYNC_MESSAGES_PENDING_TTL_SECONDS,
|
|
102
|
-
);
|
|
103
|
-
if (!acquired) return;
|
|
104
|
-
if (previous && previous.ttl < Math.floor(Date.now() / 1000)) {
|
|
105
|
-
recordImapFailure("SYNC_PENDING_EXPIRED", "other");
|
|
106
|
-
}
|
|
81
|
+
const send = () => senderFor(fullEvent.type, queues)(fullEvent);
|
|
82
|
+
if (fullEvent.type !== "SYNC_MESSAGES") {
|
|
83
|
+
await send();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
107
86
|
|
|
108
|
-
|
|
109
|
-
|
|
87
|
+
const { mailboxLock } = await getClient();
|
|
88
|
+
const { accountId, mailboxId, eventId } = fullEvent;
|
|
89
|
+
const previous = await mailboxLock.get(
|
|
110
90
|
accountId,
|
|
111
91
|
mailboxId,
|
|
112
92
|
SYNC_MESSAGES_PENDING,
|
|
93
|
+
);
|
|
94
|
+
const acquired = await mailboxLock.tryAcquireLock(
|
|
95
|
+
mailboxId,
|
|
96
|
+
SYNC_MESSAGES_PENDING,
|
|
97
|
+
accountId,
|
|
113
98
|
eventId,
|
|
99
|
+
SYNC_MESSAGES_PENDING_TTL_SECONDS,
|
|
114
100
|
);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
if (!acquired) return;
|
|
102
|
+
if (previous && previous.ttl < Math.floor(Date.now() / 1000)) {
|
|
103
|
+
recordImapFailure("SYNC_PENDING_EXPIRED", "other");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await send().catch(async (error: unknown) => {
|
|
107
|
+
await mailboxLock.releaseLock(
|
|
108
|
+
accountId,
|
|
109
|
+
mailboxId,
|
|
110
|
+
SYNC_MESSAGES_PENDING,
|
|
111
|
+
eventId,
|
|
112
|
+
);
|
|
113
|
+
throw error;
|
|
114
|
+
});
|
|
115
|
+
};
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { noopLogger } from "@remit/logger";
|
|
13
13
|
import { mockClient } from "aws-sdk-client-mock";
|
|
14
14
|
import type { DeleteAccountObjectsEvent } from "../events.js";
|
|
15
|
-
import {
|
|
15
|
+
import { workerQueuesOn } from "../test-helpers/worker-queues.js";
|
|
16
16
|
import { handleDeleteAccountObjects } from "./delete-account-objects.js";
|
|
17
17
|
|
|
18
18
|
const s3Mock = mockClient(S3Client);
|
|
@@ -26,14 +26,6 @@ const event: DeleteAccountObjectsEvent = {
|
|
|
26
26
|
describe("handleDeleteAccountObjects", () => {
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
queue = recordingQueue();
|
|
29
|
-
setWorkerQueues({
|
|
30
|
-
mailboxes: queue,
|
|
31
|
-
messages: queue,
|
|
32
|
-
flags: queue,
|
|
33
|
-
body: queue,
|
|
34
|
-
mailboxManagement: queue,
|
|
35
|
-
messageManagement: queue,
|
|
36
|
-
});
|
|
37
29
|
});
|
|
38
30
|
|
|
39
31
|
afterEach(() => {
|
|
@@ -50,7 +42,7 @@ describe("handleDeleteAccountObjects", () => {
|
|
|
50
42
|
});
|
|
51
43
|
s3Mock.on(DeleteObjectsCommand).resolves({});
|
|
52
44
|
|
|
53
|
-
await handleDeleteAccountObjects(event, noopLogger);
|
|
45
|
+
await handleDeleteAccountObjects(event, noopLogger, workerQueuesOn(queue));
|
|
54
46
|
|
|
55
47
|
const listCall = s3Mock.commandCalls(ListObjectsV2Command)[0];
|
|
56
48
|
assert.equal(listCall.args[0].input.Prefix, "accounts/cfg-1/");
|
|
@@ -77,7 +69,7 @@ describe("handleDeleteAccountObjects", () => {
|
|
|
77
69
|
});
|
|
78
70
|
s3Mock.on(DeleteObjectsCommand).resolves({});
|
|
79
71
|
|
|
80
|
-
await handleDeleteAccountObjects(event, noopLogger);
|
|
72
|
+
await handleDeleteAccountObjects(event, noopLogger, workerQueuesOn(queue));
|
|
81
73
|
|
|
82
74
|
const listCalls = s3Mock.commandCalls(ListObjectsV2Command);
|
|
83
75
|
assert.equal(listCalls.length, 2);
|
|
@@ -88,7 +80,7 @@ describe("handleDeleteAccountObjects", () => {
|
|
|
88
80
|
it("skips the delete call when a page holds no keys", async () => {
|
|
89
81
|
s3Mock.on(ListObjectsV2Command).resolves({ IsTruncated: false });
|
|
90
82
|
|
|
91
|
-
await handleDeleteAccountObjects(event, noopLogger);
|
|
83
|
+
await handleDeleteAccountObjects(event, noopLogger, workerQueuesOn(queue));
|
|
92
84
|
|
|
93
85
|
assert.equal(s3Mock.commandCalls(DeleteObjectsCommand).length, 0);
|
|
94
86
|
});
|
|
@@ -99,6 +91,7 @@ describe("handleDeleteAccountObjects", () => {
|
|
|
99
91
|
await handleDeleteAccountObjects(
|
|
100
92
|
{ ...event, continuationToken: "tok-mid" },
|
|
101
93
|
noopLogger,
|
|
94
|
+
workerQueuesOn(queue),
|
|
102
95
|
() => 5_000,
|
|
103
96
|
);
|
|
104
97
|
|
|
@@ -120,7 +113,12 @@ describe("handleDeleteAccountObjects", () => {
|
|
|
120
113
|
});
|
|
121
114
|
s3Mock.on(DeleteObjectsCommand).resolves({});
|
|
122
115
|
|
|
123
|
-
await handleDeleteAccountObjects(
|
|
116
|
+
await handleDeleteAccountObjects(
|
|
117
|
+
event,
|
|
118
|
+
noopLogger,
|
|
119
|
+
workerQueuesOn(queue),
|
|
120
|
+
() => 120_000,
|
|
121
|
+
);
|
|
124
122
|
|
|
125
123
|
assert.equal(s3Mock.commandCalls(ListObjectsV2Command).length, 1);
|
|
126
124
|
assert.equal(queue.sent.length, 0);
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import type { Logger } from "@remit/logger";
|
|
7
7
|
import { env } from "expect-env";
|
|
8
8
|
import type { DeleteAccountObjectsEvent } from "../events.js";
|
|
9
|
-
import {
|
|
9
|
+
import type { WorkerQueues } from "../queues.js";
|
|
10
10
|
|
|
11
11
|
const s3 = new S3Client({});
|
|
12
12
|
|
|
@@ -16,6 +16,7 @@ const MIN_REMAINING_MS = 30_000;
|
|
|
16
16
|
export const handleDeleteAccountObjects = async (
|
|
17
17
|
event: DeleteAccountObjectsEvent,
|
|
18
18
|
log: Logger,
|
|
19
|
+
queues: WorkerQueues,
|
|
19
20
|
getRemainingTimeMs?: () => number,
|
|
20
21
|
): Promise<void> => {
|
|
21
22
|
const BUCKET_NAME = env.S3_BUCKET_NAME;
|
|
@@ -37,7 +38,7 @@ export const handleDeleteAccountObjects = async (
|
|
|
37
38
|
{ accountConfigId, totalDeleted, continuationToken: currentToken },
|
|
38
39
|
"Near timeout, re-enqueuing with continuation token",
|
|
39
40
|
);
|
|
40
|
-
await reenqueue(accountConfigId, currentToken);
|
|
41
|
+
await reenqueue(queues, accountConfigId, currentToken);
|
|
41
42
|
return;
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -81,6 +82,7 @@ export const handleDeleteAccountObjects = async (
|
|
|
81
82
|
};
|
|
82
83
|
|
|
83
84
|
const reenqueue = async (
|
|
85
|
+
queues: WorkerQueues,
|
|
84
86
|
accountConfigId: string,
|
|
85
87
|
continuationToken: string | undefined,
|
|
86
88
|
): Promise<void> => {
|
|
@@ -90,5 +92,5 @@ const reenqueue = async (
|
|
|
90
92
|
continuationToken,
|
|
91
93
|
};
|
|
92
94
|
|
|
93
|
-
await
|
|
95
|
+
await queues.messageManagement.send({ body: event });
|
|
94
96
|
};
|
|
@@ -73,7 +73,7 @@ const arrange = async (
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
const run = (a: Arranged, receiveCount = 1) =>
|
|
76
|
-
handleMessageCopy(a.event, a.w.log, receiveCount, a.w.scope);
|
|
76
|
+
handleMessageCopy(a.event, a.w.log, a.w.queues, receiveCount, a.w.scope);
|
|
77
77
|
|
|
78
78
|
describe("a message copy", () => {
|
|
79
79
|
it("lands at the destination, leaves the source alone and settles the copy row", async () => {
|
|
@@ -264,7 +264,7 @@ describe("a fresh copy acted on before the server confirms it", () => {
|
|
|
264
264
|
assert.ok(event, "the move enqueued a MESSAGE_MOVE");
|
|
265
265
|
assert.equal(event.uid, copyUid);
|
|
266
266
|
|
|
267
|
-
await handleMessageMove(event, a.w.log, 1, a.w.scope);
|
|
267
|
+
await handleMessageMove(event, a.w.log, a.w.queues, 1, a.w.scope);
|
|
268
268
|
|
|
269
269
|
assert.deepEqual(a.w.imap.uids("Projects", a.messageIdHeader), []);
|
|
270
270
|
assert.equal(a.w.imap.uids("INBOX", a.messageIdHeader).length, 2);
|
|
@@ -288,7 +288,7 @@ describe("a fresh copy acted on before the server confirms it", () => {
|
|
|
288
288
|
const event = a.w.queued().find(isDelete);
|
|
289
289
|
assert.ok(event, "the delete enqueued a MESSAGE_DELETE");
|
|
290
290
|
|
|
291
|
-
await handleMessageDelete(event, a.w.log, 1, a.w.scope);
|
|
291
|
+
await handleMessageDelete(event, a.w.log, a.w.queues, 1, a.w.scope);
|
|
292
292
|
|
|
293
293
|
assert.deepEqual(a.w.imap.uids("Projects", a.messageIdHeader), []);
|
|
294
294
|
assert.deepEqual(a.w.imap.uids("INBOX"), [a.uid]);
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
import { attemptBudget } from "@remit/sqs-client/attempt-budget";
|
|
15
15
|
import { isAccountDeleted } from "../account-check.js";
|
|
16
16
|
import { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
17
|
-
import {
|
|
17
|
+
import { createEmitEvent } from "../emit.js";
|
|
18
18
|
import type { MessageCopyEvent } from "../events.js";
|
|
19
19
|
import { findMailboxRow } from "../mailbox-row.js";
|
|
20
|
+
import type { WorkerQueues } from "../queues.js";
|
|
20
21
|
import { withOAuthLifecycle } from "../with-oauth-lifecycle.js";
|
|
21
22
|
import { buildLifecycleDeps } from "../with-oauth-lifecycle-deps.js";
|
|
22
23
|
import {
|
|
@@ -68,9 +69,11 @@ export type CopyProbe =
|
|
|
68
69
|
export const handleMessageCopy = async (
|
|
69
70
|
event: MessageCopyEvent,
|
|
70
71
|
log: Logger,
|
|
72
|
+
queues: WorkerQueues,
|
|
71
73
|
receiveCount = 1,
|
|
72
74
|
createConnectionScope = createConnectionScopeWithCredentials,
|
|
73
75
|
): Promise<void> => {
|
|
76
|
+
const emit = createEmitEvent(queues);
|
|
74
77
|
const {
|
|
75
78
|
account: accountService,
|
|
76
79
|
message: messageService,
|
|
@@ -383,7 +386,7 @@ export const handleMessageCopy = async (
|
|
|
383
386
|
}
|
|
384
387
|
}
|
|
385
388
|
|
|
386
|
-
await emitMoveResync(
|
|
389
|
+
await emitMoveResync(emit, {
|
|
387
390
|
accountId,
|
|
388
391
|
sourceMailboxId,
|
|
389
392
|
destinationMailboxId,
|
|
@@ -72,7 +72,7 @@ const arrange = async (
|
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
const run = (a: Arranged, receiveCount = 1, event = a.event) =>
|
|
75
|
-
handleMessageDelete(event, a.w.log, receiveCount, a.w.scope);
|
|
75
|
+
handleMessageDelete(event, a.w.log, a.w.queues, receiveCount, a.w.scope);
|
|
76
76
|
|
|
77
77
|
const resyncs = (w: World): unknown[] =>
|
|
78
78
|
w
|
|
@@ -24,9 +24,10 @@ import {
|
|
|
24
24
|
import { attemptBudget } from "@remit/sqs-client/attempt-budget";
|
|
25
25
|
import { isAccountDeleted } from "../account-check.js";
|
|
26
26
|
import { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
27
|
-
import {
|
|
27
|
+
import { createEmitEvent } from "../emit.js";
|
|
28
28
|
import type { MessageDeleteEvent } from "../events.js";
|
|
29
29
|
import { findMailboxRow } from "../mailbox-row.js";
|
|
30
|
+
import type { WorkerQueues } from "../queues.js";
|
|
30
31
|
import { withOAuthLifecycle } from "../with-oauth-lifecycle.js";
|
|
31
32
|
import { buildLifecycleDeps } from "../with-oauth-lifecycle-deps.js";
|
|
32
33
|
import { resolveExhaustedMessageDeleteFailure } from "./message-delete-terminal.js";
|
|
@@ -153,9 +154,11 @@ const confirmTrashMoveUid = async (
|
|
|
153
154
|
export const handleMessageDelete = async (
|
|
154
155
|
event: MessageDeleteEvent,
|
|
155
156
|
log: Logger,
|
|
157
|
+
queues: WorkerQueues,
|
|
156
158
|
receiveCount = 1,
|
|
157
159
|
createConnectionScope = createConnectionScopeWithCredentials,
|
|
158
160
|
): Promise<void> => {
|
|
161
|
+
const emit = createEmitEvent(queues);
|
|
159
162
|
const {
|
|
160
163
|
account: accountService,
|
|
161
164
|
message: messageService,
|
|
@@ -246,14 +249,14 @@ export const handleMessageDelete = async (
|
|
|
246
249
|
*/
|
|
247
250
|
const emitSettleResync = async (): Promise<void> => {
|
|
248
251
|
if (destinationMailboxId) {
|
|
249
|
-
await emitMoveResync(
|
|
252
|
+
await emitMoveResync(emit, {
|
|
250
253
|
accountId,
|
|
251
254
|
sourceMailboxId: mailboxId,
|
|
252
255
|
destinationMailboxId,
|
|
253
256
|
});
|
|
254
257
|
return;
|
|
255
258
|
}
|
|
256
|
-
await emitMailboxResync(
|
|
259
|
+
await emitMailboxResync(emit, { accountId, mailboxId });
|
|
257
260
|
};
|
|
258
261
|
|
|
259
262
|
/**
|
|
@@ -623,7 +626,7 @@ export const handleMessageDelete = async (
|
|
|
623
626
|
"Refused to delete: the destination mailbox no longer exists (deleted)",
|
|
624
627
|
"message_delete_destination_gone",
|
|
625
628
|
);
|
|
626
|
-
await emitMailboxResync(
|
|
629
|
+
await emitMailboxResync(emit, { accountId, mailboxId });
|
|
627
630
|
return;
|
|
628
631
|
}
|
|
629
632
|
|
|
@@ -68,7 +68,7 @@ const arrange = async (
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
const run = (a: Arranged, receiveCount = 1) =>
|
|
71
|
-
handleMessageMove(a.event, a.w.log, receiveCount, a.w.scope);
|
|
71
|
+
handleMessageMove(a.event, a.w.log, a.w.queues, receiveCount, a.w.scope);
|
|
72
72
|
|
|
73
73
|
const resyncs = (w: World): unknown[] =>
|
|
74
74
|
w
|
|
@@ -17,9 +17,10 @@ import {
|
|
|
17
17
|
import { attemptBudget } from "@remit/sqs-client/attempt-budget";
|
|
18
18
|
import { isAccountDeleted } from "../account-check.js";
|
|
19
19
|
import { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
20
|
-
import {
|
|
20
|
+
import { createEmitEvent } from "../emit.js";
|
|
21
21
|
import type { MessageMoveEvent, SyncMessagesEvent } from "../events.js";
|
|
22
22
|
import { findMailboxRow } from "../mailbox-row.js";
|
|
23
|
+
import type { WorkerQueues } from "../queues.js";
|
|
23
24
|
import { withOAuthLifecycle } from "../with-oauth-lifecycle.js";
|
|
24
25
|
import { buildLifecycleDeps } from "../with-oauth-lifecycle-deps.js";
|
|
25
26
|
import { resolveExhaustedMessageMoveFailure } from "./message-move-terminal.js";
|
|
@@ -244,9 +245,11 @@ export const buildThreadMessageMoveUpdate = (
|
|
|
244
245
|
export const handleMessageMove = async (
|
|
245
246
|
event: MessageMoveEvent,
|
|
246
247
|
log: Logger,
|
|
248
|
+
queues: WorkerQueues,
|
|
247
249
|
receiveCount = 1,
|
|
248
250
|
createConnectionScope = createConnectionScopeWithCredentials,
|
|
249
251
|
): Promise<void> => {
|
|
252
|
+
const emit = createEmitEvent(queues);
|
|
250
253
|
const {
|
|
251
254
|
account: accountService,
|
|
252
255
|
message: messageService,
|
|
@@ -343,7 +346,7 @@ export const handleMessageMove = async (
|
|
|
343
346
|
},
|
|
344
347
|
"MESSAGE_MOVE not pushed: source mailbox no longer exists (deleted); stale row reconciled",
|
|
345
348
|
);
|
|
346
|
-
await emitMailboxResync(
|
|
349
|
+
await emitMailboxResync(emit, {
|
|
347
350
|
accountId,
|
|
348
351
|
mailboxId: destinationMailboxId,
|
|
349
352
|
});
|
|
@@ -449,7 +452,7 @@ export const handleMessageMove = async (
|
|
|
449
452
|
{ metric: "message_move_destination_gone" },
|
|
450
453
|
"destination mailbox no longer exists (deleted)",
|
|
451
454
|
);
|
|
452
|
-
await emitMailboxResync(
|
|
455
|
+
await emitMailboxResync(emit, {
|
|
453
456
|
accountId,
|
|
454
457
|
mailboxId: sourceMailboxId,
|
|
455
458
|
});
|
|
@@ -543,7 +546,7 @@ export const handleMessageMove = async (
|
|
|
543
546
|
);
|
|
544
547
|
}
|
|
545
548
|
|
|
546
|
-
await emitMoveResync(
|
|
549
|
+
await emitMoveResync(emit, {
|
|
547
550
|
accountId,
|
|
548
551
|
sourceMailboxId,
|
|
549
552
|
destinationMailboxId,
|
|
@@ -624,7 +627,7 @@ export const handleMessageMove = async (
|
|
|
624
627
|
await settleMoved(newUid);
|
|
625
628
|
},
|
|
626
629
|
() =>
|
|
627
|
-
emitMoveResync(
|
|
630
|
+
emitMoveResync(emit, {
|
|
628
631
|
accountId,
|
|
629
632
|
sourceMailboxId,
|
|
630
633
|
destinationMailboxId,
|
|
@@ -739,7 +742,7 @@ export const handleMessageMove = async (
|
|
|
739
742
|
// re-projects it with the server's own UID. BROKEN, the row has just
|
|
740
743
|
// been put back at the source the server confirmed, and the resync
|
|
741
744
|
// is what carries any drift either folder has picked up since.
|
|
742
|
-
await emitMoveResync(
|
|
745
|
+
await emitMoveResync(emit, {
|
|
743
746
|
accountId,
|
|
744
747
|
sourceMailboxId,
|
|
745
748
|
destinationMailboxId,
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
import { attemptBudget } from "@remit/sqs-client/attempt-budget";
|
|
15
15
|
import { isAccountDeleted } from "../account-check.js";
|
|
16
16
|
import { createConnectionScopeWithCredentials } from "../connection-scope.js";
|
|
17
|
-
import {
|
|
17
|
+
import { createEmitEvent } from "../emit.js";
|
|
18
18
|
import type { PlacementMovePushEvent } from "../events.js";
|
|
19
19
|
import { findMailboxRow } from "../mailbox-row.js";
|
|
20
|
+
import type { WorkerQueues } from "../queues.js";
|
|
20
21
|
import { withOAuthLifecycle } from "../with-oauth-lifecycle.js";
|
|
21
22
|
import { buildLifecycleDeps } from "../with-oauth-lifecycle-deps.js";
|
|
22
23
|
import {
|
|
@@ -152,9 +153,11 @@ export const attemptMove = async (
|
|
|
152
153
|
export const handlePlacementMovePush = async (
|
|
153
154
|
event: PlacementMovePushEvent,
|
|
154
155
|
log: Logger,
|
|
156
|
+
queues: WorkerQueues,
|
|
155
157
|
receiveCount = 1,
|
|
156
158
|
createConnectionScope = createConnectionScopeWithCredentials,
|
|
157
159
|
): Promise<void> => {
|
|
160
|
+
const emit = createEmitEvent(queues);
|
|
158
161
|
const {
|
|
159
162
|
account: accountService,
|
|
160
163
|
mailbox: mailboxService,
|
|
@@ -238,7 +241,7 @@ export const handlePlacementMovePush = async (
|
|
|
238
241
|
"Placement move not pushed: its source folder is gone; the stale row was reconciled",
|
|
239
242
|
);
|
|
240
243
|
if (destination.kind === "present") {
|
|
241
|
-
await emitMailboxResync(
|
|
244
|
+
await emitMailboxResync(emit, {
|
|
242
245
|
accountId,
|
|
243
246
|
mailboxId: marker.destinationMailboxId,
|
|
244
247
|
});
|
|
@@ -261,7 +264,7 @@ export const handlePlacementMovePush = async (
|
|
|
261
264
|
context,
|
|
262
265
|
"Placement move not pushed: its destination folder is gone; the row was handed back to its source",
|
|
263
266
|
);
|
|
264
|
-
await emitMailboxResync(
|
|
267
|
+
await emitMailboxResync(emit, {
|
|
265
268
|
accountId,
|
|
266
269
|
mailboxId: marker.sourceMailboxId,
|
|
267
270
|
});
|
|
@@ -354,7 +357,7 @@ export const handlePlacementMovePush = async (
|
|
|
354
357
|
},
|
|
355
358
|
"Message no longer at its pending-move source (external delete or moved away); marker dropped, stale row reconciled",
|
|
356
359
|
);
|
|
357
|
-
await emitMoveResync(
|
|
360
|
+
await emitMoveResync(emit, {
|
|
358
361
|
accountId,
|
|
359
362
|
sourceMailboxId: marker.sourceMailboxId,
|
|
360
363
|
destinationMailboxId: marker.destinationMailboxId,
|
|
@@ -404,7 +407,7 @@ export const handlePlacementMovePush = async (
|
|
|
404
407
|
"Placement move confirmed on IMAP; marker cleared",
|
|
405
408
|
);
|
|
406
409
|
|
|
407
|
-
await emitMoveResync(
|
|
410
|
+
await emitMoveResync(emit, {
|
|
408
411
|
accountId,
|
|
409
412
|
sourceMailboxId: marker.sourceMailboxId,
|
|
410
413
|
destinationMailboxId: marker.destinationMailboxId,
|
|
@@ -452,7 +455,7 @@ export const handlePlacementMovePush = async (
|
|
|
452
455
|
);
|
|
453
456
|
|
|
454
457
|
if (outcome === "reconciled") {
|
|
455
|
-
await emitMoveResync(
|
|
458
|
+
await emitMoveResync(emit, {
|
|
456
459
|
accountId,
|
|
457
460
|
sourceMailboxId: marker.sourceMailboxId,
|
|
458
461
|
destinationMailboxId: marker.destinationMailboxId,
|
|
@@ -92,7 +92,8 @@ const pushMove = (
|
|
|
92
92
|
a: Arranged,
|
|
93
93
|
event: PlacementMovePushEvent,
|
|
94
94
|
receiveCount = 1,
|
|
95
|
-
) =>
|
|
95
|
+
) =>
|
|
96
|
+
handlePlacementMovePush(event, a.w.log, a.w.queues, receiveCount, a.w.scope);
|
|
96
97
|
|
|
97
98
|
const seenOnServer = (a: Arranged, path: string): boolean[] =>
|
|
98
99
|
a.w.imap
|