@comicrelief/lambda-wrapper 1.10.1 → 2.0.0-beta.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 +172 -47
- package/dist/core/DependencyAwareClass.d.ts +15 -0
- package/dist/core/DependencyAwareClass.d.ts.map +1 -0
- package/dist/core/DependencyAwareClass.js +20 -0
- package/dist/core/DependencyAwareClass.js.map +1 -0
- package/dist/core/DependencyInjection.d.ts +62 -0
- package/dist/core/DependencyInjection.d.ts.map +1 -0
- package/dist/core/DependencyInjection.js +83 -0
- package/dist/core/DependencyInjection.js.map +1 -0
- package/dist/core/LambdaWrapper.d.ts +61 -0
- package/dist/core/LambdaWrapper.d.ts.map +1 -0
- package/dist/core/LambdaWrapper.js +126 -0
- package/dist/core/LambdaWrapper.js.map +1 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +21 -0
- package/dist/core/config.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -162
- package/dist/index.js.map +1 -0
- package/dist/models/ResponseModel.d.ts +91 -0
- package/dist/models/ResponseModel.d.ts.map +1 -0
- package/dist/models/ResponseModel.js +100 -0
- package/dist/models/ResponseModel.js.map +1 -0
- package/dist/models/SQSMessageModel.d.ts +46 -0
- package/dist/models/SQSMessageModel.d.ts.map +1 -0
- package/dist/models/SQSMessageModel.js +65 -0
- package/dist/models/SQSMessageModel.js.map +1 -0
- package/dist/models/StatusModel.d.ts +40 -0
- package/dist/models/StatusModel.d.ts.map +1 -0
- package/dist/models/StatusModel.js +50 -0
- package/dist/models/StatusModel.js.map +1 -0
- package/dist/services/BaseConfigService.d.ts +101 -0
- package/dist/services/BaseConfigService.d.ts.map +1 -0
- package/dist/services/BaseConfigService.js +180 -0
- package/dist/services/BaseConfigService.js.map +1 -0
- package/dist/services/HTTPService.d.ts +29 -0
- package/dist/services/HTTPService.d.ts.map +1 -0
- package/dist/services/HTTPService.js +55 -0
- package/dist/services/HTTPService.js.map +1 -0
- package/dist/services/LoggerService.d.ts +102 -0
- package/dist/services/LoggerService.d.ts.map +1 -0
- package/dist/services/LoggerService.js +258 -0
- package/dist/services/LoggerService.js.map +1 -0
- package/dist/services/RequestService.d.ts +119 -0
- package/dist/services/RequestService.d.ts.map +1 -0
- package/dist/services/RequestService.js +293 -0
- package/dist/services/RequestService.js.map +1 -0
- package/dist/services/SQSService.d.ts +181 -0
- package/dist/services/SQSService.d.ts.map +1 -0
- package/dist/services/SQSService.js +366 -0
- package/dist/services/SQSService.js.map +1 -0
- package/dist/services/TimerService.d.ts +22 -0
- package/dist/services/TimerService.d.ts.map +1 -0
- package/dist/services/TimerService.js +40 -0
- package/dist/services/TimerService.js.map +1 -0
- package/dist/utils/LambdaTermination.d.ts +14 -0
- package/dist/utils/LambdaTermination.d.ts.map +1 -0
- package/dist/utils/LambdaTermination.js +23 -0
- package/dist/utils/LambdaTermination.js.map +1 -0
- package/dist/utils/PromisifiedDelay.d.ts +17 -0
- package/dist/utils/PromisifiedDelay.d.ts.map +1 -0
- package/dist/utils/PromisifiedDelay.js +48 -0
- package/dist/utils/PromisifiedDelay.js.map +1 -0
- package/package.json +29 -19
- package/.eslintrc.yml +0 -16
- package/.github/dependabot.yml +0 -11
- package/.github/workflows/main.yml +0 -74
- package/babel.config.js +0 -13
- package/dist/Config/Dependencies.js +0 -40
- package/dist/DependencyInjection/DependencyAware.class.js +0 -47
- package/dist/DependencyInjection/DependencyInjection.class.js +0 -131
- package/dist/Model/CloudEvent.model.js +0 -156
- package/dist/Model/Model.model.js +0 -45
- package/dist/Model/Response.model.js +0 -150
- package/dist/Model/SQS/MarketingPreference.constraints.json +0 -28
- package/dist/Model/SQS/MarketingPreference.model.js +0 -641
- package/dist/Model/SQS/Message.model.js +0 -104
- package/dist/Model/Status.model.js +0 -82
- package/dist/Service/BaseConfig.service.js +0 -225
- package/dist/Service/HTTP.service.js +0 -70
- package/dist/Service/Logger.service.js +0 -293
- package/dist/Service/Request.service.js +0 -341
- package/dist/Service/SQS.service.js +0 -420
- package/dist/Service/Timer.service.js +0 -55
- package/dist/Wrapper/LambdaTermination.js +0 -39
- package/dist/Wrapper/LambdaWrapper.js +0 -164
- package/dist/Wrapper/PromisifiedDelay.js +0 -59
package/README.md
CHANGED
|
@@ -4,95 +4,220 @@
|
|
|
4
4
|
[](https://github.com/semantic-release/semantic-release)
|
|
5
5
|
[](https://www.npmjs.com/package/@comicrelief/lambda-wrapper)
|
|
6
6
|
|
|
7
|
-
When writing Serverless
|
|
7
|
+
When writing Serverless applications, we have found ourselves replicating a lot of boilerplate code to do basic actions, such as reading request data or sending messages to SQS. The aim of this package is to provide a wrapper for our Lambda functions, to provide some level of dependency and configuration injection and to reduce time spent on project setup.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
If you're coming from v1 and updating to v2, check out the [v2 migration guide](docs/migration/v2.md).
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
## Getting started
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
npm install --save @comicrelief/lambda-wrapper
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Or via yarn:
|
|
13
|
+
Install via npm or Yarn:
|
|
18
14
|
|
|
19
15
|
```bash
|
|
16
|
+
npm i @comicrelief/lambda-wrapper
|
|
17
|
+
# or
|
|
20
18
|
yarn add @comicrelief/lambda-wrapper
|
|
21
19
|
```
|
|
22
20
|
|
|
23
|
-
You can then wrap your
|
|
21
|
+
You can then wrap your Lambda handler functions like this:
|
|
24
22
|
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
```ts
|
|
24
|
+
// src/action/Hello.ts
|
|
25
|
+
import lambdaWrapper, {
|
|
28
26
|
ResponseModel,
|
|
29
27
|
RequestService,
|
|
30
28
|
} from '@comicrelief/lambda-wrapper';
|
|
31
29
|
|
|
32
|
-
export default
|
|
33
|
-
const
|
|
34
|
-
|
|
30
|
+
export default lambdaWrapper.wrap(async (di) => {
|
|
31
|
+
const request = di.get(RequestService);
|
|
32
|
+
return ResponseModel.generate(
|
|
33
|
+
{},
|
|
34
|
+
200,
|
|
35
|
+
`hello ${request.get('name', 'nobody')}`,
|
|
36
|
+
);
|
|
35
37
|
});
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
Here we've used the default export `lambdaWrapper` which is a preconfigured instance that can be used out of the box. You'll likely want to add your own dependencies and service config using the `configure` method:
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
```ts
|
|
43
|
+
// src/config/LambdaWrapper.ts
|
|
44
|
+
import lambdaWrapper from '@comicrelief/lambda-wrapper';
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
export default lambdaWrapper.configure({
|
|
47
|
+
// your config goes here
|
|
48
|
+
});
|
|
49
|
+
```
|
|
43
50
|
|
|
44
|
-
|
|
45
|
-
- `local`: send messages to an offline SQS endpoint, such as Localstack
|
|
46
|
-
- `aws`: no special handling of SQS offline; messages will be sent to AWS
|
|
51
|
+
`configure` returns a new Lambda Wrapper instance with the given configuration. You'll want to export it and then use this when wrapping your handler functions.
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
Read the next section to see what goes inside the config object!
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
If you want to start from scratch without the built-in dependencies, you can use the `LambdaWrapper` constructor directly.
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
```ts
|
|
58
|
+
// src/config/LambdaWrapper.ts
|
|
59
|
+
import { LambdaWrapper } from '@comicrelief/lambda-wrapper';
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
export default new LambdaWrapper({
|
|
62
|
+
// your config goes here
|
|
63
|
+
});
|
|
64
|
+
```
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
## Dependencies
|
|
57
67
|
|
|
58
|
-
|
|
68
|
+
Lambda Wrapper comes with some commonly used dependencies built in:
|
|
59
69
|
|
|
60
|
-
|
|
70
|
+
- [HTTPService](docs/services/HTTPService.md)
|
|
71
|
+
- [LoggerService](docs/services/LoggerService.md)
|
|
72
|
+
- [RequestService](docs/services/RequestService.md)
|
|
73
|
+
- [SQSService](docs/services/SQSService.md)
|
|
74
|
+
- [TimerService](docs/services/TimerService.md)
|
|
61
75
|
|
|
62
|
-
|
|
76
|
+
Access these via dependency injection. You've already seen an example of this where we got `RequestService`. Pass the dependency class to `di.get()` to get its instance:
|
|
63
77
|
|
|
64
|
-
|
|
78
|
+
```ts
|
|
79
|
+
export default lambdaWrapper.wrap(async (di) => {
|
|
80
|
+
const request = di.get(RequestService);
|
|
81
|
+
const sqs = di.get(SQSService);
|
|
82
|
+
// ...
|
|
83
|
+
});
|
|
84
|
+
```
|
|
65
85
|
|
|
66
|
-
|
|
86
|
+
To add your own dependencies, first extend `DependencyAwareClass`.
|
|
67
87
|
|
|
68
|
-
|
|
88
|
+
```ts
|
|
89
|
+
// src/services/MyService.ts
|
|
90
|
+
import { DependencyAwareClass } from '@comicrelief/lambda-wrapper';
|
|
69
91
|
|
|
70
|
-
|
|
92
|
+
export default class MyService extends DependencyAwareClass {
|
|
93
|
+
doSomething() {
|
|
94
|
+
// ...
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
71
98
|
|
|
72
|
-
|
|
99
|
+
Then add it to your Lambda Wrapper configuration in the `dependencies` key.
|
|
73
100
|
|
|
74
|
-
|
|
101
|
+
```ts
|
|
102
|
+
// src/config/LambdaWrapper.ts
|
|
103
|
+
import lambdaWrapper from '@comicrelief/lambda-wrapper';
|
|
75
104
|
|
|
76
|
-
|
|
105
|
+
import MyService from '@/src/services/MyService';
|
|
77
106
|
|
|
78
|
-
|
|
107
|
+
export default lambdaWrapper.configure({
|
|
108
|
+
dependencies: {
|
|
109
|
+
MyService,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Now you can use it inside your handler functions and other dependencies!
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// src/action/DoSomething.ts
|
|
118
|
+
import lambdaWrapper from '@/src/config/LambdaWrapper';
|
|
119
|
+
import MyService from '@/src/services/MyService';
|
|
120
|
+
|
|
121
|
+
export default lambdaWrapper.wrap(async (di) => {
|
|
122
|
+
di.get(MyService).doSomething();
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Service config
|
|
127
|
+
|
|
128
|
+
Some dependencies need their own config. This goes in per-service keys within your Lambda Wrapper config. For an example, see [SQSService](docs/services/SQSService.md) which uses the `sqs` key.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
export default lambdaWrapper.configure({
|
|
132
|
+
dependencies: {
|
|
133
|
+
// your dependencies
|
|
134
|
+
},
|
|
135
|
+
sqs: {
|
|
136
|
+
// your SQSService config
|
|
137
|
+
},
|
|
138
|
+
// ... other configs ...
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
To use config with your own dependencies, you need to do three things:
|
|
143
|
+
|
|
144
|
+
1. Define the key and type of your config object.
|
|
145
|
+
|
|
146
|
+
Using `SQSService` as an example, we have the `sqs` key which has the `SQSServiceConfig` type:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
export interface SQSServiceConfig {
|
|
150
|
+
queues?: Record<string, string>;
|
|
151
|
+
queueConsumers?: Record<string, string>;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
2. Define a type that can be applied to a Lambda Wrapper config.
|
|
156
|
+
|
|
157
|
+
This simply combines the key and type defined in step 1. Conventionally we name these `With...` types.
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
export interface WithSQSServiceConfig {
|
|
161
|
+
sqs?: SQSServiceConfig;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
In the case of `SQSService`, the `sqs` key is optional because this dependency is included by default and not all applications need it. If your dependency requires config in order to work, you can make this a required key.
|
|
166
|
+
|
|
167
|
+
3. In your dependency constructor, cast the config to this type.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
export default class SQSService extends DependencyAwareClass {
|
|
171
|
+
constructor(di: DependencyInjection) {
|
|
172
|
+
super(di);
|
|
173
|
+
|
|
174
|
+
const config = (this.di.config as WithSQSServiceConfig).sqs;
|
|
175
|
+
// Bear in mind that because the `sqs` key is optional, the type of
|
|
176
|
+
// `config` will be `SQSServiceConfig | undefined`. Take care when
|
|
177
|
+
// accessing its properties! You can use optional chaining:
|
|
178
|
+
const queues = config?.queues || {};
|
|
179
|
+
// ...
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
When you go to configure your Lambda Wrapper, you can now include your dependency's config type in the generic for `configure` to get IntelliSense completions and type checking for your config keys.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
lambdaWrapper.configure<WithSQSServiceConfig>({
|
|
188
|
+
sqs: {
|
|
189
|
+
queues: 42 // Oops! This will be flaggeed as a type error by TypeScript
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
You can combine types for multiple dependencies if needed using `&`:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
lambdaWrapper.configure<WithSQSServiceConfig & WithOtherServiceConfig>({
|
|
198
|
+
sqs: {
|
|
199
|
+
// SQSService config
|
|
200
|
+
},
|
|
201
|
+
other: {
|
|
202
|
+
// OtherService config
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
```
|
|
79
206
|
|
|
80
|
-
|
|
207
|
+
## Development
|
|
81
208
|
|
|
82
|
-
|
|
209
|
+
### Testing
|
|
83
210
|
|
|
84
|
-
|
|
85
|
-
Also, if you need to change the port, you can set `process.env.LAMBDA_WRAPPER_OFFLINE_SQS_PORT`.
|
|
211
|
+
Run `yarn test` to run the unit tests.
|
|
86
212
|
|
|
87
|
-
|
|
213
|
+
When writing a bugfix, start by writing a test that reproduces the problem. It should fail with the current version of Lambda Wrapper, and pass once you've implemented the fix.
|
|
88
214
|
|
|
89
|
-
|
|
215
|
+
When adding a feature, ensure it's covered by tests that adequately define its behaviour.
|
|
90
216
|
|
|
91
|
-
|
|
217
|
+
### Linting
|
|
92
218
|
|
|
93
|
-
|
|
94
|
-
- invoke offline functions via the Lambda API, passing a context that contains a realistic `invokedFunctionArn` including the account ID.
|
|
219
|
+
Run `yarn lint` to check code style complies to our standard. Many problems can be auto-fixed using `yarn lint --fix`.
|
|
95
220
|
|
|
96
|
-
|
|
221
|
+
### Releases
|
|
97
222
|
|
|
98
223
|
Release management is automated using [semantic-release](https://www.npmjs.com/package/semantic-release).
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import DependencyInjection from './DependencyInjection';
|
|
2
|
+
/**
|
|
3
|
+
* Base class for dependencies.
|
|
4
|
+
*/
|
|
5
|
+
export default class DependencyAwareClass {
|
|
6
|
+
readonly di: DependencyInjection;
|
|
7
|
+
constructor(di: DependencyInjection);
|
|
8
|
+
/**
|
|
9
|
+
* Get dependency injection container.
|
|
10
|
+
*
|
|
11
|
+
* @deprecated Use `this.di` instead.
|
|
12
|
+
*/
|
|
13
|
+
getContainer(): DependencyInjection;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=DependencyAwareClass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DependencyAwareClass.d.ts","sourceRoot":"","sources":["../../src/core/DependencyAwareClass.ts"],"names":[],"mappings":"AAAA,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AAExD;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,oBAAoB;IAC3B,QAAQ,CAAC,EAAE,EAAE,mBAAmB;gBAAvB,EAAE,EAAE,mBAAmB;IAE5C;;;;OAIG;IACH,YAAY,IAAI,mBAAmB;CAGpC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Base class for dependencies.
|
|
5
|
+
*/
|
|
6
|
+
class DependencyAwareClass {
|
|
7
|
+
constructor(di) {
|
|
8
|
+
this.di = di;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get dependency injection container.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated Use `this.di` instead.
|
|
14
|
+
*/
|
|
15
|
+
getContainer() {
|
|
16
|
+
return this.di;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.default = DependencyAwareClass;
|
|
20
|
+
//# sourceMappingURL=DependencyAwareClass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DependencyAwareClass.js","sourceRoot":"","sources":["../../src/core/DependencyAwareClass.ts"],"names":[],"mappings":";;AAEA;;GAEG;AACH,MAAqB,oBAAoB;IACvC,YAAqB,EAAuB;QAAvB,OAAE,GAAF,EAAE,CAAqB;IAAG,CAAC;IAEhD;;;;OAIG;IACH,YAAY;QACV,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CACF;AAXD,uCAWC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Context } from 'aws-lambda';
|
|
2
|
+
import DependencyAwareClass from './DependencyAwareClass';
|
|
3
|
+
import { LambdaWrapperConfig } from './config';
|
|
4
|
+
declare type Class<T> = new (di: DependencyInjection) => T;
|
|
5
|
+
/**
|
|
6
|
+
* Dependency injection container.
|
|
7
|
+
*
|
|
8
|
+
* Dependencies (singleton instances of dependency-aware classes) are provided
|
|
9
|
+
* to the main Lambda handler and other dependencies via this class.
|
|
10
|
+
*/
|
|
11
|
+
export default class DependencyInjection {
|
|
12
|
+
readonly config: LambdaWrapperConfig;
|
|
13
|
+
readonly event: any;
|
|
14
|
+
readonly context: Context;
|
|
15
|
+
/**
|
|
16
|
+
* Instantiated dependencies.
|
|
17
|
+
*/
|
|
18
|
+
readonly dependencies: Record<string, DependencyAwareClass>;
|
|
19
|
+
/**
|
|
20
|
+
* True until all dependencies have been constructed.
|
|
21
|
+
*/
|
|
22
|
+
private isConstructing;
|
|
23
|
+
constructor(config: LambdaWrapperConfig, event: any, context: Context);
|
|
24
|
+
/**
|
|
25
|
+
* Get the singleton instance of the given dependency.
|
|
26
|
+
*
|
|
27
|
+
* @param dependency
|
|
28
|
+
*/
|
|
29
|
+
get<T extends DependencyAwareClass>(dependency: Class<T>): T;
|
|
30
|
+
/**
|
|
31
|
+
* Get the event passed to AWS Lambda.
|
|
32
|
+
*
|
|
33
|
+
* @deprecated Use `di.event` instead.
|
|
34
|
+
*/
|
|
35
|
+
getEvent(): any;
|
|
36
|
+
/**
|
|
37
|
+
* Get the AWS Lambda context object.
|
|
38
|
+
*
|
|
39
|
+
* @deprecated Use `di.context` instead.
|
|
40
|
+
*/
|
|
41
|
+
getContext(): Context;
|
|
42
|
+
/**
|
|
43
|
+
* Get Lambda Wrapper configuration.
|
|
44
|
+
*
|
|
45
|
+
* @deprecated Use `di.config` instead.
|
|
46
|
+
*/
|
|
47
|
+
getConfiguration(): LambdaWrapperConfig;
|
|
48
|
+
/**
|
|
49
|
+
* True if the function is being executed in `serverless-offline`.
|
|
50
|
+
*
|
|
51
|
+
* We use the following checks for this:
|
|
52
|
+
*
|
|
53
|
+
* - if there is no function ARN, or the ARN includes 'offline'
|
|
54
|
+
* - if `process.env.USE_SERVERLESS_OFFLINE` is set
|
|
55
|
+
*
|
|
56
|
+
* TODO: This is nothing to do with dependency injection and should be moved
|
|
57
|
+
* somewhere else! Any ideas?
|
|
58
|
+
*/
|
|
59
|
+
get isOffline(): boolean;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=DependencyInjection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DependencyInjection.d.ts","sourceRoot":"","sources":["../../src/core/DependencyInjection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAErC,OAAO,oBAAoB,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG/C,aAAK,KAAK,CAAC,CAAC,IAAI,KAAK,EAAE,EAAE,mBAAmB,KAAK,CAAC,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,CAAC,OAAO,OAAO,mBAAmB;IAYpC,QAAQ,CAAC,MAAM,EAAE,mBAAmB;IACpC,QAAQ,CAAC,KAAK,EAAE,GAAG;IACnB,QAAQ,CAAC,OAAO,EAAE,OAAO;IAb3B;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAE5D;;OAEG;IACH,OAAO,CAAC,cAAc,CAAQ;gBAGnB,MAAM,EAAE,mBAAmB,EAC3B,KAAK,EAAE,GAAG,EACV,OAAO,EAAE,OAAO;IAU3B;;;;OAIG;IACH,GAAG,CAAC,CAAC,SAAS,oBAAoB,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IAsB5D;;;;OAIG;IACH,QAAQ;IAIR;;;;OAIG;IACH,UAAU;IAIV;;;;OAIG;IACH,gBAAgB;IAIhB;;;;;;;;;;OAUG;IACH,IAAI,SAAS,IAAI,OAAO,CAIvB;CACF"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Dependency injection container.
|
|
5
|
+
*
|
|
6
|
+
* Dependencies (singleton instances of dependency-aware classes) are provided
|
|
7
|
+
* to the main Lambda handler and other dependencies via this class.
|
|
8
|
+
*/
|
|
9
|
+
class DependencyInjection {
|
|
10
|
+
constructor(config, event, context) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.event = event;
|
|
13
|
+
this.context = context;
|
|
14
|
+
/**
|
|
15
|
+
* True until all dependencies have been constructed.
|
|
16
|
+
*/
|
|
17
|
+
this.isConstructing = true;
|
|
18
|
+
const classes = Object.values(config.dependencies);
|
|
19
|
+
this.dependencies = Object.fromEntries(classes.map((Constructor) => [Constructor.name, new Constructor(this)]));
|
|
20
|
+
this.isConstructing = false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the singleton instance of the given dependency.
|
|
24
|
+
*
|
|
25
|
+
* @param dependency
|
|
26
|
+
*/
|
|
27
|
+
get(dependency) {
|
|
28
|
+
if (this.isConstructing) {
|
|
29
|
+
throw new Error('Dependencies are not available in dependency class constructors.\n\n'
|
|
30
|
+
+ 'To fix this, call `di.get` in the function where the dependency is'
|
|
31
|
+
+ 'used instead of inside your constructor.');
|
|
32
|
+
}
|
|
33
|
+
const name = dependency.name;
|
|
34
|
+
if (!this.dependencies[name]) {
|
|
35
|
+
throw new Error(`${name} does not exist in dependency container\n\n`
|
|
36
|
+
+ `Make sure you've included ${name} in the 'dependencies' key of your `
|
|
37
|
+
+ 'Lambda Wrapper config.');
|
|
38
|
+
}
|
|
39
|
+
return this.dependencies[name];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the event passed to AWS Lambda.
|
|
43
|
+
*
|
|
44
|
+
* @deprecated Use `di.event` instead.
|
|
45
|
+
*/
|
|
46
|
+
getEvent() {
|
|
47
|
+
return this.event;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get the AWS Lambda context object.
|
|
51
|
+
*
|
|
52
|
+
* @deprecated Use `di.context` instead.
|
|
53
|
+
*/
|
|
54
|
+
getContext() {
|
|
55
|
+
return this.context;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get Lambda Wrapper configuration.
|
|
59
|
+
*
|
|
60
|
+
* @deprecated Use `di.config` instead.
|
|
61
|
+
*/
|
|
62
|
+
getConfiguration() {
|
|
63
|
+
return this.config;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* True if the function is being executed in `serverless-offline`.
|
|
67
|
+
*
|
|
68
|
+
* We use the following checks for this:
|
|
69
|
+
*
|
|
70
|
+
* - if there is no function ARN, or the ARN includes 'offline'
|
|
71
|
+
* - if `process.env.USE_SERVERLESS_OFFLINE` is set
|
|
72
|
+
*
|
|
73
|
+
* TODO: This is nothing to do with dependency injection and should be moved
|
|
74
|
+
* somewhere else! Any ideas?
|
|
75
|
+
*/
|
|
76
|
+
get isOffline() {
|
|
77
|
+
return !this.context.invokedFunctionArn
|
|
78
|
+
|| this.context.invokedFunctionArn.includes('offline')
|
|
79
|
+
|| !!process.env.USE_SERVERLESS_OFFLINE;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.default = DependencyInjection;
|
|
83
|
+
//# sourceMappingURL=DependencyInjection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DependencyInjection.js","sourceRoot":"","sources":["../../src/core/DependencyInjection.ts"],"names":[],"mappings":";;AAQA;;;;;GAKG;AACH,MAAqB,mBAAmB;IAWtC,YACW,MAA2B,EAC3B,KAAU,EACV,OAAgB;QAFhB,WAAM,GAAN,MAAM,CAAqB;QAC3B,UAAK,GAAL,KAAK,CAAK;QACV,YAAO,GAAP,OAAO,CAAS;QAR3B;;WAEG;QACK,mBAAc,GAAG,IAAI,CAAC;QAO5B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QACnD,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,WAAW,CACpC,OAAO,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CACxE,CAAC;QAEF,IAAI,CAAC,cAAc,GAAG,KAAK,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACH,GAAG,CAAiC,UAAoB;QACtD,IAAI,IAAI,CAAC,cAAc,EAAE;YACvB,MAAM,IAAI,KAAK,CACb,sEAAsE;kBACpE,oEAAoE;kBACpE,0CAA0C,CAC7C,CAAC;SACH;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC;QAE7B,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE;YAC5B,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,6CAA6C;kBAClD,6BAA6B,IAAI,qCAAqC;kBACtE,wBAAwB,CAC3B,CAAC;SACH;QAED,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAM,CAAC;IACtC,CAAC;IAED;;;;OAIG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;OAIG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,gBAAgB;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED;;;;;;;;;;OAUG;IACH,IAAI,SAAS;QACX,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB;eAClC,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,QAAQ,CAAC,SAAS,CAAC;eACnD,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAC5C,CAAC;CACF;AA9FD,sCA8FC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Context } from '../index';
|
|
2
|
+
import DependencyInjection from './DependencyInjection';
|
|
3
|
+
import { LambdaWrapperConfig } from './config';
|
|
4
|
+
export interface WrapOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Whether uncaught errors should be handled to return an HTTP 500 response
|
|
7
|
+
* instead of causing a function error. (default: `true`)
|
|
8
|
+
*
|
|
9
|
+
* This is what you usually want when working on an HTTP endpoint, but in
|
|
10
|
+
* other contexts (e.g. queue consumers) you may want AWS Lambda to report a
|
|
11
|
+
* failure so that the event is retried.
|
|
12
|
+
*/
|
|
13
|
+
handleUncaughtErrors?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export default class LambdaWrapper<TConfig extends LambdaWrapperConfig = LambdaWrapperConfig> {
|
|
16
|
+
readonly config: TConfig;
|
|
17
|
+
constructor(config: TConfig);
|
|
18
|
+
/**
|
|
19
|
+
* Returns a new Lambda Wrapper with the given configuration applied.
|
|
20
|
+
*
|
|
21
|
+
* @param config
|
|
22
|
+
*/
|
|
23
|
+
configure<TMoreConfig>(config: Partial<TConfig> & TMoreConfig): LambdaWrapper<TConfig & TMoreConfig>;
|
|
24
|
+
/**
|
|
25
|
+
* Wrap the given function.
|
|
26
|
+
*/
|
|
27
|
+
wrap<T>(handler: (di: DependencyInjection) => Promise<T>, options?: WrapOptions): (event: any, context: Context) => Promise<any>;
|
|
28
|
+
/**
|
|
29
|
+
* Process the result once we have one.
|
|
30
|
+
*
|
|
31
|
+
* @param di
|
|
32
|
+
* @param result
|
|
33
|
+
*/
|
|
34
|
+
static handleSuccess(di: DependencyInjection, result: any): any;
|
|
35
|
+
/**
|
|
36
|
+
* Gracefully handles an error, logging in Epsagon and generating a response
|
|
37
|
+
* reflecting the `code` of the error, if defined.
|
|
38
|
+
*
|
|
39
|
+
* Note about Epsagon:
|
|
40
|
+
* Epsagon generates alerts for logs on level ERROR. This means that
|
|
41
|
+
* `logger.error` will produce an alert. To avoid meaningless notifications,
|
|
42
|
+
* most likely coming from tests, we log INFO unless either:
|
|
43
|
+
*
|
|
44
|
+
* 1. `error.raiseOnEpsagon` is defined & truthy
|
|
45
|
+
* 2. `error.code` is defined and `error.code >= 500`.
|
|
46
|
+
*
|
|
47
|
+
* @param di
|
|
48
|
+
* @param error
|
|
49
|
+
* @param [throwError=false]
|
|
50
|
+
*/
|
|
51
|
+
static handleError(di: DependencyInjection, error: Error, throwError?: boolean): {
|
|
52
|
+
statusCode: any;
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': string;
|
|
55
|
+
'Access-Control-Allow-Origin': string;
|
|
56
|
+
'Access-Control-Allow-Credentials': boolean;
|
|
57
|
+
};
|
|
58
|
+
body: string;
|
|
59
|
+
} | Error;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=LambdaWrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LambdaWrapper.d.ts","sourceRoot":"","sources":["../../src/core/LambdaWrapper.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAInC,OAAO,mBAAmB,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAe,MAAM,UAAU,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,CAAC,OAAO,OAAO,aAAa,CAAC,OAAO,SAAS,mBAAmB,GAAG,mBAAmB;IAC9E,QAAQ,CAAC,MAAM,EAAE,OAAO;gBAAf,MAAM,EAAE,OAAO;IAEpC;;;;OAIG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,WAAW,GAAG,aAAa,CAAC,OAAO,GAAG,WAAW,CAAC;IAIpG;;OAEG;IACH,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,mBAAmB,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,WAKjD,GAAG,WAAW,OAAO;IAyDnD;;;;;OAKG;IACH,MAAM,CAAC,aAAa,CAAC,EAAE,EAAE,mBAAmB,EAAE,MAAM,EAAE,GAAG;IASzD;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,WAAW,CAAC,EAAE,EAAE,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,UAAQ;;;;;;;;;CA8B7E"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const epsagon_1 = __importDefault(require("epsagon"));
|
|
7
|
+
const ResponseModel_1 = __importDefault(require("../models/ResponseModel"));
|
|
8
|
+
const LoggerService_1 = __importDefault(require("../services/LoggerService"));
|
|
9
|
+
const RequestService_1 = __importDefault(require("../services/RequestService"));
|
|
10
|
+
const DependencyInjection_1 = __importDefault(require("./DependencyInjection"));
|
|
11
|
+
const config_1 = require("./config");
|
|
12
|
+
class LambdaWrapper {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Returns a new Lambda Wrapper with the given configuration applied.
|
|
18
|
+
*
|
|
19
|
+
* @param config
|
|
20
|
+
*/
|
|
21
|
+
configure(config) {
|
|
22
|
+
return new LambdaWrapper((0, config_1.mergeConfig)(this.config, config));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Wrap the given function.
|
|
26
|
+
*/
|
|
27
|
+
wrap(handler, options) {
|
|
28
|
+
const { handleUncaughtErrors = true, } = options || {};
|
|
29
|
+
let wrapper = async (event, context) => {
|
|
30
|
+
const di = new DependencyInjection_1.default(this.config, event, context);
|
|
31
|
+
const request = di.get(RequestService_1.default);
|
|
32
|
+
const logger = di.get(LoggerService_1.default);
|
|
33
|
+
context.callbackWaitsForEmptyEventLoop = false;
|
|
34
|
+
// if the event is a warmup, don't bother running the function
|
|
35
|
+
if (event.source === 'serverless-plugin-warmup') {
|
|
36
|
+
return 'Lambda is warm!';
|
|
37
|
+
}
|
|
38
|
+
// log the user's IP address silently for use in error tracing
|
|
39
|
+
const ipAddress = request.getIp();
|
|
40
|
+
if (ipAddress) {
|
|
41
|
+
logger.metric('ipAddress', ipAddress, true);
|
|
42
|
+
}
|
|
43
|
+
// add metrics with user browser information for rapid debugging
|
|
44
|
+
const userBrowserAndDevice = request.getUserBrowserAndDevice();
|
|
45
|
+
if (userBrowserAndDevice) {
|
|
46
|
+
Object.entries(userBrowserAndDevice).forEach(([key, value]) => {
|
|
47
|
+
logger.metric(key, value, true);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const result = await handler.call(wrapper, di);
|
|
52
|
+
return LambdaWrapper.handleSuccess(di, result);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const handled = LambdaWrapper.handleError(di, error, !handleUncaughtErrors);
|
|
56
|
+
if (!handleUncaughtErrors) {
|
|
57
|
+
// AWS Lambda with async handler is looking for a rejection
|
|
58
|
+
// and not an error object directly
|
|
59
|
+
// and will treat resolved errors as successful
|
|
60
|
+
// as it will cast the error to JSON, i.e. `{}`
|
|
61
|
+
throw handled;
|
|
62
|
+
}
|
|
63
|
+
return handled;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
// If Epsagon is enabled, wrap the instance in the Epsagon wrapper
|
|
67
|
+
if (process.env.EPSAGON_TOKEN && process.env.EPSAGON_SERVICE_NAME) {
|
|
68
|
+
epsagon_1.default.init({
|
|
69
|
+
token: process.env.EPSAGON_TOKEN,
|
|
70
|
+
appName: process.env.EPSAGON_SERVICE_NAME,
|
|
71
|
+
});
|
|
72
|
+
wrapper = epsagon_1.default.lambdaWrapper(wrapper);
|
|
73
|
+
}
|
|
74
|
+
return wrapper;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Process the result once we have one.
|
|
78
|
+
*
|
|
79
|
+
* @param di
|
|
80
|
+
* @param result
|
|
81
|
+
*/
|
|
82
|
+
static handleSuccess(di, result) {
|
|
83
|
+
const logger = di.get(LoggerService_1.default);
|
|
84
|
+
// result may be undefined as not all lambdas have a return value
|
|
85
|
+
logger.metric('lambda.statusCode', result?.statusCode || 200);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Gracefully handles an error, logging in Epsagon and generating a response
|
|
90
|
+
* reflecting the `code` of the error, if defined.
|
|
91
|
+
*
|
|
92
|
+
* Note about Epsagon:
|
|
93
|
+
* Epsagon generates alerts for logs on level ERROR. This means that
|
|
94
|
+
* `logger.error` will produce an alert. To avoid meaningless notifications,
|
|
95
|
+
* most likely coming from tests, we log INFO unless either:
|
|
96
|
+
*
|
|
97
|
+
* 1. `error.raiseOnEpsagon` is defined & truthy
|
|
98
|
+
* 2. `error.code` is defined and `error.code >= 500`.
|
|
99
|
+
*
|
|
100
|
+
* @param di
|
|
101
|
+
* @param error
|
|
102
|
+
* @param [throwError=false]
|
|
103
|
+
*/
|
|
104
|
+
static handleError(di, error, throwError = false) {
|
|
105
|
+
const logger = di.get(LoggerService_1.default);
|
|
106
|
+
const { code, raiseOnEpsagon, body = {}, details = 'unknown error', } = error;
|
|
107
|
+
logger.metric('lambda.statusCode', code || 500);
|
|
108
|
+
if (raiseOnEpsagon || !code || code >= 500) {
|
|
109
|
+
logger.error(error);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
logger.info(error);
|
|
113
|
+
}
|
|
114
|
+
if (throwError) {
|
|
115
|
+
if (error instanceof Error) {
|
|
116
|
+
return error;
|
|
117
|
+
}
|
|
118
|
+
// We want to be absolutely sure that we are returning an error, as
|
|
119
|
+
// Lambda sync handlers will only fail if the object is instanceof Error
|
|
120
|
+
return new Error(error);
|
|
121
|
+
}
|
|
122
|
+
return ResponseModel_1.default.generate(body, code || 500, details);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.default = LambdaWrapper;
|
|
126
|
+
//# sourceMappingURL=LambdaWrapper.js.map
|