@remit/imap-worker 0.0.90 → 0.0.92
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 +22 -0
- package/dist/scheduler/calendar-subscriptions.js +35 -0
- package/dist/scheduler/config.d.ts +29 -0
- package/{src/scheduler/config.ts → dist/scheduler/config.js} +9 -28
- 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/dist/scheduler/handler.js +50 -0
- 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 +66 -0
- package/dist/scheduler/run-tick.js +164 -0
- package/dist/scheduler/runner.d.ts +2 -0
- package/dist/scheduler/runner.js +102 -0
- package/dist/scheduler/stranded-placement-redrive.d.ts +16 -0
- package/dist/scheduler/stranded-placement-redrive.js +172 -0
- package/dist/scheduler/tick.d.ts +5 -0
- package/dist/scheduler/tick.js +5 -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 +79 -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 -218
- 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 -145
- package/src/emit.ts +0 -115
- 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 -126
- package/src/handlers/delete-account-objects.ts +0 -96
- 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 -569
- 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 -888
- 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 -755
- package/src/handlers/placement-move-push.test.ts +0 -137
- package/src/handlers/placement-move-push.ts +0 -478
- package/src/handlers/placement-push.sqlite.test.ts +0 -299
- 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 -425
- 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 -366
- package/src/handlers/sync-messages.sqlite.test.ts +0 -195
- package/src/handlers/sync-messages.ts +0 -537
- package/src/index.test.ts +0 -22
- package/src/index.ts +0 -82
- package/src/mail-sync-selection.sqlite.test.ts +0 -227
- package/src/mailbox-row.ts +0 -20
- package/src/pending-sync.sqlite.test.ts +0 -165
- package/src/poller.ts +0 -55
- package/src/processor.ts +0 -62
- package/src/queues.ts +0 -40
- package/src/scheduler/attachment-sweep.sqlite.test.ts +0 -74
- 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/handler.ts +0 -69
- 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/runner.ts +0 -125
- package/src/scheduler/stranded-placement-redrive.ts +0 -265
- package/src/stranded-placement.sqlite.test.ts +0 -799
- package/src/test-helpers/test-world.ts +0 -425
- package/src/test-helpers/worker-queues.ts +0 -11
- 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
package/src/scheduler/runner.ts
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
-
import { finishHalfDeletedAccounts } from "@remit/backend/account-deletion";
|
|
4
|
-
import { getClient } from "@remit/backend/client";
|
|
5
|
-
import { createLogger } from "@remit/logger";
|
|
6
|
-
import { installNodeLogger } from "@remit/logger-lambda/node";
|
|
7
|
-
import { backfillSearchText } from "@remit/mailbox-service";
|
|
8
|
-
import { clearHeartbeats, createHeartbeat } from "@remit/sqs-client/heartbeat";
|
|
9
|
-
|
|
10
|
-
import { createEmitEvent } from "../emit.js";
|
|
11
|
-
import { sqsWorkerQueues, workerQueueUrlsFromEnv } from "../queues.js";
|
|
12
|
-
import { buildAttachmentSweep } from "./attachment-sweep.js";
|
|
13
|
-
import { buildCalendarSubscriptionRefresh } from "./calendar-subscriptions.js";
|
|
14
|
-
import { getOfflineIntervalMs, getTickIntervalMs } from "./config.js";
|
|
15
|
-
import { runSchedulerLoop } from "./loop.js";
|
|
16
|
-
import { runSchedulerTick } from "./run-tick.js";
|
|
17
|
-
import { redriveStrandedPlacements } from "./stranded-placement-redrive.js";
|
|
18
|
-
|
|
19
|
-
installNodeLogger();
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* The scheduled-sync runner for every deployment that has no EventBridge: the
|
|
23
|
-
* self-host stack's `scheduler` service and the local dev compose stack. A
|
|
24
|
-
* managed deployment fires `runSchedulerTick` off an EventBridge schedule (see
|
|
25
|
-
* handler.ts); this process ticks the same function on a plain loop at the same
|
|
26
|
-
* `MAILBOX_SYNC_TICK_INTERVAL_SECONDS` cadence, so there is one scheduling
|
|
27
|
-
* implementation and one set of knobs.
|
|
28
|
-
*
|
|
29
|
-
* A tick failure crashes the process loudly rather than being swallowed —
|
|
30
|
-
* compose's `restart: unless-stopped` brings it back for the next tick.
|
|
31
|
-
*
|
|
32
|
-
* Liveness is a heartbeat file, the same mechanism as a worker's poll loop (D1
|
|
33
|
-
* of docs/design/standalone-observability.md); loop.ts carries where in the loop
|
|
34
|
-
* it is written and why. Clearing the previous generation's file here is what
|
|
35
|
-
* extends that answer across a restart. A completed round is still not the same
|
|
36
|
-
* as mail arriving: the accounts that came due are enqueued for workers to
|
|
37
|
-
* fetch, and whether that fetch lands is what
|
|
38
|
-
* `remit_account_sync_age_seconds` measures.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
const log = createLogger();
|
|
42
|
-
|
|
43
|
-
const queues = sqsWorkerQueues(workerQueueUrlsFromEnv());
|
|
44
|
-
|
|
45
|
-
const tickIntervalMs = getTickIntervalMs();
|
|
46
|
-
const offlineIntervalMs = getOfflineIntervalMs();
|
|
47
|
-
|
|
48
|
-
// A persistent failure (e.g. Postgres not up yet at container boot) throws
|
|
49
|
-
// before the loop ever reaches its own `delay`, so `restart: unless-stopped`
|
|
50
|
-
// would otherwise respawn the process immediately — a tight, log-flooding
|
|
51
|
-
// crash loop (review #1250). This fixed pause before exiting is not retry
|
|
52
|
-
// logic (there is nothing to retry here; the container restart IS the
|
|
53
|
-
// retry) — it only paces how fast that restart can happen.
|
|
54
|
-
const CRASH_BACKOFF_MS = 5_000;
|
|
55
|
-
|
|
56
|
-
log.info(
|
|
57
|
-
{ tickIntervalMs, offlineIntervalMs },
|
|
58
|
-
"Scheduled-sync runner started",
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const runLoop = async (): Promise<void> => {
|
|
62
|
-
// Neither call on the monitoring file may take the scheduler down with it. An
|
|
63
|
-
// unreadable or full /data/heartbeat is a reason to keep enqueuing mail, not
|
|
64
|
-
// to stop. A clear that fails leaves the previous generation's file, which
|
|
65
|
-
// ages out at the same threshold; a write that fails is the missed beat that
|
|
66
|
-
// is itself the signal. Two messages because they are different problems: one
|
|
67
|
-
// is a volume this container could not read at boot, the other a write it
|
|
68
|
-
// could not make this round.
|
|
69
|
-
const onClearError = (error: unknown): void => {
|
|
70
|
-
log.error({ error }, "Scheduled-sync heartbeat clear failed");
|
|
71
|
-
};
|
|
72
|
-
const onBeatError = (error: unknown): void => {
|
|
73
|
-
log.error({ error }, "Scheduled-sync heartbeat write failed");
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
await clearHeartbeats().catch(onClearError);
|
|
77
|
-
const client = await getClient();
|
|
78
|
-
const { account } = client;
|
|
79
|
-
await finishHalfDeletedAccounts({
|
|
80
|
-
account,
|
|
81
|
-
configImport: client.configImport,
|
|
82
|
-
queue: client.queues.accountFanout,
|
|
83
|
-
log,
|
|
84
|
-
});
|
|
85
|
-
const searchTextBackfill = backfillSearchText(
|
|
86
|
-
{ messageService: client.message, storageService: client.storage },
|
|
87
|
-
log,
|
|
88
|
-
).then((result) => {
|
|
89
|
-
log.debug({ ...result }, "Search text backfill finished");
|
|
90
|
-
});
|
|
91
|
-
const schedulerLoop = runSchedulerLoop({
|
|
92
|
-
tick: runSchedulerTick,
|
|
93
|
-
tickDeps: {
|
|
94
|
-
accountService: account,
|
|
95
|
-
queue: queues.mailboxes,
|
|
96
|
-
log,
|
|
97
|
-
tickIntervalMs,
|
|
98
|
-
offlineIntervalMs,
|
|
99
|
-
sweepAttachments: buildAttachmentSweep(client, log),
|
|
100
|
-
refreshCalendarSubscriptions: buildCalendarSubscriptionRefresh(
|
|
101
|
-
client,
|
|
102
|
-
log,
|
|
103
|
-
),
|
|
104
|
-
redriveStrandedPlacements: (stranded, now) =>
|
|
105
|
-
redriveStrandedPlacements(
|
|
106
|
-
{ client, emit: createEmitEvent(queues), log },
|
|
107
|
-
stranded,
|
|
108
|
-
now,
|
|
109
|
-
),
|
|
110
|
-
},
|
|
111
|
-
heartbeat: createHeartbeat("tick"),
|
|
112
|
-
onHeartbeatError: onBeatError,
|
|
113
|
-
tickIntervalMs,
|
|
114
|
-
});
|
|
115
|
-
await Promise.all([searchTextBackfill, schedulerLoop]);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
runLoop()
|
|
119
|
-
.catch(async (error) => {
|
|
120
|
-
log.error({ error }, "Scheduled-sync tick failed");
|
|
121
|
-
await delay(CRASH_BACKOFF_MS);
|
|
122
|
-
})
|
|
123
|
-
.finally(() => {
|
|
124
|
-
process.exit(1);
|
|
125
|
-
});
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
import type { RemitClient } from "@remit/backend/client";
|
|
2
|
-
import type { AccountItem, MessageItem } from "@remit/data-ports";
|
|
3
|
-
import { trashMailboxAt } from "@remit/data-ports/folder-role";
|
|
4
|
-
import {
|
|
5
|
-
deriveCopyMessageId,
|
|
6
|
-
deriveMessageId,
|
|
7
|
-
isValidMessageId,
|
|
8
|
-
} from "@remit/data-ports/id";
|
|
9
|
-
import { PATHLESS_MUTATION_EVENT_SCHEMA_VERSION } from "@remit/data-ports/mutation-events";
|
|
10
|
-
import { MessageStatus, MessageSyncStatus } from "@remit/domain-enums";
|
|
11
|
-
import type { Logger } from "@remit/logger";
|
|
12
|
-
import { isCursorRebuildNeeded } from "@remit/mailbox-service";
|
|
13
|
-
import type {
|
|
14
|
-
MessageCopyEventV3,
|
|
15
|
-
MessageDeleteEventV3,
|
|
16
|
-
MessageMoveEventV3,
|
|
17
|
-
PlacementMovePushEvent,
|
|
18
|
-
} from "../events.js";
|
|
19
|
-
|
|
20
|
-
/** Well past three attempts on a 60 s visibility timeout. */
|
|
21
|
-
export const STRANDED_PLACEMENT_AFTER_MILLIS = 60 * 60 * 1000;
|
|
22
|
-
|
|
23
|
-
type Unstamped<T> = Omit<T, "eventId" | "timestamp">;
|
|
24
|
-
|
|
25
|
-
export type StrandedPlacementEvent =
|
|
26
|
-
| Unstamped<PlacementMovePushEvent>
|
|
27
|
-
| Unstamped<MessageDeleteEventV3>
|
|
28
|
-
| Unstamped<MessageMoveEventV3>
|
|
29
|
-
| Unstamped<MessageCopyEventV3>;
|
|
30
|
-
|
|
31
|
-
export interface StrandedPlacementRedriveDeps {
|
|
32
|
-
client: Pick<
|
|
33
|
-
RemitClient,
|
|
34
|
-
| "mailbox"
|
|
35
|
-
| "mailboxSpecialUse"
|
|
36
|
-
| "message"
|
|
37
|
-
| "placementMove"
|
|
38
|
-
| "threadMessage"
|
|
39
|
-
>;
|
|
40
|
-
emit: (event: StrandedPlacementEvent) => Promise<void>;
|
|
41
|
-
log: Logger;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const isStrandedCopy = (row: MessageItem): boolean =>
|
|
45
|
-
row.status === MessageStatus.moving &&
|
|
46
|
-
row.uid === 0 &&
|
|
47
|
-
typeof row.originalUid !== "number";
|
|
48
|
-
|
|
49
|
-
type CopyRedrive =
|
|
50
|
-
| { kind: "redrive"; event: Unstamped<MessageCopyEventV3> }
|
|
51
|
-
| { kind: "unresolvable"; reason: string }
|
|
52
|
-
| { kind: "wait" };
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* The message a uid-0 copy was made from: its id derives from the Message-ID
|
|
56
|
-
* the copy's listing row carries, and the copy's own id derives from it.
|
|
57
|
-
*/
|
|
58
|
-
const copyRedriveFor = async (
|
|
59
|
-
client: StrandedPlacementRedriveDeps["client"],
|
|
60
|
-
account: AccountItem,
|
|
61
|
-
copy: MessageItem,
|
|
62
|
-
): Promise<CopyRedrive> => {
|
|
63
|
-
const listing = await client.threadMessage.findByMessageId(
|
|
64
|
-
account.accountConfigId,
|
|
65
|
-
copy.messageId,
|
|
66
|
-
);
|
|
67
|
-
if (!listing) return { kind: "unresolvable", reason: "listing row missing" };
|
|
68
|
-
const header = listing.messageIdHeader;
|
|
69
|
-
if (!header || !isValidMessageId(header)) {
|
|
70
|
-
return { kind: "unresolvable", reason: "listing row has no Message-ID" };
|
|
71
|
-
}
|
|
72
|
-
const sourceId = deriveMessageId(account.accountId, header);
|
|
73
|
-
if (deriveCopyMessageId(sourceId, copy.mailboxId) !== copy.messageId) {
|
|
74
|
-
return {
|
|
75
|
-
kind: "unresolvable",
|
|
76
|
-
reason: "copy was not made from the message its Message-ID names",
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
const [source] = await client.message.get([sourceId]);
|
|
80
|
-
if (!source) return { kind: "unresolvable", reason: "source row missing" };
|
|
81
|
-
if (source.status === MessageStatus.deleted) {
|
|
82
|
-
return { kind: "unresolvable", reason: "source row deleted" };
|
|
83
|
-
}
|
|
84
|
-
if (source.status !== MessageStatus.active) return { kind: "wait" };
|
|
85
|
-
return {
|
|
86
|
-
kind: "redrive",
|
|
87
|
-
event: {
|
|
88
|
-
type: "MESSAGE_COPY",
|
|
89
|
-
schemaVersion: PATHLESS_MUTATION_EVENT_SCHEMA_VERSION,
|
|
90
|
-
accountId: account.accountId,
|
|
91
|
-
sourceMessageId: source.messageId,
|
|
92
|
-
newMessageId: copy.messageId,
|
|
93
|
-
sourceMailboxId: source.mailboxId,
|
|
94
|
-
destinationMailboxId: copy.mailboxId,
|
|
95
|
-
uid: source.uid,
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** A copy with no source to copy from has nothing to bind and nothing to hide. */
|
|
101
|
-
const removeUnresolvableCopy = async (
|
|
102
|
-
deps: StrandedPlacementRedriveDeps,
|
|
103
|
-
account: AccountItem,
|
|
104
|
-
row: MessageItem,
|
|
105
|
-
reason: string,
|
|
106
|
-
now: number,
|
|
107
|
-
): Promise<boolean> => {
|
|
108
|
-
const removed = await deps.client.message.deleteIfPlacement(row.messageId, {
|
|
109
|
-
status: MessageStatus.moving,
|
|
110
|
-
mailboxId: row.mailboxId,
|
|
111
|
-
uid: 0,
|
|
112
|
-
});
|
|
113
|
-
if (!removed) return false;
|
|
114
|
-
deps.log.error(
|
|
115
|
-
{
|
|
116
|
-
alert: "stranded_copy_unresolvable",
|
|
117
|
-
accountId: account.accountId,
|
|
118
|
-
messageId: row.messageId,
|
|
119
|
-
mailboxId: row.mailboxId,
|
|
120
|
-
reason,
|
|
121
|
-
untouchedForMs: now - row.updatedAt,
|
|
122
|
-
},
|
|
123
|
-
"A copy stranded at uid 0 has no source to copy from; the copy row was removed",
|
|
124
|
-
);
|
|
125
|
-
return true;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const eventFor = async (
|
|
129
|
-
client: StrandedPlacementRedriveDeps["client"],
|
|
130
|
-
account: AccountItem,
|
|
131
|
-
row: MessageItem,
|
|
132
|
-
trashMailboxId: string | undefined,
|
|
133
|
-
): Promise<StrandedPlacementEvent | undefined> => {
|
|
134
|
-
const { accountId, accountConfigId } = account;
|
|
135
|
-
const { messageId } = row;
|
|
136
|
-
|
|
137
|
-
if (row.status === MessageStatus.deleting) {
|
|
138
|
-
return {
|
|
139
|
-
type: "MESSAGE_DELETE",
|
|
140
|
-
schemaVersion: PATHLESS_MUTATION_EVENT_SCHEMA_VERSION,
|
|
141
|
-
accountId,
|
|
142
|
-
messageId,
|
|
143
|
-
mailboxId: row.mailboxId,
|
|
144
|
-
uid: row.uid,
|
|
145
|
-
operation: "permanent_delete",
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
if (
|
|
149
|
-
typeof row.originalMailboxId !== "string" ||
|
|
150
|
-
typeof row.originalUid !== "number"
|
|
151
|
-
) {
|
|
152
|
-
return undefined;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const marker = await client.placementMove.find(messageId);
|
|
156
|
-
if (marker && marker.destinationMailboxId === row.mailboxId) {
|
|
157
|
-
return {
|
|
158
|
-
type: "PLACEMENT_MOVE_PUSH",
|
|
159
|
-
accountId,
|
|
160
|
-
accountConfigId,
|
|
161
|
-
messageId,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
if (row.mailboxId === trashMailboxId) {
|
|
165
|
-
return {
|
|
166
|
-
type: "MESSAGE_DELETE",
|
|
167
|
-
schemaVersion: PATHLESS_MUTATION_EVENT_SCHEMA_VERSION,
|
|
168
|
-
accountId,
|
|
169
|
-
messageId,
|
|
170
|
-
mailboxId: row.originalMailboxId,
|
|
171
|
-
uid: row.originalUid,
|
|
172
|
-
operation: "move_to_trash",
|
|
173
|
-
destinationMailboxId: row.mailboxId,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
type: "MESSAGE_MOVE",
|
|
178
|
-
schemaVersion: PATHLESS_MUTATION_EVENT_SCHEMA_VERSION,
|
|
179
|
-
accountId,
|
|
180
|
-
messageId,
|
|
181
|
-
sourceMailboxId: row.originalMailboxId,
|
|
182
|
-
destinationMailboxId: row.mailboxId,
|
|
183
|
-
uid: row.originalUid,
|
|
184
|
-
};
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
/** The claim restarts the row's clock; a folder awaiting a cursor rebuild waits until it is normal. */
|
|
188
|
-
export const redriveStrandedPlacements = async (
|
|
189
|
-
deps: StrandedPlacementRedriveDeps,
|
|
190
|
-
account: AccountItem,
|
|
191
|
-
now: number,
|
|
192
|
-
): Promise<number> => {
|
|
193
|
-
const { client, emit, log } = deps;
|
|
194
|
-
const { accountId } = account;
|
|
195
|
-
|
|
196
|
-
const mailboxes = await client.mailbox.listAllByAccount(accountId);
|
|
197
|
-
const paused = new Set(
|
|
198
|
-
mailboxes
|
|
199
|
-
.filter((mailbox) => isCursorRebuildNeeded(mailbox.cursorState))
|
|
200
|
-
.map((mailbox) => mailbox.mailboxId),
|
|
201
|
-
);
|
|
202
|
-
const stranded = (
|
|
203
|
-
await client.message.listStrandedPlacements(
|
|
204
|
-
mailboxes.map((mailbox) => mailbox.mailboxId),
|
|
205
|
-
now - STRANDED_PLACEMENT_AFTER_MILLIS,
|
|
206
|
-
)
|
|
207
|
-
).filter(
|
|
208
|
-
(row) =>
|
|
209
|
-
!paused.has(row.mailboxId) &&
|
|
210
|
-
!(row.originalMailboxId && paused.has(row.originalMailboxId)),
|
|
211
|
-
);
|
|
212
|
-
if (stranded.length === 0) return 0;
|
|
213
|
-
|
|
214
|
-
const trash = trashMailboxAt(
|
|
215
|
-
await client.mailboxSpecialUse.resolveTrashRole(accountId),
|
|
216
|
-
"resolved",
|
|
217
|
-
);
|
|
218
|
-
const trashMailboxId = trash.allowed ? trash.mailbox.mailboxId : undefined;
|
|
219
|
-
|
|
220
|
-
let redriven = 0;
|
|
221
|
-
for (const row of stranded) {
|
|
222
|
-
const copy = isStrandedCopy(row)
|
|
223
|
-
? await copyRedriveFor(client, account, row)
|
|
224
|
-
: undefined;
|
|
225
|
-
if (copy?.kind === "wait") continue;
|
|
226
|
-
if (copy?.kind === "unresolvable") {
|
|
227
|
-
if (await removeUnresolvableCopy(deps, account, row, copy.reason, now)) {
|
|
228
|
-
redriven += 1;
|
|
229
|
-
}
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
const event =
|
|
233
|
-
copy?.kind === "redrive"
|
|
234
|
-
? copy.event
|
|
235
|
-
: await eventFor(client, account, row, trashMailboxId);
|
|
236
|
-
if (!event) continue;
|
|
237
|
-
|
|
238
|
-
// A copy's earlier COPY may have landed with its answer lost, so the
|
|
239
|
-
// re-drive marks it `failed`: the copy handler then asks the destination
|
|
240
|
-
// before it copies again.
|
|
241
|
-
const claimed = await client.message.transitionPlacement(
|
|
242
|
-
row.messageId,
|
|
243
|
-
{ status: row.status, mailboxId: row.mailboxId, uid: row.uid },
|
|
244
|
-
{
|
|
245
|
-
syncStatus: copy ? MessageSyncStatus.failed : MessageSyncStatus.pending,
|
|
246
|
-
},
|
|
247
|
-
);
|
|
248
|
-
if (!claimed) continue;
|
|
249
|
-
|
|
250
|
-
await emit(event);
|
|
251
|
-
redriven += 1;
|
|
252
|
-
log.warn(
|
|
253
|
-
{
|
|
254
|
-
metric: "stranded_placement_redriven",
|
|
255
|
-
accountId,
|
|
256
|
-
messageId: row.messageId,
|
|
257
|
-
event: event.type,
|
|
258
|
-
syncStatus: row.syncStatus,
|
|
259
|
-
untouchedForMs: now - row.updatedAt,
|
|
260
|
-
},
|
|
261
|
-
"A move or delete had no event left to settle it; re-driven",
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
return redriven;
|
|
265
|
-
};
|