@remit/account-worker 0.0.29 → 0.0.31
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 +5 -1
- package/src/config.ts +6 -3
- package/src/handlers/account-fanout.test.ts +2 -27
- package/src/handlers/account-fanout.ts +7 -14
- package/src/handlers/account-purge.test.ts +1 -1
- package/src/handlers/organize-job.ts +5 -5
- package/src/handlers/sender-category-backapply.test.ts +9 -10
- package/src/handlers/sender-category-backapply.ts +5 -5
- package/src/process-bundle.test.ts +66 -0
- package/src/process.ts +28 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/account-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"./finalize": {
|
|
24
24
|
"types": "./src/handlers/account-finalize.ts",
|
|
25
25
|
"default": "./src/handlers/account-finalize.ts"
|
|
26
|
+
},
|
|
27
|
+
"./process": {
|
|
28
|
+
"types": "./src/process.ts",
|
|
29
|
+
"default": "./src/process.ts"
|
|
26
30
|
}
|
|
27
31
|
},
|
|
28
32
|
"scripts": {
|
package/src/config.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getClient } from "@remit/backend/client";
|
|
2
2
|
import type { GroupedQueueProducer, QueueProducer } from "@remit/data-ports";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createGroupedQueueProducer,
|
|
5
|
+
createStandardQueueProducer,
|
|
6
|
+
} from "@remit/sqs-client/producer";
|
|
4
7
|
import type { StorageService } from "@remit/storage-service";
|
|
5
8
|
import { createStorageService } from "@remit/storage-service/s3";
|
|
6
9
|
import { env } from "expect-env";
|
|
@@ -26,10 +29,10 @@ export interface FanoutQueues {
|
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export const sqsFanoutQueues = (): FanoutQueues => ({
|
|
29
|
-
accountFinalize:
|
|
32
|
+
accountFinalize: createStandardQueueProducer({
|
|
30
33
|
queueUrl: getAccountFinalizeQueueUrl(),
|
|
31
34
|
}),
|
|
32
|
-
accountPurgeDelete:
|
|
35
|
+
accountPurgeDelete: createGroupedQueueProducer({
|
|
33
36
|
queueUrl: getAccountPurgeDeleteQueueUrl(),
|
|
34
37
|
}),
|
|
35
38
|
});
|
|
@@ -6,34 +6,9 @@ import type { CascadeServices } from "../cascade.js";
|
|
|
6
6
|
import type { AccountDeleteEvent } from "../events.js";
|
|
7
7
|
import { processAccountFanout } from "./account-fanout.js";
|
|
8
8
|
|
|
9
|
-
// A CascadeServices fake with one account and no data: enough for
|
|
10
|
-
// enumerateCascadeEntities to yield a single Account row (so the stop-signal
|
|
11
|
-
// loop has one accountId to iterate) without standing up a database.
|
|
12
9
|
const buildServices = (): CascadeServices =>
|
|
13
10
|
({
|
|
14
|
-
accountConfigService: {
|
|
15
|
-
get: async () => ({ userId: "user-1" }),
|
|
16
|
-
describe: async () => ({
|
|
17
|
-
account: [{ accountId: "acc-1" }],
|
|
18
|
-
address: [],
|
|
19
|
-
}),
|
|
20
|
-
},
|
|
21
|
-
accountService: {
|
|
22
|
-
describe: async () => ({ mailbox: [] }),
|
|
23
|
-
},
|
|
24
|
-
messageService: {
|
|
25
|
-
listAllByMailbox: async () => [],
|
|
26
|
-
describe: async () => ({}),
|
|
27
|
-
},
|
|
28
|
-
outboxMessageService: { listByAccount: async () => ({ items: [] }) },
|
|
29
|
-
mailboxLockService: { listByAccount: async () => [] },
|
|
30
|
-
messagePlacementMoveService: { listByAccountId: async () => [] },
|
|
31
|
-
messageFlagPushService: { listByAccountId: async () => [] },
|
|
32
|
-
threadMessageService: { listAllByAccount: async () => [] },
|
|
33
|
-
accountSettingService: { listByAccountConfig: async () => [] },
|
|
34
|
-
filterService: { listByAccountConfig: async () => [] },
|
|
35
|
-
labelService: { listByAccountConfig: async () => [] },
|
|
36
|
-
messageLabelService: { listByLabelId: async () => [] },
|
|
11
|
+
accountConfigService: { get: async () => ({ userId: "user-1" }) },
|
|
37
12
|
}) as unknown as CascadeServices;
|
|
38
13
|
|
|
39
14
|
const deleteEvent: AccountDeleteEvent = {
|
|
@@ -48,7 +23,7 @@ describe("processAccountFanout on account delete", () => {
|
|
|
48
23
|
await processAccountFanout(deleteEvent, noopLogger, {
|
|
49
24
|
queues: {
|
|
50
25
|
accountFinalize,
|
|
51
|
-
accountPurgeDelete: recordingQueue(),
|
|
26
|
+
accountPurgeDelete: recordingQueue().grouped,
|
|
52
27
|
},
|
|
53
28
|
services: buildServices(),
|
|
54
29
|
signOut: async () => {},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getClient } from "@remit/backend/client";
|
|
1
2
|
import { createLogger, type Logger } from "@remit/logger";
|
|
2
3
|
import {
|
|
3
4
|
queueNameFromEventSource,
|
|
@@ -20,10 +21,9 @@ import type {
|
|
|
20
21
|
AccountFanoutEvent,
|
|
21
22
|
AccountFinalizeEvent,
|
|
22
23
|
} from "../events.js";
|
|
24
|
+
import { processAccountFanoutEvent } from "../process.js";
|
|
23
25
|
import { processAccountExport } from "./account-export.js";
|
|
24
26
|
import { processAccountDataPurge } from "./account-purge.js";
|
|
25
|
-
import { processOrganizeJob } from "./organize-job.js";
|
|
26
|
-
import { processSenderCategoryBackApply } from "./sender-category-backapply.js";
|
|
27
27
|
|
|
28
28
|
installNodeLogger();
|
|
29
29
|
|
|
@@ -33,11 +33,6 @@ export interface ProcessAccountFanoutDeps {
|
|
|
33
33
|
signOut?: DeletionCapabilities["signOut"];
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/**
|
|
37
|
-
* Fanout step of the account-deletion cascade. Signs the user out and
|
|
38
|
-
* enqueues the finalize step. Throws on any non-swallowable error so SQS partial batch-failure
|
|
39
|
-
* retries the whole record.
|
|
40
|
-
*/
|
|
41
36
|
export const processAccountFanout = async (
|
|
42
37
|
event: AccountFanoutEvent,
|
|
43
38
|
log: Logger,
|
|
@@ -58,13 +53,11 @@ export const processAccountFanout = async (
|
|
|
58
53
|
return;
|
|
59
54
|
}
|
|
60
55
|
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (event.type === "SenderCategoryBackApply") {
|
|
67
|
-
await processSenderCategoryBackApply(event, log);
|
|
56
|
+
if (
|
|
57
|
+
event.type === "OrganizeJob" ||
|
|
58
|
+
event.type === "SenderCategoryBackApply"
|
|
59
|
+
) {
|
|
60
|
+
await processAccountFanoutEvent(event, { client: await getClient() });
|
|
68
61
|
return;
|
|
69
62
|
}
|
|
70
63
|
|
|
@@ -42,7 +42,7 @@ const baseDeps = (queue: RecordingQueue): ProcessPurgeFanoutDeps => ({
|
|
|
42
42
|
{ threadMessageId: "tm-1", messageId: "msg-1", mailboxId: "mbx-1" },
|
|
43
43
|
{ threadMessageId: "tm-2", messageId: "msg-2", mailboxId: "mbx-1" },
|
|
44
44
|
]),
|
|
45
|
-
queue,
|
|
45
|
+
queue: queue.grouped,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
describe("processAccountDataPurge", () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RemitClient } from "@remit/backend/client";
|
|
2
2
|
import {
|
|
3
3
|
type ApplyOrganizeDeps,
|
|
4
4
|
applyOrganize,
|
|
@@ -16,7 +16,7 @@ import type { Logger } from "@remit/logger";
|
|
|
16
16
|
import type { OrganizeJobEvent } from "../events.js";
|
|
17
17
|
|
|
18
18
|
export interface ProcessOrganizeJobDeps {
|
|
19
|
-
client
|
|
19
|
+
client: RemitClient<"messageManagement">;
|
|
20
20
|
matchDeps?: OrganizeMatchDeps;
|
|
21
21
|
moveService?: ApplyOrganizeDeps["moveService"];
|
|
22
22
|
}
|
|
@@ -42,7 +42,7 @@ const rejectedOutcome = (rejected: OrganizeRejection): OrganizeOutcome => ({
|
|
|
42
42
|
* folder, every requested move applied (reader #497).
|
|
43
43
|
*/
|
|
44
44
|
const runSnapshot = async (
|
|
45
|
-
client: RemitClient
|
|
45
|
+
client: RemitClient<"messageManagement">,
|
|
46
46
|
matchDeps: OrganizeMatchDeps,
|
|
47
47
|
moveService: ApplyOrganizeDeps["moveService"],
|
|
48
48
|
job: Awaited<ReturnType<RemitClient["organizeJobRequest"]["get"]>>,
|
|
@@ -95,10 +95,10 @@ const runSnapshot = async (
|
|
|
95
95
|
export const processOrganizeJob = async (
|
|
96
96
|
event: OrganizeJobEvent,
|
|
97
97
|
log: Logger,
|
|
98
|
-
deps: ProcessOrganizeJobDeps
|
|
98
|
+
deps: ProcessOrganizeJobDeps,
|
|
99
99
|
): Promise<void> => {
|
|
100
100
|
const { accountConfigId, organizeJobId } = event;
|
|
101
|
-
const client = deps
|
|
101
|
+
const { client } = deps;
|
|
102
102
|
|
|
103
103
|
const job = await client.organizeJobRequest.get(organizeJobId);
|
|
104
104
|
await client.organizeJobRequest.update(organizeJobId, { state: "Running" });
|
|
@@ -2,9 +2,8 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
import type { RemitClient } from "@remit/backend/client";
|
|
4
4
|
import { NotFoundError } from "@remit/data-ports/errors";
|
|
5
|
-
import { noopLogger } from "@remit/logger";
|
|
6
5
|
import type { SenderCategoryBackApplyEvent } from "../events.js";
|
|
7
|
-
import {
|
|
6
|
+
import { processAccountFanoutEvent } from "../process.js";
|
|
8
7
|
|
|
9
8
|
const SET_AT = 1_700;
|
|
10
9
|
|
|
@@ -71,11 +70,11 @@ const clientOver = ({
|
|
|
71
70
|
},
|
|
72
71
|
}) as unknown as RemitClient;
|
|
73
72
|
|
|
74
|
-
describe("
|
|
73
|
+
describe("processAccountFanoutEvent on a sender category back-apply (#415)", () => {
|
|
75
74
|
it("re-labels the sender's stored mail to the category the event carries", async () => {
|
|
76
75
|
const relabelled: Array<{ messageId: string; category: unknown }> = [];
|
|
77
76
|
|
|
78
|
-
await
|
|
77
|
+
await processAccountFanoutEvent(event, {
|
|
79
78
|
client: clientOver({ relabelled }),
|
|
80
79
|
});
|
|
81
80
|
|
|
@@ -87,7 +86,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
87
86
|
it("applies nothing when the user has since corrected the category", async () => {
|
|
88
87
|
const relabelled: Array<{ messageId: string; category: unknown }> = [];
|
|
89
88
|
|
|
90
|
-
await
|
|
89
|
+
await processAccountFanoutEvent(event, {
|
|
91
90
|
client: clientOver({
|
|
92
91
|
relabelled,
|
|
93
92
|
storedFlag: { value: "promotions", setAt: SET_AT + 60 },
|
|
@@ -104,7 +103,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
104
103
|
it("applies nothing when the same category was cleared and set again", async () => {
|
|
105
104
|
const relabelled: Array<{ messageId: string; category: unknown }> = [];
|
|
106
105
|
|
|
107
|
-
await
|
|
106
|
+
await processAccountFanoutEvent(event, {
|
|
108
107
|
client: clientOver({
|
|
109
108
|
relabelled,
|
|
110
109
|
storedFlag: { value: "newsletter", setAt: SET_AT + 60 },
|
|
@@ -117,7 +116,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
117
116
|
it("applies nothing when the override was cleared outright", async () => {
|
|
118
117
|
const relabelled: Array<{ messageId: string; category: unknown }> = [];
|
|
119
118
|
|
|
120
|
-
await
|
|
119
|
+
await processAccountFanoutEvent(event, {
|
|
121
120
|
client: clientOver({ relabelled, storedFlag: "cleared" }),
|
|
122
121
|
});
|
|
123
122
|
|
|
@@ -127,7 +126,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
127
126
|
it("declines quietly when the address is gone", async () => {
|
|
128
127
|
const relabelled: Array<{ messageId: string; category: unknown }> = [];
|
|
129
128
|
|
|
130
|
-
await
|
|
129
|
+
await processAccountFanoutEvent(event, {
|
|
131
130
|
client: clientOver({ relabelled, storedFlag: "address-gone" }),
|
|
132
131
|
});
|
|
133
132
|
|
|
@@ -136,7 +135,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
136
135
|
|
|
137
136
|
it("leaves a pass with a failed message unacknowledged so it redelivers", async () => {
|
|
138
137
|
await assert.rejects(
|
|
139
|
-
|
|
138
|
+
processAccountFanoutEvent(event, {
|
|
140
139
|
client: clientOver({
|
|
141
140
|
relabelled: [],
|
|
142
141
|
updateMessage: async () => {
|
|
@@ -151,7 +150,7 @@ describe("processSenderCategoryBackApply (#415)", () => {
|
|
|
151
150
|
|
|
152
151
|
it("propagates a store failure so partial batch failure redelivers the record", async () => {
|
|
153
152
|
await assert.rejects(
|
|
154
|
-
|
|
153
|
+
processAccountFanoutEvent(event, {
|
|
155
154
|
client: clientOver({
|
|
156
155
|
relabelled: [],
|
|
157
156
|
listByFieldTerms: async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { RemitClient } from "@remit/backend/client";
|
|
2
2
|
import {
|
|
3
3
|
backApplySenderCategory,
|
|
4
4
|
SENDER_CATEGORY_BACKAPPLY_LIMIT,
|
|
@@ -8,7 +8,7 @@ import type { Logger } from "@remit/logger";
|
|
|
8
8
|
import type { SenderCategoryBackApplyEvent } from "../events.js";
|
|
9
9
|
|
|
10
10
|
export interface ProcessSenderCategoryBackApplyDeps {
|
|
11
|
-
client
|
|
11
|
+
client: RemitClient<never>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -23,7 +23,7 @@ export interface ProcessSenderCategoryBackApplyDeps {
|
|
|
23
23
|
* must not guess.
|
|
24
24
|
*/
|
|
25
25
|
const overrideStillStands = async (
|
|
26
|
-
client: RemitClient
|
|
26
|
+
client: RemitClient<never>,
|
|
27
27
|
event: SenderCategoryBackApplyEvent,
|
|
28
28
|
): Promise<boolean> => {
|
|
29
29
|
const address = await client.address
|
|
@@ -58,10 +58,10 @@ const overrideStillStands = async (
|
|
|
58
58
|
export const processSenderCategoryBackApply = async (
|
|
59
59
|
event: SenderCategoryBackApplyEvent,
|
|
60
60
|
log: Logger,
|
|
61
|
-
deps: ProcessSenderCategoryBackApplyDeps
|
|
61
|
+
deps: ProcessSenderCategoryBackApplyDeps,
|
|
62
62
|
): Promise<void> => {
|
|
63
63
|
const { accountConfigId, addressId, normalizedEmail, category } = event;
|
|
64
|
-
const client = deps
|
|
64
|
+
const { client } = deps;
|
|
65
65
|
|
|
66
66
|
if (!(await overrideStillStands(client, event))) {
|
|
67
67
|
log.warn(
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { build } from "esbuild";
|
|
6
|
+
|
|
7
|
+
const LAMBDA_ONLY = [
|
|
8
|
+
/(^|\/)aws-lambda(\/|$)/,
|
|
9
|
+
/(^|\/)@aws-sdk\//,
|
|
10
|
+
/(^|\/)expect-env(\/|$)/,
|
|
11
|
+
/(^|\/)sqs-client(\/|$)/,
|
|
12
|
+
/(^|\/)logger-lambda(\/|$)/,
|
|
13
|
+
/(^|\/)prom-client(\/|$)/,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const ENV_READERS: readonly string[] = [
|
|
17
|
+
// The organize path reaches body sync through the mailbox-service barrel,
|
|
18
|
+
// and body sync reads DEFER_BODY_PARTS itself. A new reader fails here.
|
|
19
|
+
"../mailbox-service/src/body-sync.ts",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const packageDir = fileURLToPath(new URL("..", import.meta.url));
|
|
23
|
+
|
|
24
|
+
const bundleInputs = async () => {
|
|
25
|
+
const { metafile } = await build({
|
|
26
|
+
absWorkingDir: packageDir,
|
|
27
|
+
entryPoints: ["src/process.ts"],
|
|
28
|
+
bundle: true,
|
|
29
|
+
write: false,
|
|
30
|
+
metafile: true,
|
|
31
|
+
platform: "node",
|
|
32
|
+
format: "esm",
|
|
33
|
+
logLevel: "silent",
|
|
34
|
+
});
|
|
35
|
+
return metafile.inputs;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe("the ./process entry", () => {
|
|
39
|
+
it("bundles without the Lambda runtime, the AWS SDK, SQS, expect-env or the Node metrics server", async () => {
|
|
40
|
+
const reached = Object.entries(await bundleInputs()).flatMap(
|
|
41
|
+
([path, input]) => [path, ...input.imports.map((entry) => entry.path)],
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
assert.deepEqual(
|
|
45
|
+
reached.filter((path) =>
|
|
46
|
+
LAMBDA_ONLY.some((pattern) => pattern.test(path)),
|
|
47
|
+
),
|
|
48
|
+
[],
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("reaches no workspace source that reads process.env", async () => {
|
|
53
|
+
const sources = Object.keys(await bundleInputs()).filter(
|
|
54
|
+
(path) => !path.startsWith("<") && !path.includes("node_modules/"),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
assert.deepEqual(
|
|
58
|
+
sources.filter((path) =>
|
|
59
|
+
/\bprocess\.env\b/.test(
|
|
60
|
+
readFileSync(new URL(path, `file://${packageDir}`), "utf8"),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
ENV_READERS,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/src/process.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RemitClient } from "@remit/backend/client";
|
|
2
|
+
import { createLogger } from "@remit/logger";
|
|
3
|
+
import type {
|
|
4
|
+
OrganizeJobEvent,
|
|
5
|
+
SenderCategoryBackApplyEvent,
|
|
6
|
+
} from "./events.js";
|
|
7
|
+
import { processOrganizeJob } from "./handlers/organize-job.js";
|
|
8
|
+
import { processSenderCategoryBackApply } from "./handlers/sender-category-backapply.js";
|
|
9
|
+
|
|
10
|
+
export type ClientFanoutEvent = OrganizeJobEvent | SenderCategoryBackApplyEvent;
|
|
11
|
+
|
|
12
|
+
export interface AccountFanoutContext {
|
|
13
|
+
client: RemitClient<"messageManagement">;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const log = createLogger();
|
|
17
|
+
|
|
18
|
+
export const processAccountFanoutEvent = (
|
|
19
|
+
event: ClientFanoutEvent,
|
|
20
|
+
ctx: AccountFanoutContext,
|
|
21
|
+
): Promise<void> => {
|
|
22
|
+
switch (event.type) {
|
|
23
|
+
case "OrganizeJob":
|
|
24
|
+
return processOrganizeJob(event, log, { client: ctx.client });
|
|
25
|
+
case "SenderCategoryBackApply":
|
|
26
|
+
return processSenderCategoryBackApply(event, log, { client: ctx.client });
|
|
27
|
+
}
|
|
28
|
+
};
|