@vyriy/handler 0.7.7 → 0.8.0

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 (161) hide show
  1. package/README.md +395 -180
  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/{wrapper → api/stream/wrappers}/error.js +1 -18
  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/{wrapper → api/stream/wrappers}/healthcheck.js +1 -6
  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/{wrapper → api/wrappers}/cors.d.ts +0 -1
  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/{wrapper → api/wrappers}/headers.d.ts +0 -1
  72. package/{wrapper → api/wrappers}/headers.js +1 -6
  73. package/{wrapper → api/wrappers}/healthcheck.d.ts +0 -1
  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 -22
  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 -3
  126. package/wrappers/error.js +9 -0
  127. package/wrappers/index.d.ts +6 -0
  128. package/wrappers/index.js +6 -0
  129. package/{wrapper → wrappers}/logger.d.ts +0 -1
  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 -2
  136. package/api.js +0 -24
  137. package/compose.d.ts +0 -3
  138. package/compose.js +0 -2
  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 -7
  144. package/factory.js +0 -5
  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.js +0 -20
  157. package/wrapper/logger.js +0 -39
  158. package/wrapper/smoke.d.ts +0 -3
  159. package/wrapper/timeout.js +0 -10
  160. /package/{wrapper → api/stream/wrappers}/stream.d.ts +0 -0
  161. /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.
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,52 @@ 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
49
 
50
- ```ts
51
- // handler.ts
52
- import { streamApi } from '@vyriy/handler';
53
-
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...');
59
- });
60
- ```
61
-
62
- `streamApi(...)` handlers receive `(event, responseStream, context)` and write directly to the response stream.
63
-
64
- Use the same handler locally, in Docker, or in a Fargate-style HTTP runtime:
50
+ Use `create.api(...)` when a project needs a configured API chain. Passing `headers` replaces the default API headers:
65
51
 
66
52
  ```ts
67
- // server.ts
68
- import { streamServer } from '@vyriy/server';
53
+ import { create } from '@vyriy/handler';
69
54
 
70
- import { handler } from './handler.js';
55
+ const api = create.api({
56
+ headers: {
57
+ 'access-control-allow-origin': '*',
58
+ 'content-type': 'application/json',
59
+ },
60
+ healthcheck: {
61
+ path: '/ready',
62
+ },
63
+ });
71
64
 
72
- streamServer(handler);
65
+ export const handler = api(async () => ({
66
+ statusCode: 200,
67
+ body: JSON.stringify({ ok: true }),
68
+ }));
73
69
  ```
74
70
 
75
- Use the same handler in AWS Lambda response streaming:
71
+ The API-specific entrypoint exposes the same chain as `api`:
76
72
 
77
73
  ```ts
78
- // lambda.ts
79
- import { handler } from './handler.js';
80
-
81
- export const main = awslambda.streamifyResponse(handler);
74
+ import { api, create } from '@vyriy/handler/api';
82
75
  ```
83
76
 
84
- That `handler.ts` shape already matches Lambda response streaming. For a standard non-streaming Lambda, export an `api(...)` handler directly without `responseStream`.
77
+ ## Event Sources
78
+
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.
85
80
 
86
- You can also inline the same shape in one file when a separate local entrypoint is not needed:
81
+ ### S3
87
82
 
88
83
  ```ts
89
- import { streamApi } from '@vyriy/handler';
84
+ import { s3 } from '@vyriy/handler';
90
85
 
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
- );
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
+ });
99
91
  ```
100
92
 
101
- Use a prebuilt queue or event handler chain:
93
+ ### SQS
102
94
 
103
95
  ```ts
104
96
  import { sqs } from '@vyriy/handler';
@@ -110,7 +102,7 @@ export const handler = sqs(async (event) => {
110
102
  });
111
103
  ```
112
104
 
113
- Use a prebuilt SNS handler chain:
105
+ ### SNS
114
106
 
115
107
  ```ts
116
108
  import { sns } from '@vyriy/handler';
@@ -122,7 +114,7 @@ export const handler = sns(async (event) => {
122
114
  });
123
115
  ```
124
116
 
125
- Use a prebuilt DynamoDB Streams handler chain:
117
+ ### DynamoDB Streams
126
118
 
127
119
  ```ts
128
120
  import { dynamodb } from '@vyriy/handler';
@@ -134,19 +126,27 @@ export const handler = dynamodb(async (event) => {
134
126
  });
135
127
  ```
136
128
 
137
- Use a prebuilt S3 event handler chain:
129
+ ### EventBridge
138
130
 
