@vyriy/handler 0.5.6 → 0.6.5

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/README.md CHANGED
@@ -122,6 +122,42 @@ export const handler = sns(async (event) => {
122
122
  });
123
123
  ```
124
124
 
125
+ Use a prebuilt DynamoDB Streams handler chain:
126
+
127
+ ```ts
128
+ import { dynamodb } from '@vyriy/handler';
129
+
130
+ export const handler = dynamodb(async (event) => {
131
+ for (const record of event.Records) {
132
+ console.info(record.eventName);
133
+ }
134
+ });
135
+ ```
136
+
137
+ Use a prebuilt S3 event handler chain:
138
+
139
+ ```ts
140
+ import { s3 } from '@vyriy/handler';
141
+
142
+ export const handler = s3(async (event) => {
143
+ for (const record of event.Records) {
144
+ console.info(record.s3.bucket.name, record.s3.object.key);
145
+ }
146
+ });
147
+ ```
148
+
149
+ Use a prebuilt SES receipt handler chain:
150
+
151
+ ```ts
152
+ import { ses } from '@vyriy/handler';
153
+
154
+ export const handler = ses(async (event) => {
155
+ for (const record of event.Records) {
156
+ console.info(record.ses.mail.messageId, record.ses.mail.source);
157
+ }
158
+ });
159
+ ```
160
+
125
161
  Use a prebuilt schedule handler chain:
126
162
 
127
163
  ```ts
@@ -132,6 +168,16 @@ export const handler = schedule(async (event) => {
132
168
  });
133
169
  ```
134
170
 
171
+ Use a prebuilt EventBridge custom event handler chain:
172
+
173
+ ```ts
174
+ import { eventBridge } from '@vyriy/handler';
175
+
176
+ export const handler = eventBridge(async (event) => {
177
+ console.info('EventBridge event:', event.source, event['detail-type'], event.detail);
178
+ });
179
+ ```
180
+
135
181
  Compose a custom handler pipeline from individual helpers:
136
182
 
137
183
  ```ts
