@remit/account-worker 0.0.1 → 0.0.3
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
CHANGED
|
@@ -1,50 +1,74 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
2
|
+
"name": "@remit/account-worker",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"default": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"./cascade": {
|
|
12
|
+
"types": "./src/cascade.ts",
|
|
13
|
+
"default": "./src/cascade.ts"
|
|
14
|
+
},
|
|
15
|
+
"./deletion-capabilities": {
|
|
16
|
+
"types": "./src/deletion-capabilities.ts",
|
|
17
|
+
"default": "./src/deletion-capabilities.ts"
|
|
18
|
+
},
|
|
19
|
+
"./fanout": {
|
|
20
|
+
"types": "./src/handlers/account-fanout.ts",
|
|
21
|
+
"default": "./src/handlers/account-fanout.ts"
|
|
22
|
+
},
|
|
23
|
+
"./finalize": {
|
|
24
|
+
"types": "./src/handlers/account-finalize.ts",
|
|
25
|
+
"default": "./src/handlers/account-finalize.ts"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"bundle": "esbuild src/index.ts --sourcemap --bundle --platform=node --format=esm --outfile=dist/index.js",
|
|
30
|
+
"test:typecheck": "tsgo --noEmit",
|
|
31
|
+
"test:run": "node --env-file=../../localhost-test-unit.env --test 'src/**/*.test.ts'",
|
|
32
|
+
"test": "npm run test:typecheck && npm run test:run"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@remit/data-ports": "*",
|
|
36
|
+
"@remit/logger-lambda": "*",
|
|
37
|
+
"@remit/drizzle-service": "*",
|
|
38
|
+
"@remit/backend": "*",
|
|
39
|
+
"@remit/sqs-client": "*",
|
|
40
|
+
"@remit/storage-service": "*",
|
|
41
|
+
"@remit/electrodb-entities": "*",
|
|
42
|
+
"@remit/search-index-worker": "*"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@aws-sdk/client-cloudfront": "*",
|
|
46
|
+
"@aws-sdk/client-cognito-identity-provider": "*",
|
|
47
|
+
"@aws-sdk/client-dynamodb": "*",
|
|
48
|
+
"@aws-sdk/client-s3": "*",
|
|
49
|
+
"@aws-sdk/client-sqs": "*",
|
|
50
|
+
"@aws-sdk/core": "*",
|
|
51
|
+
"@aws-sdk/lib-dynamodb": "*",
|
|
52
|
+
"@aws-sdk/lib-storage": "*",
|
|
53
|
+
"@aws-sdk/s3-request-presigner": "*",
|
|
54
|
+
"@remit/domain-enums": "*",
|
|
55
|
+
"@remit/search-service": "*",
|
|
56
|
+
"@types/archiver": "*",
|
|
57
|
+
"@types/aws-lambda": "*",
|
|
58
|
+
"archiver": "*",
|
|
59
|
+
"aws-sdk-client-mock": "*",
|
|
60
|
+
"electrodb": "*",
|
|
61
|
+
"esbuild": "^0.27.7",
|
|
62
|
+
"expect-env": "*",
|
|
63
|
+
"p-map": "*"
|
|
64
|
+
},
|
|
65
|
+
"license": "MIT",
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/remit-mail/remit.git",
|
|
72
|
+
"directory": "packages/account-worker"
|
|
73
|
+
}
|
|
50
74
|
}
|
package/src/config.ts
CHANGED
|
@@ -6,15 +6,13 @@ import { createStorageService } from "@remit/storage-service/s3";
|
|
|
6
6
|
import { env } from "expect-env";
|
|
7
7
|
import type { CascadeServices } from "./cascade.js";
|
|
8
8
|
|
|
9
|
-
const remitClient = await getClient();
|
|
10
|
-
|
|
11
9
|
export const sqsClient = new SQSClient({
|
|
12
10
|
credentials: resolveSqsCredentials(),
|
|
13
11
|
});
|
|
14
12
|
|
|
15
13
|
// SQS env vars are lazy-evaluated. The fanout worker needs them at handler time;
|
|
16
|
-
// the finalize worker imports `
|
|
17
|
-
// queue, so its Lambda doesn't carry the env vars. Eager evaluation here would
|
|
14
|
+
// the finalize worker imports `getCascadeServices` from this module but talks to
|
|
15
|
+
// no queue, so its Lambda doesn't carry the env vars. Eager evaluation here would
|
|
18
16
|
// crash finalize at module load — getters defer the read to the fanout-only call
|
|
19
17
|
// sites.
|
|
20
18
|
export const getSearchIndexQueueUrl = (): string =>
|
|
@@ -31,34 +29,42 @@ export const graceSeconds = graceSecondsRaw
|
|
|
31
29
|
? Number.parseInt(graceSecondsRaw, 10)
|
|
32
30
|
: 60;
|
|
33
31
|
|
|
32
|
+
// The RemitClient is resolved lazily and cached, not at module load: the
|
|
33
|
+
// DynamoDB backend is injected by the composition root before the first cascade
|
|
34
|
+
// runs, so building at import time would race the registration.
|
|
35
|
+
let cascadeServicesPromise: Promise<CascadeServices> | null = null;
|
|
36
|
+
|
|
34
37
|
// Every cascade service is a `RemitClient` repository, so the whole enumeration
|
|
35
38
|
// runs on whatever backend `getClient()` selected — DynamoDB (ElectroDB),
|
|
36
39
|
// Postgres, or SQLite (Drizzle). Filter/FilterAnchor/Label/MessageLabel (Smart
|
|
37
40
|
// Organize, RFC 034) are present on all three backends via the client, so they
|
|
38
41
|
// need no backend-specific wiring here.
|
|
39
|
-
export const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
export const getCascadeServices = (): Promise<CascadeServices> => {
|
|
43
|
+
if (!cascadeServicesPromise) {
|
|
44
|
+
cascadeServicesPromise = getClient().then((remitClient) => ({
|
|
45
|
+
accountConfigService: remitClient.accountConfig,
|
|
46
|
+
accountService: remitClient.account,
|
|
47
|
+
addressService: remitClient.address,
|
|
48
|
+
mailboxService: remitClient.mailbox,
|
|
49
|
+
messageService: remitClient.message,
|
|
50
|
+
messageFlagService: remitClient.messageFlag,
|
|
51
|
+
envelopeService: remitClient.envelope,
|
|
52
|
+
outboxMessageService: remitClient.outboxMessage,
|
|
53
|
+
threadMessageService: remitClient.threadMessage,
|
|
54
|
+
mailboxLockService: remitClient.mailboxLock,
|
|
55
|
+
messagePlacementMoveService: remitClient.placementMove,
|
|
56
|
+
messageFlagPushService: remitClient.flagPush,
|
|
57
|
+
accountExportRequestService: remitClient.accountExportRequest,
|
|
58
|
+
accountSettingService: remitClient.accountSetting,
|
|
59
|
+
filterService: remitClient.filter,
|
|
60
|
+
filterAnchorService: remitClient.filterAnchor,
|
|
61
|
+
labelService: remitClient.label,
|
|
62
|
+
messageLabelService: remitClient.messageLabel,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
return cascadeServicesPromise;
|
|
58
66
|
};
|
|
59
67
|
|
|
60
|
-
export const accountConfigService = cascadeServices.accountConfigService;
|
|
61
|
-
|
|
62
68
|
let storageService: StorageService | null = null;
|
|
63
69
|
export const getStorageService = (): StorageService => {
|
|
64
70
|
if (!storageService) {
|
|
@@ -6,15 +6,16 @@ import type { CascadeEntity } from "./cascade.js";
|
|
|
6
6
|
* else — enumerating the AccountConfig's rows, the FIFO purge orchestration, the
|
|
7
7
|
* queue sends — is backend-neutral and lives in the handlers unchanged.
|
|
8
8
|
*
|
|
9
|
-
* Two composition roots build this
|
|
10
|
-
* `@remit/backend`'s `compose-{dynamodb,postgres,sqlite}`:
|
|
9
|
+
* Two composition roots build this:
|
|
11
10
|
*
|
|
12
|
-
* -
|
|
11
|
+
* - The DynamoDB composition (AWS custody stack): Cognito global sign-out,
|
|
13
12
|
* CloudFront invalidation, raw-S3 prefix delete, DynamoDB `BatchWriteItem`
|
|
14
|
-
* cascade. Byte-for-byte the pre-split behavior.
|
|
15
|
-
*
|
|
16
|
-
* graph never reaches
|
|
17
|
-
*
|
|
13
|
+
* cascade. Byte-for-byte the pre-split behavior. It imports the AWS SDK /
|
|
14
|
+
* ElectroDB graph and lives outside this shared, open-core module; it is
|
|
15
|
+
* injected here through `setDeletionCapabilities` so that graph never reaches
|
|
16
|
+
* the relational tree.
|
|
17
|
+
* - `compose-relational.ts` (self-host stack, RFC 035/036), selected by
|
|
18
|
+
* `DATA_BACKEND`: no-op sign-out
|
|
18
19
|
* (deleting the AccountConfig severs the session's data resolution; there is
|
|
19
20
|
* no CDN to sign out of), no-op invalidation (Caddy proxies `/content/*`
|
|
20
21
|
* straight to the backend with no cache), filesystem prefix delete, and the
|
|
@@ -60,6 +61,20 @@ const isRelationalBackend = (): boolean => {
|
|
|
60
61
|
return backend === "postgres" || backend === "sqlite";
|
|
61
62
|
};
|
|
62
63
|
|
|
64
|
+
let injectedCapabilities: DeletionCapabilities | null = null;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Register the DynamoDB-backed deletion capabilities from the composition root.
|
|
68
|
+
* The DynamoDB composition imports the AWS SDK / ElectroDB graph and lives
|
|
69
|
+
* outside this shared, open-core module; it is never imported here. The
|
|
70
|
+
* relational backend composes in-package below and never touches this seam.
|
|
71
|
+
*/
|
|
72
|
+
export const setDeletionCapabilities = (
|
|
73
|
+
capabilities: DeletionCapabilities,
|
|
74
|
+
): void => {
|
|
75
|
+
injectedCapabilities = capabilities;
|
|
76
|
+
};
|
|
77
|
+
|
|
63
78
|
const buildDeletionCapabilitiesFromEnv =
|
|
64
79
|
async (): Promise<DeletionCapabilities> => {
|
|
65
80
|
if (isRelationalBackend()) {
|
|
@@ -68,15 +83,12 @@ const buildDeletionCapabilitiesFromEnv =
|
|
|
68
83
|
);
|
|
69
84
|
return buildRelationalDeletionCapabilities();
|
|
70
85
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
"./compose-dynamodb.js" as string
|
|
78
|
-
)) as ComposeDynamoDBModule;
|
|
79
|
-
return buildDynamoDBDeletionCapabilities();
|
|
86
|
+
if (!injectedCapabilities) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"no DynamoDB deletion capabilities registered — register them with setDeletionCapabilities() from your composition root",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return injectedCapabilities;
|
|
80
92
|
};
|
|
81
93
|
|
|
82
94
|
let capabilitiesPromise: Promise<DeletionCapabilities> | undefined;
|
|
@@ -3,7 +3,7 @@ import type { Logger } from "@remit/logger-lambda";
|
|
|
3
3
|
import type { StorageService } from "@remit/storage-service";
|
|
4
4
|
import { ZipArchive } from "archiver";
|
|
5
5
|
import type { CascadeServices } from "../cascade.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getCascadeServices, getStorageService } from "../config.js";
|
|
7
7
|
import type { AccountExportEvent } from "../events.js";
|
|
8
8
|
|
|
9
9
|
const THIRTY_DAYS_MILLIS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -13,8 +13,8 @@ export interface ProcessAccountExportDeps {
|
|
|
13
13
|
storageService: StorageService;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const defaultDeps = (): ProcessAccountExportDeps => ({
|
|
17
|
-
services:
|
|
16
|
+
const defaultDeps = async (): Promise<ProcessAccountExportDeps> => ({
|
|
17
|
+
services: await getCascadeServices(),
|
|
18
18
|
storageService: getStorageService(),
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -66,10 +66,10 @@ const appendMessageBodies = async (
|
|
|
66
66
|
export const processAccountExport = async (
|
|
67
67
|
event: AccountExportEvent,
|
|
68
68
|
log: Logger,
|
|
69
|
-
deps
|
|
69
|
+
deps?: ProcessAccountExportDeps,
|
|
70
70
|
): Promise<void> => {
|
|
71
71
|
const { accountConfigId, accountExportRequestId } = event;
|
|
72
|
-
const { services, storageService } = deps;
|
|
72
|
+
const { services, storageService } = deps ?? (await defaultDeps());
|
|
73
73
|
const { accountExportRequestService } = services;
|
|
74
74
|
|
|
75
75
|
await accountExportRequestService.get(accountExportRequestId);
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { SendMessageCommand, type SQSClient } from "@aws-sdk/client-sqs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createLogger,
|
|
4
|
+
type Logger,
|
|
5
|
+
withTelemetry,
|
|
6
|
+
} from "@remit/logger-lambda";
|
|
3
7
|
import type { SQSBatchResponse, SQSEvent, SQSHandler } from "aws-lambda";
|
|
4
8
|
import type { CascadeServices } from "../cascade.js";
|
|
5
9
|
import { enumerateCascadeEntities } from "../cascade.js";
|
|
6
10
|
import {
|
|
7
|
-
cascadeServices,
|
|
8
11
|
getAccountFinalizeQueueUrl,
|
|
9
12
|
getAccountPurgeDeleteQueueUrl,
|
|
13
|
+
getCascadeServices,
|
|
10
14
|
getImapWorkerQueueUrl,
|
|
11
15
|
sqsClient,
|
|
12
16
|
} from "../config.js";
|
|
@@ -44,7 +48,7 @@ export const processAccountFanout = async (
|
|
|
44
48
|
log: Logger,
|
|
45
49
|
deps: ProcessAccountFanoutDeps = {},
|
|
46
50
|
): Promise<void> => {
|
|
47
|
-
const services = deps.services ??
|
|
51
|
+
const services = deps.services ?? (await getCascadeServices());
|
|
48
52
|
const sqs = deps.sqs ?? sqsClient;
|
|
49
53
|
|
|
50
54
|
if (event.type === "AccountDataPurge") {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { inspect } from "node:util";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
createLogger,
|
|
4
|
+
type Logger,
|
|
5
|
+
withTelemetry,
|
|
6
|
+
} from "@remit/logger-lambda";
|
|
3
7
|
import type { SQSEvent, SQSHandler } from "aws-lambda";
|
|
4
8
|
import {
|
|
5
9
|
type CascadeEntity,
|
|
@@ -7,7 +11,7 @@ import {
|
|
|
7
11
|
collectMessageChildEntities,
|
|
8
12
|
enumerateCascadeEntities,
|
|
9
13
|
} from "../cascade.js";
|
|
10
|
-
import {
|
|
14
|
+
import { getCascadeServices } from "../config.js";
|
|
11
15
|
import {
|
|
12
16
|
type DeletionCapabilities,
|
|
13
17
|
getDeletionCapabilities,
|
|
@@ -30,7 +34,7 @@ const resolveDeps = async (
|
|
|
30
34
|
services: CascadeServices;
|
|
31
35
|
}> => ({
|
|
32
36
|
capabilities: deps.capabilities ?? (await getDeletionCapabilities()),
|
|
33
|
-
services: deps.cascadeServices ??
|
|
37
|
+
services: deps.cascadeServices ?? (await getCascadeServices()),
|
|
34
38
|
});
|
|
35
39
|
|
|
36
40
|
/**
|
|
@@ -10,10 +10,10 @@ import type { SearchIndexMessage } from "@remit/search-index-worker";
|
|
|
10
10
|
import { Entity } from "electrodb";
|
|
11
11
|
import type { CascadeServices } from "../cascade.js";
|
|
12
12
|
import {
|
|
13
|
-
cascadeServices as defaultCascadeServices,
|
|
14
13
|
dataBackend as defaultDataBackend,
|
|
15
14
|
sqsClient as defaultSqsClient,
|
|
16
15
|
getAccountPurgeDeleteQueueUrl,
|
|
16
|
+
getCascadeServices,
|
|
17
17
|
getSearchIndexQueueUrl,
|
|
18
18
|
} from "../config.js";
|
|
19
19
|
import type {
|
|
@@ -161,7 +161,7 @@ export const processAccountDataPurge = async (
|
|
|
161
161
|
deps: ProcessPurgeFanoutDeps = {},
|
|
162
162
|
): Promise<void> => {
|
|
163
163
|
const { accountId, accountConfigId } = event;
|
|
164
|
-
const services = deps.services ??
|
|
164
|
+
const services = deps.services ?? (await getCascadeServices());
|
|
165
165
|
const sqs = deps.sqs ?? defaultSqsClient;
|
|
166
166
|
const accountPurgeDeleteQueueUrl =
|
|
167
167
|
deps.accountPurgeDeleteQueueUrl ?? getAccountPurgeDeleteQueueUrl();
|