@mettlecast/domain-runtime 0.2.75 → 0.2.77

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.
@@ -7,6 +7,7 @@ import type { AuditContext } from '../runtime/audit.js';
7
7
  import type { DomainStoreContext } from './store.js';
8
8
  import type { DomainFilesContext } from './files.js';
9
9
  import type { CognitoContext } from './cognito.js';
10
+ import type { SesContext } from './ses.js';
10
11
  /**
11
12
  * The unified domain context injected into every handler.
12
13
  * All fields are always populated by the runtime; no field is ever null.
@@ -50,6 +51,8 @@ export interface DomainContext {
50
51
  files: DomainFilesContext;
51
52
  /** Cognito control-plane client (Admin* operations). Noop when no user pool is configured. */
52
53
  cognito: CognitoContext;
54
+ /** SES email-sending client. Noop when SES_FROM_EMAIL is not set. */
55
+ ses: SesContext;
53
56
  /** URL path parameters from the incoming request, excluding tenantId (e.g. { tableName: 'users' }). */
54
57
  pathParams: Record<string, string>;
55
58
  }
@@ -5,3 +5,4 @@ export type { DomainContext } from './context.js';
5
5
  export type { DomainStoreContext, StoreQueryOptions, StoreQueryResult } from './store.js';
6
6
  export type { DomainFilesContext, FilesPutOptions, FilesListResult } from './files.js';
7
7
  export type { CognitoContext } from './cognito.js';
8
+ export type { SesContext } from './ses.js';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SES email-sending context. Automatically scoped to the configured
3
+ * sending identity — domain code never passes `Source`, region, or
4
+ * the SES client configuration.
5
+ *
6
+ * The runtime creates this when `SES_FROM_EMAIL` is set in the
7
+ * Lambda environment. Domain code accesses it via `ctx.ses`.
8
+ */
9
+ export interface SesContext {
10
+ /**
11
+ * Send an email via SES. The runtime manages the SES client,
12
+ * region, and sending identity — domain code only provides
13
+ * the email content.
14
+ *
15
+ * @param params.to - Recipient email address(es).
16
+ * @param params.subject - Email subject line.
17
+ * @param params.htmlBody - HTML body content.
18
+ * @param params.textBody - Plain-text fallback body.
19
+ * @param params.from - Optional sender override (defaults to env SES_FROM_EMAIL).
20
+ * @param params.replyTo - Optional reply-to address(es).
21
+ * @returns The SES MessageId for the sent email.
22
+ */
23
+ sendEmail(params: {
24
+ to: string | string[];
25
+ subject: string;
26
+ htmlBody?: string;
27
+ textBody?: string;
28
+ from?: string;
29
+ replyTo?: string | string[];
30
+ }): Promise<{
31
+ messageId: string;
32
+ }>;
33
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export type * from './ctx/index.js';
3
3
  export * from './primitives/index.js';
4
4
  export * from './schema/index.js';
5
5
  export * from './runtime/index.js';
6
+ export { initOtel } from './runtime/tracer.js';
package/dist/index.js CHANGED
@@ -4,3 +4,6 @@ export * from './primitives/index.js';
4
4
  export * from './schema/index.js';
5
5
  // Runtime implementations
6
6
  export * from './runtime/index.js';
7
+ // Explicit re-export — `export *` wildcard doesn't reliably surface this
8
+ // for TS module resolution in all configurations.
9
+ export { initOtel } from './runtime/tracer.js';
@@ -7,6 +7,7 @@ import type { IntegrationDefinition } from '../primitives/integration.js';
7
7
  import type { DomainStoreContext } from '../ctx/store.js';
8
8
  import type { DomainFilesContext } from '../ctx/files.js';
9
9
  import type { CognitoContext } from '../ctx/cognito.js';
10
+ import type { SesContext } from '../ctx/ses.js';
10
11
  import type { ActionRegistry } from './actions.js';
11
12
  /**
12
13
  * Full options for hydrateCtx. All fields are optional — defaults are used for
@@ -113,6 +114,8 @@ export interface HydrateOptions {
113
114
  pathParams?: Record<string, string>;
114
115
  /** Override the Cognito context. If omitted, createCognito() is used (reads COGNITO_USER_POOL_ID from env). */
115
116
  cognito?: CognitoContext;
117
+ /** Override the SES context. If omitted, createSes() is used (reads SES_FROM_EMAIL from env). */
118
+ ses?: SesContext;
116
119
  }