@@ -193,6 +239,18 @@ export const handler = compose(
193
239
  - `streamApi`
194
240
  Response streaming API Gateway chain with the same wrapper behavior as `api`. Handlers receive `(event, responseStream, context)` and write directly to the Lambda response stream.
195
241
 
242
+ - `dynamodb`
243
+ DynamoDB Streams chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
244
+
245
+ - `eventBridge`
246
+ EventBridge custom event chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
247
+
248
+ - `s3`
249
+ S3 event chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
250
+
251
+ - `ses`
252
+ SES receipt rule chain for incoming email processing with logging, timeout handling, context setup, smoke checks, and rethrown errors.
253
+
196
254
  - `schedule`
197
255
  EventBridge schedule chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
198
256
 
@@ -364,7 +422,7 @@ export const handler = withSmoke()(async () => {
364
422
  });
365
423
  ```
366
424
 
367
- `withSmoke()` is used by the API, schedule, SNS, and SQS chains.
425
+ `withSmoke()` is used by the API, DynamoDB Streams, S3, SES receipt, schedule, SNS, and SQS chains.
368
426
 
369
427
  ## Types
370
428
 
@@ -377,5 +435,6 @@ import type { Context, Decorator, Handler, HandlerParams, Response } from '@vyri
377
435
  ## Notes
378
436
 
379
437
  - `api` includes API-specific wrappers such as healthcheck handling, default headers, and CORS preflight handling
380
- - `schedule`, `sns`, and `sqs` use `withError()` so failures are rethrown for event-source retry behavior
438
+ - `dynamodb`, `s3`, `ses`, `schedule`, `sns`, and `sqs` use `withError()` so failures are rethrown for event-source retry behavior
439
+ - `ses` targets SES receipt rule Lambda events for incoming email; SES event publishing notifications can still be handled through the `sns` chain when delivered via SNS
381
440
  - `withSmoke()` delegates matching to `@vyriy/smoke` and returns its API Gateway-compatible response
package/api.js CHANGED
@@ -9,16 +9,16 @@ import { streamWithHeaders, withHeaders } from './wrapper/headers.js';
9
9
  import { streamWithCors, withCors } from './wrapper/cors.js';
10
10
  import { streamWithChaos, withChaos } from './wrapper/chaos.js';
11
11
  export const api = compose(withApiError(), withLogger(), withTimeout(), withContext(), withSmoke(), withHealthcheck(), withHeaders({
12
- 'Access-Control-Allow-Origin': '*',
13
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
14
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-Api-Key, Accept, User-Agent, X-CSRF-Token',
15
- 'Content-Type': 'application/json',
16
- 'X-Robots-Tag': 'noindex, nofollow',
12
+ 'access-control-allow-origin': '*',
13
+ 'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
14
+ 'access-control-allow-headers': 'Content-Type, Authorization, X-Requested-With, X-Api-Key, Accept, User-Agent, X-CSRF-Token',
15
+ 'content-type': 'application/json',
16
+ 'x-robots-tag': 'noindex, nofollow',
17
17
  }), withCors(), withChaos());
18
18
  export const streamApi = streamCompose(streamWithApiError(), streamWithLogger(), streamWithTimeout(), streamWithContext(), streamWithSmoke(), streamWithHealthcheck(), streamWithHeaders({
19
- 'Access-Control-Allow-Origin': '*',
20
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
21
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-Api-Key, Accept, User-Agent, X-CSRF-Token',
22
- 'Content-Type': 'application/json',
23
- 'X-Robots-Tag': 'noindex, nofollow',
19
+ 'access-control-allow-origin': '*',
20
+ 'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
21
+ 'access-control-allow-headers': 'Content-Type, Authorization, X-Requested-With, X-Api-Key, Accept, User-Agent, X-CSRF-Token',
22
+ 'content-type': 'application/json',
23
+ 'x-robots-tag': 'noindex, nofollow',
24
24
  }), streamWithCors(), streamWithChaos());
package/dynamodb.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { DynamoDBStreamEvent } from 'aws-lambda';
2
+ export declare const dynamodb: import("./types.js").Decorator<DynamoDBStreamEvent, void>;
package/dynamodb.js ADDED
@@ -0,0 +1,7 @@
1
+ import { compose } from './compose.js';
2
+ import { withError } from './wrapper/error.js';
3
+ import { withLogger } from './wrapper/logger.js';
4
+ import { withTimeout } from './wrapper/timeout.js';
5
+ import { withContext } from './wrapper/context.js';
6
+ import { withSmoke } from './wrapper/smoke.js';
7
+ export const dynamodb = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
@@ -0,0 +1,2 @@
1
+ import type { EventBridgeEvent } from 'aws-lambda';
2
+ export declare const eventBridge: import("./types.js").Decorator<EventBridgeEvent<string, unknown>, void>;
package/eventBridge.js ADDED
@@ -0,0 +1,7 @@
1
+ import { compose } from './compose.js';
2
+ import { withError } from './wrapper/error.js';
3
+ import { withLogger } from './wrapper/logger.js';
4
+ import { withTimeout } from './wrapper/timeout.js';
5
+ import { withContext } from './wrapper/context.js';
6
+ import { withSmoke } from './wrapper/smoke.js';
7
+ export const eventBridge = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
package/index.d.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  export * from './api.js';
2
2
  export * from './compose.js';
3
+ export * from './dynamodb.js';
4
+ export * from './eventBridge.js';
3
5
  export * from './factory.js';
6
+ export * from './s3.js';
4
7
  export * from './schedule.js';
8
+ export * from './ses.js';
5
9
  export * from './sns.js';
6
10
  export * from './sqs.js';
7
11
  export * from './wrapper/context.js';
package/index.js CHANGED
@@ -1,7 +1,11 @@
1
1
  export * from './api.js';
2
2
  export * from './compose.js';
3
+ export * from './dynamodb.js';
4
+ export * from './eventBridge.js';
3
5
  export * from './factory.js';
6
+ export * from './s3.js';
4
7
  export * from './schedule.js';
8
+ export * from './ses.js';
5
9
  export * from './sns.js';
6
10
  export * from './sqs.js';
7
11
  export * from './wrapper/context.js';
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@vyriy/handler",
3
- "version": "0.5.6",
3
+ "version": "0.6.5",
4
4
  "description": "Composable AWS Lambda handler chains and wrappers for Vyriy projects",
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@types/aws-lambda": "^8.10.161",
8
- "@vyriy/chaos": "0.5.6",
9
- "@vyriy/config": "0.5.6",
10
- "@vyriy/logger": "0.5.6",
11
- "@vyriy/smoke": "0.5.6",
12
- "@vyriy/timeout": "0.5.6"
7
+ "@types/aws-lambda": "^8.10.162",
8
+ "@vyriy/chaos": "0.6.5",
9
+ "@vyriy/config": "0.6.5",
10
+ "@vyriy/logger": "0.6.5",
11
+ "@vyriy/smoke": "0.6.5",
12
+ "@vyriy/timeout": "0.6.5"
13
13
  },
14
14
  "agents": "./AGENTS.md",
15
15
  "license": "MIT",
@@ -46,6 +46,26 @@
46
46
  "import": "./compose.js",
47
47
  "default": "./compose.js"
48
48
  },
49
+ "./dynamodb": {
50
+ "types": "./dynamodb.d.ts",
51
+ "import": "./dynamodb.js",
52
+ "default": "./dynamodb.js"
53
+ },
54
+ "./dynamodb.js": {
55
+ "types": "./dynamodb.d.ts",
56
+ "import": "./dynamodb.js",
57
+ "default": "./dynamodb.js"
58
+ },
59
+ "./eventBridge": {
60
+ "types": "./eventBridge.d.ts",
61
+ "import": "./eventBridge.js",
62
+ "default": "./eventBridge.js"
63
+ },
64
+ "./eventBridge.js": {
65
+ "types": "./eventBridge.d.ts",
66
+ "import": "./eventBridge.js",
67
+ "default": "./eventBridge.js"
68
+ },
49
69
  "./factory": {
50
70
  "types": "./factory.d.ts",
51
71
  "import": "./factory.js",
@@ -66,6 +86,16 @@
66
86
  "import": "./index.js",
67
87
  "default": "./index.js"
68
88
  },
89
+ "./s3": {
90
+ "types": "./s3.d.ts",
91
+ "import": "./s3.js",
92
+ "default": "./s3.js"
93
+ },
94
+ "./s3.js": {
95
+ "types": "./s3.d.ts",
96
+ "import": "./s3.js",
97
+ "default": "./s3.js"
98
+ },
69
99
  "./schedule": {
70
100
  "types": "./schedule.d.ts",
71
101
  "import": "./schedule.js",
@@ -76,6 +106,16 @@
76
106
  "import": "./schedule.js",
77
107
  "default": "./schedule.js"
78
108
  },
109
+ "./ses": {
110
+ "types": "./ses.d.ts",
111
+ "import": "./ses.js",
112
+ "default": "./ses.js"
113
+ },
114
+ "./ses.js": {
115
+ "types": "./ses.d.ts",
116
+ "import": "./ses.js",
117
+ "default": "./ses.js"
118
+ },
79
119
  "./sns": {
80
120
  "types": "./sns.d.ts",
81
121
  "import": "./sns.js",
package/s3.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { S3Event } from 'aws-lambda';
2
+ export declare const s3: import("./types.js").Decorator<S3Event, void>;
package/s3.js ADDED
@@ -0,0 +1,7 @@
1
+ import { compose } from './compose.js';
2
+ import { withError } from './wrapper/error.js';
3
+ import { withLogger } from './wrapper/logger.js';
4
+ import { withTimeout } from './wrapper/timeout.js';
5
+ import { withContext } from './wrapper/context.js';
6
+ import { withSmoke } from './wrapper/smoke.js';
7
+ export const s3 = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
package/ses.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { SESEvent } from 'aws-lambda';
2
+ export declare const ses: import("./types.js").Decorator<SESEvent, void>;
package/ses.js ADDED
@@ -0,0 +1,7 @@
1
+ import { compose } from './compose.js';
2
+ import { withError } from './wrapper/error.js';
3
+ import { withLogger } from './wrapper/logger.js';
4
+ import { withTimeout } from './wrapper/timeout.js';
5
+ import { withContext } from './wrapper/context.js';
6
+ import { withSmoke } from './wrapper/smoke.js';
7
+ export const ses = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
@@ -1,9 +1,10 @@
1
1
  import { factory, streamFactory } from '../factory.js';
2
2
  import { responseStream } from './stream.js';
3
+ const normalizeHeaders = (headers) => Object.fromEntries(Object.entries(headers ?? {}).map(([key, value]) => [key.toLowerCase(), value]));
3
4
  const mergeHeaders = (result, options) => {
4
5
  result.headers = {
5
- ...options,
6
- ...result.headers,
6
+ ...normalizeHeaders(options),
7
+ ...normalizeHeaders(result.headers),
7
8
  };
8
9
  return result;
9
10
  };
@@ -13,5 +14,5 @@ export const withHeaders = factory(async (handler, args, options = {}) => {
13
14
  });
14
15
  export const streamWithHeaders = streamFactory(async (handler, args, options = {}) => {
15
16
  const [event, stream, context] = args;
16
- await handler(event, responseStream(stream, { headers: options }), context);
17
+ await handler(event, responseStream(stream, { headers: normalizeHeaders(options) }), context);
17
18
  });
package/wrapper/stream.js CHANGED
@@ -1,4 +1,7 @@
1
- const getContentType = (headers) => headers?.['content-type'] ?? headers?.['Content-Type'];
1
+ const getContentType = (headers) => {
2
+ const contentType = Object.entries(headers ?? {}).find(([key]) => key.toLowerCase() === 'content-type')?.[1];
3
+ return typeof contentType === 'string' ? contentType : undefined;
4
+ };
2
5
  export const responseStream = (stream, metadata = {}) => {
3
6
  const runtimeStream = globalThis.awslambda?.HttpResponseStream?.from?.(stream, {
4
7
  headers: metadata.headers,
@@ -9,7 +12,7 @@ export const responseStream = (stream, metadata = {}) => {
9
12
  }
10
13
  const contentType = getContentType(metadata.headers);
11
14
  if (contentType) {
12
- stream.setContentType?.(String(contentType));
15
+ stream.setContentType?.(contentType);
13
16
  }
14
17
  return stream;
15
18
  };