@vyriy/handler 0.7.8 → 0.8.1

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.
Files changed (168) hide show
  1. package/README.md +375 -253
  2. package/api/api.d.ts +10 -0
  3. package/api/api.js +19 -0
  4. package/api/http/compose/compose.d.ts +2 -0
  5. package/api/http/compose/compose.js +1 -0
  6. package/api/http/compose/index.d.ts +1 -0
  7. package/api/http/compose/index.js +1 -0
  8. package/api/http/factory/factory.d.ts +2 -0
  9. package/api/http/factory/factory.js +1 -0
  10. package/api/http/factory/index.d.ts +1 -0
  11. package/api/http/factory/index.js +1 -0
  12. package/api/http/http.d.ts +3 -0
  13. package/api/http/http.js +14 -0
  14. package/api/http/index.d.ts +3 -0
  15. package/api/http/index.js +2 -0
  16. package/api/http/types.d.ts +18 -0
  17. package/api/http/wrappers/cors.d.ts +1 -0
  18. package/api/http/wrappers/cors.js +9 -0
  19. package/api/http/wrappers/error.d.ts +3 -0
  20. package/api/http/wrappers/error.js +21 -0
  21. package/api/http/wrappers/headers.d.ts +1 -0
  22. package/api/http/wrappers/headers.js +8 -0
  23. package/api/http/wrappers/healthcheck.d.ts +5 -0
  24. package/api/http/wrappers/healthcheck.js +19 -0
  25. package/api/http/wrappers/index.d.ts +5 -0
  26. package/api/http/wrappers/index.js +5 -0
  27. package/api/http/wrappers/logger.d.ts +2 -0
  28. package/api/http/wrappers/logger.js +28 -0
  29. package/api/index.d.ts +3 -0
  30. package/api/index.js +2 -0
  31. package/api/stream/compose/compose.d.ts +2 -0
  32. package/api/stream/compose/compose.js +1 -0
  33. package/api/stream/compose/index.d.ts +1 -0
  34. package/api/stream/compose/index.js +1 -0
  35. package/api/stream/factory/factory.d.ts +5 -0
  36. package/api/stream/factory/factory.js +3 -0
  37. package/api/stream/factory/index.d.ts +1 -0
  38. package/api/stream/factory/index.js +1 -0
  39. package/api/stream/index.d.ts +3 -0
  40. package/api/stream/index.js +2 -0
  41. package/api/stream/stream.d.ts +3 -0
  42. package/api/stream/stream.js +13 -0
  43. package/api/stream/types.d.ts +36 -0
  44. package/api/stream/wrappers/chaos.d.ts +2 -0
  45. package/{wrapper → api/stream/wrappers}/chaos.js +1 -12
  46. package/api/stream/wrappers/context.d.ts +1 -0
  47. package/api/stream/wrappers/context.js +6 -0
  48. package/api/stream/wrappers/cors.d.ts +1 -0
  49. package/api/stream/wrappers/cors.js +10 -0
  50. package/api/stream/wrappers/error.d.ts +3 -0
  51. package/api/stream/wrappers/error.js +25 -0
  52. package/api/stream/wrappers/headers.d.ts +1 -0
  53. package/api/stream/wrappers/headers.js +7 -0
  54. package/api/stream/wrappers/healthcheck.d.ts +2 -0
  55. package/api/stream/wrappers/healthcheck.js +27 -0
  56. package/api/stream/wrappers/index.d.ts +10 -0
  57. package/api/stream/wrappers/index.js +10 -0
  58. package/api/stream/wrappers/logger.d.ts +2 -0
  59. package/api/stream/wrappers/logger.js +20 -0
  60. package/api/stream/wrappers/smoke.d.ts +2 -0
  61. package/{wrapper → api/stream/wrappers}/smoke.js +1 -5
  62. package/api/stream/wrappers/timeout.d.ts +1 -0
  63. package/api/stream/wrappers/timeout.js +6 -0
  64. package/api/types.d.ts +17 -0
  65. package/api/wrappers/chaos.d.ts +1 -0
  66. package/api/wrappers/chaos.js +1 -0
  67. package/api/wrappers/cors.d.ts +1 -0
  68. package/api/wrappers/cors.js +11 -0
  69. package/api/wrappers/error.d.ts +4 -0
  70. package/api/wrappers/error.js +18 -0
  71. package/api/wrappers/headers.d.ts +1 -0
  72. package/api/wrappers/headers.js +13 -0
  73. package/api/wrappers/healthcheck.d.ts +8 -0
  74. package/api/wrappers/healthcheck.js +22 -0
  75. package/api/wrappers/index.d.ts +5 -0
  76. package/api/wrappers/index.js +5 -0
  77. package/compose/compose.d.ts +2 -0
  78. package/compose/compose.js +1 -0
  79. package/compose/index.d.ts +1 -0
  80. package/compose/index.js +1 -0
  81. package/create/create.d.ts +12 -0
  82. package/create/create.js +22 -0
  83. package/create/index.d.ts +1 -0
  84. package/create/index.js +1 -0
  85. package/dynamodb/dynamodb.d.ts +4 -0
  86. package/dynamodb/dynamodb.js +8 -0
  87. package/dynamodb/index.d.ts +1 -0
  88. package/dynamodb/index.js +1 -0
  89. package/eventBridge/eventBridge.d.ts +4 -0
  90. package/eventBridge/eventBridge.js +8 -0
  91. package/eventBridge/index.d.ts +1 -0
  92. package/eventBridge/index.js +1 -0
  93. package/factory/factory.d.ts +4 -0
  94. package/factory/factory.js +2 -0
  95. package/factory/index.d.ts +1 -0
  96. package/factory/index.js +1 -0
  97. package/index.d.ts +21 -19
  98. package/index.js +21 -19
  99. package/package.json +731 -185
  100. package/s3/index.d.ts +1 -0
  101. package/s3/index.js +1 -0
  102. package/s3/s3.d.ts +4 -0
  103. package/s3/s3.js +8 -0
  104. package/schedule/index.d.ts +1 -0
  105. package/schedule/index.js +1 -0
  106. package/schedule/schedule.d.ts +4 -0
  107. package/schedule/schedule.js +8 -0
  108. package/ses/index.d.ts +1 -0
  109. package/ses/index.js +1 -0
  110. package/ses/ses.d.ts +4 -0
  111. package/ses/ses.js +8 -0
  112. package/sns/index.d.ts +1 -0
  113. package/sns/index.js +1 -0
  114. package/sns/sns.d.ts +4 -0
  115. package/sns/sns.js +8 -0
  116. package/sqs/index.d.ts +1 -0
  117. package/sqs/index.js +1 -0
  118. package/sqs/sqs.d.ts +4 -0
  119. package/sqs/sqs.js +8 -0
  120. package/types.d.ts +12 -29
  121. package/{wrapper → wrappers}/chaos.d.ts +0 -1
  122. package/wrappers/chaos.js +35 -0
  123. package/{wrapper → wrappers}/context.d.ts +0 -1
  124. package/wrappers/context.js +6 -0
  125. package/{wrapper → wrappers}/error.d.ts +1 -4
  126. package/wrappers/error.js +9 -0
  127. package/wrappers/index.d.ts +6 -0
  128. package/wrappers/index.js +6 -0
  129. package/wrappers/logger.d.ts +4 -0
  130. package/wrappers/logger.js +21 -0
  131. package/wrappers/smoke.d.ts +2 -0
  132. package/wrappers/smoke.js +5 -0
  133. package/{wrapper → wrappers}/timeout.d.ts +0 -1
  134. package/wrappers/timeout.js +6 -0
  135. package/api.d.ts +0 -3
  136. package/api.js +0 -30
  137. package/compose.d.ts +0 -4
  138. package/compose.js +0 -3
  139. package/dynamodb.d.ts +0 -2
  140. package/dynamodb.js +0 -7
  141. package/eventBridge.d.ts +0 -2
  142. package/eventBridge.js +0 -7
  143. package/factory.d.ts +0 -8
  144. package/factory.js +0 -6
  145. package/s3.d.ts +0 -2
  146. package/s3.js +0 -7
  147. package/schedule.d.ts +0 -2
  148. package/schedule.js +0 -7
  149. package/ses.d.ts +0 -2
  150. package/ses.js +0 -7
  151. package/sns.d.ts +0 -2
  152. package/sns.js +0 -7
  153. package/sqs.d.ts +0 -2
  154. package/sqs.js +0 -7
  155. package/wrapper/context.js +0 -11
  156. package/wrapper/cors.d.ts +0 -3
  157. package/wrapper/cors.js +0 -28
  158. package/wrapper/error.js +0 -62
  159. package/wrapper/headers.d.ts +0 -3
  160. package/wrapper/headers.js +0 -25
  161. package/wrapper/healthcheck.d.ts +0 -13
  162. package/wrapper/healthcheck.js +0 -49
  163. package/wrapper/logger.d.ts +0 -6
  164. package/wrapper/logger.js +0 -65
  165. package/wrapper/smoke.d.ts +0 -3
  166. package/wrapper/timeout.js +0 -10
  167. /package/{wrapper → api/stream/wrappers}/stream.d.ts +0 -0
  168. /package/{wrapper → api/stream/wrappers}/stream.js +0 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@ Composable AWS Lambda handler chains and wrappers for Vyriy projects.
