@vyriy/handler 0.3.0 → 0.3.2

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
@@ -45,33 +45,31 @@ export const handler = api(async (event) => ({
45
45
  }));
46
46
  ```
47
47
 
48
- For Lambda response streaming, keep the reusable handler separate from the runtime entrypoints:
48
+ For Lambda response streaming, use the separate stream chain:
49
49
 
50
50
  ```ts
51
51
  // handler.ts
52
- import { api, streamify } from '@vyriy/handler';
52
+ import { streamApi } from '@vyriy/handler';
53
53
 
54
- export const handler = streamify(
55
- api(async (event, responseStream, context) => {
56
- responseStream.setContentType?.('text/plain');
57
- responseStream.write(`Request path: ${event.path}\n`);
58
- responseStream.write(`Request id: ${context.awsRequestId}\n`);
59
- responseStream.write('Part 1 of the response...');
60
- responseStream.write('Part 2 of the response...');
61
- responseStream.end();
62
- }),
63
- );
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
+ });
64
60
  ```
65
61
 
62
+ `streamApi(...)` handlers receive `(event, responseStream, context)` and write directly to the response stream.
63
+
66
64
  Use the same handler locally, in Docker, or in a Fargate-style HTTP runtime:
67
65
 
68
66
  ```ts
69
67
  // server.ts
70
- import { server } from '@vyriy/server';
68
+ import { streamServer } from '@vyriy/server';
71
69
 
72
70
  import { handler } from './handler.js';
73
71
 
74
- server(handler);
72
+ streamServer(handler);
75
73
  ```
76
74
 
77
75
  Use the same handler in AWS Lambda response streaming:
@@ -83,24 +81,20 @@ import { handler } from './handler.js';
83
81
  export const main = awslambda.streamifyResponse(handler);
84
82
  ```
85
83
 
86
- That `handler.ts` shape is already streamified. For a standard non-streaming Lambda, export an `api(...)` handler directly without `streamify(...)` and without `responseStream`.
84
+ That `handler.ts` shape already matches Lambda response streaming. For a standard non-streaming Lambda, export an `api(...)` handler directly without `responseStream`.
87
85
 
88
86
  You can also inline the same shape in one file when a separate local entrypoint is not needed:
89
87
 
90
88
  ```ts
91
- import { api, streamify } from '@vyriy/handler';
89
+ import { streamApi } from '@vyriy/handler';
92
90
 
93
91
  export const main = awslambda.streamifyResponse(
94
- streamify(
95
- api(async (event, responseStream, context) => {
96
- responseStream.setContentType?.('text/plain');
97
- responseStream.write(`Request path: ${event.path}\n`);
98
- responseStream.write(`Request id: ${context.awsRequestId}\n`);
99
- responseStream.write('Part 1 of the response...');
100
- responseStream.write('Part 2 of the response...');
101
- responseStream.end();
102
- }),
103
- ),
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
+ }),
104
98
  );
105
99
  ```
106
100
 