117
120
  /**
118
121
  * Hydrates a fully populated DomainContext from a Lambda invocation event and options bag.
@@ -16,6 +16,7 @@ import { extractObservabilityBindings } from './observability-bindings.js';
16
16
  import { createStore, createNoopStore } from './store.js';
17
17
  import { createFiles, createNoopFiles } from './files.js';
18
18
  import { createCognito } from './cognito.js';
19
+ import { createSes } from './ses.js';
19
20
  /**
20
21
  * Hydrates a fully populated DomainContext from a Lambda invocation event and options bag.
21
22
  * All 14 DomainContext fields are populated. Fields with AWS dependencies fall back to
@@ -172,6 +173,7 @@ export async function hydrateCtx(_event, options = {}) {
172
173
  ? createFiles({ bucketName: options.domainBucketName ?? process.env['DOMAIN_BUCKET_NAME'], tenantId: tenant.id, tenantRoleArn: process.env['DOMAIN_TENANT_ROLE_ARN'] })
173
174
  : createNoopFiles());
174
175
  const cognito = options.cognito ?? createCognito();
176
+ const ses = options.ses ?? createSes();
175
177
  ctx = {
176
178
  db,
177
179
  publish,
@@ -192,6 +194,7 @@ export async function hydrateCtx(_event, options = {}) {
192
194
  store,
193
195
  files,
194
196
  cognito,
197
+ ses,
195
198
  pathParams: options.pathParams ?? {},
196
199
  };
197
200
  return ctx;
@@ -62,3 +62,5 @@ export { createStore, createNoopStore } from './store.js';
62
62
  export { createFiles, createNoopFiles } from './files.js';
63
63
  export { createCognito, createNoopCognito } from './cognito.js';
64
64
  export type { CognitoOptions } from './cognito.js';
65
+ export { createSes, createNoopSes } from './ses.js';
66
+ export type { SesOptions } from './ses.js';
@@ -64,3 +64,4 @@ export { healthHandler, readinessHandler } from './health-handler.js';
64
64
  export { createStore, createNoopStore } from './store.js';
65
65
  export { createFiles, createNoopFiles } from './files.js';
66
66
  export { createCognito, createNoopCognito } from './cognito.js';
67
+ export { createSes, createNoopSes } from './ses.js';
@@ -0,0 +1,29 @@
1
+ import type { SesContext } from '../ctx/ses.js';
2
+ /** Options for the SES context factory. */
3
+ export interface SesOptions {
4
+ /**
5
+ * Default from address for outgoing emails. Defaults to process.env.SES_FROM_EMAIL.
6
+ * If not set, the factory returns a noop context that throws on any call.
7
+ */
8
+ fromAddress?: string;
9
+ /**
10
+ * AWS region. Defaults to process.env.SES_AWS_REGION or process.env.AWS_REGION.
11
+ */
12
+ region?: string;
13
+ }
14
+ /**
15
+ * Create a SES-backed SesContext.
16
+ *
17
+ * The SDK client is created lazily on first use. The `fromAddress` is
18
+ * captured at construction time from `SES_FROM_EMAIL` — domain code
19
+ * never needs to pass it.
20
+ *
21
+ * @param options - SesOptions. If fromAddress is not resolved, returns a noop.
22
+ * @returns A SesContext backed by the AWS SDK, or a noop that throws.
23
+ */
24
+ export declare function createSes(options?: SesOptions): SesContext;
25
+ /**
26
+ * Create a noop SesContext for tests or environments without SES.
27
+ * Every method throws with a clear message.
28
+ */
29
+ export declare function createNoopSes(): SesContext;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Create a SES-backed SesContext.
3
+ *
4
+ * The SDK client is created lazily on first use. The `fromAddress` is
5
+ * captured at construction time from `SES_FROM_EMAIL` — domain code
6
+ * never needs to pass it.
7
+ *
8
+ * @param options - SesOptions. If fromAddress is not resolved, returns a noop.
9
+ * @returns A SesContext backed by the AWS SDK, or a noop that throws.
10
+ */
11
+ export function createSes(options = {}) {
12
+ const fromAddress = options.fromAddress ?? process.env['SES_FROM_EMAIL'];
13
+ if (!fromAddress) {
14
+ return createNoopSes();
15
+ }
16
+ const region = options.region ?? process.env['SES_AWS_REGION'] ?? process.env['AWS_REGION'] ?? 'eu-north-1';
17
+ let client;
18
+ async function getClient() {
19
+ if (!client) {
20
+ const sdk = await import('@aws-sdk/client-ses');
21
+ client = new sdk.SESClient({ region });
22
+ }
23
+ return client;
24
+ }
25
+ return {
26
+ async sendEmail(params) {
27
+ const sdk = await import('@aws-sdk/client-ses');
28
+ const c = await getClient();
29
+ const result = await c.send(new sdk.SendEmailCommand({
30
+ Source: params.from ?? fromAddress,
31
+ Destination: {
32
+ ToAddresses: Array.isArray(params.to) ? params.to : [params.to],
33
+ },
34
+ Message: {
35
+ Subject: { Data: params.subject, Charset: 'UTF-8' },
36
+ Body: {
37
+ ...(params.htmlBody ? { Html: { Data: params.htmlBody, Charset: 'UTF-8' } } : {}),
38
+ ...(params.textBody ? { Text: { Data: params.textBody, Charset: 'UTF-8' } } : {}),
39
+ },
40
+ },
41
+ ...(params.replyTo ? {
42
+ ReplyToAddresses: Array.isArray(params.replyTo) ? params.replyTo : [params.replyTo],
43
+ } : {}),
44
+ }));
45
+ return { messageId: result.MessageId };
46
+ },
47
+ };
48
+ }
49
+ /**
50
+ * Create a noop SesContext for tests or environments without SES.
51
+ * Every method throws with a clear message.
52
+ */
53
+ export function createNoopSes() {
54
+ const msg = 'ctx.ses: SES_FROM_EMAIL is not set. Configure the email stack to enable SES operations.';
55
+ return {
56
+ async sendEmail() { throw new Error(msg); },
57
+ };
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mettlecast/domain-runtime",
3
- "version": "0.2.75",
3
+ "version": "0.2.77",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",
@@ -48,6 +48,7 @@
48
48
  "@aws-sdk/client-eventbridge": "^3.0.0",
49
49
  "@aws-sdk/client-lambda": "^3.0.0",
50
50
  "@aws-sdk/client-s3": "^3.0.0",
51
+ "@aws-sdk/client-ses": "^3.0.0",
51
52
  "@aws-sdk/client-secrets-manager": "^3.0.0",
52
53
  "@aws-sdk/client-sfn": "^3.0.0",
53
54
  "@aws-sdk/client-sqs": "^3.0.0",