4
4
 
5
5
  ## Purpose
6
6
 
7
- This package provides ready-made Lambda handler chains for common Vyriy workloads and a small set of reusable wrappers for logging, timeouts, smoke checks, development chaos injection, context setup, and error handling. It also ships a native Node HTTP chain (`httpApi`) with matching `httpWith*` wrappers for handlers that work directly with `IncomingMessage` and `ServerResponse`.
7
+ `@vyriy/handler` provides ready-made handler chains for common AWS Lambda workloads and small reusable wrappers for logging, timeouts, smoke checks, development chaos injection, context setup, headers, CORS, healthchecks, and error handling.
8
8
 
9
9
  It is designed for projects that want a consistent handler pipeline without repeating the same boilerplate in every Lambda entrypoint.
10
10
 
@@ -30,9 +30,9 @@ yarn add @types/aws-lambda
30
30
 
31
31
  The `awslambda` response streaming helper is a global provided by the AWS Lambda Node.js runtime. It is not imported from `aws-lambda`; `@types/aws-lambda` only lets TypeScript type-check that global.
32
32
 
33
- ## Usage
33
+ ## API
34
34
 
35
- Use a prebuilt API Gateway handler chain:
35
+ Use `api` for API Gateway-style Lambda handlers:
36
36
 
37
37
  ```ts
38
38
  import { api } from '@vyriy/handler';
@@ -45,60 +45,124 @@ export const handler = api(async (event) => ({
45
45
  }));
46
46
  ```