@@ -141,15 +135,14 @@ export const handler = schedule(async (event) => {
141
135
  Compose a custom handler pipeline from individual helpers:
142
136
 
143
137
  ```ts
144
- import { compose, withChaos, withContext, withError, withLogger, withSmoke, withTimeout } from '@vyriy/handler';
138
+ import { compose, withChaos, withContext, withError, withLogger, withTimeout } from '@vyriy/handler';
145
139
 
146
140
  export const handler = compose(
147
- withError({ throwError: true }),
141
+ withError(),
148
142
  withLogger(),
149
143
  withChaos(),
150
144
  withTimeout(),
151
145
  withContext(),
152
- withSmoke(),
153
146
  )(async (event) => {
154
147
  return {
155
148
  ok: true,
@@ -179,7 +172,7 @@ const withRequestId = factory<{ headerName?: string }>(async (handler, args, opt
179
172
  });
180
173
 
181
174
  export const handler = compose(
182
- withError({ throwError: true }),
175
+ withError(),
183
176
  withLogger(),
184
177
  withTimeout(),
185
178
  withRequestId({
@@ -196,11 +189,9 @@ export const handler = compose(
196
189
  ## Prebuilt Chains
197
190
 
198
191
  - `api`
199
- API Gateway chain with error handling, logging, timeout handling, context setup, smoke checks, healthcheck handling, default headers, CORS preflight handling, and structural support for streaming/static-file response results.
200
- - `streamifyApiResponse`
201
- Adapts an `api(...)` handler that returns a streaming result to the three-argument handler shape expected by `awslambda.streamifyResponse`.
202
- - `streamify`
203
- Alias for `streamifyApiResponse` intended for direct Lambda response streaming handlers.
192
+ API Gateway chain with error handling, logging, timeout handling, context setup, smoke checks, healthcheck handling, default headers, and CORS preflight handling.
193
+ - `streamApi`
194
+ 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.
204
195
 
205
196
  - `schedule`
206
197
  EventBridge schedule chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
@@ -215,22 +206,18 @@ export const handler = compose(
215
206
 
216
207
  ### `withError(options?)`
217
208
 
218
- Catches handler failures, optionally runs an async error handler, and rethrows only when `throwError` is enabled.
209
+ Catches handler failures, optionally runs a side-effect `errorHandler`, and rethrows the original error.
219
210
 
220
211
  Options:
221
212
 
222
213
  ```ts
223
214
  {
224
- errorHandler?: (error: unknown) => Promise<void>;
225
- throwError?: boolean;
215
+ errorHandler?: (error: unknown, args: HandlerParams<Event>) => Promise<void> | void;
226
216
  }
227
217
  ```
228
218
 
229
219
  - `errorHandler`
230
- Async callback invoked with the caught error.
231
-
232
- - `throwError`
233
- Re-throws the original error after `errorHandler` runs. Defaults to `false`.
220
+ Callback invoked with the caught error and handler arguments before the original error is rethrown.
234
221
 
235
222
  Example:
236
223
 
@@ -241,12 +228,15 @@ export const handler = withError({
241
228
  errorHandler: async (error) => {
242
229
  console.error('Handler failed:', error);
243
230
  },
244
- throwError: true,
245
231
  })(async () => {
246
232
  throw new Error('boom');
247
233
  });
248
234
  ```
249
235
 
236
+ ### `withApiError(options?)`
237
+
238
+ Catches API handler failures and converts them to an API Gateway result. Without a custom `errorHandler`, it returns a JSON `500`.
239
+
250
240
  ### `withLogger(options?)`
251
241
 
252
242
  Logs the incoming event and context, then logs either the result or the thrown error.
@@ -357,7 +347,7 @@ export const handler = withChaos({
357
347
 
358
348
  ### `withSmoke()`
359
349
 
360
- Returns the smoke response when the incoming event matches the smoke request payload.
350
+ Returns the smoke response when the incoming event has `isSmoke: true`.
361
351
 
362
352
  Example:
363
353
 
@@ -374,16 +364,18 @@ export const handler = withSmoke()(async () => {
374
364
  });
375
365
  ```
376
366
 
367
+ `withSmoke()` is used by the API, schedule, SNS, and SQS chains.
368
+
377
369
  ## Types
378
370
 
379
371
  The package also exports shared handler types:
380
372
 
381
373
  ```ts
382
- import type { Context, Decorator, Handler, Response } from '@vyriy/handler';
374
+ import type { Context, Decorator, Handler, HandlerParams, Response } from '@vyriy/handler';
383
375
  ```
384
376
 
385
377
  ## Notes
386
378
 
387
379
  - `api` includes API-specific wrappers such as healthcheck handling, default headers, and CORS preflight handling
388
- - `schedule`, `sns`, and `sqs` enable `throwError: true` in `withError(...)`
389
- - `withSmoke()` delegates matching to `@vyriy/smoke`
380
+ - `schedule`, `sns`, and `sqs` use `withError()` so failures are rethrown for event-source retry behavior
381
+ - `withSmoke()` delegates matching to `@vyriy/smoke` and returns its API Gateway-compatible response
package/api.d.ts CHANGED
@@ -1,7 +1,2 @@
1
- import type { ApiEvent, ApiResult, Context, Handler, ResponseStream } from './types.js';
2
- type ApiDecorator = {
3
- (handler: Handler<ApiEvent, ApiResult | void, [context: Context]>): Handler<ApiEvent, ApiResult | void, [context: Context]>;
4
- (handler: Handler<ApiEvent, ApiResult | void, [responseStream: ResponseStream, context: Context]>): Handler<ApiEvent, ApiResult | void, [responseStream: ResponseStream, context: Context]>;
5
- };
6
- export declare const api: ApiDecorator;
7
- export {};
1
+ export declare const api: import("./types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult>;
2
+ export declare const streamApi: import("./types.js").StreamDecorator<import("aws-lambda").APIGatewayProxyEvent>;
package/api.js CHANGED
@@ -1,17 +1,24 @@
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
- import { withHealthcheck } from './wrapper/healthcheck.js';
8
- import { withHeaders } from './wrapper/headers.js';
9
- import { withCors } from './wrapper/cors.js';
10
- import { withChaos } from './wrapper/chaos.js';
11
- export const api = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke(), withHealthcheck(), withHeaders({
1
+ import { compose, streamCompose } from './compose.js';
2
+ import { streamWithApiError, withApiError } from './wrapper/error.js';
3
+ import { streamWithLogger, withLogger } from './wrapper/logger.js';
4
+ import { streamWithTimeout, withTimeout } from './wrapper/timeout.js';
5
+ import { streamWithContext, withContext } from './wrapper/context.js';
6
+ import { streamWithSmoke, withSmoke } from './wrapper/smoke.js';
7
+ import { streamWithHealthcheck, withHealthcheck } from './wrapper/healthcheck.js';
8
+ import { streamWithHeaders, withHeaders } from './wrapper/headers.js';
9
+ import { streamWithCors, withCors } from './wrapper/cors.js';
10
+ import { streamWithChaos, withChaos } from './wrapper/chaos.js';
11
+ export const api = compose(withApiError(), withLogger(), withTimeout(), withContext(), withSmoke(), withHealthcheck(), withHeaders({
12
12
  'Access-Control-Allow-Origin': '*',
13
13
  'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
14
14
  'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-Api-Key, Accept, User-Agent, X-CSRF-Token',
15
15
  'Content-Type': 'application/json',
16
16
  'X-Robots-Tag': 'noindex, nofollow',
17
17
  }), withCors(), withChaos());
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',
24
+ }), streamWithCors(), streamWithChaos());
package/compose.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import type { Compose } from './types.js';
1
+ import type { Compose, StreamCompose } from './types.js';
2
2
  export declare const compose: Compose;
3
+ export declare const streamCompose: StreamCompose;
package/compose.js CHANGED
@@ -1 +1,2 @@
1
1
  export const compose = (...fns) => fns.reduceRight((prevDecorator, nextDecorator) => (handler) => nextDecorator(prevDecorator(handler)));
2
+ export const streamCompose = (...fns) => fns.reduceRight((prevDecorator, nextDecorator) => (handler) => nextDecorator(prevDecorator(handler)));
package/factory.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { Context } from 'aws-lambda';
2
- import type { Factory, HandlerArgs, HandlerParams } from './types.js';
3
- export declare const getContext: <Event, Args extends HandlerArgs>(args: HandlerParams<Event, Args>) => Context;
2
+ import type { Factory, HandlerParams, ResponseStream, StreamFactory, StreamHandlerParams } from './types.js';
3
+ export declare const getContext: <Event>(args: HandlerParams<Event>) => Context;
4
+ export declare const getResponseStream: <Event>(args: StreamHandlerParams<Event>) => ResponseStream;
5
+ export declare const getStreamContext: <Event>(args: StreamHandlerParams<Event>) => Context;
4
6
  export declare const factory: Factory;
7
+ export declare const streamFactory: StreamFactory;
package/factory.js CHANGED
@@ -1,3 +1,5 @@
1
- const createFactory = (wrapper) => (options) => (handler) => async (event, ...args) => wrapper(handler, [event, ...args], options);
2
- export const getContext = (args) => args.at(-1);
3
- export const factory = createFactory;
1
+ export const getContext = (args) => args[1];
2
+ export const getResponseStream = (args) => args[1];
3
+ export const getStreamContext = (args) => args[2];
4
+ export const factory = (wrapper) => (options) => (handler) => async (event, context) => wrapper(handler, [event, context], options);
5
+ export const streamFactory = (wrapper) => (options) => (handler) => async (event, responseStream, context) => wrapper(handler, [event, responseStream, context], options);
package/index.d.ts CHANGED
@@ -4,7 +4,6 @@ export * from './factory.js';
4
4
  export * from './schedule.js';
5
5
  export * from './sns.js';
6
6
  export * from './sqs.js';
7
- export * from './streamify.js';
8
7
  export * from './wrapper/context.js';
9
8
  export * from './wrapper/cors.js';
10
9
  export * from './wrapper/chaos.js';
package/index.js CHANGED
@@ -4,7 +4,6 @@ export * from './factory.js';
4
4
  export * from './schedule.js';
5
5
  export * from './sns.js';
6
6
  export * from './sqs.js';
7
- export * from './streamify.js';
8
7
  export * from './wrapper/context.js';
9
8
  export * from './wrapper/cors.js';
10
9
  export * from './wrapper/chaos.js';
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@vyriy/handler",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Composable AWS Lambda handler chains and wrappers for Vyriy projects",
5
5
  "type": "module",
6
6
  "main": "./index.js",
7
7
  "dependencies": {
8
8
  "@types/aws-lambda": "^8.10.161",
9
- "@vyriy/chaos": "0.3.0",
10
- "@vyriy/config": "0.3.0",
11
- "@vyriy/logger": "0.3.0",
12
- "@vyriy/smoke": "0.3.0",
13
- "@vyriy/timeout": "0.3.0"
9
+ "@vyriy/chaos": "0.3.2",
10
+ "@vyriy/config": "0.3.2",
11
+ "@vyriy/logger": "0.3.2",
12
+ "@vyriy/smoke": "0.3.2",
13
+ "@vyriy/timeout": "0.3.2"
14
14
  },
15
15
  "agents": "./AGENTS.md",
16
16
  "license": "MIT",
@@ -96,16 +96,6 @@
96
96
  "import": "./sqs.js",
97
97
  "default": "./sqs.js"
98
98
  },
99
- "./streamify": {
100
- "types": "./streamify.d.ts",
101
- "import": "./streamify.js",
102
- "default": "./streamify.js"
103
- },
104
- "./streamify.js": {
105
- "types": "./streamify.d.ts",
106
- "import": "./streamify.js",
107
- "default": "./streamify.js"
108
- },
109
99
  "./wrapper/chaos": {
110
100
  "types": "./wrapper/chaos.d.ts",
111
101
  "import": "./wrapper/chaos.js",
@@ -186,6 +176,16 @@
186
176
  "import": "./wrapper/smoke.js",
187
177
  "default": "./wrapper/smoke.js"
188
178
  },
179
+ "./wrapper/stream": {
180
+ "types": "./wrapper/stream.d.ts",
181
+ "import": "./wrapper/stream.js",
182
+ "default": "./wrapper/stream.js"
183
+ },
184
+ "./wrapper/stream.js": {
185
+ "types": "./wrapper/stream.d.ts",
186
+ "import": "./wrapper/stream.js",
187
+ "default": "./wrapper/stream.js"
188
+ },
189
189
  "./wrapper/timeout": {
190
190
  "types": "./wrapper/timeout.d.ts",
191
191
  "import": "./wrapper/timeout.js",
package/schedule.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import type { ScheduledEvent } from 'aws-lambda';
2
- export declare const schedule: import("./types.js").Decorator<ScheduledEvent<any>, void, [context: import("aws-lambda").Context]>;
2
+ export declare const schedule: import("./types.js").Decorator<ScheduledEvent<any>, void>;
package/schedule.js CHANGED
@@ -4,4 +4,4 @@ import { withLogger } from './wrapper/logger.js';
4
4
  import { withTimeout } from './wrapper/timeout.js';
5
5
  import { withContext } from './wrapper/context.js';
6
6
  import { withSmoke } from './wrapper/smoke.js';
7
- export const schedule = compose(withError({ throwError: true }), withLogger(), withTimeout(), withContext(), withSmoke());
7
+ export const schedule = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
package/sns.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import type { SNSEvent } from 'aws-lambda';
2
- export declare const sns: import("./types.js").Decorator<SNSEvent, void, [context: import("aws-lambda").Context]>;
2
+ export declare const sns: import("./types.js").Decorator<SNSEvent, void>;
package/sns.js CHANGED
@@ -4,4 +4,4 @@ import { withLogger } from './wrapper/logger.js';
4
4
  import { withTimeout } from './wrapper/timeout.js';
5
5
  import { withContext } from './wrapper/context.js';
6
6
  import { withSmoke } from './wrapper/smoke.js';
7
- export const sns = compose(withError({ throwError: true }), withLogger(), withTimeout(), withContext(), withSmoke());
7
+ export const sns = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
package/sqs.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import type { SQSEvent } from 'aws-lambda';
2
- export declare const sqs: import("./types.js").Decorator<SQSEvent, void, [context: import("aws-lambda").Context]>;
2
+ export declare const sqs: import("./types.js").Decorator<SQSEvent, void>;
package/sqs.js CHANGED
@@ -4,4 +4,4 @@ import { withLogger } from './wrapper/logger.js';
4
4
  import { withTimeout } from './wrapper/timeout.js';
5
5
  import { withContext } from './wrapper/context.js';
6
6
  import { withSmoke } from './wrapper/smoke.js';
7
- export const sqs = compose(withError({ throwError: true }), withLogger(), withTimeout(), withContext(), withSmoke());
7
+ export const sqs = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
package/types.d.ts CHANGED
@@ -9,26 +9,26 @@ export type ResponseStream = {
9
9
  writableEnded?: boolean;
10
10
  write(chunk: string | Buffer | Uint8Array): unknown;
11
11
  };
12
- export type StreamWriter = (responseStream: ResponseStream) => Promise<void> | void;
13
- export type StreamResult = Omit<Partial<APIGatewayProxyResult>, 'body'> & {
14
- stream: StreamWriter;
15
- body?: never;
16
- };
17
- export type StaticFileResult = Omit<Partial<APIGatewayProxyResult>, 'body'> & {
18
- filePath: string;
19
- body?: never;
20
- };
21
- export type ApiResult = APIGatewayProxyResult | StaticFileResult | StreamResult;
12
+ export type ApiResult = APIGatewayProxyResult;
22
13
  export type ApiEvent = APIGatewayProxyEvent;
23
- export type HandlerArgs = [...args: unknown[], context: Context];
24
- export type HandlerParams<Event, Args extends HandlerArgs = [context: Context]> = [event: Event, ...args: Args];
14
+ export type HandlerParams<Event> = [event: Event, context: Context];
15
+ export type StreamHandlerParams<Event> = [event: Event, responseStream: ResponseStream, context: Context];
25
16
  export type Response<Result> = Promise<Result>;
26
- export type Handler<Event, Result, Args extends HandlerArgs = [context: Context]> = (event: Event, ...args: Args) => Response<Result>;
27
- export type Decorator<Event, Result, Args extends HandlerArgs = [context: Context]> = (handler: Handler<Event, Result, Args>) => Handler<Event, Result, Args>;
28
- export type Compose = <Event, Result, Args extends HandlerArgs = [context: Context]>(...decorators: Array<Decorator<Event, Result, Args>>) => Decorator<Event, Result, Args>;
29
- export type Wrapper<Options> = <Event, Result, Args extends HandlerArgs = [context: Context]>(handler: Handler<Event, Result, Args>, args: HandlerParams<Event, Args>, options?: Options) => Response<Result>;
30
- export type TypedWrapper<Event, Result, Options, Args extends HandlerArgs = [context: Context]> = (handler: Handler<Event, Result, Args>, args: HandlerParams<Event, Args>, options?: Options) => Response<Result>;
17
+ export type Handler<Event, Result> = (event: Event, context: Context) => Response<Result>;
18
+ export type StreamHandler<Event> = (event: Event, responseStream: ResponseStream, context: Context) => Response<void>;
19
+ export type Decorator<Event, Result> = (handler: Handler<Event, Result>) => Handler<Event, Result>;
20
+ export type StreamDecorator<Event> = (handler: StreamHandler<Event>) => StreamHandler<Event>;
21
+ export type Compose = <Event, Result>(...decorators: Array<Decorator<Event, Result>>) => Decorator<Event, Result>;
22
+ export type StreamCompose = <Event>(...decorators: Array<StreamDecorator<Event>>) => StreamDecorator<Event>;
23
+ export type Wrapper<Options> = <Event, Result>(handler: Handler<Event, Result>, args: HandlerParams<Event>, options?: Options) => Response<Result>;
24
+ export type StreamWrapper<Options> = <Event>(handler: StreamHandler<Event>, args: StreamHandlerParams<Event>, options?: Options) => Response<void>;
25
+ export type TypedWrapper<Event, Result, Options> = (handler: Handler<Event, Result>, args: HandlerParams<Event>, options?: Options) => Response<Result>;
26
+ export type StreamTypedWrapper<Event, Options> = (handler: StreamHandler<Event>, args: StreamHandlerParams<Event>, options?: Options) => Response<void>;
31
27
  export type Factory = {
32
- <Options = undefined>(wrapper: Wrapper<Options>): <Event, Result, Args extends HandlerArgs = [context: Context]>(options?: Options) => Decorator<Event, Result, Args>;
33
- <Options, Event, Result, Args extends HandlerArgs = [context: Context]>(wrapper: TypedWrapper<Event, Result, Options, Args>): (options?: Options) => Decorator<Event, Result, Args>;
28
+ <Options = undefined>(wrapper: Wrapper<Options>): <Event, Result>(options?: Options) => Decorator<Event, Result>;
29
+ <Options, Event, Result>(wrapper: TypedWrapper<Event, Result, Options>): (options?: Options) => Decorator<Event, Result>;
30
+ };
31
+ export type StreamFactory = {
32
+ <Options = undefined>(wrapper: StreamWrapper<Options>): <Event>(options?: Options) => StreamDecorator<Event>;
33
+ <Options, Event>(wrapper: StreamTypedWrapper<Event, Options>): (options?: Options) => StreamDecorator<Event>;
34
34
  };
@@ -1,2 +1,3 @@
1
1
  import { type ChaosOptions } from '@vyriy/chaos';
2
- export declare const withChaos: <Event, Result, Args extends import("../types.js").HandlerArgs = [context: import("aws-lambda").Context]>(options?: ChaosOptions | undefined) => import("../types.js").Decorator<Event, Result, Args>;
2
+ export declare const withChaos: <Event, Result>(options?: ChaosOptions | undefined) => import("../types.js").Decorator<Event, Result>;
3
+ export declare const streamWithChaos: <Event>(options?: ChaosOptions | undefined) => import("../types.js").StreamDecorator<Event>;
package/wrapper/chaos.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { chaos } from '@vyriy/chaos';
2
2
  import { getConfig } from '@vyriy/config';
3
- import { factory } from '../factory.js';
3
+ import { factory, streamFactory } from '../factory.js';
4
4
  const getChaosEnabled = () => getConfig('CHAOS_ENABLED', false, 'boolean');
5
5
  const getChaosErrorEnabled = () => getConfig('CHAOS_ERROR_ENABLED', true, 'boolean');
6
6
  const getChaosTimeoutEnabled = () => getConfig('CHAOS_TIMEOUT_ENABLED', true, 'boolean');
@@ -33,3 +33,14 @@ export const withChaos = factory(async (handler, args, options = {}) => {
33
33
  });
34
34
  return handler(...args);
35
35
  });
36
+ export const streamWithChaos = streamFactory(async (handler, args, options = {}) => {
37
+ const enabled = options.enabled ?? getChaosEnabled();
38
+ const strategy = getStrategy(options);
39
+ await chaos({
40
+ ...options,
41
+ enabled: enabled && (strategy !== 'random' || getChaosErrorEnabled() || getChaosTimeoutEnabled()),
42
+ strategy,
43
+ timeoutMs: options.timeoutMs ?? getChaosTimeoutMs(),
44
+ });
45
+ return handler(...args);
46
+ });
@@ -1 +1,2 @@
1
- export declare const withContext: <Event, Result, Args extends import("../types.js").HandlerArgs = [context: import("aws-lambda").Context]>(options?: undefined) => import("../types.js").Decorator<Event, Result, Args>;
1
+ export declare const withContext: <Event, Result>(options?: undefined) => import("../types.js").Decorator<Event, Result>;
2
+ export declare const streamWithContext: <Event>(options?: undefined) => import("../types.js").StreamDecorator<Event>;
@@ -1,6 +1,11 @@
1
- import { factory, getContext } from '../factory.js';
1
+ import { factory, getContext, getStreamContext, streamFactory } from '../factory.js';
2
2
  export const withContext = factory(async (handler, args) => {
3
3
  const ctx = getContext(args);
4
4
  ctx.callbackWaitsForEmptyEventLoop = false;
5
5
  return handler(...args);
6
6
  });
7
+ export const streamWithContext = streamFactory(async (handler, args) => {
8
+ const ctx = getStreamContext(args);
9
+ ctx.callbackWaitsForEmptyEventLoop = false;
10
+ return handler(...args);
11
+ });
package/wrapper/cors.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import type { ApiResult, HandlerArgs } from '../types.js';
2
- export declare const withCors: (options?: undefined) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, void | ApiResult, HandlerArgs>;
1
+ export declare const withCors: (options?: unknown) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult>;
2
+ export declare const streamWithCors: (options?: unknown) => import("../types.js").StreamDecorator<import("aws-lambda").APIGatewayProxyEvent>;
package/wrapper/cors.js CHANGED
@@ -1,4 +1,5 @@
1
- import { factory } from '../factory.js';
1
+ import { factory, streamFactory } from '../factory.js';
2
+ import { responseStream } from './stream.js';
2
3
  export const withCors = factory(async (handler, args) => {
3
4
  const [request] = args;
4
5
  if (request.httpMethod === 'OPTIONS') {
@@ -9,3 +10,11 @@ export const withCors = factory(async (handler, args) => {
9
10
  }
10
11
  return handler(...args);
11
12
  });
13
+ export const streamWithCors = streamFactory(async (handler, args) => {
14
+ const [request, stream] = args;
15
+ if (request.httpMethod === 'OPTIONS') {
16
+ responseStream(stream, { statusCode: 204 }).end();
17
+ return;
18
+ }
19
+ await handler(...args);
20
+ });
@@ -1,5 +1,12 @@
1
- export type ErrorOptions = {
2
- errorHandler?: (err: unknown) => Promise<void>;
3
- throwError?: boolean;
1
+ import type { ApiEvent, ApiResult, Decorator, HandlerParams, StreamDecorator, StreamHandlerParams } from '../types.js';
2
+ export type ErrorHandler<Params extends unknown[]> = (err: unknown, args: Params) => Promise<void> | void;
3
+ export type ApiErrorHandler<Params extends unknown[], Result> = (err: unknown, args: Params) => Promise<Result> | Result;
4
+ export type ErrorOptions<Params extends unknown[]> = {
5
+ errorHandler?: ErrorHandler<Params>;
4
6
  };
5
- export declare const withError: <Event, Result, Args extends import("../types.js").HandlerArgs = [context: import("aws-lambda").Context]>(options?: ErrorOptions | undefined) => import("../types.js").Decorator<Event, Result, Args>;
7
+ export type ApiErrorOptions<Params extends unknown[], Result> = {
8
+ errorHandler?: ApiErrorHandler<Params, Result>;
9
+ };
10
+ export declare const withError: <Event, Result>(options?: ErrorOptions<HandlerParams<Event>>) => Decorator<Event, Result>;
11
+ export declare const withApiError: (options?: ApiErrorOptions<HandlerParams<ApiEvent>, ApiResult>) => Decorator<ApiEvent, ApiResult>;
12
+ export declare const streamWithApiError: (options?: ApiErrorOptions<StreamHandlerParams<ApiEvent>, ApiResult | void>) => StreamDecorator<ApiEvent>;
package/wrapper/error.js CHANGED
@@ -1,17 +1,42 @@
1
- import { factory } from '../factory.js';
2
- export const withError = factory(async (handler, args, options = {}) => {
3
- const { errorHandler, throwError = false } = options;
4
- let result;
1
+ import { STATUS_CODES } from 'node:http';
2
+ import { responseStream } from './stream.js';
3
+ const defaultErrorResult = () => ({
4
+ statusCode: 500,
5
+ headers: {
6
+ 'content-type': 'application/json',
7
+ },
8
+ body: JSON.stringify({
9
+ message: STATUS_CODES[500],
10
+ }),
11
+ });
12
+ export const withError = (options = {}) => (handler) => async (event, context) => {
5
13
  try {
6
- result = await handler(...args);
14
+ return await handler(event, context);
7
15
  }
8
16
  catch (err) {
9
- if (errorHandler) {
10
- await errorHandler(err);
11
- }
12
- if (throwError) {
13
- throw err;
17
+ await options.errorHandler?.(err, [event, context]);
18
+ throw err;
19
+ }
20
+ };
21
+ export const withApiError = (options = {}) => (handler) => async (event, context) => {
22
+ try {
23
+ return await handler(event, context);
24
+ }
25
+ catch (err) {
26
+ return options.errorHandler?.(err, [event, context]) ?? defaultErrorResult();
27
+ }
28
+ };
29
+ export const streamWithApiError = (options = {}) => (handler) => async (event, stream, context) => {
30
+ try {
31
+ await handler(event, stream, context);
32
+ }
33
+ catch (err) {
34
+ const result = (await options.errorHandler?.(err, [event, stream, context])) ?? defaultErrorResult();
35
+ if (result) {
36
+ responseStream(stream, {
37
+ headers: result.headers,
38
+ statusCode: result.statusCode,
39
+ }).end(result.body);
14
40
  }
15
41
  }
16
- return result;
17
- });
42
+ };
@@ -1,2 +1,2 @@
1
- import type { ApiResult, HandlerArgs } from '../types.js';
2
- export declare const withHeaders: (options?: Record<string, string> | undefined) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, void | ApiResult, HandlerArgs>;
1
+ export declare const withHeaders: (options?: Record<string, string> | undefined) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult>;
2
+ export declare const streamWithHeaders: (options?: Record<string, string> | undefined) => import("../types.js").StreamDecorator<import("aws-lambda").APIGatewayProxyEvent>;
@@ -1,12 +1,17 @@
1
- import { factory } from '../factory.js';
2
- export const withHeaders = factory(async (handler, args, options = {}) => {
3
- const result = await handler(...args);
4
- if (!result) {
5
- return result;
6
- }
1
+ import { factory, streamFactory } from '../factory.js';
2
+ import { responseStream } from './stream.js';
3
+ const mergeHeaders = (result, options) => {
7
4
  result.headers = {
8
5
  ...options,
9
6
  ...result.headers,
10
7
  };
11
8
  return result;
9
+ };
10
+ export const withHeaders = factory(async (handler, args, options = {}) => {
11
+ const result = await handler(...args);
12
+ return mergeHeaders(result, options);
13
+ });
14
+ export const streamWithHeaders = streamFactory(async (handler, args, options = {}) => {
15
+ const [event, stream, context] = args;
16
+ await handler(event, responseStream(stream, { headers: options }), context);
12
17
  });
@@ -1,5 +1,9 @@
1
- import type { ApiResult, HandlerArgs } from '../types.js';
2
- export declare const withHealthcheck: (options?: {
1
+ export type HealthcheckOptions = {
3
2
  path?: string;
4
3
  action?: () => Promise<void>;
5
- } | undefined) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, void | ApiResult, HandlerArgs>;
4
+ };
5
+ export declare const withHealthcheck: (options?: HealthcheckOptions | undefined) => import("../types.js").Decorator<import("aws-lambda").APIGatewayProxyEvent, import("aws-lambda").APIGatewayProxyResult | {
6
+ statusCode: number;
7
+ body: string;
8
+ }>;
9
+ export declare const streamWithHealthcheck: (options?: HealthcheckOptions | undefined) => import("../types.js").StreamDecorator<import("aws-lambda").APIGatewayProxyEvent>;
@@ -1,18 +1,32 @@
1
1
  import { STATUS_CODES } from 'node:http';
2
- import { factory } from '../factory.js';
2
+ import { factory, streamFactory } from '../factory.js';
3
+ import { responseStream } from './stream.js';
4
+ const getHealthcheckResult = async (event, options = {}) => {
5
+ const { path = '/healthcheck', action } = options;
6
+ if (event.path !== path) {
7
+ return undefined;
8
+ }
9
+ if (action) {
10
+ await action();
11
+ }
12
+ return {
13
+ statusCode: 200,
14
+ body: JSON.stringify({
15
+ message: STATUS_CODES[200],
16
+ }),
17
+ };
18
+ };
3
19
  export const withHealthcheck = factory(async (handler, args, options = {}) => {
4
20
  const [event] = args;
5
- const { path = '/healthcheck', action } = options;
6
- if (event.path === path) {
7
- if (action) {
8
- await action();
9
- }
10
- return {
11
- statusCode: 200,
12
- body: JSON.stringify({
13
- message: STATUS_CODES[200],
14
- }),
15
- };
21
+ const result = await getHealthcheckResult(event, options);
22
+ return result ?? handler(...args);
23
+ });
24
+ export const streamWithHealthcheck = streamFactory(async (handler, args, options = {}) => {
25
+ const [event, stream] = args;
26
+ const result = await getHealthcheckResult(event, options);
27
+ if (result) {
28
+ responseStream(stream, { statusCode: result.statusCode }).end(result.body);
29
+ return;
16
30
  }
17
- return handler(...args);
31
+ await handler(...args);
18
32
  });
@@ -1,4 +1,5 @@
1
1
  export type LoggerOptions = {
2
2
  logger?: typeof console;
3
3
  };
4
- export declare const withLogger: <Event, Result, Args extends import("../types.js").HandlerArgs = [context: import("aws-lambda").Context]>(options?: LoggerOptions | undefined) => import("../types.js").Decorator<Event, Result, Args>;
4
+ export declare const withLogger: <Event, Result>(options?: LoggerOptions | undefined) => import("../types.js").Decorator<Event, Result>;
5
+ export declare const streamWithLogger: <Event>(options?: LoggerOptions | undefined) => import("../types.js").StreamDecorator<Event>;
package/wrapper/logger.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createLogger } from '@vyriy/logger';
2
- import { factory, getContext } from '../factory.js';
2
+ import { factory, getContext, getStreamContext, streamFactory } from '../factory.js';
3
3
  export const withLogger = factory(async (handler, args, options = {}) => {
4
4
  const { logger = createLogger() } = options;
5
5
  const [event] = args;
@@ -19,3 +19,21 @@ export const withLogger = factory(async (handler, args, options = {}) => {
19
19
  throw error;
20
20
  }
21
21
  });
22
+ export const streamWithLogger = streamFactory(async (handler, args, options = {}) => {
23
+ const { logger = createLogger() } = options;
24
+ const [event] = args;
25
+ const context = getStreamContext(args);
26
+ logger.info('Event:', event);
27
+ logger.info('Context:', context);
28
+ try {
29
+ await handler(...args);
30
+ logger.info('Result:', undefined);
31
+ }
32
+ catch (error) {
33
+ if (error instanceof Error) {
34
+ logger.error('Error:', error.message);
35
+ }
36
+ logger.error(error);
37
+ throw error;
38
+ }
39
+ });
@@ -1,2 +1,3 @@
1
- import type { Context, Decorator, HandlerArgs } from '../types.js';
2
- export declare const withSmoke: <Event, Result, Args extends HandlerArgs = [context: Context]>() => Decorator<Event, Result, Args>;
1
+ import type { ApiResult, Decorator, StreamDecorator } from '../types.js';
2
+ export declare const withSmoke: <Event, Result extends ApiResult | void = ApiResult>() => Decorator<Event, Result>;
3
+ export declare const streamWithSmoke: <Event>() => StreamDecorator<Event>;
package/wrapper/smoke.js CHANGED
@@ -1,4 +1,17 @@
1
1
  import { smoke } from '@vyriy/smoke';
2
- import { factory } from '../factory.js';
3
- const smokeWrapper = async (handler, args) => smoke(args[0]) || handler(...args);
4
- export const withSmoke = factory(smokeWrapper);
2
+ import { responseStream } from './stream.js';
3
+ export const withSmoke = () => (handler) => async (event, context) => {
4
+ const result = smoke(event);
5
+ return (result || (await handler(event, context)));
6
+ };
7
+ export const streamWithSmoke = () => (handler) => async (event, stream, context) => {
8
+ const result = smoke(event);
9
+ if (result) {
10
+ responseStream(stream, {
11
+ headers: result.headers,
12
+ statusCode: result.statusCode,
13
+ }).end(result.body);
14
+ return;
15
+ }
16
+ await handler(event, stream, context);
17
+ };
@@ -0,0 +1,6 @@
1
+ import type { ApiResult, ResponseStream } from '../types.js';
2
+ export type StreamMetadata = {
3
+ headers?: ApiResult['headers'];
4
+ statusCode?: ApiResult['statusCode'];
5
+ };
6
+ export declare const responseStream: (stream: ResponseStream, metadata?: StreamMetadata) => ResponseStream;
@@ -0,0 +1,15 @@
1
+ const getContentType = (headers) => headers?.['content-type'] ?? headers?.['Content-Type'];
2
+ export const responseStream = (stream, metadata = {}) => {
3
+ const runtimeStream = globalThis.awslambda?.HttpResponseStream?.from?.(stream, {
4
+ headers: metadata.headers,
5
+ statusCode: metadata.statusCode ?? 200,
6
+ });
7
+ if (runtimeStream) {
8
+ return runtimeStream;
9
+ }
10
+ const contentType = getContentType(metadata.headers);
11
+ if (contentType) {
12
+ stream.setContentType?.(String(contentType));
13
+ }
14
+ return stream;
15
+ };
@@ -1 +1,2 @@
1
- export declare const withTimeout: <Event, Result, Args extends import("../types.js").HandlerArgs = [context: import("aws-lambda").Context]>(options?: undefined) => import("../types.js").Decorator<Event, Result, Args>;
1
+ export declare const withTimeout: <Event, Result>(options?: undefined) => import("../types.js").Decorator<Event, Result>;
2
+ export declare const streamWithTimeout: <Event>(options?: undefined) => import("../types.js").StreamDecorator<Event>;
@@ -1,6 +1,10 @@
1
1
  import { timeout as error } from '@vyriy/timeout';
2
- import { factory, getContext } from '../factory.js';
2
+ import { factory, getContext, getStreamContext, streamFactory } from '../factory.js';
3
3
  export const withTimeout = factory(async (handler, args) => Promise.race([
4
4
  error(getContext(args).getRemainingTimeInMillis() - 1000),
5
5
  handler(...args),
6
6
  ]));
7
+ export const streamWithTimeout = streamFactory(async (handler, args) => Promise.race([
8
+ error(getStreamContext(args).getRemainingTimeInMillis() - 1000),
9
+ handler(...args),
10
+ ]));
package/streamify.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import type { Context } from 'aws-lambda';
2
- import type { ApiEvent, ApiResult, Handler, ResponseStream } from './types.js';
3
- type LambdaResponseStream = ResponseStream & {
4
- setContentType?: (contentType: string) => void;
5
- };
6
- export type StreamifiedApiHandler = (event: ApiEvent, responseStream: LambdaResponseStream, context: Context) => Promise<void>;
7
- export declare const streamifyApiResponse: (handler: Handler<ApiEvent, ApiResult | void, [context: Context]>) => StreamifiedApiHandler;
8
- export declare const streamify: (handler: Handler<ApiEvent, ApiResult | void, [responseStream: ResponseStream, context: Context]>) => StreamifiedApiHandler;
9
- export {};
package/streamify.js DELETED
@@ -1,33 +0,0 @@
1
- const getLambdaResponseStream = (responseStream, result) => {
2
- const response = globalThis.awslambda?.HttpResponseStream?.from?.(responseStream, {
3
- headers: result.headers,
4
- statusCode: result.statusCode ?? 200,
5
- });
6
- return response ?? responseStream;
7
- };
8
- const isStreamResult = (result) => 'stream' in result && typeof result.stream === 'function';
9
- const writeResult = async (responseStream, result) => {
10
- if (!result) {
11
- if (!responseStream.writableEnded) {
12
- responseStream.end();
13
- }
14
- return;
15
- }
16
- const stream = getLambdaResponseStream(responseStream, result);
17
- if (isStreamResult(result)) {
18
- await result.stream(stream);
19
- return;
20
- }
21
- if ('body' in result && result.body !== undefined) {
22
- stream.write(result.body);
23
- }
24
- stream.end();
25
- };
26
- export const streamifyApiResponse = (handler) => async (event, responseStream, context) => {
27
- const result = await handler(event, context);
28
- await writeResult(responseStream, result);
29
- };
30
- export const streamify = (handler) => async (event, responseStream, context) => {
31
- const result = await handler(event, responseStream, context);
32
- await writeResult(responseStream, result);
33
- };