@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/smtp-worker",
3
- "version": "0.0.13",
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 over
15
- * the three backends (DynamoDB / Postgres / SQLite), selected by
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` and `drizzle-orm/node-postgres` are loaded
27
- // lazily, inside these functions, instead of as static top-level imports. The
28
- // relational branches only ever run when `DATA_BACKEND` selects them never on
29
- // the deployed Lambda but a static import is bundled (and evaluated at module
30
- // load) regardless of whether its branch runs. Both packages are marked
31
- // `external` for the Lambda esbuild build (LAMBDA_ESBUILD_OPTIONS), so esbuild
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(genericDb),
92
- outboxMessage: new OutboxMessageRepo(genericDb),
93
- address: new AddressRepo(genericDb),
94
- message: new DrizzleMessageRepository(messageDataDb),
95
- envelope: new DrizzleEnvelopeRepository(messageDataDb),
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 backends compose in-package below and never
105
- * touch this seam.
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
- * Select the SMTP data ports from the environment, mirroring the other workers'
113
- * `DATA_BACKEND` selection:
114
- *
115
- * - `DATA_BACKEND=postgres` → Drizzle repos over `PG_CONNECTION_URL`.
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 (process.env.DATA_BACKEND === "postgres") return buildPostgresDataPorts();
121
- if (process.env.DATA_BACKEND === "sqlite") return buildSqliteDataPorts();
122
- if (!injectedDataPorts) {
123
- throw new Error(
124
- "no DynamoDB SMTP data ports registered — register them with setSmtpDataPorts() from your composition root",
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` only reads `functionName`, for the
50
- // line it logs per invocation; the prod handler never touches the rest.
51
- const lambdaContext = {
52
- functionName: `e2e-smtp-worker-${queueName}`,
53
- } as Context;
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, lambdaContext)) as
124
+ const result = (await handler(event, nextContext())) as
119
125
  | SQSBatchResponse
120
126
  | undefined;
121
127