47
47
 
48
- For Lambda response streaming, use the separate stream chain:
48
+ `api` includes error handling, logging, timeout handling, context setup, smoke checks, healthcheck handling, default headers, CORS preflight handling, and development chaos injection.
49
+
50
+ Use `create.api(...)` when a project needs a configured API chain. Passing `headers` replaces the default API headers:
49
51
 
50
52
  ```ts
51
- // handler.ts
52
- import { streamApi } from '@vyriy/handler';
53
+ import { create } from '@vyriy/handler';
53
54
 
54
- export const handler = streamApi(async (event, responseStream) => {
55
- responseStream.setContentType?.('text/plain');
56
- responseStream.write(`Request path: ${event.path}\n`);
57
- responseStream.write('Part 1 of the response...');
58
- responseStream.end('Part 2 of the response...');
55
+ const api = create.api({
56
+ headers: {
57
+ 'access-control-allow-origin': '*',
58
+ 'content-type': 'application/json',
59
+ },
60
+ healthcheck: {
61
+ path: '/ready',
62
+ },
59
63
  });
64
+
65
+ export const handler = api(async () => ({
66
+ statusCode: 200,
67
+ body: JSON.stringify({ ok: true }),
68
+ }));
69
+ ```
70
+
71
+ The API-specific entrypoint exposes the same chain as `api`:
72
+
73
+ ```ts
74
+ import { api, create } from '@vyriy/handler/api';
60
75
  ```
61
76
 
62
- `streamApi(...)` handlers receive `(event, responseStream, context)` and write directly to the response stream.
77
+ ## Event Sources
63
78
 
64
- Use the same handler locally, in Docker, or in a Fargate-style HTTP runtime:
79
+ The event-source chains share the Lambda wrapper set: `withError`, `withLogger`, `withTimeout`, `withContext`, and `withSmoke`. Failures are rethrown so AWS event-source retry behavior still works.
80
+
81
+ ### S3
65
82
 
66
83
  ```ts
67
- // server.ts
68
- import { streamServer } from '@vyriy/server';
84
+ import { s3 } from '@vyriy/handler';
69
85
 
70
- import { handler } from './handler.js';
86
+ export const handler = s3(async (event) => {
87
+ for (const record of event.Records) {
88
+ console.info(record.s3.bucket.name, record.s3.object.key);
89
+ }
90
+ });
91
+ ```
71
92
 
72
- streamServer(handler);
93
+ ### SQS
94
+
95
+ ```ts
96
+ import { sqs } from '@vyriy/handler';
97
+
98
+ export const handler = sqs(async (event) => {
99
+ for (const record of event.Records) {
100
+ console.info(record.body);
101
+ }
102
+ });
73
103
  ```
74
104
 
75
- Use the same handler in AWS Lambda response streaming:
105
+ ### SNS
76
106
 
77
107
  ```ts
78
- // lambda.ts
79
- import { handler } from './handler.js';
108
+ import { sns } from '@vyriy/handler';
80
109
 
81
- export const main = awslambda.streamifyResponse(handler);
110
+ export const handler = sns(async (event) => {
111
+ for (const record of event.Records) {
112
+ console.info(record.Sns.Message);
113
+ }
114
+ });
82
115
  ```
83
116
 
84
- That `handler.ts` shape already matches Lambda response streaming. For a standard non-streaming Lambda, export an `api(...)` handler directly without `responseStream`.
117
+ ### DynamoDB Streams
85
118
 
86
- You can also inline the same shape in one file when a separate local entrypoint is not needed:
119
+ ```ts
120
+ import { dynamodb } from '@vyriy/handler';
121
+
122
+ export const handler = dynamodb(async (event) => {
123
+ for (const record of event.Records) {
124
+ console.info(record.eventName);
125
+ }
126
+ });
127
+ ```
128
+
129
+ ### EventBridge
87
130
 
88
131
  ```ts
89
- import { streamApi } from '@vyriy/handler';
132
+ import { eventBridge } from '@vyriy/handler';
90
133
 
91
- export const main = awslambda.streamifyResponse(
92
- streamApi(async (event, responseStream) => {
93
- responseStream.setContentType?.('text/plain');
94
- responseStream.write(`Request path: ${event.path}\n`);
95
- responseStream.write('Part 1 of the response...');
96
- responseStream.end('Part 2 of the response...');
97
- }),
98
- );
134
+ export const handler = eventBridge(async (event) => {
135
+ console.info('EventBridge event:', event.source, event['detail-type'], event.detail);
136
+ });
137
+ ```
138
+
139
+ ### Schedule
140
+
141
+ ```ts
142
+ import { schedule } from '@vyriy/handler';
143
+
144
+ export const handler = schedule(async (event) => {
145
+ console.info('Scheduled event:', event['detail-type']);
146
+ });
99
147
  ```
100
148
 
