@vyriy/handler 0.2.1 → 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 +79 -16
- package/api.d.ts +2 -2
- package/api.js +18 -11
- package/compose.d.ts +2 -1
- package/compose.js +1 -0
- package/factory.d.ts +6 -1
- package/factory.js +5 -2
- package/package.json +16 -6
- package/schedule.js +1 -1
- package/sns.js +1 -1
- package/sqs.js +1 -1
- package/types.d.ts +25 -3
- package/wrapper/chaos.d.ts +1 -0
- package/wrapper/chaos.js +12 -1
- package/wrapper/context.d.ts +1 -0
- package/wrapper/context.js +7 -2
- package/wrapper/cors.d.ts +2 -2
- package/wrapper/cors.js +10 -1
- package/wrapper/error.d.ts +11 -4
- package/wrapper/error.js +37 -12
- package/wrapper/headers.d.ts +2 -2
- package/wrapper/headers.js +11 -3
- package/wrapper/healthcheck.d.ts +7 -3
- package/wrapper/healthcheck.js +27 -13
- package/wrapper/logger.d.ts +1 -0
- package/wrapper/logger.js +21 -2
- package/wrapper/smoke.d.ts +3 -2
- package/wrapper/smoke.js +16 -3
- package/wrapper/stream.d.ts +6 -0
- package/wrapper/stream.js +15 -0
- package/wrapper/timeout.d.ts +1 -0
- package/wrapper/timeout.js +6 -2
package/README.md
CHANGED
|
@@ -22,6 +22,14 @@ With Yarn:
|
|
|
22
22
|
yarn add @vyriy/handler
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
For TypeScript Lambda projects, install AWS Lambda types as a development dependency:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
yarn add @types/aws-lambda
|
|
29
|
+
```
|
|
30
|
+
|
|
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
|
+
|
|
25
33
|
## Usage
|
|
26
34
|
|
|
27
35
|
Use a prebuilt API Gateway handler chain:
|
|
@@ -37,6 +45,59 @@ export const handler = api(async (event) => ({
|
|
|
37
45
|
}));
|
|
38
46
|
```
|
|
39
47
|
|
|
48
|
+
For Lambda response streaming, use the separate stream chain:
|
|
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:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// server.ts
|
|
68
|
+
import { streamServer } from '@vyriy/server';
|
|
69
|
+
|
|
70
|
+
import { handler } from './handler.js';
|
|
71
|
+
|
|
72
|
+
streamServer(handler);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Use the same handler in AWS Lambda response streaming:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
// lambda.ts
|
|
79
|
+
import { handler } from './handler.js';
|
|
80
|
+
|
|
81
|
+
export const main = awslambda.streamifyResponse(handler);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
That `handler.ts` shape already matches Lambda response streaming. For a standard non-streaming Lambda, export an `api(...)` handler directly without `responseStream`.
|
|
85
|
+
|
|
86
|
+
You can also inline the same shape in one file when a separate local entrypoint is not needed:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { streamApi } from '@vyriy/handler';
|
|
90
|
+
|
|
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
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
40
101
|
Use a prebuilt queue or event handler chain:
|
|
41
102
|
|
|
42
103
|
```ts
|
|
@@ -74,15 +135,14 @@ export const handler = schedule(async (event) => {
|
|
|
74
135
|
Compose a custom handler pipeline from individual helpers:
|
|
75
136
|
|
|
76
137
|
```ts
|
|
77
|
-
import { compose, withChaos, withContext, withError, withLogger,
|
|
138
|
+
import { compose, withChaos, withContext, withError, withLogger, withTimeout } from '@vyriy/handler';
|
|
78
139
|
|
|
79
140
|
export const handler = compose(
|
|
80
|
-
withError(
|
|
141
|
+
withError(),
|
|
81
142
|
withLogger(),
|
|
82
143
|
withChaos(),
|
|
83
144
|
withTimeout(),
|
|
84
145
|
withContext(),
|
|
85
|
-
withSmoke(),
|
|
86
146
|
)(async (event) => {
|
|
87
147
|
return {
|
|
88
148
|
ok: true,
|
|
@@ -112,7 +172,7 @@ const withRequestId = factory<{ headerName?: string }>(async (handler, args, opt
|
|
|
112
172
|
});
|
|
113
173
|
|
|
114
174
|
export const handler = compose(
|
|
115
|
-
withError(
|
|
175
|
+
withError(),
|
|
116
176
|
withLogger(),
|
|
117
177
|
withTimeout(),
|
|
118
178
|
withRequestId({
|
|
@@ -130,6 +190,8 @@ export const handler = compose(
|
|
|
130
190
|
|
|
131
191
|
- `api`
|
|
132
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.
|
|
133
195
|
|
|
134
196
|
- `schedule`
|
|
135
197
|
EventBridge schedule chain with logging, timeout handling, context setup, smoke checks, and rethrown errors.
|
|
@@ -144,22 +206,18 @@ export const handler = compose(
|
|
|
144
206
|
|
|
145
207
|
### `withError(options?)`
|
|
146
208
|
|
|
147
|
-
Catches handler failures, optionally runs
|
|
209
|
+
Catches handler failures, optionally runs a side-effect `errorHandler`, and rethrows the original error.
|
|
148
210
|
|
|
149
211
|
Options:
|
|
150
212
|
|
|
151
213
|
```ts
|
|
152
214
|
{
|
|
153
|
-
errorHandler?: (error: unknown) => Promise<void
|
|
154
|
-
throwError?: boolean;
|
|
215
|
+
errorHandler?: (error: unknown, args: HandlerParams<Event>) => Promise<void> | void;
|
|
155
216
|
}
|
|
156
217
|
```
|
|
157
218
|
|
|
158
219
|
- `errorHandler`
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
- `throwError`
|
|
162
|
-
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.
|
|
163
221
|
|
|
164
222
|
Example:
|
|
165
223
|
|
|
@@ -170,12 +228,15 @@ export const handler = withError({
|
|
|
170
228
|
errorHandler: async (error) => {
|
|
171
229
|
console.error('Handler failed:', error);
|
|
172
230
|
},
|
|
173
|
-
throwError: true,
|
|
174
231
|
})(async () => {
|
|
175
232
|
throw new Error('boom');
|
|
176
233
|
});
|
|
177
234
|
```
|
|
178
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
|
+
|
|
179
240
|
### `withLogger(options?)`
|
|
180
241
|
|
|
181
242
|
Logs the incoming event and context, then logs either the result or the thrown error.
|
|
@@ -286,7 +347,7 @@ export const handler = withChaos({
|
|
|
286
347
|
|
|
287
348
|
### `withSmoke()`
|
|
288
349
|
|
|
289
|
-
Returns the smoke response when the incoming event
|
|
350
|
+
Returns the smoke response when the incoming event has `isSmoke: true`.
|
|
290
351
|
|
|
291
352
|
Example:
|
|
292
353
|
|
|
@@ -303,16 +364,18 @@ export const handler = withSmoke()(async () => {
|
|
|
303
364
|
});
|
|
304
365
|
```
|
|
305
366
|
|
|
367
|
+
`withSmoke()` is used by the API, schedule, SNS, and SQS chains.
|
|
368
|
+
|
|
306
369
|
## Types
|
|
307
370
|
|
|
308
371
|
The package also exports shared handler types:
|
|
309
372
|
|
|
310
373
|
```ts
|
|
311
|
-
import type { Context, Decorator, Handler, Response } from '@vyriy/handler';
|
|
374
|
+
import type { Context, Decorator, Handler, HandlerParams, Response } from '@vyriy/handler';
|
|
312
375
|
```
|
|
313
376
|
|
|
314
377
|
## Notes
|
|
315
378
|
|
|
316
379
|
- `api` includes API-specific wrappers such as healthcheck handling, default headers, and CORS preflight handling
|
|
317
|
-
- `schedule`, `sns`, and `sqs`
|
|
318
|
-
- `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,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
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 {
|
|
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(
|
|
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
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,2 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Context } from 'aws-lambda';
|
|
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;
|
|
2
6
|
export declare const factory: Factory;
|
|
7
|
+
export declare const streamFactory: StreamFactory;
|
package/factory.js
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
const
|
|
2
|
-
export const
|
|
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/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vyriy/handler",
|
|
3
|
-
"version": "0.2
|
|
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.2
|
|
10
|
-
"@vyriy/config": "0.2
|
|
11
|
-
"@vyriy/logger": "0.2
|
|
12
|
-
"@vyriy/smoke": "0.2
|
|
13
|
-
"@vyriy/timeout": "0.2
|
|
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",
|
|
@@ -176,6 +176,16 @@
|
|
|
176
176
|
"import": "./wrapper/smoke.js",
|
|
177
177
|
"default": "./wrapper/smoke.js"
|
|
178
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
|
+
},
|
|
179
189
|
"./wrapper/timeout": {
|
|
180
190
|
"types": "./wrapper/timeout.d.ts",
|
|
181
191
|
"import": "./wrapper/timeout.js",
|
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(
|
|
7
|
+
export const schedule = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
|
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(
|
|
7
|
+
export const sns = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
|
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(
|
|
7
|
+
export const sqs = compose(withError(), withLogger(), withTimeout(), withContext(), withSmoke());
|
package/types.d.ts
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
|
-
import type { Context } from 'aws-lambda';
|
|
1
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
|
2
2
|
export type { Context } from 'aws-lambda';
|
|
3
|
+
export type ResponseStream = {
|
|
4
|
+
end: {
|
|
5
|
+
(): unknown;
|
|
6
|
+
(chunk: string | Buffer | Uint8Array): unknown;
|
|
7
|
+
};
|
|
8
|
+
setContentType?: (contentType: string) => unknown;
|
|
9
|
+
writableEnded?: boolean;
|
|
10
|
+
write(chunk: string | Buffer | Uint8Array): unknown;
|
|
11
|
+
};
|
|
12
|
+
export type ApiResult = APIGatewayProxyResult;
|
|
13
|
+
export type ApiEvent = APIGatewayProxyEvent;
|
|
14
|
+
export type HandlerParams<Event> = [event: Event, context: Context];
|
|
15
|
+
export type StreamHandlerParams<Event> = [event: Event, responseStream: ResponseStream, context: Context];
|
|
3
16
|
export type Response<Result> = Promise<Result>;
|
|
4
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>;
|
|
5
19
|
export type Decorator<Event, Result> = (handler: Handler<Event, Result>) => Handler<Event, Result>;
|
|
20
|
+
export type StreamDecorator<Event> = (handler: StreamHandler<Event>) => StreamHandler<Event>;
|
|
6
21
|
export type Compose = <Event, Result>(...decorators: Array<Decorator<Event, Result>>) => Decorator<Event, Result>;
|
|
7
|
-
export type
|
|
8
|
-
export type
|
|
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>;
|
|
9
27
|
export type Factory = {
|
|
10
28
|
<Options = undefined>(wrapper: Wrapper<Options>): <Event, Result>(options?: Options) => Decorator<Event, Result>;
|
|
11
29
|
<Options, Event, Result>(wrapper: TypedWrapper<Event, Result, Options>): (options?: Options) => Decorator<Event, Result>;
|
|
12
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
|
+
};
|
package/wrapper/chaos.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type ChaosOptions } from '@vyriy/chaos';
|
|
2
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
|
+
});
|
package/wrapper/context.d.ts
CHANGED
package/wrapper/context.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { factory } from '../factory.js';
|
|
1
|
+
import { factory, getContext, getStreamContext, streamFactory } from '../factory.js';
|
|
2
2
|
export const withContext = factory(async (handler, args) => {
|
|
3
|
-
const
|
|
3
|
+
const ctx = getContext(args);
|
|
4
|
+
ctx.callbackWaitsForEmptyEventLoop = false;
|
|
5
|
+
return handler(...args);
|
|
6
|
+
});
|
|
7
|
+
export const streamWithContext = streamFactory(async (handler, args) => {
|
|
8
|
+
const ctx = getStreamContext(args);
|
|
4
9
|
ctx.callbackWaitsForEmptyEventLoop = false;
|
|
5
10
|
return handler(...args);
|
|
6
11
|
});
|
package/wrapper/cors.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
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
|
+
});
|
package/wrapper/error.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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 {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
14
|
+
return await handler(event, context);
|
|
7
15
|
}
|
|
8
16
|
catch (err) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
});
|
|
42
|
+
};
|
package/wrapper/headers.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
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>;
|
package/wrapper/headers.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import { factory } from '../factory.js';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { factory, streamFactory } from '../factory.js';
|
|
2
|
+
import { responseStream } from './stream.js';
|
|
3
|
+
const mergeHeaders = (result, options) => {
|
|
4
4
|
result.headers = {
|
|
5
5
|
...options,
|
|
6
6
|
...result.headers,
|
|
7
7
|
};
|
|
8
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);
|
|
9
17
|
});
|
package/wrapper/healthcheck.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const withHealthcheck: (options?: {
|
|
1
|
+
export type HealthcheckOptions = {
|
|
3
2
|
path?: string;
|
|
4
3
|
action?: () => Promise<void>;
|
|
5
|
-
}
|
|
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>;
|
package/wrapper/healthcheck.js
CHANGED
|
@@ -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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
31
|
+
await handler(...args);
|
|
18
32
|
});
|
package/wrapper/logger.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ export type LoggerOptions = {
|
|
|
2
2
|
logger?: typeof console;
|
|
3
3
|
};
|
|
4
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,8 +1,9 @@
|
|
|
1
1
|
import { createLogger } from '@vyriy/logger';
|
|
2
|
-
import { factory } 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
|
-
const [event
|
|
5
|
+
const [event] = args;
|
|
6
|
+
const context = getContext(args);
|
|
6
7
|
logger.info('Event:', event);
|
|
7
8
|
logger.info('Context:', context);
|
|
8
9
|
try {
|
|
@@ -18,3 +19,21 @@ export const withLogger = factory(async (handler, args, options = {}) => {
|
|
|
18
19
|
throw error;
|
|
19
20
|
}
|
|
20
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
|
+
});
|
package/wrapper/smoke.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type { Decorator } from '../types.js';
|
|
2
|
-
export declare const withSmoke: <Event, Result>() => Decorator<Event, Result>;
|
|
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 {
|
|
3
|
-
const
|
|
4
|
-
|
|
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
|
+
};
|
package/wrapper/timeout.d.ts
CHANGED
package/wrapper/timeout.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { timeout as error } from '@vyriy/timeout';
|
|
2
|
-
import { factory } from '../factory.js';
|
|
2
|
+
import { factory, getContext, getStreamContext, streamFactory } from '../factory.js';
|
|
3
3
|
export const withTimeout = factory(async (handler, args) => Promise.race([
|
|
4
|
-
error(args
|
|
4
|
+
error(getContext(args).getRemainingTimeInMillis() - 1000),
|
|
5
|
+
handler(...args),
|
|
6
|
+
]));
|
|
7
|
+
export const streamWithTimeout = streamFactory(async (handler, args) => Promise.race([
|
|
8
|
+
error(getStreamContext(args).getRemainingTimeInMillis() - 1000),
|
|
5
9
|
handler(...args),
|
|
6
10
|
]));
|