@remit/smtp-worker 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -3
- package/src/data-ports.ts +24 -73
- package/src/e2e-processor-shim.ts +12 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/smtp-worker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -30,14 +30,12 @@
|
|
|
30
30
|
"@remit/mail-oauth-service": "*",
|
|
31
31
|
"@remit/mailbox-service": "*",
|
|
32
32
|
"@aws-sdk/client-sqs": "*",
|
|
33
|
-
"drizzle-orm": "^0.45.2",
|
|
34
33
|
"expect-env": "*",
|
|
35
34
|
"@types/aws-lambda": "*"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
37
|
"@aws-sdk/core": "*",
|
|
39
38
|
"@remit/storage-service": "*",
|
|
40
|
-
"@types/pg": "^8.0.0",
|
|
41
39
|
"esbuild": "^0.27.7",
|
|
42
40
|
"p-map": "*"
|
|
43
41
|
},
|
package/src/data-ports.ts
CHANGED
|
@@ -5,15 +5,10 @@ import type {
|
|
|
5
5
|
IMessageRepository,
|
|
6
6
|
IOutboxMessageRepository,
|
|
7
7
|
} from "@remit/data-ports";
|
|
8
|
-
// Type-only: erased at build, so it carries no runtime dependency on
|
|
9
|
-
// drizzle-orm. The value import lives inside the relational builders as a
|
|
10
|
-
// dynamic `import()` — see the comment there for why.
|
|
11
|
-
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
12
8
|
|
|
13
9
|
/**
|
|
14
|
-
* The repositories the SMTP send path reads and writes. One neutral seam
|
|
15
|
-
*
|
|
16
|
-
* `buildDataPortsFromEnv` so the same handler serves every stack.
|
|
10
|
+
* The repositories the SMTP send path reads and writes. One neutral seam so the
|
|
11
|
+
* same handler serves every stack.
|
|
17
12
|
*/
|
|
18
13
|
export interface SmtpDataPorts {
|
|
19
14
|
account: IAccountRepository;
|
|
@@ -23,48 +18,13 @@ export interface SmtpDataPorts {
|
|
|
23
18
|
envelope: IEnvelopeRepository;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
// `@remit/drizzle-service`
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
// leaves this `import()` unresolved in the bundle; it resolves only on the
|
|
33
|
-
// relational path, which runs via `tsx`/the container bundle with both packages
|
|
21
|
+
// `@remit/drizzle-service` is loaded lazily, inside this function, instead of as
|
|
22
|
+
// a static top-level import. A static import is bundled (and evaluated at module
|
|
23
|
+
// load) regardless of whether the branch that uses it ever runs; the package is
|
|
24
|
+
// marked `external` for the Lambda esbuild build (LAMBDA_ESBUILD_OPTIONS), so
|
|
25
|
+
// esbuild leaves this `import()` unresolved in the bundle. It resolves only
|
|
26
|
+
// where the relational composition actually runs, which has the package
|
|
34
27
|
// installed. Mirrors the search-index-worker data-ports seam.
|
|
35
|
-
const buildPostgresDataPorts = async (): Promise<SmtpDataPorts> => {
|
|
36
|
-
const pgConnectionUrl = process.env.PG_CONNECTION_URL;
|
|
37
|
-
if (!pgConnectionUrl) throw new Error("PG_CONNECTION_URL is required");
|
|
38
|
-
|
|
39
|
-
const {
|
|
40
|
-
AccountRepo,
|
|
41
|
-
AddressRepo,
|
|
42
|
-
DrizzleEnvelopeRepository,
|
|
43
|
-
DrizzleMessageRepository,
|
|
44
|
-
messageDataSchema,
|
|
45
|
-
OutboxMessageRepo,
|
|
46
|
-
} = await import("@remit/drizzle-service");
|
|
47
|
-
const { drizzle } = await import("drizzle-orm/node-postgres");
|
|
48
|
-
|
|
49
|
-
const db = drizzle(pgConnectionUrl, { schema: messageDataSchema });
|
|
50
|
-
const genericDb = db as unknown as NodePgDatabase<Record<string, unknown>>;
|
|
51
|
-
const messageDataDb = db as unknown as NodePgDatabase<
|
|
52
|
-
typeof messageDataSchema
|
|
53
|
-
>;
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
account: new AccountRepo(genericDb),
|
|
57
|
-
outboxMessage: new OutboxMessageRepo(genericDb),
|
|
58
|
-
address: new AddressRepo(genericDb),
|
|
59
|
-
message: new DrizzleMessageRepository(messageDataDb),
|
|
60
|
-
envelope: new DrizzleEnvelopeRepository(messageDataDb),
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// The SQLite twin of `buildPostgresDataPorts` (RFC 036): the same Drizzle repos
|
|
65
|
-
// over the one shared SQLite file. `createSqliteDatabase` (and better-sqlite3
|
|
66
|
-
// behind it) is kept external from the DynamoDB Lambda bundle by the same
|
|
67
|
-
// dynamic-import treatment.
|
|
68
28
|
const buildSqliteDataPorts = async (): Promise<SmtpDataPorts> => {
|
|
69
29
|
const sqliteDbPath = process.env.SQLITE_DB_PATH;
|
|
70
30
|
if (!sqliteDbPath) throw new Error("SQLITE_DB_PATH is required");
|
|
@@ -82,17 +42,13 @@ const buildSqliteDataPorts = async (): Promise<SmtpDataPorts> => {
|
|
|
82
42
|
const { db } = await createSqliteDatabase(messageDataSchema, {
|
|
83
43
|
filename: sqliteDbPath,
|
|
84
44
|
});
|
|
85
|
-
const genericDb = db as unknown as NodePgDatabase<Record<string, unknown>>;
|
|
86
|
-
const messageDataDb = db as unknown as NodePgDatabase<
|
|
87
|
-
typeof messageDataSchema
|
|
88
|
-
>;
|
|
89
45
|
|
|
90
46
|
return {
|
|
91
|
-
account: new AccountRepo(
|
|
92
|
-
outboxMessage: new OutboxMessageRepo(
|
|
93
|
-
address: new AddressRepo(
|
|
94
|
-
message: new DrizzleMessageRepository(
|
|
95
|
-
envelope: new DrizzleEnvelopeRepository(
|
|
47
|
+
account: new AccountRepo(db),
|
|
48
|
+
outboxMessage: new OutboxMessageRepo(db),
|
|
49
|
+
address: new AddressRepo(db),
|
|
50
|
+
message: new DrizzleMessageRepository(db),
|
|
51
|
+
envelope: new DrizzleEnvelopeRepository(db),
|
|
96
52
|
};
|
|
97
53
|
};
|
|
98
54
|
|
|
@@ -101,28 +57,23 @@ let injectedDataPorts: SmtpDataPorts | null = null;
|
|
|
101
57
|
/**
|
|
102
58
|
* Register the DynamoDB-backed SMTP data ports from the composition root. The
|
|
103
59
|
* DynamoDB composition lives outside this shared, open-core module and is never
|
|
104
|
-
* imported here. The relational
|
|
105
|
-
*
|
|
60
|
+
* imported here. The relational backend composes in-package above and never
|
|
61
|
+
* touches this seam.
|
|
106
62
|
*/
|
|
107
63
|
export const setSmtpDataPorts = (ports: SmtpDataPorts): void => {
|
|
108
64
|
injectedDataPorts = ports;
|
|
109
65
|
};
|
|
110
66
|
|
|
111
67
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* - `DATA_BACKEND=sqlite` → Drizzle repos over the shared `SQLITE_DB_PATH` file.
|
|
117
|
-
* - otherwise → the DynamoDB ElectroDB ports the composition root injected.
|
|
68
|
+
* The SMTP data ports for this process: the ones a composition root registered,
|
|
69
|
+
* or — for a process that registered none — the SQLite composition this build
|
|
70
|
+
* contains. `SQLITE_DB_PATH` is that composition's precondition, not a backend
|
|
71
|
+
* selection.
|
|
118
72
|
*/
|
|
119
73
|
export const buildDataPortsFromEnv = async (): Promise<SmtpDataPorts> => {
|
|
120
|
-
if (
|
|
121
|
-
if (process.env.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
return injectedDataPorts;
|
|
74
|
+
if (injectedDataPorts) return injectedDataPorts;
|
|
75
|
+
if (process.env.SQLITE_DB_PATH) return buildSqliteDataPorts();
|
|
76
|
+
throw new Error(
|
|
77
|
+
"no SMTP data ports registered — register them with setSmtpDataPorts() from your composition root",
|
|
78
|
+
);
|
|
128
79
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import {
|
|
3
4
|
DeleteMessageCommand,
|
|
4
5
|
ReceiveMessageCommand,
|
|
@@ -46,11 +47,16 @@ process.on("SIGTERM", () => {
|
|
|
46
47
|
isShuttingDown = true;
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
// Minimal Lambda Context: `withTelemetry`
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// Minimal Lambda Context: `withTelemetry` reads `functionName` for the line it
|
|
51
|
+
// logs per invocation and `awsRequestId` for the scope it opens around it, so
|
|
52
|
+
// every line one batch produces shares a `requestId` (deploy/vps/README.md,
|
|
53
|
+
// "Logs"). Minted per batch, like the production poller does — a context built
|
|
54
|
+
// once would put the whole run under one id, which correlates nothing.
|
|
55
|
+
const nextContext = (): Context =>
|
|
56
|
+
({
|
|
57
|
+
functionName: `e2e-smtp-worker-${queueName}`,
|
|
58
|
+
awsRequestId: randomUUID(),
|
|
59
|
+
}) as Context;
|
|
54
60
|
|
|
55
61
|
const pollQueue = async (): Promise<void> => {
|
|
56
62
|
log.info("Worker started, polling...", { maxMessages });
|
|
@@ -115,7 +121,7 @@ const pollQueue = async (): Promise<void> => {
|
|
|
115
121
|
})),
|
|
116
122
|
};
|
|
117
123
|
|
|
118
|
-
const result = (await handler(event,
|
|
124
|
+
const result = (await handler(event, nextContext())) as
|
|
119
125
|
| SQSBatchResponse
|
|
120
126
|
| undefined;
|
|
121
127
|
|