@remit/backend 0.0.144 → 0.0.146
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/src/api.d.ts +0 -2
- package/dist/src/api.js +9 -80
- package/dist/src/auth.d.ts +13 -19
- package/dist/src/auth.js +51 -43
- package/dist/src/config/api-config.d.ts +20 -0
- package/dist/src/config/api-config.js +1 -0
- package/dist/src/config/from-env.d.ts +2 -0
- package/dist/src/config/from-env.js +65 -0
- package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
- package/dist/src/config/msoauth-secrets-manager.js +28 -0
- package/dist/src/config/msoauth.d.ts +18 -29
- package/dist/src/config/msoauth.js +4 -28
- package/dist/src/derive/contentSignature.d.ts +3 -4
- package/dist/src/derive/contentSignature.js +4 -13
- package/dist/src/derive/contentUrl.d.ts +0 -13
- package/dist/src/derive/contentUrl.js +0 -19
- package/dist/src/handlers/account-oauth.d.ts +9 -3
- package/dist/src/handlers/account-oauth.js +197 -250
- package/dist/src/handlers/account.d.ts +13 -4
- package/dist/src/handlers/account.js +26 -39
- package/dist/src/handlers/address.d.ts +11 -3
- package/dist/src/handlers/address.js +11 -19
- package/dist/src/handlers/calendar-event.js +18 -26
- package/dist/src/handlers/calendar-feed.d.ts +2 -2
- package/dist/src/handlers/calendar-feed.js +24 -32
- package/dist/src/handlers/calendar-suggestion.js +18 -26
- package/dist/src/handlers/calendar.d.ts +1 -1
- package/dist/src/handlers/calendar.js +15 -22
- package/dist/src/handlers/config.d.ts +16 -6
- package/dist/src/handlers/config.js +23 -35
- package/dist/src/handlers/filter.d.ts +16 -3
- package/dist/src/handlers/filter.js +17 -32
- package/dist/src/handlers/folder-role.js +3 -6
- package/dist/src/handlers/index.d.ts +187 -2
- package/dist/src/handlers/index.js +6 -4
- package/dist/src/handlers/label.js +15 -22
- package/dist/src/handlers/mailbox.d.ts +20 -4
- package/dist/src/handlers/mailbox.js +18 -26
- package/dist/src/handlers/me.d.ts +10 -2
- package/dist/src/handlers/me.js +26 -47
- package/dist/src/handlers/message.d.ts +78 -6
- package/dist/src/handlers/message.js +34 -46
- package/dist/src/handlers/organize.d.ts +10 -4
- package/dist/src/handlers/organize.js +11 -20
- package/dist/src/handlers/outbox-attachment.d.ts +6 -5
- package/dist/src/handlers/outbox-attachment.js +11 -15
- package/dist/src/handlers/outbox.d.ts +25 -4
- package/dist/src/handlers/outbox.js +27 -35
- package/dist/src/handlers/sync.d.ts +19 -6
- package/dist/src/handlers/sync.js +13 -22
- package/dist/src/handlers/system-update.js +14 -27
- package/dist/src/handlers/thread.js +9 -14
- package/dist/src/handlers/unified-threads.js +3 -6
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.js +18 -2
- package/dist/src/jwt-auth.d.ts +9 -12
- package/dist/src/jwt-auth.js +19 -71
- package/dist/src/lambda.d.ts +13 -0
- package/dist/src/lambda.js +33 -0
- package/dist/src/request.d.ts +8 -2
- package/dist/src/request.js +16 -6
- package/dist/src/response.d.ts +20 -3
- package/dist/src/response.js +15 -22
- package/dist/src/serve.d.ts +31 -0
- package/dist/src/serve.js +117 -0
- package/dist/src/service/account-deletion.d.ts +2 -4
- package/dist/src/service/account-deletion.js +3 -9
- package/dist/src/service/compose-sqlite.d.ts +2 -1
- package/dist/src/service/compose-sqlite.js +5 -3
- package/dist/src/service/create-remit-client.d.ts +25 -11
- package/dist/src/service/create-remit-client.js +97 -116
- package/dist/src/service/data-client.js +3 -5
- package/dist/src/service/export-identity.d.ts +2 -1
- package/dist/src/service/export-identity.js +4 -6
- package/dist/src/service/fire-and-forget.js +1 -2
- package/dist/src/service/organize.d.ts +7 -10
- package/dist/src/service/organize.js +10 -27
- package/dist/src/service/queues.d.ts +12 -0
- package/dist/src/service/queues.js +1 -0
- package/dist/src/service/shared-deps.d.ts +6 -0
- package/dist/src/service/shared-deps.js +26 -0
- package/dist/src/service/trigger-sync.d.ts +11 -5
- package/dist/src/service/trigger-sync.js +10 -18
- package/dist/src/types.d.ts +24 -1
- package/package.json +24 -8
- package/dist/src/data-backend.d.ts +0 -12
- package/dist/src/data-backend.js +0 -12
- package/dist/src/request-context.d.ts +0 -9
- package/dist/src/request-context.js +0 -22
- package/dist/src/service/sqs.d.ts +0 -1
- package/dist/src/service/sqs.js +0 -3
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { logger } from "@remit/logger";
|
|
2
|
-
import { BodySyncQueueService, BodySyncService, createConnection, FlagPushService, FlagQueueService, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, PlacementMoveService, SpamReportService, SyncWarningService, } from "@remit/mailbox-service";
|
|
2
|
+
import { BodySyncQueueService, BodySyncService, createConnection, FlagPushService, FlagQueueService, fifoMailboxManagement, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, PlacementMoveService, SpamReportService, SyncWarningService, } from "@remit/mailbox-service";
|
|
3
3
|
import { buildFilterConfig } from "@remit/mailbox-service/filter-config";
|
|
4
|
-
import {
|
|
5
|
-
import { createStorageService } from "@remit/storage-service/s3";
|
|
6
|
-
import { env } from "expect-env";
|
|
4
|
+
import { deserializeEncryptedPayload, } from "@remit/secrets-service";
|
|
7
5
|
const buildConnectionScope = (accountService, secretsService) => async (accountId) => {
|
|
8
6
|
const account = await accountService.get(accountId);
|
|
9
7
|
if (!account.passwordHash) {
|
|
@@ -36,34 +34,6 @@ const buildConnectionScope = (accountService, secretsService) => async (accountI
|
|
|
36
34
|
};
|
|
37
35
|
return { getConnection, disconnect };
|
|
38
36
|
};
|
|
39
|
-
// The body queue lives on the worker infra; the API only knows it locally
|
|
40
|
-
// (dev / e2e set SQS_QUEUE_URL_BODY). When unset the read path still returns a
|
|
41
|
-
// retryable 202 — it just cannot re-arm the cue itself.
|
|
42
|
-
const buildBodySyncQueue = () => {
|
|
43
|
-
const bodyQueueUrl = process.env.SQS_QUEUE_URL_BODY;
|
|
44
|
-
if (!bodyQueueUrl)
|
|
45
|
-
return undefined;
|
|
46
|
-
return new BodySyncQueueService({ sqsQueueUrl: bodyQueueUrl, logger });
|
|
47
|
-
};
|
|
48
|
-
// Everything a RemitClient needs that is chosen from the environment and does
|
|
49
|
-
// not vary by data backend: storage, secrets, the SQS queue URLs, and
|
|
50
|
-
// the optional body-sync queue. Each composition root builds its backend's
|
|
51
|
-
// repositories and merges them with this.
|
|
52
|
-
export const buildSharedDeps = () => {
|
|
53
|
-
const storage = createStorageService();
|
|
54
|
-
const kmsKeyId = process.env.KMS_KEY_ID ?? FAKE_KMS_KEY_ID;
|
|
55
|
-
const dataKeyProvider = createCachedDataKeyProvider(createKmsDataKeyProvider(kmsKeyId));
|
|
56
|
-
const secrets = createSecretsService(dataKeyProvider);
|
|
57
|
-
const sqsQueueUrl = env.SQS_QUEUE_URL;
|
|
58
|
-
const sqsSmtpQueueUrl = process.env.SQS_QUEUE_URL_SMTP ?? sqsQueueUrl;
|
|
59
|
-
return {
|
|
60
|
-
storage,
|
|
61
|
-
secrets,
|
|
62
|
-
sqsQueueUrl,
|
|
63
|
-
sqsSmtpQueueUrl,
|
|
64
|
-
bodySyncQueue: buildBodySyncQueue(),
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
37
|
// The read-path body backfill (describeMessage / getRawMessage →
|
|
68
38
|
// fetchAndGetBody) materializes a body that was never sync-synced, and must run
|
|
69
39
|
// the account's standing filters the same way the imap-worker's sync path does.
|
|
@@ -73,77 +43,26 @@ export const buildSharedDeps = () => {
|
|
|
73
43
|
// #223). Only the explicit user-rule half is wired here; heuristic classifier
|
|
74
44
|
// placement stays on the sync path. It shares `buildFilterConfig` with
|
|
75
45
|
// `sync-message-body.ts`, so a filter fires on either (issue #298). Over the
|
|
76
|
-
// message-management queue's
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const placementMoveQueueUrl = process.env.SQS_QUEUE_URL_MESSAGE_MGMT;
|
|
46
|
+
// message-management queue's local-first PlacementMoveService.
|
|
47
|
+
export const buildReadPathFilterConfig = (repositories, queues) => {
|
|
48
|
+
const { messageManagement } = queues;
|
|
80
49
|
return buildFilterConfig({
|
|
81
50
|
filterService: repositories.filter,
|
|
82
51
|
messageLabelService: repositories.messageLabel,
|
|
83
52
|
mailboxService: repositories.mailbox,
|
|
84
|
-
placementMoveService:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
})
|
|
94
|
-
: undefined,
|
|
53
|
+
placementMoveService: new PlacementMoveService({
|
|
54
|
+
messageService: repositories.message,
|
|
55
|
+
threadMessageService: repositories.threadMessage,
|
|
56
|
+
markerService: repositories.placementMove,
|
|
57
|
+
addressService: repositories.address,
|
|
58
|
+
mailboxSpecialUseService: repositories.mailboxSpecialUse,
|
|
59
|
+
mailboxService: repositories.mailbox,
|
|
60
|
+
queue: messageManagement,
|
|
61
|
+
}),
|
|
95
62
|
});
|
|
96
63
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// and assemble a RemitClient. Imports neither ElectroDB nor Drizzle — the
|
|
100
|
-
// caller chooses the backend.
|
|
101
|
-
export const createRemitClient = (deps) => {
|
|
102
|
-
const { repositories, storage, secrets, sqsQueueUrl, sqsSmtpQueueUrl, bodySyncQueue, } = deps;
|
|
103
|
-
const filterConfig = buildReadPathFilterConfig(repositories);
|
|
104
|
-
const flagPushService = new FlagPushService({
|
|
105
|
-
markerService: repositories.flagPush,
|
|
106
|
-
sqsQueueUrl,
|
|
107
|
-
logger,
|
|
108
|
-
});
|
|
109
|
-
// Shared with the returned `flagQueue` below (issue #302): body sync
|
|
110
|
-
// auto-marks-read a message from an unsubscribed sender through the exact
|
|
111
|
-
// same `FlagQueueService.markAsRead` a manual mark-as-read goes through, not
|
|
112
|
-
// a second instance or a second primitive.
|
|
113
|
-
const flagQueueService = new FlagQueueService({
|
|
114
|
-
messageFlagService: repositories.messageFlag,
|
|
115
|
-
messageService: repositories.message,
|
|
116
|
-
threadMessageService: repositories.threadMessage,
|
|
117
|
-
flagPushService,
|
|
118
|
-
logger,
|
|
119
|
-
});
|
|
120
|
-
const messageMoveService = new MessageMoveService({
|
|
121
|
-
messageService: repositories.message,
|
|
122
|
-
mailboxService: repositories.mailbox,
|
|
123
|
-
mailboxSpecialUseService: repositories.mailboxSpecialUse,
|
|
124
|
-
threadMessageService: repositories.threadMessage,
|
|
125
|
-
addressService: repositories.address,
|
|
126
|
-
sqsQueueUrl,
|
|
127
|
-
logger,
|
|
128
|
-
});
|
|
129
|
-
// One instance, shared with the queue service below: the per-draft chain that
|
|
130
|
-
// makes the attachment cap safe under concurrency lives on it, and a discard
|
|
131
|
-
// racing an upload has to take the same lock the upload does.
|
|
132
|
-
const outboxAttachmentService = new OutboxAttachmentService({
|
|
133
|
-
outboxMessageService: repositories.outboxMessage,
|
|
134
|
-
outboxAttachmentService: repositories.outboxAttachment,
|
|
135
|
-
storage,
|
|
136
|
-
});
|
|
137
|
-
const bodySync = new BodySyncService(repositories.message, storage, repositories.threadMessage, repositories.address, repositories.envelope, new SyncWarningService(repositories.syncWarning), logger, undefined, filterConfig, undefined, { flagQueueService },
|
|
138
|
-
// The read-path backfill materializes a body the sync path never got to,
|
|
139
|
-
// so it is a message's first sight as much as the sync pass is — and an
|
|
140
|
-
// invitation the user opens before background sync must still get its
|
|
141
|
-
// card (issue #1033, the same argument as the filter wiring above).
|
|
142
|
-
{
|
|
143
|
-
calendarSuggestionService: repositories.calendarSuggestion,
|
|
144
|
-
calendarStore: repositories.calendarStore,
|
|
145
|
-
filterService: repositories.filter,
|
|
146
|
-
});
|
|
64
|
+
export const createRemitCore = (deps) => {
|
|
65
|
+
const { repositories, storage, secrets } = deps;
|
|
147
66
|
return {
|
|
148
67
|
accountConfig: repositories.accountConfig,
|
|
149
68
|
account: repositories.account,
|
|
@@ -174,34 +93,96 @@ export const createRemitClient = (deps) => {
|
|
|
174
93
|
calendarStore: repositories.calendarStore,
|
|
175
94
|
storage,
|
|
176
95
|
secrets,
|
|
177
|
-
bodySync,
|
|
178
|
-
bodySyncQueue,
|
|
179
96
|
placementMove: repositories.placementMove,
|
|
180
97
|
flagPush: repositories.flagPush,
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
98
|
+
// One instance, shared with the outbox queue service: the per-draft chain
|
|
99
|
+
// that makes the attachment cap safe under concurrency lives on it, and a
|
|
100
|
+
// discard racing an upload has to take the same lock the upload does.
|
|
101
|
+
outboxAttachment: new OutboxAttachmentService({
|
|
102
|
+
outboxMessageService: repositories.outboxMessage,
|
|
103
|
+
outboxAttachmentService: repositories.outboxAttachment,
|
|
104
|
+
storage,
|
|
105
|
+
}),
|
|
106
|
+
createConnectionScope: buildConnectionScope(repositories.account, secrets),
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
export const composeSyncServices = (core, queues) => {
|
|
110
|
+
const flagPushService = new FlagPushService({
|
|
111
|
+
markerService: core.flagPush,
|
|
112
|
+
queue: queues.sync,
|
|
113
|
+
logger,
|
|
114
|
+
});
|
|
115
|
+
const messageMove = new MessageMoveService({
|
|
116
|
+
messageService: core.message,
|
|
117
|
+
mailboxService: core.mailbox,
|
|
118
|
+
mailboxSpecialUseService: core.mailboxSpecialUse,
|
|
119
|
+
threadMessageService: core.threadMessage,
|
|
120
|
+
addressService: core.address,
|
|
121
|
+
queue: queues.sync,
|
|
122
|
+
logger,
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
flagQueue: new FlagQueueService({
|
|
126
|
+
messageFlagService: core.messageFlag,
|
|
127
|
+
messageService: core.message,
|
|
128
|
+
threadMessageService: core.threadMessage,
|
|
129
|
+
flagPushService,
|
|
185
130
|
logger,
|
|
186
131
|
}),
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
outboxMessageService: repositories.outboxMessage,
|
|
191
|
-
outboxAttachmentService,
|
|
192
|
-
accountService: repositories.account,
|
|
193
|
-
sqsSmtpQueueUrl,
|
|
132
|
+
mailboxQueue: new MailboxQueueService({
|
|
133
|
+
mailboxService: core.mailbox,
|
|
134
|
+
enqueue: fifoMailboxManagement(queues.sync),
|
|
194
135
|
logger,
|
|
195
136
|
}),
|
|
137
|
+
messageMove,
|
|
196
138
|
spamReport: new SpamReportService({
|
|
197
|
-
messageService:
|
|
198
|
-
addressService:
|
|
199
|
-
accountService:
|
|
200
|
-
mailboxSpecialUseService:
|
|
201
|
-
messageMoveService,
|
|
139
|
+
messageService: core.message,
|
|
140
|
+
addressService: core.address,
|
|
141
|
+
accountService: core.account,
|
|
142
|
+
mailboxSpecialUseService: core.mailboxSpecialUse,
|
|
143
|
+
messageMoveService: messageMove,
|
|
202
144
|
flagPushService,
|
|
203
145
|
logger,
|
|
204
146
|
}),
|
|
205
|
-
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
export const composeSmtpServices = (core, queues) => ({
|
|
150
|
+
outboxQueue: new OutboxQueueService({
|
|
151
|
+
outboxMessageService: core.outboxMessage,
|
|
152
|
+
outboxAttachmentService: core.outboxAttachment,
|
|
153
|
+
accountService: core.account,
|
|
154
|
+
queue: queues.smtp,
|
|
155
|
+
logger,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
export const composeBodyServices = (queues) => ({
|
|
159
|
+
bodySyncQueue: new BodySyncQueueService({ queue: queues.body, logger }),
|
|
160
|
+
});
|
|
161
|
+
export const composeReadPathServices = (core, queues, sync) => ({
|
|
162
|
+
// The flag service is the one `flagQueue` is (issue #302): body sync
|
|
163
|
+
// auto-marks-read a message from an unsubscribed sender through the exact
|
|
164
|
+
// same `FlagQueueService.markAsRead` a manual mark-as-read goes through.
|
|
165
|
+
bodySync: new BodySyncService(core.message, core.storage, core.threadMessage, core.address, core.envelope, new SyncWarningService(core.syncWarning), logger, undefined, buildReadPathFilterConfig(core, queues), undefined, { flagQueueService: sync.flagQueue },
|
|
166
|
+
// The read-path backfill materializes a body the sync path never got to,
|
|
167
|
+
// so it is a message's first sight as much as the sync pass is — and an
|
|
168
|
+
// invitation the user opens before background sync must still get its
|
|
169
|
+
// card (issue #1033, the same argument as the filter wiring above).
|
|
170
|
+
{
|
|
171
|
+
calendarSuggestionService: core.calendarSuggestion,
|
|
172
|
+
calendarStore: core.calendarStore,
|
|
173
|
+
filterService: core.filter,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
export const createRemitClient = (deps) => {
|
|
177
|
+
const { queues } = deps;
|
|
178
|
+
const core = createRemitCore(deps);
|
|
179
|
+
const sync = composeSyncServices(core, queues);
|
|
180
|
+
return {
|
|
181
|
+
...core,
|
|
182
|
+
queues,
|
|
183
|
+
...sync,
|
|
184
|
+
...composeSmtpServices(core, queues),
|
|
185
|
+
...composeBodyServices(queues),
|
|
186
|
+
...composeReadPathServices(core, queues, sync),
|
|
206
187
|
};
|
|
207
188
|
};
|
|
@@ -30,11 +30,9 @@ const buildRelationalClient = async () => {
|
|
|
30
30
|
return composition.buildSqliteClient();
|
|
31
31
|
};
|
|
32
32
|
export const getClient = () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
: buildRelationalClient();
|
|
37
|
-
}
|
|
33
|
+
clientPromise ??= injected
|
|
34
|
+
? Promise.resolve(injected)
|
|
35
|
+
: buildRelationalClient();
|
|
38
36
|
return clientPromise;
|
|
39
37
|
};
|
|
40
38
|
/** Reset the singleton and any injected client — test use only. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { ConfigExportIdentity } from "@remit/config-transfer";
|
|
2
|
+
import type { ApiConfig } from "../config/api-config.js";
|
|
2
3
|
/** Who wrote a configuration file, and from where. Provenance only. */
|
|
3
|
-
export declare const exportIdentity: (now?: Date) => ConfigExportIdentity;
|
|
4
|
+
export declare const exportIdentity: (config: Pick<ApiConfig, "updateControlDir" | "publicOrigin">, now?: Date) => ConfigExportIdentity;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
const DEFAULT_CONTROL_DIR = "/data/control";
|
|
4
3
|
/**
|
|
5
4
|
* The version a file says it was written by. The updater owns this fact: it
|
|
6
5
|
* reads the tag from `.env` and writes it into `state.json`, which is the only
|
|
@@ -9,8 +8,7 @@ const DEFAULT_CONTROL_DIR = "/data/control";
|
|
|
9
8
|
* stamp, and an export says so rather than inventing a version an importing
|
|
10
9
|
* reader would then trust.
|
|
11
10
|
*/
|
|
12
|
-
const runningVersion = () => {
|
|
13
|
-
const dir = process.env.REMIT_UPDATE_CONTROL_DIR ?? DEFAULT_CONTROL_DIR;
|
|
11
|
+
const runningVersion = (dir) => {
|
|
14
12
|
let raw;
|
|
15
13
|
try {
|
|
16
14
|
raw = readFileSync(join(dir, "state.json"), "utf8");
|
|
@@ -27,9 +25,9 @@ const runningVersion = () => {
|
|
|
27
25
|
: "unknown";
|
|
28
26
|
};
|
|
29
27
|
/** Who wrote a configuration file, and from where. Provenance only. */
|
|
30
|
-
export const exportIdentity = (now = new Date()) => ({
|
|
28
|
+
export const exportIdentity = (config, now = new Date()) => ({
|
|
31
29
|
app: "reader",
|
|
32
|
-
version: runningVersion(),
|
|
30
|
+
version: runningVersion(config.updateControlDir),
|
|
33
31
|
exportedAt: now.toISOString(),
|
|
34
|
-
instance:
|
|
32
|
+
instance: config.publicOrigin,
|
|
35
33
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { inspect } from "node:util";
|
|
2
1
|
import { logger } from "@remit/logger";
|
|
3
2
|
/**
|
|
4
3
|
* Run unawaited background work that MUST NOT be able to fail the request that
|
|
@@ -32,7 +31,7 @@ export const fireAndForget = async (work, context) => {
|
|
|
32
31
|
errorName: error?.name,
|
|
33
32
|
errorCode: error?.Code ??
|
|
34
33
|
error?.code,
|
|
35
|
-
error
|
|
34
|
+
error,
|
|
36
35
|
}, context.message);
|
|
37
36
|
});
|
|
38
37
|
};
|
|
@@ -117,22 +117,19 @@ export declare const organizePredicateRejection: (predicate: OrganizePredicate)
|
|
|
117
117
|
*/
|
|
118
118
|
export declare const matchOrganize: (deps: OrganizeMatchDeps, accountConfigId: string, predicate: OrganizePredicate, limit?: number, scope?: OrganizeMatchScope) => Promise<OrganizeMatchResult>;
|
|
119
119
|
/** The matcher deps for the running backend/worker. */
|
|
120
|
-
export declare const buildOrganizeMatchDeps: (client: RemitClient) => OrganizeMatchDeps;
|
|
120
|
+
export declare const buildOrganizeMatchDeps: (client: RemitClient<never>) => OrganizeMatchDeps;
|
|
121
121
|
/**
|
|
122
122
|
* The exclusive-move arm of a back-apply, wired exactly like the body-sync
|
|
123
123
|
* placement mover (`sync-message-body.ts`): the local-first
|
|
124
124
|
* {@link PlacementMoveService} over the shared message-management queue. A move
|
|
125
125
|
* commits locally (marker + ThreadMessage + Message row) and enqueues a
|
|
126
126
|
* `PLACEMENT_MOVE_PUSH` for the reconciler; its marker state engine makes a
|
|
127
|
-
* redelivered job idempotent (mirrors #1297).
|
|
128
|
-
* message-management queue is wired — the same gate body sync uses to keep the
|
|
129
|
-
* move path off — so a move requested in that environment is counted as failed
|
|
130
|
-
* rather than silently dropped (see {@link applyOrganize}).
|
|
127
|
+
* redelivered job idempotent (mirrors #1297).
|
|
131
128
|
*/
|
|
132
|
-
export declare const buildOrganizeMoveService: (client: RemitClient) => PlacementMoveService
|
|
129
|
+
export declare const buildOrganizeMoveService: (client: RemitClient<"messageManagement">) => PlacementMoveService;
|
|
133
130
|
export interface ApplyOrganizeDeps {
|
|
134
|
-
client: RemitClient
|
|
135
|
-
moveService
|
|
131
|
+
client: RemitClient<never>;
|
|
132
|
+
moveService: PlacementMoveService;
|
|
136
133
|
}
|
|
137
134
|
export interface ApplyOrganizeResult {
|
|
138
135
|
applied: number;
|
|
@@ -165,9 +162,9 @@ export interface FilterBackApplyResult {
|
|
|
165
162
|
failures: string[];
|
|
166
163
|
}
|
|
167
164
|
export interface FilterBackApplyDeps {
|
|
168
|
-
client: RemitClient
|
|
165
|
+
client: RemitClient<never>;
|
|
169
166
|
matchDeps: OrganizeMatchDeps;
|
|
170
|
-
moveService
|
|
167
|
+
moveService: PlacementMoveService;
|
|
171
168
|
}
|
|
172
169
|
/**
|
|
173
170
|
* Run one standing filter over the inbox as if each matching message had just
|
|
@@ -154,25 +154,17 @@ export const buildOrganizeMatchDeps = (client) => ({
|
|
|
154
154
|
* {@link PlacementMoveService} over the shared message-management queue. A move
|
|
155
155
|
* commits locally (marker + ThreadMessage + Message row) and enqueues a
|
|
156
156
|
* `PLACEMENT_MOVE_PUSH` for the reconciler; its marker state engine makes a
|
|
157
|
-
* redelivered job idempotent (mirrors #1297).
|
|
158
|
-
* message-management queue is wired — the same gate body sync uses to keep the
|
|
159
|
-
* move path off — so a move requested in that environment is counted as failed
|
|
160
|
-
* rather than silently dropped (see {@link applyOrganize}).
|
|
157
|
+
* redelivered job idempotent (mirrors #1297).
|
|
161
158
|
*/
|
|
162
|
-
export const buildOrganizeMoveService = (client) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
mailboxSpecialUseService: client.mailboxSpecialUse,
|
|
172
|
-
mailboxService: client.mailbox,
|
|
173
|
-
sqsQueueUrl: queueUrl,
|
|
174
|
-
});
|
|
175
|
-
};
|
|
159
|
+
export const buildOrganizeMoveService = (client) => new PlacementMoveService({
|
|
160
|
+
messageService: client.message,
|
|
161
|
+
threadMessageService: client.threadMessage,
|
|
162
|
+
markerService: client.placementMove,
|
|
163
|
+
addressService: client.address,
|
|
164
|
+
mailboxSpecialUseService: client.mailboxSpecialUse,
|
|
165
|
+
mailboxService: client.mailbox,
|
|
166
|
+
queue: client.queues.messageManagement,
|
|
167
|
+
});
|
|
176
168
|
/**
|
|
177
169
|
* Apply the back-apply action to every matched message, reusing the index-time
|
|
178
170
|
* apply plumbing: an idempotent MessageLabel upsert (additive) with
|
|
@@ -195,12 +187,6 @@ export const applyOrganize = async (deps, accountConfigId, messageIds, predicate
|
|
|
195
187
|
const applyMove = predicate.actionMailboxId !== NO_ACTION && predicate.actionMailboxId !== "";
|
|
196
188
|
const applyToMessage = async (messageId) => {
|
|
197
189
|
if (applyMove) {
|
|
198
|
-
if (!moveService) {
|
|
199
|
-
// An exclusive move was requested but this caller wired no move
|
|
200
|
-
// service. Never silently pretend it applied — surface it as a
|
|
201
|
-
// failed message so the job's failedCount reflects reality.
|
|
202
|
-
throw new Error("Organize move action requested but no move service is wired");
|
|
203
|
-
}
|
|
204
190
|
const message = await client.message.get(messageId);
|
|
205
191
|
const accountId = await client.mailbox.resolveAccountId(message.mailboxId);
|
|
206
192
|
if (!accountId) {
|
|
@@ -324,9 +310,6 @@ export const backApplyFilter = async (deps, accountConfigId, filterId) => {
|
|
|
324
310
|
mailboxService: client.mailbox,
|
|
325
311
|
placementMoveService: moveService,
|
|
326
312
|
});
|
|
327
|
-
if (!config) {
|
|
328
|
-
throw new Error("Filter back-apply needs the message-management queue to move mail");
|
|
329
|
-
}
|
|
330
313
|
const pipeline = new FilterPipeline(config, logger);
|
|
331
314
|
const mailboxIds = await inboxMailboxIds(client, accountConfigId);
|
|
332
315
|
const nothing = {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AccountFanoutEvent, FifoQueueProducer, QueueProducer } from "@remit/data-ports";
|
|
2
|
+
import type { FlagPushEvent, MailboxManagementEvent, MessageMoveQueueEvent, PlacementMovePushEvent, SendMessageEvent, SyncMessageBodyEvent } from "@remit/mailbox-service";
|
|
3
|
+
import type { SyncMailboxesEvent } from "./trigger-sync.js";
|
|
4
|
+
export type SyncQueueEvent = SyncMailboxesEvent | FlagPushEvent | MailboxManagementEvent | MessageMoveQueueEvent;
|
|
5
|
+
export interface RemitClientQueues {
|
|
6
|
+
sync: FifoQueueProducer<SyncQueueEvent>;
|
|
7
|
+
accountFanout: QueueProducer<AccountFanoutEvent>;
|
|
8
|
+
smtp: QueueProducer<SendMessageEvent>;
|
|
9
|
+
messageManagement: QueueProducer<PlacementMovePushEvent>;
|
|
10
|
+
body: QueueProducer<SyncMessageBodyEvent>;
|
|
11
|
+
}
|
|
12
|
+
export type QueueName = keyof RemitClientQueues;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RemitClientSharedDeps } from "./create-remit-client.js";
|
|
2
|
+
import type { QueueName, RemitClientQueues } from "./queues.js";
|
|
3
|
+
export type SqsQueueUrls = Record<QueueName, string>;
|
|
4
|
+
export declare const sqsQueueUrlsFromEnv: () => SqsQueueUrls;
|
|
5
|
+
export declare const sqsQueues: (urls: SqsQueueUrls) => RemitClientQueues;
|
|
6
|
+
export declare const buildSharedDeps: (queues: RemitClientQueues) => RemitClientSharedDeps;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createCachedDataKeyProvider, createSecretsService, } from "@remit/secrets-service";
|
|
2
|
+
import { createKmsDataKeyProvider, FAKE_KMS_KEY_ID, } from "@remit/secrets-service/kms";
|
|
3
|
+
import { createFifoQueueProducer, createStandardQueueProducer, } from "@remit/sqs-client/producer";
|
|
4
|
+
import { createStorageService } from "@remit/storage-service/s3";
|
|
5
|
+
import { env } from "expect-env";
|
|
6
|
+
export const sqsQueueUrlsFromEnv = () => ({
|
|
7
|
+
sync: env.SQS_QUEUE_URL,
|
|
8
|
+
accountFanout: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
|
|
9
|
+
smtp: env.SQS_QUEUE_URL_SMTP,
|
|
10
|
+
messageManagement: env.SQS_QUEUE_URL_MESSAGE_MGMT,
|
|
11
|
+
body: env.SQS_QUEUE_URL_BODY,
|
|
12
|
+
});
|
|
13
|
+
export const sqsQueues = (urls) => ({
|
|
14
|
+
sync: createFifoQueueProducer({ queueUrl: urls.sync }),
|
|
15
|
+
accountFanout: createStandardQueueProducer({ queueUrl: urls.accountFanout }),
|
|
16
|
+
smtp: createStandardQueueProducer({ queueUrl: urls.smtp }),
|
|
17
|
+
messageManagement: createStandardQueueProducer({
|
|
18
|
+
queueUrl: urls.messageManagement,
|
|
19
|
+
}),
|
|
20
|
+
body: createStandardQueueProducer({ queueUrl: urls.body }),
|
|
21
|
+
});
|
|
22
|
+
export const buildSharedDeps = (queues) => ({
|
|
23
|
+
storage: createStorageService(),
|
|
24
|
+
secrets: createSecretsService(createCachedDataKeyProvider(createKmsDataKeyProvider(process.env.KMS_KEY_ID ?? FAKE_KMS_KEY_ID))),
|
|
25
|
+
queues,
|
|
26
|
+
});
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FifoQueueMessage, FifoQueueProducer } from "@remit/data-ports";
|
|
2
|
+
export interface SyncMailboxesEvent {
|
|
3
|
+
type: "SYNC_MAILBOXES";
|
|
4
|
+
eventId: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
accountId: string;
|
|
7
|
+
explicitRequest?: boolean;
|
|
8
|
+
}
|
|
2
9
|
interface TriggerAccountSyncInput {
|
|
3
|
-
|
|
4
|
-
queueUrl: string;
|
|
10
|
+
queue: FifoQueueProducer<SyncMailboxesEvent>;
|
|
5
11
|
accountId: string;
|
|
6
12
|
/**
|
|
7
13
|
* Set by POST /sync, whose callers ask for a sync of one named account: the
|
|
@@ -21,7 +27,7 @@ interface TriggerAccountSyncInput {
|
|
|
21
27
|
*/
|
|
22
28
|
explicitRequest?: boolean;
|
|
23
29
|
/**
|
|
24
|
-
* Override the
|
|
30
|
+
* Override the queue deduplication id. Defaults to the event's own
|
|
25
31
|
* id, so the queue suppresses a re-send of one event and nothing else —
|
|
26
32
|
* every manual call site (POST /sync, OAuth connect, config load,
|
|
27
33
|
* pull-to-refresh, the client's online-poll) always enqueues. A shared,
|
|
@@ -44,7 +50,7 @@ interface TriggerAccountSyncInput {
|
|
|
44
50
|
* retried Lambda) — see `dedupId` above.
|
|
45
51
|
*/
|
|
46
52
|
export declare const buildScheduledSyncDedupId: (accountId: string, now: number, bucketMs: number) => string;
|
|
47
|
-
export declare const
|
|
53
|
+
export declare const buildSyncMailboxesMessage: (input: Omit<TriggerAccountSyncInput, "queue">) => FifoQueueMessage<SyncMailboxesEvent>;
|
|
48
54
|
export declare const triggerAccountSync: (input: TriggerAccountSyncInput) => Promise<{
|
|
49
55
|
eventId: string;
|
|
50
56
|
}>;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { SendMessageCommand } from "@aws-sdk/client-sqs";
|
|
3
|
-
const isFifoQueue = (queueUrl) => queueUrl.endsWith(".fifo");
|
|
4
2
|
/**
|
|
5
3
|
* Build the scheduler's own dedup namespace, bucketed by tick interval so
|
|
6
4
|
* consecutive ticks each get a fresh id (never colliding with each other)
|
|
@@ -11,8 +9,8 @@ export const buildScheduledSyncDedupId = (accountId, now, bucketMs) => {
|
|
|
11
9
|
const bucket = Math.floor(now / bucketMs);
|
|
12
10
|
return `SYNC_MAILBOXES:scheduled:${accountId}:${bucket}`;
|
|
13
11
|
};
|
|
14
|
-
export const
|
|
15
|
-
const {
|
|
12
|
+
export const buildSyncMailboxesMessage = (input) => {
|
|
13
|
+
const { accountId, dedupId, explicitRequest } = input;
|
|
16
14
|
const event = {
|
|
17
15
|
type: "SYNC_MAILBOXES",
|
|
18
16
|
eventId: randomUUID(),
|
|
@@ -20,20 +18,14 @@ export const buildSyncMailboxesCommand = (input) => {
|
|
|
20
18
|
accountId,
|
|
21
19
|
...(explicitRequest && { explicitRequest }),
|
|
22
20
|
};
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
MessageGroupId: accountId,
|
|
29
|
-
MessageDeduplicationId: dedupId ?? event.eventId,
|
|
30
|
-
}),
|
|
31
|
-
});
|
|
21
|
+
return {
|
|
22
|
+
body: event,
|
|
23
|
+
groupId: accountId,
|
|
24
|
+
deduplicationId: dedupId ?? event.eventId,
|
|
25
|
+
};
|
|
32
26
|
};
|
|
33
27
|
export const triggerAccountSync = async (input) => {
|
|
34
|
-
const
|
|
35
|
-
await input.
|
|
36
|
-
|
|
37
|
-
const parsed = JSON.parse(body);
|
|
38
|
-
return { eventId: parsed.eventId };
|
|
28
|
+
const message = buildSyncMailboxesMessage(input);
|
|
29
|
+
await input.queue.send(message);
|
|
30
|
+
return { eventId: message.body.eventId };
|
|
39
31
|
};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Context } from "openapi-backend";
|
|
2
|
+
import type { ApiConfig } from "./config/api-config.js";
|
|
3
|
+
import type { RemitClient } from "./service/create-remit-client.js";
|
|
4
|
+
import type { QueueName } from "./service/queues.js";
|
|
2
5
|
type MatchPrefix<Prefix extends string, S extends string> = S extends `${Prefix}${infer _}` ? S : never;
|
|
3
6
|
export type OperationIds = "MeOperations_deleteMe" | "MeOperations_listVipSuggestions" | "MeOperations_createExport" | "MeOperations_getExport" | "MeOperations_listQuarantine" | "ConfigOperations_getConfig" | "ConfigOperations_exportConfig" | "ConfigOperations_importConfig" | "SystemOperations_getSystemUpdate" | "SystemOperations_applySystemUpdate" | "AccountOperations_createAccount" | "AccountOperations_testConnection" | "AccountDetailOperations_updateAccount" | "AccountDetailOperations_deleteAccount" | "MicrosoftOAuthOperations_microsoftOAuthStart" | "MicrosoftOAuthOperations_microsoftOAuthCallback" | "MailboxOperations_listMailboxes" | "MailboxOperations_createMailbox" | "MailboxDetailOperations_getMailbox" | "MailboxDetailOperations_renameMailbox" | "MailboxDetailOperations_deleteMailbox" | "FolderRoleOperations_appointFolderRole" | "FilterOperations_listFilters" | "FilterOperations_createFilter" | "FilterDetailOperations_getFilter" | "FilterDetailOperations_updateFilter" | "FilterDetailOperations_deleteFilter" | "LabelOperations_listLabels" | "LabelOperations_createLabel" | "LabelDetailOperations_getLabel" | "LabelDetailOperations_updateLabel" | "LabelDetailOperations_deleteLabel" | "OrganizeOperations_createOrganizeJob" | "OrganizeOperations_previewOrganize" | "OrganizeJobDetailOperations_getOrganizeJob" | "SyncOperations_triggerSync" | "SyncOperations_getSyncStatus" | "ThreadDetailOperations_listThreadMessages" | "UnifiedThreadOperations_listAllThreads" | "ThreadOperations_listThreads" | "ThreadOperations_searchThreads" | "MessageOperations_describeMessage" | "MessageOperations_getRawMessage" | "MessageOperations_updateMessageFlags" | "MessageBulkOperations_deleteMessages" | "MessageBulkOperations_moveMessages" | "MessageBulkOperations_updateFlags" | "MessageBulkOperations_copyMessages" | "MessageBulkOperations_updateMessageLabels" | "MessageBulkOperations_reportSpam" | "MessageBulkOperations_notSpam" | "CalendarOperations_listCalendars" | "CalendarOperations_createCalendar" | "CalendarDetailOperations_getCalendar" | "CalendarDetailOperations_updateCalendar" | "CalendarDetailOperations_deleteCalendar" | "CalendarDetailOperations_getCalendarFeed" | "CalendarDetailOperations_putCalendarFeed" | "CalendarDetailOperations_revokeCalendarFeed" | "CalendarFeedOperations_getCalendarFeedIcal" | "CalendarEventOperations_listCalendarEvents" | "CalendarEventOperations_createCalendarEvent" | "CalendarEventDetailOperations_getCalendarEvent" | "CalendarEventDetailOperations_updateCalendarEvent" | "CalendarEventDetailOperations_deleteCalendarEvent" | "CalendarFreeBusyOperations_listCalendarFreeBusy" | "TrashOperations_emptyTrash" | "OutboxOperations_createOutboxMessage" | "OutboxOperations_listOutboxMessages" | "OutboxDetailOperations_getOutboxMessage" | "OutboxDetailOperations_updateOutboxMessage" | "OutboxDetailOperations_deleteOutboxMessage" | "OutboxDetailOperations_sendOutboxMessage" | "OutboxDetailOperations_mintOutboxAttachment" | "OutboxAttachmentOperations_completeOutboxAttachment" | "AddressOperations_searchAddresses" | "AddressDetailOperations_updateAddress" | "CalendarSuggestionOperations_listCalendarSuggestions" | "MessageCalendarSuggestionOperations_listMessageCalendarSuggestions" | "CalendarSuggestionActionOperations_acceptCalendarSuggestion" | "CalendarSuggestionActionOperations_declineCalendarSuggestion" | "CalendarSuggestionActionOperations_dismissCalendarSuggestion" | "CalendarSuggestionActionOperations_reopenCalendarSuggestion";
|
|
4
7
|
export type MeOperationIds = MatchPrefix<"MeOperations_", OperationIds>;
|
|
@@ -45,5 +48,25 @@ export type CalendarSuggestionOperationIds = MatchPrefix<"CalendarSuggestionOper
|
|
|
45
48
|
export type MessageCalendarSuggestionOperationIds = MatchPrefix<"MessageCalendarSuggestionOperations_", OperationIds>;
|
|
46
49
|
export type CalendarSuggestionActionOperationIds = MatchPrefix<"CalendarSuggestionActionOperations_", OperationIds>;
|
|
47
50
|
type HandlerResponse = Record<string, any>;
|
|
48
|
-
export
|
|
51
|
+
export interface UserIdentity {
|
|
52
|
+
kind: "user";
|
|
53
|
+
accountConfigId: string;
|
|
54
|
+
sub: string;
|
|
55
|
+
email: string;
|
|
56
|
+
}
|
|
57
|
+
export type HandlerIdentity = {
|
|
58
|
+
kind: "anonymous";
|
|
59
|
+
} | UserIdentity;
|
|
60
|
+
export interface HandlerContext<K extends QueueName = never> {
|
|
61
|
+
client: RemitClient<K>;
|
|
62
|
+
identity: UserIdentity;
|
|
63
|
+
config: ApiConfig;
|
|
64
|
+
}
|
|
65
|
+
export interface PublicHandlerContext<K extends QueueName = never> {
|
|
66
|
+
client: RemitClient<K>;
|
|
67
|
+
config: ApiConfig;
|
|
68
|
+
}
|
|
69
|
+
export type PublicOperationIds = "MicrosoftOAuthOperations_microsoftOAuthCallback" | "CalendarFeedOperations_getCalendarFeedIcal";
|
|
70
|
+
export type OperationHandler<_T extends OperationIds = OperationIds, K extends QueueName = never> = (context: Context, ctx: HandlerContext<K>) => Promise<HandlerResponse>;
|
|
71
|
+
export type PublicOperationHandler<K extends QueueName = never> = (context: Context, ctx: PublicHandlerContext<K>) => Promise<HandlerResponse>;
|
|
49
72
|
export {};
|