139
131
  ```ts
140
- import { s3 } from '@vyriy/handler';
132
+ import { eventBridge } from '@vyriy/handler';
141
133
 
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
- }
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
146
  });
147
147
  ```
148
148
 
149
- Use a prebuilt SES receipt handler chain:
149
+ ### SES
150
150
 
151
151
  ```ts
152
152
  import { ses } from '@vyriy/handler';
@@ -158,27 +158,243 @@ export const handler = ses(async (event) => {
158
158
  });
159
159
  ```
160
160
 
161
- Use a prebuilt schedule handler chain:
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:
162
166
 
163
167
  ```ts
164
- import { schedule } from '@vyriy/handler';
168
+ import { httpApi } from '@vyriy/handler';
169
+
170
+ export const handler = httpApi(async (request, response) => {
171
+ response
172
+ .writeHead(200, {
173
+ 'content-type': 'application/json',
174
+ })
175
+ .end(JSON.stringify({ ok: true, url: request.url }));
176
+ });
177
+ ```
165
178
 
166
- export const handler = schedule(async (event) => {
167
- console.info('Scheduled event:', event['detail-type']);
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');
168
203
  });
169
204
  ```
170
205
 
171
- Use a prebuilt EventBridge custom event handler chain:
206
+ Run it locally or in a container with `httpServer` from `@vyriy/server`:
172
207
 
173
208
  ```ts
174
- import { eventBridge } from '@vyriy/handler';
209
+ import { httpServer } from '@vyriy/server';
175
210
 
176
- export const handler = eventBridge(async (event) => {
177
- console.info('EventBridge event:', event.source, event['detail-type'], event.detail);
211
+ import { handler } from './handler.js';
212
+
213
+ httpServer(handler);
214
+ ```
215
+
216
+ Build a custom native HTTP pipeline with `httpCompose(...)` and `httpFactory(...)`:
217
+
218
+ ```ts
219
+ import { httpCompose, httpFactory, httpWithError } from '@vyriy/handler';
220
+
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);
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');
237
+ });
238
+ ```
239
+
240
+ The lower-level helpers are also available from runtime subpaths:
241
+
242
+ ```ts
243
+ import { compose } from '@vyriy/handler/api/http/compose';
244
+ import { factory } from '@vyriy/handler/api/http/factory';
245
+ ```
246
+
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...');
259
+ });
260
+ ```
261
+
262
+ Use the same handler in AWS Lambda response streaming:
263
+
264
+ ```ts
265
+ import { handler } from './handler.js';
266
+
267
+ export const main = awslambda.streamifyResponse(handler);
268
+ ```
269
+
270
+ Run the same handler locally, in Docker, or in a Fargate-style HTTP runtime:
271
+
272
+ ```ts
273
+ import { streamServer } from '@vyriy/server';
274
+
275
+ import { handler } from './handler.js';
276
+
277
+ streamServer(handler);
278
+ ```
279
+
280
+ You can also inline the same shape in one file:
281
+
282
+ ```ts
283
+ import { streamApi } from '@vyriy/handler';
284
+
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);
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');
178
323
  });
179
324
  ```
180
325
 
181
- Compose a custom handler pipeline from individual helpers:
326
+ The lower-level helpers are also available from runtime subpaths:
327
+
328
+ ```ts
329
+ import { compose } from '@vyriy/handler/api/stream/compose';
330
+ import { factory } from '@vyriy/handler/api/stream/factory';
331
+ ```
332
+
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');
362
+ });
363
+ ```
364
+
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:
381
+
382
+ ```ts
383
+ import { create as createHttpApi } from '@vyriy/handler/api/http';
384
+ import { create as createStreamApi } from '@vyriy/handler/api/stream';
385
+
386
+ const httpApi = createHttpApi({
387
+ headers: {
388
+ 'access-control-allow-origin': '*',
389
+ },
390
+ });
391
+
392
+ const streamApi = createStreamApi();
393
+ ```
394
+
395
+ ## Custom Wrappers
396
+
397
+ Use `compose(...)` to build a custom Lambda pipeline from decorators:
182
398
 
183
399
  ```ts
184
400
  import { compose, withChaos, withContext, withError, withLogger, withTimeout } from '@vyriy/handler';
@@ -197,7 +413,7 @@ export const handler = compose(
197
413
  });
198
414
  ```
199
415
 
200
- Create a custom wrapper with `factory(...)` and compose it with the built-in helpers:
416
+ Use `factory(...)` to create a custom Lambda wrapper:
201
417
 
202
418
  ```ts
