@vyriy/handler 0.3.0 → 0.3.4
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/AGENTS.md +59 -22
- package/README.md +39 -47
- package/api.d.ts +2 -7
- package/api.js +18 -11
- package/compose.d.ts +2 -1
- package/compose.js +1 -0
- package/factory.d.ts +5 -2
- package/factory.js +5 -3
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/package.json +16 -16
- package/schedule.d.ts +1 -1
- package/schedule.js +1 -1
- package/sns.d.ts +1 -1
- package/sns.js +1 -1
- package/sqs.d.ts +1 -1
- package/sqs.js +1 -1
- package/types.d.ts +19 -19
- package/wrapper/chaos.d.ts +2 -1
- package/wrapper/chaos.js +12 -1
- package/wrapper/context.d.ts +2 -1
- package/wrapper/context.js +6 -1
- 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 -6
- package/wrapper/healthcheck.d.ts +7 -3
- package/wrapper/healthcheck.js +27 -13
- package/wrapper/logger.d.ts +2 -1
- package/wrapper/logger.js +19 -1
- 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 +2 -1
- package/wrapper/timeout.js +5 -1
- package/streamify.d.ts +0 -9
- package/streamify.js +0 -33
package/AGENTS.md
CHANGED
|
@@ -1,41 +1,62 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Project Agent Guide
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This repository follows a calm engineering style: changes should be explicit, reusable, typed, documented, tested, and easy to reason about.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Use this guide as the default behavior for AI agents and contributors working in this repository. Prefer local package conventions when they are more specific than this document.
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
6
8
|
|
|
7
9
|
- Prefer simple modules over clever frameworks or hidden conventions.
|
|
8
|
-
- Keep package
|
|
10
|
+
- Keep package and project boundaries explicit.
|
|
11
|
+
- Avoid project-specific coupling in reusable code.
|
|
9
12
|
- Extract only proven reusable behavior.
|
|
10
13
|
- Keep public APIs small, typed, documented, and stable.
|
|
11
|
-
- Prefer SSR-friendly and SSG-friendly code paths.
|
|
12
|
-
- Keep integrations replaceable and avoid hard coupling to a CMS or runtime host.
|
|
13
|
-
- Prefer
|
|
14
|
+
- Prefer SSR-friendly and SSG-friendly code paths when working with frontend or shared code.
|
|
15
|
+
- Keep integrations replaceable and avoid hard coupling to a CMS, framework, vendor, or runtime host.
|
|
16
|
+
- Prefer infrastructure assumptions that are easy to deploy, observe, and replace.
|
|
17
|
+
- Prefer the option that is simpler to explain, easier to evolve, and calmer to maintain.
|
|
14
18
|
|
|
15
19
|
## File Shape
|
|
16
20
|
|
|
17
|
-
- Prefer one exported runtime method, component, or
|
|
21
|
+
- Prefer one exported runtime method, component, helper, or class per production file when it stays readable.
|
|
18
22
|
- Prefer one matching test file per production file, for example `feature.ts` and `feature.test.ts`.
|
|
19
23
|
- Use focused folders when behavior naturally splits into several related files.
|
|
20
24
|
- Keep `index.ts` as a public re-export surface only. Do not place implementation logic in it.
|
|
21
|
-
- Use
|
|
25
|
+
- Use relative import and export specifiers that match the package module style.
|
|
26
|
+
- Use `.js` relative specifiers in TypeScript source for ESM/NodeNext packages.
|
|
22
27
|
- Add `types.ts` when public shared types are part of the package contract.
|
|
23
28
|
- Keep constants near the code that owns them unless they are shared or clarify repeated behavior.
|
|
24
29
|
|
|
25
30
|
## Public Surface
|
|
26
31
|
|
|
27
|
-
- Every new public export
|
|
28
|
-
- Add or update
|
|
32
|
+
- Every new public export must be re-exported from the package or module public entry point.
|
|
33
|
+
- Add or update public-surface tests when exports change.
|
|
29
34
|
- Add JSDoc for public exports when behavior, parameters, return values, or usage expectations need explanation.
|
|
30
|
-
-
|
|
35
|
+
- Avoid exporting internal helpers only to make tests easier.
|
|
36
|
+
- Do not hand-maintain package `exports` maps unless the project has a real custom publishing need.
|
|
31
37
|
|
|
32
38
|
## Tests
|
|
33
39
|
|
|
34
|
-
- Tests use Jest and `@jest/globals`.
|
|
35
40
|
- Cover public behavior and meaningful edge cases.
|
|
36
41
|
- Prefer behavior-focused tests over private implementation lock-in.
|
|
42
|
+
- Keep tests deterministic.
|
|
43
|
+
- Avoid real network, filesystem, timers, browser, or cloud dependencies unless the behavior specifically requires them.
|
|
37
44
|
- When mocking modules, install mocks before loading the module under test.
|
|
38
|
-
- Use focused validation when changing
|
|
45
|
+
- Use focused validation when changing behavior.
|
|
46
|
+
|
|
47
|
+
Example validation commands:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
yarn test
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For workspaces, prefer the project convention, for example:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
yarn workspace <package-name> test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For Jest-based packages, focused validation may look like:
|
|
39
60
|
|
|
40
61
|
```bash
|
|
41
62
|
yarn jest packages/<package> --runInBand --coverage=false
|
|
@@ -44,22 +65,38 @@ yarn jest packages/<package> --runInBand --coverage=false
|
|
|
44
65
|
## Documentation
|
|
45
66
|
|
|
46
67
|
- Keep `README.md` concise and usage-oriented.
|
|
47
|
-
- Start package READMEs with `#
|
|
68
|
+
- Start package READMEs with `# <package>`.
|
|
48
69
|
- Document real public exports, supported options, and examples that actually work.
|
|
49
|
-
-
|
|
50
|
-
-
|
|
70
|
+
- Update docs when public behavior changes.
|
|
71
|
+
- Keep generated docs wrappers, such as `doc.mdx`, aligned with the README when the project uses them.
|
|
72
|
+
- For component packages, include visual documentation or stories for supported states and common usage.
|
|
51
73
|
|
|
52
74
|
## Components
|
|
53
75
|
|
|
54
|
-
- Prefer lightweight React
|
|
76
|
+
- Prefer lightweight React components with TypeScript when working in React packages.
|
|
55
77
|
- Keep components SSR-friendly and avoid browser globals during render.
|
|
56
|
-
- Prefer composable props and
|
|
78
|
+
- Prefer composable props and predictable ergonomics.
|
|
57
79
|
- Put each public component in its own file with a matching test.
|
|
58
|
-
- Add
|
|
80
|
+
- Add stories or examples when a component has visual states, variants, or interaction states.
|
|
81
|
+
- Keep styling explicit and reusable. Avoid hidden theme assumptions unless they are part of the package contract.
|
|
59
82
|
|
|
60
83
|
## Change Discipline
|
|
61
84
|
|
|
62
85
|
- Keep changes scoped to the requested behavior.
|
|
63
86
|
- Avoid unrelated refactors and metadata churn.
|
|
64
|
-
- Sync implementation, tests,
|
|
65
|
-
-
|
|
87
|
+
- Sync implementation, tests, docs, examples, and public re-exports together.
|
|
88
|
+
- Do not introduce new dependencies unless they clearly reduce complexity or are already part of the project direction.
|
|
89
|
+
- Prefer small, reviewable changes over broad rewrites.
|
|
90
|
+
- Preserve existing conventions unless there is a clear reason to change them.
|
|
91
|
+
|
|
92
|
+
## Before Finishing
|
|
93
|
+
|
|
94
|
+
Check that the change is complete:
|
|
95
|
+
|
|
96
|
+
- Public exports are updated.
|
|
97
|
+
- Public-surface tests are updated when exports change.
|
|
98
|
+
- Matching unit tests exist for new behavior.
|
|
99
|
+
- README examples still match the real API.
|
|
100
|
+
- Visual docs, stories, or examples are updated for visible component behavior.
|
|
101
|
+
- TypeScript imports follow the package module style.
|
|
102
|
+
- No unrelated files, formatting churn, or generated artifacts were changed.
|
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,
|
|
48
|
+
For Lambda response streaming, use the separate stream chain:
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
51
|
// handler.ts
|
|
52
|
-
import {
|
|
52
|
+
import { streamApi } from '@vyriy/handler';
|
|
53
53
|
|
|
54
|
-
export const handler =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 {
|
|
68
|
+
import { streamServer } from '@vyriy/server';
|
|
71
69
|
|
|
72
70
|
import { handler } from './handler.js';
|
|
73
71
|
|
|
74
|
-
|
|
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
|
|
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 {
|
|
89
|
+
import { streamApi } from '@vyriy/handler';
|
|
92
90
|
|
|
93
91
|
export const main = awslambda.streamifyResponse(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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,
|
|
138
|
+
import { compose, withChaos, withContext, withError, withLogger, withTimeout } from '@vyriy/handler';
|
|
145
139
|
|
|
146
140
|
export const handler = compose(
|
|
147
|
-
withError(
|
|
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(
|
|
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
|
|
200
|
-
- `
|
|
201
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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`
|
|
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
|
-
|
|
2
|
-
|
|
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 {
|
|
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,4 +1,7 @@
|
|
|
1
1
|
import type { Context } from 'aws-lambda';
|
|
2
|
-
import type { Factory,
|
|
3
|
-
export declare const getContext: <Event
|
|
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
|
|
2
|
-
export const
|
|
3
|
-
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/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vyriy/handler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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.
|
|
10
|
-
"@vyriy/config": "0.3.
|
|
11
|
-
"@vyriy/logger": "0.3.
|
|
12
|
-
"@vyriy/smoke": "0.3.
|
|
13
|
-
"@vyriy/timeout": "0.3.
|
|
9
|
+
"@vyriy/chaos": "0.3.4",
|
|
10
|
+
"@vyriy/config": "0.3.4",
|
|
11
|
+
"@vyriy/logger": "0.3.4",
|
|
12
|
+
"@vyriy/smoke": "0.3.4",
|
|
13
|
+
"@vyriy/timeout": "0.3.4"
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
24
|
-
export type
|
|
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
|
|
27
|
-
export type
|
|
28
|
-
export type
|
|
29
|
-
export type
|
|
30
|
-
export type
|
|
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
|
|
33
|
-
<Options, Event, Result
|
|
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
|
};
|
package/wrapper/chaos.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import { type ChaosOptions } from '@vyriy/chaos';
|
|
2
|
-
export declare const withChaos: <Event, Result
|
|
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
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare const withContext: <Event, Result
|
|
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>;
|
package/wrapper/context.js
CHANGED
|
@@ -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
|
-
|
|
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,12 +1,17 @@
|
|
|
1
|
-
import { factory } from '../factory.js';
|
|
2
|
-
|
|
3
|
-
|
|
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
|
});
|
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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type LoggerOptions = {
|
|
2
2
|
logger?: typeof console;
|
|
3
3
|
};
|
|
4
|
-
export declare const withLogger: <Event, Result
|
|
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
|
+
});
|
package/wrapper/smoke.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const withSmoke: <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
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export declare const withTimeout: <Event, Result
|
|
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>;
|
package/wrapper/timeout.js
CHANGED
|
@@ -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
|
-
};
|