serverless-plugin-warmup 8.2.1 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/biome.json +47 -0
- package/package.json +36 -38
- package/src/config.js +159 -147
- package/src/index.js +245 -225
- package/src/schema.js +178 -162
- package/src/utils.js +1 -1
- package/src/warmer.js +139 -149
- package/.eslintignore +0 -2
- package/.eslintrc.js +0 -9
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
Keep your lambdas warm during winter.
|
|
10
10
|
|
|
11
11
|
**Requirements:**
|
|
12
|
-
* Node *
|
|
12
|
+
* Node *v20.x* or higher
|
|
13
13
|
* Serverless *v3.8* or higher
|
|
14
14
|
* AWS provider
|
|
15
15
|
|
|
@@ -64,7 +64,7 @@ The options are the same for all the warmers:
|
|
|
64
64
|
* **cleanFolder** Whether to automatically delete the generated code folder. You might want to keep it if you are doing some custom packaging (defaults to `true`)
|
|
65
65
|
* **name** Name of the generated warmer lambda (defaults to `${service}-${stage}-warmup-plugin-${warmerName}`)
|
|
66
66
|
* **roleName** Name to be applied to the default warmer lambda role. Ignored if a the role setting is used (defaults to `${service.service}-${stage}-${region}-${warmerName.toLowerCase()}-role`)
|
|
67
|
-
* **role** Role to apply to the warmer lambda (defaults to
|
|
67
|
+
* **role** Role to apply to the warmer lambda (defaults to a custom role with minimal [permissions](#permissions))
|
|
68
68
|
* **tags** Tag to apply to the generated warmer lambda (defaults to the serverless default tags)
|
|
69
69
|
* **vpc** The VPC and subnets in which to deploy. Can be any [Serverless VPC configuration](https://serverless.com/framework/docs/providers/aws/guide/functions#vpc-configuration) or be set to `false` in order to deploy the warmup function outside of a VPC (defaults to the vpc in the provider)
|
|
70
70
|
* **memorySize** The memory to be assigned to the warmer lambda (defaults to `128`)
|
|
@@ -258,7 +258,7 @@ The permissions can also be added to all lambdas using setting the role to `IamR
|
|
|
258
258
|
```yaml
|
|
259
259
|
provider:
|
|
260
260
|
name: aws
|
|
261
|
-
runtime:
|
|
261
|
+
runtime: nodejs24.x
|
|
262
262
|
iamRoleStatements:
|
|
263
263
|
- Effect: 'Allow'
|
|
264
264
|
Action:
|
|
@@ -585,13 +585,13 @@ You can check the Lambda [pricing](https://aws.amazon.com/lambda/pricing/) and C
|
|
|
585
585
|
|
|
586
586
|
#### Example
|
|
587
587
|
|
|
588
|
-
If you have a single warmer and want to warm 10 functions, each with `memorySize = 1024` and `duration = 10`, using the default settings (and
|
|
588
|
+
If you have a single warmer and want to warm 10 functions, each with `memorySize = 1024` and `duration = 10`, using the default settings ($0.0000166667 for every GB-second) and ignoring the free tier:
|
|
589
589
|
|
|
590
|
-
* WarmUp: runs 8640 times per month = $0.
|
|
591
|
-
* 10 warm lambdas: each invoked 8640 times per month = $
|
|
592
|
-
* Total = $
|
|
590
|
+
* WarmUp: runs 8640 times per month = $0.0
|
|
591
|
+
* 10 warm lambdas: each invoked 8640 times per month = $0.3
|
|
592
|
+
* Total = $0.3
|
|
593
593
|
|
|
594
|
-
CloudWatch costs are not in this example
|
|
594
|
+
CloudWatch costs are not consdiered in this example.
|
|
595
595
|
|
|
596
596
|
## Contribute
|
|
597
597
|
|
package/biome.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"includes": ["**", "!!**/coverage", "!!**/test_integration", "!!**/node_modules"]
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"indentWidth": 2,
|
|
13
|
+
"lineWidth": 100
|
|
14
|
+
},
|
|
15
|
+
"linter": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"rules": {
|
|
18
|
+
"recommended": true,
|
|
19
|
+
"style": {
|
|
20
|
+
"useNodejsImportProtocol": "off"
|
|
21
|
+
},
|
|
22
|
+
"suspicious": {
|
|
23
|
+
"noEmptyBlockStatements": "off",
|
|
24
|
+
"noAssignInExpressions": "off"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"javascript": {
|
|
29
|
+
"formatter": {
|
|
30
|
+
"quoteStyle": "single",
|
|
31
|
+
"trailingCommas": "all",
|
|
32
|
+
"semicolons": "always",
|
|
33
|
+
"arrowParentheses": "always"
|
|
34
|
+
},
|
|
35
|
+
"globals": [
|
|
36
|
+
"jest",
|
|
37
|
+
"describe",
|
|
38
|
+
"it",
|
|
39
|
+
"expect",
|
|
40
|
+
"beforeEach",
|
|
41
|
+
"afterEach",
|
|
42
|
+
"beforeAll",
|
|
43
|
+
"afterAll",
|
|
44
|
+
"test"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,38 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"jest": "^29.1.2"
|
|
39
|
-
}
|
|
2
|
+
"name": "serverless-plugin-warmup",
|
|
3
|
+
"version": "8.4.0",
|
|
4
|
+
"description": "Keep your lambdas warm during winter.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"prepare": "husky",
|
|
8
|
+
"lint": "biome check .",
|
|
9
|
+
"lint:fix": "biome check --write .",
|
|
10
|
+
"format": "biome format --write .",
|
|
11
|
+
"test": "jest test",
|
|
12
|
+
"test-with-coverage": "jest --coverage --collectCoverageFrom=src/**/*.js test"
|
|
13
|
+
},
|
|
14
|
+
"author": "Juanjo Diaz <juanjo.diazmo@gmail.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/juanjoDiaz/serverless-plugin-warmup.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"serverless plugin warm up lambdas",
|
|
22
|
+
"serverless warmup",
|
|
23
|
+
"serverless plugin",
|
|
24
|
+
"lambda serverless",
|
|
25
|
+
"warm lambda",
|
|
26
|
+
"lambda coldstart",
|
|
27
|
+
"lambda cold"
|
|
28
|
+
],
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/juanjoDiaz/serverless-plugin-warmup/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/juanjoDiaz/serverless-plugin-warmup",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "2.3.11",
|
|
35
|
+
"husky": "^9.1.7",
|
|
36
|
+
"jest": "^30.2.0"
|
|
37
|
+
}
|
|
40
38
|
}
|
package/src/config.js
CHANGED
|
@@ -7,51 +7,60 @@ const path = require('path');
|
|
|
7
7
|
* @return {Object} - Global configuration options
|
|
8
8
|
* */
|
|
9
9
|
function getWarmerConfig(config, defaultOpts) {
|
|
10
|
-
|
|
10
|
+
const folderName =
|
|
11
|
+
typeof config.folderName === 'string' ? config.folderName : defaultOpts.folderName;
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
13
|
+
/* eslint-disable no-nested-ternary */
|
|
14
|
+
return {
|
|
15
|
+
folderName,
|
|
16
|
+
pathHandler: path.join(folderName, 'index.warmUp'),
|
|
17
|
+
cleanFolder:
|
|
18
|
+
typeof config.cleanFolder === 'boolean' ? config.cleanFolder : defaultOpts.cleanFolder,
|
|
19
|
+
name: config.name !== undefined ? config.name : defaultOpts.name,
|
|
20
|
+
roleName: config.roleName !== undefined ? config.roleName : defaultOpts.roleName,
|
|
21
|
+
role: config.role !== undefined ? config.role : defaultOpts.role,
|
|
22
|
+
tags: config.tags !== undefined ? config.tags : defaultOpts.tags,
|
|
23
|
+
vpc:
|
|
24
|
+
config.vpc === false
|
|
25
|
+
? { securityGroupIds: [], subnetIds: [] }
|
|
26
|
+
: config.vpc !== undefined
|
|
27
|
+
? config.vpc
|
|
28
|
+
: defaultOpts.vpc,
|
|
29
|
+
events: Array.isArray(config.events) ? config.events : defaultOpts.events,
|
|
30
|
+
architecture:
|
|
31
|
+
config.architecture !== undefined ? config.architecture : defaultOpts.architecture,
|
|
32
|
+
package:
|
|
33
|
+
typeof config.package === 'object'
|
|
34
|
+
? {
|
|
35
|
+
individually:
|
|
36
|
+
config.package.individually !== undefined
|
|
37
|
+
? config.package.individually
|
|
38
|
+
: defaultOpts.package.individually,
|
|
39
|
+
patterns: [
|
|
40
|
+
'!**',
|
|
41
|
+
...(Array.isArray(config.package.patterns)
|
|
42
|
+
? config.package.patterns.includes(path.join(folderName, '**'))
|
|
43
|
+
? config.package.patterns
|
|
44
|
+
: [...config.package.patterns, path.join(folderName, '**')]
|
|
45
|
+
: [...defaultOpts.package.patterns, path.join(folderName, '**')]),
|
|
46
|
+
],
|
|
47
|
+
}
|
|
48
|
+
: {
|
|
49
|
+
...defaultOpts.package,
|
|
50
|
+
patterns: ['!**', ...defaultOpts.package.patterns, path.join(folderName, '**')],
|
|
51
|
+
},
|
|
52
|
+
memorySize: config.memorySize !== undefined ? config.memorySize : defaultOpts.memorySize,
|
|
53
|
+
timeout: config.timeout !== undefined ? config.timeout : defaultOpts.timeout,
|
|
54
|
+
environment: config.environment !== undefined ? config.environment : defaultOpts.environment,
|
|
55
|
+
tracing: config.tracing !== undefined ? config.tracing : defaultOpts.tracing,
|
|
56
|
+
verbose: config.verbose !== undefined ? config.verbose : defaultOpts.verbose,
|
|
57
|
+
logRetentionInDays:
|
|
58
|
+
config.logRetentionInDays !== undefined
|
|
59
|
+
? config.logRetentionInDays
|
|
60
|
+
: defaultOpts.logRetentionInDays,
|
|
61
|
+
prewarm: config.prewarm !== undefined ? config.prewarm : defaultOpts.prewarm,
|
|
62
|
+
};
|
|
63
|
+
/* eslint-enable no-nested-ternary */
|
|
55
64
|
}
|
|
56
65
|
|
|
57
66
|
/**
|
|
@@ -61,25 +70,23 @@ function getWarmerConfig(config, defaultOpts) {
|
|
|
61
70
|
* @return {Object} - Function-specific configuration options
|
|
62
71
|
* */
|
|
63
72
|
function getFunctionConfig(config, defaultOpts) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
};
|
|
82
|
-
/* eslint-enable no-nested-ternary */
|
|
73
|
+
/* eslint-disable no-nested-ternary */
|
|
74
|
+
return {
|
|
75
|
+
enabled: config.enabled !== undefined ? config.enabled : defaultOpts.enabled,
|
|
76
|
+
alias: config.alias !== undefined ? config.alias : defaultOpts.alias,
|
|
77
|
+
clientContext:
|
|
78
|
+
config.clientContext !== undefined
|
|
79
|
+
? config.clientContext && JSON.stringify(config.clientContext)
|
|
80
|
+
: defaultOpts.clientContext,
|
|
81
|
+
payload:
|
|
82
|
+
config.payload !== undefined
|
|
83
|
+
? config.payloadRaw
|
|
84
|
+
? config.payload
|
|
85
|
+
: JSON.stringify(config.payload)
|
|
86
|
+
: defaultOpts.payload,
|
|
87
|
+
concurrency: config.concurrency !== undefined ? config.concurrency : defaultOpts.concurrency,
|
|
88
|
+
};
|
|
89
|
+
/* eslint-enable no-nested-ternary */
|
|
83
90
|
}
|
|
84
91
|
|
|
85
92
|
/**
|
|
@@ -88,53 +95,54 @@ function getFunctionConfig(config, defaultOpts) {
|
|
|
88
95
|
* @return {Array} - List of functions to be warmed up and their specific configs
|
|
89
96
|
* */
|
|
90
97
|
function getFunctionsByWarmer(service, stage, configsByWarmer, serverlessClasses) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
98
|
+
const functions = service
|
|
99
|
+
.getAllFunctions()
|
|
100
|
+
.map((name) => service.getFunction(name))
|
|
101
|
+
.map((config) => {
|
|
102
|
+
if (config.warmup === undefined) {
|
|
103
|
+
return {
|
|
104
|
+
name: config.name,
|
|
105
|
+
config: Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
|
|
106
|
+
warmers[warmerName] = getFunctionConfig({}, warmerConfig);
|
|
107
|
+
return warmers;
|
|
108
|
+
}, {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const unknownWarmers = Object.keys(config.warmup).filter(
|
|
112
|
+
(warmerName) => configsByWarmer[warmerName] === undefined,
|
|
113
|
+
);
|
|
114
|
+
if (unknownWarmers.length > 0) {
|
|
115
|
+
throw new serverlessClasses.Error(
|
|
116
|
+
`WarmUp: Invalid function-level warmup configuration (${unknownWarmers.join(', ')}) in function ${config.name}. Every warmer should be declared in the custom section.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
return {
|
|
121
|
+
name: config.name,
|
|
122
|
+
config: Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
|
|
123
|
+
warmers[warmerName] = getFunctionConfig(config.warmup[warmerName] || {}, warmerConfig);
|
|
124
|
+
return warmers;
|
|
125
|
+
}, {}),
|
|
126
|
+
};
|
|
127
|
+
});
|
|
128
|
+
function isEnabled(enabled) {
|
|
129
|
+
return (
|
|
130
|
+
enabled === true ||
|
|
131
|
+
enabled === 'true' ||
|
|
132
|
+
enabled === stage ||
|
|
133
|
+
(Array.isArray(enabled) && enabled.indexOf(stage) !== -1)
|
|
134
|
+
);
|
|
135
|
+
}
|
|
110
136
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
function isEnabled(enabled) {
|
|
122
|
-
return enabled === true
|
|
123
|
-
|| enabled === 'true'
|
|
124
|
-
|| enabled === stage
|
|
125
|
-
|| (Array.isArray(enabled) && enabled.indexOf(stage) !== -1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return functions.reduce((warmersAcc, fn) => {
|
|
129
|
-
Object.entries(fn.config)
|
|
130
|
-
.forEach(([warmerName, config]) => {
|
|
131
|
-
if (!isEnabled(config.enabled)) return;
|
|
132
|
-
// eslint-disable-next-line no-param-reassign
|
|
133
|
-
if (!warmersAcc[warmerName]) warmersAcc[warmerName] = [];
|
|
134
|
-
warmersAcc[warmerName].push({ name: fn.name, config });
|
|
135
|
-
});
|
|
136
|
-
return warmersAcc;
|
|
137
|
-
}, {});
|
|
137
|
+
return functions.reduce((warmersAcc, fn) => {
|
|
138
|
+
Object.entries(fn.config).forEach(([warmerName, config]) => {
|
|
139
|
+
if (!isEnabled(config.enabled)) return;
|
|
140
|
+
// eslint-disable-next-line no-param-reassign
|
|
141
|
+
if (!warmersAcc[warmerName]) warmersAcc[warmerName] = [];
|
|
142
|
+
warmersAcc[warmerName].push({ name: fn.name, config });
|
|
143
|
+
});
|
|
144
|
+
return warmersAcc;
|
|
145
|
+
}, {});
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
/**
|
|
@@ -143,50 +151,54 @@ function getFunctionsByWarmer(service, stage, configsByWarmer, serverlessClasses
|
|
|
143
151
|
* @return {Object} - Configuration options to be used by the plugin
|
|
144
152
|
* */
|
|
145
153
|
function getConfigsByWarmer({ service, classes }, stage) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
const getWarmerDefaultOpts = (warmerName) => ({
|
|
155
|
+
folderName: path.join('.warmup', warmerName),
|
|
156
|
+
cleanFolder: true,
|
|
157
|
+
memorySize: 128,
|
|
158
|
+
name: `${service.service}-${stage}-warmup-plugin-${warmerName}`,
|
|
159
|
+
events: [{ schedule: 'rate(5 minutes)' }],
|
|
160
|
+
package: {
|
|
161
|
+
individually: true,
|
|
162
|
+
patterns: [],
|
|
163
|
+
},
|
|
164
|
+
timeout: 10,
|
|
165
|
+
environment: Object.keys(service.provider.environment || []).reduce((obj, k) => {
|
|
166
|
+
obj[k] = undefined;
|
|
167
|
+
return obj;
|
|
168
|
+
}, {}),
|
|
169
|
+
verbose: true,
|
|
170
|
+
prewarm: false,
|
|
171
|
+
});
|
|
162
172
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
const functionDefaultOpts = {
|
|
174
|
+
enabled: false,
|
|
175
|
+
clientContext: undefined,
|
|
176
|
+
payload: JSON.stringify({ source: 'serverless-plugin-warmup' }),
|
|
177
|
+
concurrency: 1,
|
|
178
|
+
};
|
|
169
179
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
180
|
+
const configsByWarmer = Object.entries(service.custom?.warmup || {}).reduce(
|
|
181
|
+
(warmers, [warmerName, warmerConfig]) => {
|
|
182
|
+
warmers[warmerName] = {
|
|
183
|
+
...getWarmerConfig(warmerConfig, getWarmerDefaultOpts(warmerName)),
|
|
184
|
+
...getFunctionConfig(warmerConfig, functionDefaultOpts),
|
|
185
|
+
};
|
|
186
|
+
return warmers;
|
|
187
|
+
},
|
|
188
|
+
{},
|
|
189
|
+
);
|
|
178
190
|
|
|
179
|
-
|
|
191
|
+
const functionsByWarmer = getFunctionsByWarmer(service, stage, configsByWarmer, classes);
|
|
180
192
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
return Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
|
|
194
|
+
warmers[warmerName] = {
|
|
195
|
+
...warmerConfig,
|
|
196
|
+
functions: functionsByWarmer[warmerName] || [],
|
|
197
|
+
};
|
|
198
|
+
return warmers;
|
|
199
|
+
}, {});
|
|
188
200
|
}
|
|
189
201
|
|
|
190
202
|
module.exports = {
|
|
191
|
-
|
|
203
|
+
getConfigsByWarmer,
|
|
192
204
|
};
|