203
419
  import { compose, factory, withError, withLogger, withTimeout } from '@vyriy/handler';
@@ -232,37 +448,28 @@ export const handler = compose(
232
448
  });
233
449
  ```
234
450
 
235
- ## Prebuilt Chains
451
+ Each runtime has matching helpers:
236
452
 
237
- - `api`
238
- API Gateway chain with error handling, logging, timeout handling, context setup, smoke checks, healthcheck handling, default headers, and CORS preflight handling.
239
- - `streamApi`
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.
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`
241
456
 
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.
457
+ ## Wrappers
253
458
 
254
- - `schedule`
255
- EventBridge schedule chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
459
+ Wrapper exports are split by runtime:
256
460
 
257
- - `sns`
258
- 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
+ ```
259
467
 
260
- - `sqs`
261
- 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.
262
469
 
263
- ## Wrappers
470
+ ### Common Lambda Wrappers
264
471
 
265
- ### `withError(options?)`
472
+ #### `withError(options?)`
266
473
 
267
474
  Catches handler failures, optionally runs a side-effect `errorHandler`, and rethrows the original error.
268
475
 
@@ -274,167 +481,175 @@ Options:
274
481
  }
275
482
  ```
276
483
 
277
- - `errorHandler`
278
- Callback invoked with the caught error and handler arguments before the original error is rethrown.
484
+ #### `withLogger(options?)`
279
485
 
280
- Example:
486
+ Logs the incoming event and context, then logs either the result or the thrown error.
281
487
 
282
- ```ts
283
- import { withError } from '@vyriy/handler';
488
+ Options:
284
489
 
285
- export const handler = withError({
286
- errorHandler: async (error) => {
287
- console.error('Handler failed:', error);
288
- },
289
- })(async () => {
290
- throw new Error('boom');
291
- });
490
+ ```ts
491
+ {
492
+ logger?: typeof console;
493
+ }
292
494
  ```
293
495
 
294
- ### `withApiError(options?)`
496
+ By default the wrapper creates a logger via `@vyriy/logger`.
295
497
 
296
- Catches API handler failures and converts them to an API Gateway result. Without a custom `errorHandler`, it returns a JSON `500`.
498
+ #### `withTimeout()`
297
499
 
298
- ### `withLogger(options?)`
500
+ Races the handler against a timeout scheduled one second before the Lambda runtime limit.
299
501
 
300
- Logs the incoming event and context, then logs either the result or the thrown error.
502
+ #### `withContext()`
503
+
504
+ Sets `context.callbackWaitsForEmptyEventLoop = false` before calling the handler.
505
+
506
+ #### `withSmoke()`
507
+
508
+ Returns the smoke response when the incoming event has `isSmoke: true`. Matching is delegated to `@vyriy/smoke`.
509
+
510
+ #### `withChaos(options?)`
511
+
512
+ Injects development-only random failures before the wrapped handler runs.
301
513
 
302
514
  Options:
303
515
 
304
516
  ```ts
305
517
  {
306
- logger?: typeof console;
518
+ enabled?: boolean;
519
+ probability?: number;
520
+ strategy?: 'error' | 'timeout' | 'random';
521
+ timeoutMs?: number;
522
+ error?: unknown;
307
523
  }
308
524
  ```
309
525
 
310
- - `logger`
311
- Console-compatible logger implementation. By default the wrapper creates one via `@vyriy/logger`.
526
+ `enabled` and `timeoutMs` can be read through `@vyriy/config` when they are not passed directly.
312
527
 
313
- Example:
528
+ ### API Wrappers
314
529
 
315
- ```ts
316
- import { withLogger } from '@vyriy/handler';
530
+ #### `withApiError(options?)`
317
531
 
318
- export const handler = withLogger({
319
- logger: console,
320
- })(async (event) => {
321
- return {
322
- ok: true,
323
- event,
324
- };
325
- });
532
+ Catches API handler failures and converts them to an API Gateway result. Without a custom `errorHandler`, it returns a JSON `500`.
533
+
534
+ Options:
535
+
536
+ ```ts
537
+ {
538
+ errorHandler?: (error: unknown, args: HandlerParams<ApiEvent>) => Promise<ApiResult> | ApiResult;
539
+ }
326
540
  ```
327
541
 
328
- ### `withTimeout()`
542
+ #### `withHealthcheck(options?)`
329
543
 
330
- Races the handler against a timeout scheduled one second before the Lambda runtime limit.
544
+ Returns a JSON `200` response when `event.path` matches the configured `path`.
331
545
 
332
- Example:
546
+ Options:
333
547
 