101
- For native Node HTTP handlers, use the HTTP chain. Handlers receive `(request, response)` and own the response lifecycle, which fits transports such as MCP Streamable HTTP:
149
+ ### SES
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
+
161
+ `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.
162
+
163
+ ## HTTP
164
+
165
+ Use `httpApi` for native Node HTTP handlers. Handlers receive `(request, response)` and own the response lifecycle, which fits transports such as MCP Streamable HTTP:
102
166
 
103
167
  ```ts
104
168
  import { httpApi } from '@vyriy/handler';
@@ -112,6 +176,33 @@ export const handler = httpApi(async (request, response) => {
112
176
  });
113
177
  ```
114
178
 
179
+ `httpApi` includes error handling, request logging, healthcheck handling, default headers, and CORS preflight handling. It sets no default `content-type` because native handlers own the response body format.
180
+
181
+ Use the runtime-specific entrypoint when that reads better:
182
+
183
+ ```ts
184
+ import { api, create } from '@vyriy/handler/api/http';
185
+ ```
186
+
187
+ Configure a native HTTP chain with `create.httpApi(...)`. Passing `headers` replaces the default HTTP headers:
188
+
189
+ ```ts
190
+ import { create } from '@vyriy/handler';
191
+
192
+ const httpApi = create.httpApi({
193
+ headers: {
194
+ 'access-control-allow-headers': 'content-type, mcp-protocol-version',
195
+ 'access-control-allow-methods': 'GET, POST, OPTIONS',
196
+ 'access-control-allow-origin': '*',
197
+ 'x-content-type-options': 'nosniff',
198
+ },
199
+ });
200
+
201
+ export const handler = httpApi(async (_request, response) => {
202
+ response.writeHead(200).end('ok');
203
+ });
204
+ ```
205
+
115
206
  Run it locally or in a container with `httpServer` from `@vyriy/server`:
116
207
 
117
208
  ```ts
@@ -122,89 +213,188 @@ import { handler } from './handler.js';
122
213
  httpServer(handler);
123
214
  ```
124
215
 
125
- Use a prebuilt queue or event handler chain:
216
+ Build a custom native HTTP pipeline with `httpCompose(...)` and `httpFactory(...)`:
126
217
 
127
218
  ```ts
128
- import { sqs } from '@vyriy/handler';
219
+ import { httpCompose, httpFactory, httpWithError } from '@vyriy/handler';
129
220
 