334
548
  ```ts
335
- import { withTimeout } from '@vyriy/handler';
336
-
337
- export const handler = withTimeout()(async () => {
338
- await doWork();
339
- });
549
+ {
550
+ path?: string;
551
+ action?: () => Promise<void>;
552
+ }
340
553
  ```
341
554
 
342
- ### `withContext()`
555
+ #### `withHeaders(options?)`
343
556
 
344
- Sets `context.callbackWaitsForEmptyEventLoop = false` before calling the handler.
557
+ Adds configured headers to the API result.
345
558
 
346
- Example:
559
+ Options:
347
560
 
348
561
  ```ts
349
- import { withContext } from '@vyriy/handler';
350
-
351
- export const handler = withContext()(async (_event, context) => {
352
- return {
353
- waitForEmptyLoop: context.callbackWaitsForEmptyEventLoop,
354
- };
355
- });
562
+ Record<string, string>;
356
563
  ```
357
564
 
358
- ### `withChaos(options?)`
565
+ #### `withCors()`
359
566
 
360
- Injects development-only random failures before the wrapped handler runs.
567
+ Short-circuits API Gateway `OPTIONS` requests with a `204` response and delegates all other requests.
568
+
569
+ ### HTTP Wrappers
570
+
571
+ #### `httpWithError(options?)`
572
+
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.
361
574
 
362
575
  Options:
363
576
 
364
577
  ```ts
365
578
  {
366
- enabled?: boolean;
367
- probability?: number;
368
- strategy?: 'error' | 'timeout' | 'random';
369
- timeoutMs?: number;
370
- error?: unknown;
579
+ errorHandler?: (error: unknown, args: HttpHandlerParams) => Promise<void> | void;
371
580
  }
372
581
  ```
373
582
 
374
- - `enabled`
375
- Turns chaos injection on. By default the wrapper reads `CHAOS_ENABLED` through `@vyriy/config`.
376
-
377
- - `probability`
378
- Probability from `0` to `1` that a failure is injected.
583
+ #### `httpWithLogger(options?)`
379
584
 
380
- - `strategy`
381
- Chooses whether to throw an error, wait and time out, or pick one randomly. Defaults to `'random'`.
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`.
382
586
 
383
- - `timeoutMs`
384
- Timeout delay used when the timeout strategy is selected. By default the wrapper reads `CHAOS_TIMEOUT_MS` through `@vyriy/config`.
587
+ #### `httpWithHealthcheck(options?)`
385
588
 
386
- - `error`
387
- Error value normalized through `@vyriy/error` when the error strategy is selected.
589
+ Writes a JSON `200` response when the request path matches the configured `path`.
388
590
 
389
- Example:
591
+ Options:
390
592
 
391
593
  ```ts
392
- import { withChaos } from '@vyriy/handler';
393
-
394
- export const handler = withChaos({
395
- enabled: true,
396
- probability: 0.2,
397
- strategy: 'random',
398
- timeoutMs: 1500,
399
- })(async () => {
400
- return {
401
- ok: true,
402
- };
403
- });
594
+ {
595
+ path?: string;
596
+ action?: () => Promise<void>;
597
+ body?: unknown;
598
+ }
404
599
  ```
405
600
 
406
- ### `withSmoke()`
601
+ #### `httpWithHeaders(options?)`
407
602
 
408
- Returns the smoke response when the incoming event has `isSmoke: true`.
603
+ Sets configured headers on the response before delegating, so handler-defined headers win on key conflicts.
409
604
 
410
- Example:
605
+ Options:
411
606
 
412
607
  ```ts
413
- import { withSmoke } from '@vyriy/handler';
414
-
415
- export const handler = withSmoke()(async () => {
416
- return {
417
- statusCode: 200,
418
- body: JSON.stringify({
419
- status: 'runtime',
420
- }),
421
- };
422
- });
608
+ Record<string, string>;
423
609
  ```
424
610
 
425
- `withSmoke()` is used by the API, DynamoDB Streams, S3, SES receipt, schedule, SNS, and SQS chains.
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.
426
630
 
427
631
  ## Types
428
632
 
429
- The package also exports shared handler types:
633
+ The package exports shared Lambda handler types from the root entrypoint:
430
634
 
431
635
  ```ts
432
636
  import type { Context, Decorator, Handler, HandlerParams, Response } from '@vyriy/handler';
433
637
  ```
434
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
+
435
649
  ## Notes
436
650
 
437
651
  - `api` includes API-specific wrappers such as healthcheck handling, default headers, and CORS preflight handling
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
440
- - `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