130
- export const handler = sqs(async (event) => {
131
- for (const record of event.Records) {
132
- console.info(record.body);
221
+ const httpWithRequestId = httpFactory<{ headerName?: string }>(async (handler, args, options = {}) => {
222
+ const [request] = args;
223
+ const requestId = request.headers[options.headerName ?? 'x-request-id'];
224
+
225
+ if (requestId) {
226
+ console.info('Request ID:', requestId);
133
227
  }
228
+
229
+ await handler(...args);
230
+ });
231
+
232
+ export const handler = httpCompose(
233
+ httpWithError(),
234
+ httpWithRequestId(),
235
+ )(async (_request, response) => {
236
+ response.writeHead(200).end('ok');
134
237
  });
135
238
  ```
136
239
 
137
- Use a prebuilt SNS handler chain:
240
+ The lower-level helpers are also available from runtime subpaths:
138
241
 
139
242
  ```ts
140
- import { sns } from '@vyriy/handler';
243
+ import { compose } from '@vyriy/handler/api/http/compose';
244
+ import { factory } from '@vyriy/handler/api/http/factory';
245
+ ```
141
246
 
142
- export const handler = sns(async (event) => {
143
- for (const record of event.Records) {
144
- console.info(record.Sns.Message);
145
- }
247
+ ## Stream
248
+
249
+ Use `streamApi` for Lambda response streaming. Stream handlers receive `(event, responseStream, context)` and write directly to the response stream:
250
+
251
+ ```ts
252
+ import { streamApi } from '@vyriy/handler';
253
+
254
+ export const handler = streamApi(async (event, responseStream) => {
255
+ responseStream.setContentType?.('text/plain');
256
+ responseStream.write(`Request path: ${event.path}\n`);
257
+ responseStream.write('Part 1 of the response...');
258
+ responseStream.end('Part 2 of the response...');
146
259
  });
147
260
  ```
148
261
 
149
- Use a prebuilt DynamoDB Streams handler chain:
262
+ Use the same handler in AWS Lambda response streaming:
150
263
 
151
264
  ```ts
152
- import { dynamodb } from '@vyriy/handler';
265
+ import { handler } from './handler.js';
153
266
 
154
- export const handler = dynamodb(async (event) => {
155
- for (const record of event.Records) {
156
- console.info(record.eventName);
157
- }
158
- });
267
+ export const main = awslambda.streamifyResponse(handler);
159
268
  ```
160
269
 
161
- Use a prebuilt S3 event handler chain:
270
+ Run the same handler locally, in Docker, or in a Fargate-style HTTP runtime:
162
271
 
163
272
  ```ts
164
- import { s3 } from '@vyriy/handler';
273
+ import { streamServer } from '@vyriy/server';
165
274
 
166
- export const handler = s3(async (event) => {
167
- for (const record of event.Records) {
168
- console.info(record.s3.bucket.name, record.s3.object.key);
169
- }
170
- });
275
+ import { handler } from './handler.js';
276
+
277
+ streamServer(handler);
171
278
  ```
172
279
 
173
- Use a prebuilt SES receipt handler chain:
280
+ You can also inline the same shape in one file:
174
281
 
175
282
  ```ts
176
- import { ses } from '@vyriy/handler';
283
+ import { streamApi } from '@vyriy/handler';
177
284
 
178
- export const handler = ses(async (event) => {
179
- for (const record of event.Records) {
180
- console.info(record.ses.mail.messageId, record.ses.mail.source);
285
+ export const main = awslambda.streamifyResponse(
286
+ streamApi(async (event, responseStream) => {
287
+ responseStream.setContentType?.('text/plain');
288
+ responseStream.write(`Request path: ${event.path}\n`);
289
+ responseStream.end('ok');
290
+ }),
291
+ );
292
+ ```
293
+
294
+ The runtime-specific entrypoint exposes the same chain as `api`:
295
+
296
+ ```ts
297
+ import { api, create } from '@vyriy/handler/api/stream';
298
+ ```
299
+
300
+ `streamApi` includes the same wrapper behavior as `api`, adapted for response streaming. Passing `headers` to `create.streamApi(...)` replaces the default stream headers.
301
+
302
+ Custom stream pipelines use stream-specific compose and factory helpers:
303
+
304
+ ```ts
305
+ import { streamCompose, streamFactory, streamWithApiError } from '@vyriy/handler';
306
+
307
+ const streamWithRequestId = streamFactory<{ headerName?: string }>(async (handler, args, options = {}) => {
308
+ const [event] = args;
309
+ const requestId = event.headers?.[options.headerName ?? 'x-request-id'];
310
+
311
+ if (requestId) {
312
+ console.info('Request ID:', requestId);
181
313
  }
314
+
315
+ await handler(...args);
316
+ });
317
+
318
+ export const handler = streamCompose(
319
+ streamWithApiError(),
320
+ streamWithRequestId(),
321
+ )(async (_event, responseStream) => {
322
+ responseStream.end('ok');
182
323
  });
183
324
  ```
184
325
 
185
- Use a prebuilt schedule handler chain:
326
+ The lower-level helpers are also available from runtime subpaths:
186
327
 
187
328
  ```ts
188
- import { schedule } from '@vyriy/handler';
329
+ import { compose } from '@vyriy/handler/api/stream/compose';
330
+ import { factory } from '@vyriy/handler/api/stream/factory';
331
+ ```
189
332
 
190
- export const handler = schedule(async (event) => {
191
- console.info('Scheduled event:', event['detail-type']);
333
+ ## Create
334
+
335
+ `create` builds configured versions of the prebuilt chains while keeping the standard exports unchanged:
336
+
337
+ ```ts
338
+ import { create } from '@vyriy/handler';
339
+
340
+ const { api, httpApi, s3, sqs, streamApi } = create;
341
+ ```
342
+
343
+ Each key is a factory:
344
+
345
+ ```ts
346
+ import { create } from '@vyriy/handler';
347
+
348
+ const httpApi = create.httpApi({
349
+ healthcheck: {
350
+ path: '/health',
351
+ },
352
+ headers: {
353
+ 'access-control-allow-origin': '*',
354
+ 'access-control-allow-methods': 'GET, POST, OPTIONS',
355
+ 'access-control-allow-headers': 'content-type, mcp-protocol-version',
356
+ 'x-content-type-options': 'nosniff',
357
+ },
358
+ });
359
+
360
+ export const handler = httpApi(async (_request, response) => {
361
+ response.writeHead(200).end('ok');
192
362
  });
193
363
  ```
194
364
 
195
- Use a prebuilt EventBridge custom event handler chain:
365
+ Available factories:
366
+
367
+ - `create.api(options?)`
368
+ - `create.httpApi(options?)`
369
+ - `create.streamApi(options?)`
370
+ - `create.dynamodb(options?)`
371
+ - `create.eventBridge(options?)`
372
+ - `create.s3(options?)`
373
+ - `create.schedule(options?)`
374
+ - `create.ses(options?)`
375
+ - `create.sns(options?)`
376
+ - `create.sqs(options?)`
377
+
378
+ For `api`, `httpApi`, and `streamApi`, passing `headers` replaces the default header set for that configured chain.
379
+
380
+ Runtime subpaths also expose local factories:
196
381
 
197
382
  ```ts
198
- import { eventBridge } from '@vyriy/handler';
383
+ import { create as createHttpApi } from '@vyriy/handler/api/http';
384
+ import { create as createStreamApi } from '@vyriy/handler/api/stream';
199
385
 
200
- export const handler = eventBridge(async (event) => {
201
- console.info('EventBridge event:', event.source, event['detail-type'], event.detail);
386
+ const httpApi = createHttpApi({
387
+ headers: {
388
+ 'access-control-allow-origin': '*',
389
+ },
202
390
  });
391
+
392
+ const streamApi = createStreamApi();
203
393
  ```
204
394
 
205
- Each chain has its own composition and factory helpers: `compose`/`factory` for Lambda handlers, `streamCompose`/`streamFactory` for response streaming handlers, and `httpCompose`/`httpFactory` for native Node HTTP handlers.
395
+ ## Custom Wrappers
206
396
 
207
- Compose a custom handler pipeline from individual helpers:
397
+ Use `compose(...)` to build a custom Lambda pipeline from decorators:
208
398
 
209
399
  ```ts
210
400
  import { compose, withChaos, withContext, withError, withLogger, withTimeout } from '@vyriy/handler';
@@ -223,7 +413,7 @@ export const handler = compose(
223
413
  });
224
414
  ```
225
415
 
226
- Create a custom wrapper with `factory(...)` and compose it with the built-in helpers:
416
+ Use `factory(...)` to create a custom Lambda wrapper:
227
417
 
228
418
  ```ts
229
419
  import { compose, factory, withError, withLogger, withTimeout } from '@vyriy/handler';
@@ -258,59 +448,28 @@ export const handler = compose(
258
448
  });
259
449
  ```
260
450
 
261
- Compose a custom native HTTP pipeline with `httpCompose(...)` and the `httpWith*` wrappers:
262
-
263
- ```ts
264
- import { httpCompose, httpWithCors, httpWithError, httpWithHealthcheck, httpWithLogger } from '@vyriy/handler';
265
-
266
- export const handler = httpCompose(
267
- httpWithError(),
268
- httpWithLogger(),
269
- httpWithHealthcheck(),
270
- httpWithCors(),
271
- )(async (request, response) => {
272
- response
273
- .writeHead(200, {
274
- 'content-type': 'application/json',
275
- })
276
- .end(JSON.stringify({ ok: true }));
277
- });
278
- ```
279
-
280
- ## Prebuilt Chains
281
-
282
- - `api`
283
- API Gateway chain with error handling, logging, timeout handling, context setup, smoke checks, healthcheck handling, default headers, and CORS preflight handling.
284
- - `streamApi`
285
- 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.
451
+ Each runtime has matching helpers:
286
452
 
287
- - `httpApi`
288
- Native Node HTTP chain with error handling, logging, healthcheck handling, default headers, and CORS preflight handling. Handlers receive `(request, response)` and write the response themselves. Unlike `api`, the chain sets no default `content-type` because native handlers own the response body format.
453
+ - Lambda: `compose` and `factory` from `@vyriy/handler`
454
+ - HTTP: `httpCompose` and `httpFactory` from `@vyriy/handler`, or `compose` / `factory` from `@vyriy/handler/api/http/compose` and `@vyriy/handler/api/http/factory`
455
+ - Stream: `streamCompose` and `streamFactory` from `@vyriy/handler`, or `compose` / `factory` from `@vyriy/handler/api/stream/compose` and `@vyriy/handler/api/stream/factory`
289
456
 
290
- - `dynamodb`
291
- DynamoDB Streams chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
292
-
293
- - `eventBridge`
294
- EventBridge custom event chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
295
-
296
- - `s3`
297
- S3 event chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
298
-
299
- - `ses`
300
- SES receipt rule chain for incoming email processing with logging, timeout handling, context setup, smoke checks, and rethrown errors.
457
+ ## Wrappers
301
458
 
302
- - `schedule`
303
- EventBridge schedule chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
459
+ Wrapper exports are split by runtime:
304
460
 
305
- - `sns`
306
- SNS chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
461
+ ```ts
462
+ import { withError, withLogger } from '@vyriy/handler/wrappers';
463
+ import { withHeaders } from '@vyriy/handler/api/wrappers';
464
+ import { httpWithHeaders } from '@vyriy/handler/api/http/wrappers';
465
+ import { streamWithHeaders } from '@vyriy/handler/api/stream/wrappers';
466
+ ```
307
467
 
308
- - `sqs`
309
- SQS chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
468
+ The root `@vyriy/handler` entrypoint re-exports the public wrappers for convenience.
310
469
 
311
- ## Wrappers
470
+ ### Common Lambda Wrappers
312
471
 
313
- ### `withError(options?)`
472
+ #### `withError(options?)`
314
473
 
315
474
  Catches handler failures, optionally runs a side-effect `errorHandler`, and rethrows the original error.
316
475
 
@@ -322,28 +481,7 @@ Options:
322
481
  }
323
482
  ```
324
483
 
325
- - `errorHandler`
326
- Callback invoked with the caught error and handler arguments before the original error is rethrown.
327
-
328
- Example:
329
-
330
- ```ts
331
- import { withError } from '@vyriy/handler';
332
-
333
- export const handler = withError({
334
- errorHandler: async (error) => {
335
- console.error('Handler failed:', error);
336
- },
337
- })(async () => {
338
- throw new Error('boom');
339
- });
340
- ```
341
-
342
- ### `withApiError(options?)`
343
-
344
- Catches API handler failures and converts them to an API Gateway result. Without a custom `errorHandler`, it returns a JSON `500`.
345
-
346
- ### `withLogger(options?)`
484
+ #### `withLogger(options?)`
347
485
 
348
486
  Logs the incoming event and context, then logs either the result or the thrown error.
349
487
 
@@ -355,55 +493,21 @@ Options:
355
493
  }
356
494
  ```
357
495
 
358
- - `logger`
359
- Console-compatible logger implementation. By default the wrapper creates one via `@vyriy/logger`.
360
-
361
- Example:
362
-
363
- ```ts
364
- import { withLogger } from '@vyriy/handler';
496
+ By default the wrapper creates a logger via `@vyriy/logger`.
365
497
 
366
- export const handler = withLogger({
367
- logger: console,
368
- })(async (event) => {
369
- return {
370
- ok: true,
371
- event,
372
- };
373
- });
374
- ```
375
-
376
- ### `withTimeout()`
498
+ #### `withTimeout()`
377
499
 
378
500
  Races the handler against a timeout scheduled one second before the Lambda runtime limit.
379
501
 
380
- Example:
381
-
382
- ```ts
383
- import { withTimeout } from '@vyriy/handler';
384
-
385
- export const handler = withTimeout()(async () => {
386
- await doWork();
387
- });
388
- ```
389
-
390
- ### `withContext()`
502
+ #### `withContext()`
391
503
 
392
504
  Sets `context.callbackWaitsForEmptyEventLoop = false` before calling the handler.
393
505
 
394
- Example:
395
-
396
- ```ts
397
- import { withContext } from '@vyriy/handler';
506
+ #### `withSmoke()`
398
507
 
399
- export const handler = withContext()(async (_event, context) => {
400
- return {
401
- waitForEmptyLoop: context.callbackWaitsForEmptyEventLoop,
402
- };
403
- });
404
- ```
508
+ Returns the smoke response when the incoming event has `isSmoke: true`. Matching is delegated to `@vyriy/smoke`.
405
509
 
406
- ### `withChaos(options?)`
510
+ #### `withChaos(options?)`
407
511
 
408
512
  Injects development-only random failures before the wrapped handler runs.
409
513
 
@@ -419,115 +523,133 @@ Options:
419
523
  }
420
524
  ```
421
525
 
422
- - `enabled`
423
- Turns chaos injection on. By default the wrapper reads `CHAOS_ENABLED` through `@vyriy/config`.
424
-
425
- - `probability`
426
- Probability from `0` to `1` that a failure is injected.
526
+ `enabled` and `timeoutMs` can be read through `@vyriy/config` when they are not passed directly.
427
527
 
428
- - `strategy`
429
- Chooses whether to throw an error, wait and time out, or pick one randomly. Defaults to `'random'`.
528
+ ### API Wrappers
430
529
 
431
- - `timeoutMs`
432
- Timeout delay used when the timeout strategy is selected. By default the wrapper reads `CHAOS_TIMEOUT_MS` through `@vyriy/config`.
530
+ #### `withApiError(options?)`
433
531
 
434
- - `error`
435
- Error value normalized through `@vyriy/error` when the error strategy is selected.
532
+ Catches API handler failures and converts them to an API Gateway result. Without a custom `errorHandler`, it returns a JSON `500`.
436
533
 
437
- Example:
534
+ Options:
438
535
 
439
536
  ```ts
440
- import { withChaos } from '@vyriy/handler';
441
-
442
- export const handler = withChaos({
443
- enabled: true,
444
- probability: 0.2,
445
- strategy: 'random',
446
- timeoutMs: 1500,
447
- })(async () => {
448
- return {
449
- ok: true,
450
- };
451
- });
537
+ {
538
+ errorHandler?: (error: unknown, args: HandlerParams<ApiEvent>) => Promise<ApiResult> | ApiResult;
539
+ }
452
540
  ```
453
541
 
454
- ### `withSmoke()`
542
+ #### `withHealthcheck(options?)`
455
543
 
456
- Returns the smoke response when the incoming event has `isSmoke: true`.
544
+ Returns a JSON `200` response when `event.path` matches the configured `path`.
457
545
 
458
- Example:
546
+ Options:
459
547
 
460
548
  ```ts
461
- import { withSmoke } from '@vyriy/handler';
549
+ {
550
+ path?: string;
551
+ action?: () => Promise<void>;
552
+ }
553
+ ```
462
554
 
463
- export const handler = withSmoke()(async () => {
464
- return {
465
- statusCode: 200,
466
- body: JSON.stringify({
467
- status: 'runtime',
468
- }),
469
- };
470
- });
555
+ #### `withHeaders(options?)`
556
+
557
+ Adds configured headers to the API result.
558
+
559
+ Options:
560
+
561
+ ```ts
562
+ Record<string, string>;
471
563
  ```
472
564
 
473
- `withSmoke()` is used by the API, DynamoDB Streams, S3, SES receipt, schedule, SNS, and SQS chains.
565
+ #### `withCors()`
566
+
567
+ Short-circuits API Gateway `OPTIONS` requests with a `204` response and delegates all other requests.
568
+
569
+ ### HTTP Wrappers
474
570
 
475
- ### Native HTTP Wrappers
571
+ #### `httpWithError(options?)`
476
572
 
477
- The `httpWith*` wrappers decorate native Node HTTP handlers and are composed with `httpCompose(...)`:
573
+ Catches native HTTP handler failures and runs an optional side-effect `errorHandler`. When the response is still open it writes a JSON `500`; when headers were already sent it only ends the response.
574
+
575
+ Options:
478
576
 
479
- - `httpWithError(options?)`
480
- Catches handler failures and runs an optional side-effect `errorHandler`. When the response is still open it writes a JSON `500`; when headers were already sent it only ends the response.
577
+ ```ts
578
+ {
579
+ errorHandler?: (error: unknown, args: HttpHandlerParams) => Promise<void> | void;
580
+ }
581
+ ```
481
582
 
482
- - `httpWithLogger(options?)`
483
- Logs the incoming request method and URL, then logs either the response status code or the thrown error. Accepts the same `logger` option as `withLogger`.
583
+ #### `httpWithLogger(options?)`
484
584
 
485
- - `httpWithHealthcheck(options?)`
486
- Writes a JSON `200` response when the request path matches the configured `path` (default `/healthcheck`). Besides `path` and `action`, it accepts an optional JSON-serializable `body` for the healthcheck response.
585
+ Logs the incoming request method and URL, then logs either the final response status code or the thrown error. Accepts the same `logger` option as `withLogger`.
487
586
 
488
- - `httpWithHeaders(options?)`
489
- Sets configured headers on the response before delegating, so handler-defined headers win on key conflicts.
587
+ #### `httpWithHealthcheck(options?)`
490
588
 
491
- - `httpWithCors()`
492
- Short-circuits `OPTIONS` preflight requests with a `204` response and delegates all other requests.
589
+ Writes a JSON `200` response when the request path matches the configured `path`.
493
590
 
494
- Custom native HTTP wrappers are created with `httpFactory(...)`:
591
+ Options:
495
592
 
496
593
  ```ts
497
- import { httpCompose, httpFactory, httpWithError } from '@vyriy/handler';
594
+ {
595
+ path?: string;
596
+ action?: () => Promise<void>;
597
+ body?: unknown;
598
+ }
599
+ ```
498
600
 
499
- const httpWithRequestId = httpFactory<{ headerName?: string }>(async (handler, args, options = {}) => {
500
- const [request] = args;
501
- const requestId = request.headers[options.headerName ?? 'x-request-id'];
601
+ #### `httpWithHeaders(options?)`
502
602
 
503
- if (requestId) {
504
- console.info('Request ID:', requestId);
505
- }
603
+ Sets configured headers on the response before delegating, so handler-defined headers win on key conflicts.
506
604
 
507
- await handler(...args);
508
- });
605
+ Options:
509
606
 
510
- export const handler = httpCompose(
511
- httpWithError(),
512
- httpWithRequestId(),
513
- )(async (request, response) => {
514
- response.writeHead(200).end('ok');
515
- });
607
+ ```ts
608
+ Record<string, string>;
516
609
  ```
517
610
 
611
+ #### `httpWithCors()`
612
+
613
+ Short-circuits `OPTIONS` preflight requests with a `204` response and delegates all other requests.
614
+
615
+ ### Stream Wrappers
616
+
617
+ The `streamWith*` wrappers mirror the API and common Lambda wrappers for response streaming handlers:
618
+
619
+ - `streamWithApiError(options?)`
620
+ - `streamWithLogger(options?)`
621
+ - `streamWithTimeout()`
622
+ - `streamWithContext()`
623
+ - `streamWithSmoke()`
624
+ - `streamWithHealthcheck(options?)`
625
+ - `streamWithHeaders(options?)`
626
+ - `streamWithCors()`
627
+ - `streamWithChaos(options?)`
628
+
629
+ Stream-specific wrappers write metadata through the response stream before or instead of delegating to the wrapped stream handler.
630
+
518
631
  ## Types
519
632
 
520
- The package also exports shared handler types:
633
+ The package exports shared Lambda handler types from the root entrypoint:
521
634
 
522
635
  ```ts
523
636
  import type { Context, Decorator, Handler, HandlerParams, Response } from '@vyriy/handler';
524
- import type { HttpDecorator, HttpHandler, HttpHandlerParams } from '@vyriy/handler';
525
637
  ```
526
638
 
639
+ Runtime-specific types are available from their runtime entrypoints:
640
+
641
+ ```ts
642
+ import type { ApiEvent, ApiOptions, ApiResult } from '@vyriy/handler/api';
643
+ import type { HttpApiOptions, HttpDecorator, HttpHandler, HttpHandlerParams } from '@vyriy/handler/api/http';
644
+ import type { ResponseStream, StreamApiOptions, StreamHandler } from '@vyriy/handler/api/stream';
645
+ ```
646
+
647
+ The root entrypoint still re-exports runtime-specific types for compatibility.
648
+
527
649
  ## Notes
528
650
 
529
651
  - `api` includes API-specific wrappers such as healthcheck handling, default headers, and CORS preflight handling
530
- - `httpApi` skips timeout, context, smoke, and chaos wrappers because they rely on the Lambda context and event shape; it also sets no default `content-type`
531
- - `dynamodb`, `s3`, `ses`, `schedule`, `sns`, and `sqs` use `withError()` so failures are rethrown for event-source retry behavior
532
- - `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
533
- - `withSmoke()` delegates matching to `@vyriy/smoke` and returns its API Gateway-compatible response
652
+ - `httpApi` skips timeout, context, smoke, and chaos wrappers because they rely on the Lambda context and event shape
653
+ - `streamApi` keeps the API-style behavior but adapts result writing to Lambda response streams
654
+ - `dynamodb`, `s3`, `ses`, `schedule`, `sns`, `sqs`, and `eventBridge` use `withError()` so failures are rethrown for event-source retry behavior
655
+ - Passing `headers` to `create.api`, `create.httpApi`, or `create.streamApi` replaces that chain's default headers