jaypie 1.0.49 → 1.1.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/LICENSE.txt +21 -0
- package/README.md +5 -1042
- package/package.json +12 -34
- package/eslint.config.mjs +0 -2
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Finlayson Studio, LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,1051 +1,14 @@
|
|
|
1
|
-
# Jaypie
|
|
1
|
+
# Jaypie Package 🐦⬛📦
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The main entrypoint for Jaypie packages
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 🐦⬛ Introduction
|
|
8
|
-
|
|
9
|
-
Jaypie is an opinionated approach to application development centered around JavaScript and the JSON:API specification in an event-driven architecture.
|
|
10
|
-
|
|
11
|
-
Jaypie is suited for applications that require custom infrastructure beyond HTTP requests (e.g., message queues). Without custom infrastructure, fullstack hosts like Vercel or Netlify are recommended.
|
|
12
|
-
|
|
13
|
-
### "Jaypie Stack"
|
|
14
|
-
|
|
15
|
-
* AWS infrastructure managed by CDK in Node.js
|
|
16
|
-
* Express server running on AWS Lambda
|
|
17
|
-
* Node.js worker processes running on AWS Lambda
|
|
18
|
-
* MongoDB via Mongoose
|
|
19
|
-
* Vue ecosystem frontend: Vue 3 composition, Vuetify, Pinia
|
|
20
|
-
* Vitest for testing
|
|
21
|
-
* ES6 syntax enforced via ESLint
|
|
22
|
-
* Prettier formatting
|
|
23
|
-
* JSON logging with custom metadata
|
|
24
|
-
|
|
25
|
-
### Philosophy
|
|
26
|
-
|
|
27
|
-
Jaypie is for building fullstack JavaScript applications.
|
|
28
|
-
|
|
29
|
-
#### JavaScript Only 💛
|
|
30
|
-
|
|
31
|
-
Jaypie uses the AWS Cloud Development Kit (CDK) to manage infrastructure, which is written in Node.js. This makes managing infrastructure accessible to the fullstack developer without learning a new syntax and living without language constructs like loops and inheritance.
|
|
32
|
-
|
|
33
|
-
Does NOT use Kubernetes, Docker, Terraform, or the "Serverless" framework.
|
|
34
|
-
|
|
35
|
-
#### Eject Anything ⏏️
|
|
36
|
-
|
|
37
|
-
Jaypie embraces "ejectability," the philosophy that any part of the code can be removed (and therefore replaced) without disturbing the whole.
|
|
38
|
-
|
|
39
|
-
#### Mock Everywhere 🎴
|
|
40
|
-
|
|
41
|
-
Jaypie strives to be "mockable-first" meaning all components should be easily tested via default or provided mocks.
|
|
42
|
-
|
|
43
|
-
## 📋 Usage
|
|
44
|
-
|
|
45
|
-
### Installation
|
|
46
|
-
|
|
47
|
-
#### Base Package
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm install jaypie
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
`@jaypie/core` is included in `jaypie`. Almost every Jaypie package requires core.
|
|
54
|
-
|
|
55
|
-
#### Peer Packages
|
|
56
|
-
|
|
57
|
-
These packages are included in `jaypie`. They may be installed separately in the future.
|
|
58
|
-
|
|
59
|
-
| Package | Exports | Description |
|
|
60
|
-
| ------- | ------- | ----------- |
|
|
61
|
-
| `@jaypie/aws` | `getMessages`, `getSecret`, `sendBatchMessages`, `sendMessage` | AWS helpers |
|
|
62
|
-
| `@jaypie/datadog` | `submitMetric`, `submitMetricSet` | Datadog helpers |
|
|
63
|
-
| `@jaypie/express` | `expressHandler` | Express entry point |
|
|
64
|
-
| `@jaypie/lambda` | `lambdaHandler` | Lambda entry point |
|
|
65
|
-
| `@jaypie/mongoose` | `connect`, `connectFromSecretEnv`, `disconnect`, `mongoose` | MongoDB management |
|
|
66
|
-
|
|
67
|
-
#### TestKit
|
|
68
|
-
|
|
69
|
-
Matchers, mocks, and utilities to test Jaypie projects.
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
npm install --save-dev @jaypie/testkit
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Example
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
npm install jaypie @jaypie/lambda
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
```javascript
|
|
82
|
-
const { InternalError, lambdaHandler, log } = require("jaypie");
|
|
83
|
-
|
|
84
|
-
export const handler = lambdaHandler(async({event}) => {
|
|
85
|
-
// await new Promise(r => setTimeout(r, 2000));
|
|
86
|
-
if (event.something === "problem") {
|
|
87
|
-
throw new InternalError();
|
|
88
|
-
}
|
|
89
|
-
// log.debug("Hello World");
|
|
90
|
-
return "Hello World";
|
|
91
|
-
}, { name: "example"});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
This example would then be deployed to AWS via CDK or similar orchestration. See [@jaypie/cdk](https://github.com/finlaysonstudio/jaypie-cdk).
|
|
95
|
-
|
|
96
|
-
## 📖 Reference
|
|
97
|
-
|
|
98
|
-
### AWS
|
|
99
|
-
|
|
100
|
-
```javascript
|
|
101
|
-
import {
|
|
102
|
-
getMessages,
|
|
103
|
-
getSecret,
|
|
104
|
-
sendBatchMessages,
|
|
105
|
-
sendMessage,
|
|
106
|
-
} from "jaypie";
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
#### `getMessages(event)`
|
|
110
|
-
|
|
111
|
-
Return an array of message bodies from an SQS event.
|
|
112
|
-
|
|
113
|
-
```javascript
|
|
114
|
-
import { getMessages } from '@jaypie/aws';
|
|
115
|
-
|
|
116
|
-
const messages = getMessages(event);
|
|
117
|
-
// messages = [{ salutation: "Hello, world!" }, { salutation: "Hola, dushi!" }]
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
#### `getSecret(secretName: string)`
|
|
121
|
-
|
|
122
|
-
Retrieve a secret from AWS Secrets Manager using the secret name.
|
|
123
|
-
|
|
124
|
-
```javascript
|
|
125
|
-
import { getSecret } from '@jaypie/aws';
|
|
126
|
-
|
|
127
|
-
const secret = await getSecret("MongoConnectionStringN0NC3-nSg1bR1sh");
|
|
128
|
-
// secret = "mongodb+srv://username:password@env-project.n0nc3.mongodb.net/app?retryWrites=true&w=majority";
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
#### `sendBatchMessages({ messages, queueUrl })`
|
|
132
|
-
|
|
133
|
-
Batch and send messages to an SQS queue. If more than ten messages are provided, the function will batch them into groups of ten or less (per AWS).
|
|
134
|
-
|
|
135
|
-
```javascript
|
|
136
|
-
import { sendBatchMessages } from '@jaypie/aws';
|
|
137
|
-
|
|
138
|
-
const messages = [
|
|
139
|
-
{ salutation: "Hello, world!" },
|
|
140
|
-
{ salutation: "Hola, dushi!" },
|
|
141
|
-
];
|
|
142
|
-
const queueUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue";
|
|
143
|
-
|
|
144
|
-
await sendBatchMessages({ messages, queueUrl });
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
| Parameter | Type | Required | Description |
|
|
148
|
-
| --------- | ---- | -------- | ----------- |
|
|
149
|
-
| `delaySeconds` | `number` | No | Seconds to delay message delivery; default `0` |
|
|
150
|
-
| `messages` | `Array` | Yes | Array of message objects (or strings) |
|
|
151
|
-
| `messageAttributes` | `object` | No | Message attributes |
|
|
152
|
-
| `messageGroupId` | `string` | No | Custom message group for FIFO queues; default provided |
|
|
153
|
-
| `queueUrl` | `string` | Yes | URL of the SQS queue |
|
|
154
|
-
|
|
155
|
-
#### `sendMessage({ body, queueUrl })`
|
|
156
|
-
|
|
157
|
-
Send a single message to an SQS queue.
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
import { sendMessage } from '@jaypie/aws';
|
|
161
|
-
|
|
162
|
-
const body = "Hello, world!";
|
|
163
|
-
const queueUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue";
|
|
164
|
-
|
|
165
|
-
const response = await sendMessage({ body, queueUrl });
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
| Parameter | Type | Required | Description |
|
|
169
|
-
| --------- | ---- | -------- | ----------- |
|
|
170
|
-
| `body` | `string` | Yes | Message body |
|
|
171
|
-
| `delaySeconds` | `number` | No | Seconds to delay message delivery; default `0` |
|
|
172
|
-
| `messageAttributes` | `object` | No | Message attributes |
|
|
173
|
-
| `messageGroupId` | `string` | No | Custom message group for FIFO queues; default provided |
|
|
174
|
-
| `queueUrl` | `string` | Yes | URL of the SQS queue |
|
|
175
|
-
|
|
176
|
-
### Constants
|
|
177
|
-
|
|
178
|
-
```javascript
|
|
179
|
-
import {
|
|
180
|
-
CDK,
|
|
181
|
-
DATADOG,
|
|
182
|
-
ERROR,
|
|
183
|
-
EXPRESS,
|
|
184
|
-
HTTP,
|
|
185
|
-
VALIDATE,
|
|
186
|
-
} from "jaypie";
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
#### `CDK`
|
|
190
|
-
|
|
191
|
-
* `CDK.ACCOUNT`
|
|
192
|
-
* `CDK.ENV`
|
|
193
|
-
* `CDK.ROLE`
|
|
194
|
-
* `CDK.SERVICE`
|
|
195
|
-
* `CDK.TAG`
|
|
196
|
-
|
|
197
|
-
See [constants.js in @jaypie/core](https://github.com/finlaysonstudio/jaypie-core/blob/main/src/core/constants.js).
|
|
198
|
-
|
|
199
|
-
#### `DATADOG`
|
|
200
|
-
|
|
201
|
-
* `DATADOG.METRIC.TYPE.UNKNOWN`
|
|
202
|
-
* `DATADOG.METRIC.TYPE.COUNT`
|
|
203
|
-
* `DATADOG.METRIC.TYPE.RATE`
|
|
204
|
-
* `DATADOG.METRIC.TYPE.GAUGE`
|
|
205
|
-
|
|
206
|
-
#### `ERROR`
|
|
207
|
-
|
|
208
|
-
Default messages and titles for Jaypie errors.
|
|
209
|
-
|
|
210
|
-
* `ERROR.MESSAGE`
|
|
211
|
-
* `ERROR.TITLE`
|
|
212
|
-
|
|
213
|
-
See `HTTP` for status codes.
|
|
214
|
-
|
|
215
|
-
#### `EXPRESS`
|
|
216
|
-
|
|
217
|
-
* `EXPRESS.PATH.ANY` - String `*` for any path
|
|
218
|
-
* `EXPRESS.PATH.ID` - String `/:id` for an ID path
|
|
219
|
-
* `EXPRESS.PATH.ROOT` - Regular expression for root path
|
|
220
|
-
|
|
221
|
-
#### `HTTP`
|
|
222
|
-
|
|
223
|
-
* `HTTP.ALLOW.ANY`
|
|
224
|
-
* `HTTP.CODE`: `OK`, `CREATED`, ...
|
|
225
|
-
* `HTTP.CONTENT.ANY`
|
|
226
|
-
* `HTTP.CONTENT.HTML`
|
|
227
|
-
* `HTTP.CONTENT.JSON`
|
|
228
|
-
* `HTTP.CONTENT.TEXT`
|
|
229
|
-
* `HTTP.HEADER`: ...
|
|
230
|
-
* `HTTP.METHOD`: `GET`, `POST`, ...
|
|
231
|
-
|
|
232
|
-
#### `VALIDATE`
|
|
233
|
-
|
|
234
|
-
* `VALIDATE.ANY` - Default
|
|
235
|
-
* `VALIDATE.ARRAY`
|
|
236
|
-
* `VALIDATE.CLASS`
|
|
237
|
-
* `VALIDATE.FUNCTION`
|
|
238
|
-
* `VALIDATE.NUMBER`
|
|
239
|
-
* `VALIDATE.NULL`
|
|
240
|
-
* `VALIDATE.OBJECT`
|
|
241
|
-
* `VALIDATE.STRING`
|
|
242
|
-
* `VALIDATE.UNDEFINED`
|
|
243
|
-
|
|
244
|
-
#### Internal Constants
|
|
245
|
-
|
|
246
|
-
* `JAYPIE` - for consistency across Jaypie
|
|
247
|
-
* `PROJECT` - for consistency across projects
|
|
248
|
-
|
|
249
|
-
### Datadog
|
|
250
|
-
|
|
251
|
-
```javascript
|
|
252
|
-
const {
|
|
253
|
-
submitMetric,
|
|
254
|
-
submitMetricSet
|
|
255
|
-
} = require("jaypie");
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
#### `submitMetric({ name, tags, type, value })`
|
|
259
|
-
|
|
260
|
-
```javascript
|
|
261
|
-
import { submitMetric } from "jaypie";
|
|
262
|
-
|
|
263
|
-
await submitMetric({
|
|
264
|
-
name: "jaypie.metric",
|
|
265
|
-
type: DATADOG.METRIC.TYPE.COUNT,
|
|
266
|
-
value: 1,
|
|
267
|
-
});
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
| Parameter | Type | Required | Description |
|
|
271
|
-
| --------- | ---- | -------- | ----------- |
|
|
272
|
-
| apiKey | `string` | No | Datadog API key; checks `process.env.DATADOG_API_KEY` |
|
|
273
|
-
| apiSecret | `string` | No | AWS Secret name holding Datadog API key; checks `process.env.SECRET_DATADOG_API_KEY`. Preferred method of retrieving key |
|
|
274
|
-
| name | `string` | Yes | Name of the metric |
|
|
275
|
-
| type | `string` | No | Defaults to `DATADOG.METRIC.TYPE.UNKNOWN` |
|
|
276
|
-
| value | `number` | Yes | Value of the metric |
|
|
277
|
-
| tags | `array`, `object` | No | Tags for the metric. Accepts arrays `["key:value"]` or objects `{"key":"value"}` |
|
|
278
|
-
| timestamp | `number` | No | Unix timestamp; defaults to `Date.now()` |
|
|
279
|
-
|
|
280
|
-
#### `submitMetricSet({ tags, type, valueSet })`
|
|
281
|
-
|
|
282
|
-
```javascript
|
|
283
|
-
import { submitMetricSet } from "jaypie";
|
|
284
|
-
|
|
285
|
-
await submitMetricSet({
|
|
286
|
-
type: DATADOG.METRIC.TYPE.GAUGE,
|
|
287
|
-
valueSet: {
|
|
288
|
-
"jaypie.metric.a": 1,
|
|
289
|
-
"jaypie.metric.b": 2,
|
|
290
|
-
"jaypie.metric.c": 3,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
| Parameter | Type | Required | Description |
|
|
296
|
-
| --------- | ---- | -------- | ----------- |
|
|
297
|
-
| apiKey | `string` | No | Datadog API key; checks `process.env.DATADOG_API_KEY` |
|
|
298
|
-
| apiSecret | `string` | No | AWS Secret name holding Datadog API key; checks `process.env.SECRET_DATADOG_API_KEY`. Preferred method of retrieving key |
|
|
299
|
-
| type | `string` | No | Defaults to `DATADOG.METRIC.TYPE.UNKNOWN` |
|
|
300
|
-
| valueSet | `object` | Yes | Key-value pairs where the key is the metric name and the value is the metric value (number) |
|
|
301
|
-
| tags | `array`, `object` | No | Tags for the metric. Accepts arrays `["key:value"]` or objects `{"key":"value"}` |
|
|
302
|
-
| timestamp | `number` | No | Unix timestamp; defaults to `Date.now()` |
|
|
303
|
-
|
|
304
|
-
### Errors
|
|
305
|
-
|
|
306
|
-
#### Throwing/Catching Errors
|
|
307
|
-
|
|
308
|
-
``` javascript
|
|
309
|
-
// See `Error Reference` for full list
|
|
310
|
-
const { InternalError } = require("jaypie");
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
// Code happens...
|
|
314
|
-
throw InternalError("Oh, I am slain!");
|
|
315
|
-
} catch (error) {
|
|
316
|
-
// Is this from a jaypie project?
|
|
317
|
-
if(error.isProjectError) {
|
|
318
|
-
{
|
|
319
|
-
name, // ProjectError
|
|
320
|
-
title, // "Internal Server Error"
|
|
321
|
-
detail, // "Oh, I am slain"
|
|
322
|
-
status, // 500 (HTTP code)
|
|
323
|
-
} = error;
|
|
324
|
-
} else {
|
|
325
|
-
// Not from jaypie
|
|
326
|
-
throw error;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
#### Format Error
|
|
332
|
-
|
|
333
|
-
``` javascript
|
|
334
|
-
if(error.isProjectError) {
|
|
335
|
-
return error.json();
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
#### Multi-Error Usage
|
|
340
|
-
|
|
341
|
-
``` javascript
|
|
342
|
-
const errors = [];
|
|
343
|
-
errors.push(BadGatewayError());
|
|
344
|
-
errors.push(NotFoundError());
|
|
345
|
-
throw MultiError(errors);
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
#### Error Reference
|
|
349
|
-
|
|
350
|
-
| Error | Status | Notes |
|
|
351
|
-
| ----------------------- | ------ | ----- |
|
|
352
|
-
| `BadGatewayError` | 502 | Something I need gave me an error |
|
|
353
|
-
| `BadRequestError` | 400 | You did something wrong |
|
|
354
|
-
| `ConfigurationError` | 500 | "The developer" (probably you) or an associate did something wrong |
|
|
355
|
-
| `ForbiddenError` | 403 | You are not allowed |
|
|
356
|
-
| `GatewayTimeoutError` | 504 | Something I need is taking too long |
|
|
357
|
-
| `GoneError` | 410 | The thing you are looking for was here but is now gone forever |
|
|
358
|
-
| `IllogicalError` | 500 | Code is in a state that "should never happen" |
|
|
359
|
-
| `InternalError` | 500 | General "something went wrong" |
|
|
360
|
-
| `MethodNotAllowedError` | 405 | You tried a good path but the wrong method |
|
|
361
|
-
| `MultiError` | Varies | Takes an array of errors |
|
|
362
|
-
| `NotFoundError` | 404 | The thing you are looking for is not here and maybe never was |
|
|
363
|
-
| `NotImplementedError` | 400 | "The developer" (you again?) didn't finish this part yet - hopefully a temporary message |
|
|
364
|
-
| `RejectedError` | 403 | Request filtered prior to processing |
|
|
365
|
-
| `TeapotError` | 418 | RFC 2324 section-2.3.2 |
|
|
366
|
-
| `UnauthorizedError` | 401 | You need to log in |
|
|
367
|
-
| `UnavailableError` | 503 | The thing you are looking for cannot come to the phone right now |
|
|
368
|
-
| `UnhandledError` | 500 | An error that should have been handled wasn't |
|
|
369
|
-
| `UnreachableCodeError` | 500 | Should not be possible |
|
|
370
|
-
|
|
371
|
-
##### Special Errors
|
|
372
|
-
|
|
373
|
-
ALWAYS internal to the app, NEVER something the client did
|
|
374
|
-
|
|
375
|
-
* Configuration
|
|
376
|
-
* "The person writing the code did something wrong" like forgot to pass or passed bad arguments
|
|
377
|
-
* "The person who configured the application made a mistake" like set mutually exclusive settings to true
|
|
378
|
-
* Illogical
|
|
379
|
-
* A combination of truth conditions occurred that should not be able to occur at the same time
|
|
380
|
-
* Not Implemented
|
|
381
|
-
* A marker to come back and finish this, but allows stubbing out HTTP endpoints
|
|
382
|
-
* Unhandled
|
|
383
|
-
* Internal to Jaypie, should not be thrown directly
|
|
384
|
-
* Jaypie expects code in handlers to handler errors and re-throw a Jaypie error
|
|
385
|
-
* If an unexpected error escapes the handler, Jaypie returns this when it is caught
|
|
386
|
-
* Unreachable
|
|
387
|
-
* In theory the block is literally not reachable and we want to put something there to make sure it stays that way
|
|
388
|
-
* For example, a complicated chain of `if`/`else` that should always return and cover all cases, may throw this as the last `else`
|
|
389
|
-
* A configuration error means what happened was possible but should not have happened, an unreachable error means it should not have been possible
|
|
390
|
-
|
|
391
|
-
### Express
|
|
392
|
-
|
|
393
|
-
The Express handler wraps the Jaypie handler for Express running on AWS Lambda. It will call lifecycle methods and provide logging. Unhandled errors will be thrown as `UnhandledError`. It adds the `locals` lifecycle call. It extends `jaypieHandler`, not the lambda handler.
|
|
394
|
-
|
|
395
|
-
```javascript
|
|
396
|
-
const { expressHandler } = require("jaypie");
|
|
397
|
-
|
|
398
|
-
const handler = expressHandler(async(req, res) => {
|
|
399
|
-
// await new Promise(r => setTimeout(r, 2000));
|
|
400
|
-
// log.debug("Hello World");
|
|
401
|
-
return { message: "Hello World" };
|
|
402
|
-
}, { name: "lambdaReference"});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
The order of options and handler may be swapped to improve readability. Having lifecycle methods listed before the handler may read more intuitively.
|
|
406
|
-
|
|
407
|
-
```javascript
|
|
408
|
-
const handler = expressHandler({
|
|
409
|
-
name: "expressOptions",
|
|
410
|
-
setup: [connect],
|
|
411
|
-
teardown: [disconnect],
|
|
412
|
-
}, async(req, res) => {
|
|
413
|
-
// await new Promise(r => setTimeout(r, 2000));
|
|
414
|
-
// log.debug("Hello World");
|
|
415
|
-
return { message: "Hello World" };
|
|
416
|
-
});
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
#### Return Types
|
|
420
|
-
|
|
421
|
-
Do not use `res.send` or `res.json` in the handler. The return value of the handler is the response body. The status code is determined by the error thrown or the return value. Custom status codes can be set by calling `res.status` in the handler.
|
|
422
|
-
|
|
423
|
-
| Return Type | Status Code | Content-Type |
|
|
424
|
-
| ----------- | ----------- | ------------ |
|
|
425
|
-
| Object, Array | 200 OK | `application/json` |
|
|
426
|
-
| String | 200 OK | `text/html` |
|
|
427
|
-
| `true` | 201 Created | None |
|
|
428
|
-
| Falsy values | 204 No Content | None |
|
|
429
|
-
|
|
430
|
-
Errors can be returned by throwing the appropriate Jaypie error.
|
|
431
|
-
|
|
432
|
-
#### Lifecycle Methods
|
|
433
|
-
|
|
434
|
-
In addition to the Jaypie lifecycle methods, `expressHandler` adds `locals`, an object of scalars or functions that will be called at the end of `setup` and available to the handler as `req.locals`.
|
|
435
|
-
|
|
436
|
-
```javascript
|
|
437
|
-
const handler = expressHandler(async(req) => {
|
|
438
|
-
// req.locals.asyncFn = "async"
|
|
439
|
-
// req.locals.fn = "function"
|
|
440
|
-
// req.locals.key = "static"
|
|
441
|
-
return { message: "Hello World" };
|
|
442
|
-
}, {
|
|
443
|
-
name: "expressReference",
|
|
444
|
-
locals: {
|
|
445
|
-
asyncFn: async() => "async",
|
|
446
|
-
fn: () => "function",
|
|
447
|
-
key: "static"
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
#### Convenience Routes
|
|
453
|
-
|
|
454
|
-
_A "handler" returns a function that can be used as an Express route. A "route" does not require instantiation._
|
|
455
|
-
|
|
456
|
-
```javascript
|
|
457
|
-
import {
|
|
458
|
-
echoRoute,
|
|
459
|
-
EXPRESS,
|
|
460
|
-
forbiddenRoute,
|
|
461
|
-
goneRoute,
|
|
462
|
-
noContentRoute,
|
|
463
|
-
notFoundRoute,
|
|
464
|
-
notImplementedRoute,
|
|
465
|
-
} from "jaypie";
|
|
466
|
-
|
|
467
|
-
app.get(EXPRESS.PATH.ROOT, noContentRoute); // 204 No Content
|
|
468
|
-
app.get(EXPRESS.PATH.ANY, echoRoute); // 200 OK returning the request
|
|
469
|
-
app.post(EXPRESS.PATH.ANY, forbiddenRoute); // 403 Forbidden
|
|
470
|
-
app.any("/future", notImplementedRoute); // 400 Bad Request
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
`notImplementedRoute` returns "400 Bad Request" as a placeholder for future functionality. In this regard, calling it is a "user error." The "501 Not Implemented" status code is reserved for the server not supporting parts of the HTTP protocol such as `POST` or `PUT`.
|
|
474
|
-
|
|
475
|
-
### Functions
|
|
476
|
-
|
|
477
|
-
#### `cloneDeep`
|
|
478
|
-
|
|
479
|
-
`lodash.clonedeep` from [NPM](https://www.npmjs.com/package/lodash.clonedeep)
|
|
480
|
-
|
|
481
|
-
```javascript
|
|
482
|
-
import { cloneDeep } from "jaypie";
|
|
483
|
-
|
|
484
|
-
const original = { a: 1, b: { c: 2 }};
|
|
485
|
-
const clone = cloneDeep(original);
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
#### `envBoolean`
|
|
489
|
-
|
|
490
|
-
Look up a key in `process.env` and coerce it into a boolean.
|
|
491
|
-
Returns `true` for `true` (case-insensitive) and `1` for string, boolean, and numeric types.
|
|
492
|
-
Returns `false` for `false` (case-insensitive) and `0` for string, boolean, and numeric types.
|
|
493
|
-
Returns `undefined` otherwise.
|
|
494
|
-
|
|
495
|
-
``` javascript
|
|
496
|
-
const { envBoolean } = require("jaypie");
|
|
497
|
-
|
|
498
|
-
process.env.AWESOME = true;
|
|
499
|
-
|
|
500
|
-
if (envBoolean("AWESOME")) {
|
|
501
|
-
console.log("Awesome!");
|
|
502
|
-
}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
##### `envBoolean`: `defaultValue`
|
|
506
|
-
|
|
507
|
-
``` javascript
|
|
508
|
-
const { envBoolean } = require("jaypie");
|
|
509
|
-
|
|
510
|
-
if (envBoolean("AWESOME", { defaultValue: true })) {
|
|
511
|
-
console.log("Awesome!");
|
|
512
|
-
}
|
|
513
|
-
```
|
|
514
|
-
|
|
515
|
-
#### `envsKey`
|
|
516
|
-
|
|
517
|
-
`envsKey(key:string, { env:string = process.env.PROJECT_ENV || process.env.DEFAULT_ENV })`
|
|
518
|
-
|
|
519
|
-
return `process.env.${KEY} || ENV_${ENV}_${KEY} || false`
|
|
520
|
-
|
|
521
|
-
```bash
|
|
522
|
-
DEFAULT_ENV=sandbox
|
|
523
|
-
|
|
524
|
-
MONGODB_URI=...
|
|
525
|
-
ENV_SANDBOX_MONGODB_URI=...
|
|
526
|
-
ENV_DEVELOPMENT_MONGODB_URI=...
|
|
527
|
-
|
|
528
|
-
PROJECT_ENV=development
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
Return order:
|
|
532
|
-
1. `MONGODB_URI` - the exact key always takes precedence, if set
|
|
533
|
-
2. `ENV_DEVELOPMENT_MONGODB_URI` - the `PROJECT_ENV`, if set. Usually this is set in the deploy workflow
|
|
534
|
-
3. `ENV_SANDBOX_MONGODB_URI` - the `DEFAULT_ENV`, if set. Usually this is set in the local environment
|
|
535
|
-
4. `false`
|
|
536
|
-
|
|
537
|
-
```javascript
|
|
538
|
-
import { envsKey } from "jaypie";
|
|
539
|
-
|
|
540
|
-
const url = envsKey("MONGODB_URI");
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
#### `force`
|
|
544
|
-
|
|
545
|
-
Coerce a value into a type or throw an error.
|
|
546
|
-
Forcing arrays is the primary use case.
|
|
547
|
-
|
|
548
|
-
```javascript
|
|
549
|
-
import { force } from "jaypie";
|
|
550
|
-
|
|
551
|
-
argument = force(thing, Array);
|
|
552
|
-
argument = force([thing], Array);
|
|
553
|
-
// argument = [thing]
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
`force` supports Array, Boolean, Number, Object, and String.
|
|
557
|
-
|
|
558
|
-
```javascript
|
|
559
|
-
argument = force(argument, Array);
|
|
560
|
-
argument = force(argument, Boolean, "true");
|
|
561
|
-
argument = force(argument, Number, "12");
|
|
562
|
-
argument = force(argument, Object, "key");
|
|
563
|
-
argument = force(argument, String, "default");
|
|
564
|
-
|
|
565
|
-
// Convenience functions
|
|
566
|
-
argument = force.array(argument);
|
|
567
|
-
argument = force.boolean(argument);
|
|
568
|
-
argument = force.number(argument);
|
|
569
|
-
argument = force.object(argument, "key");
|
|
570
|
-
argument = force.string(argument);
|
|
571
|
-
```
|
|
572
|
-
|
|
573
|
-
#### `getHeaderFrom`
|
|
574
|
-
|
|
575
|
-
`getHeaderFrom(headerKey:string, searchObject:object)`
|
|
576
|
-
|
|
577
|
-
Case-insensitive search inside `searchObject` for `headerKey`. Also looks in `header` and `headers` child object of `searchObject`, if `headerKey` not found at top-level.
|
|
578
|
-
|
|
579
|
-
#### `getObjectKeyCaseInsensitive(object:object, key:string)`
|
|
580
|
-
|
|
581
|
-
Case-insensitive search for `key` in `object`. Returns the value of the key or `undefined`.
|
|
582
|
-
|
|
583
|
-
#### `placeholders`
|
|
584
|
-
|
|
585
|
-
Lightweight string interpolation
|
|
586
|
-
|
|
587
|
-
```javascript
|
|
588
|
-
import { placeholders } from "jaypie";
|
|
589
|
-
|
|
590
|
-
const string = placeholders("Hello, {name}!", { name: "World" });
|
|
591
|
-
// string = "Hello, World!"
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
The code for placeholders was written by Chris Ferdinandi and distributed under the MIT License in 2018-2019. Their web site is https://gomakethings.com
|
|
595
|
-
|
|
596
|
-
#### `safeParseFloat`
|
|
597
|
-
|
|
598
|
-
`parseFloat` that returns `0` for `NaN`
|
|
599
|
-
|
|
600
|
-
#### `sleep`
|
|
601
|
-
|
|
602
|
-
`sleep` is a promise-based `setTimeout` that resolves after a specified number of milliseconds. It will NOT run when `NODE_ENV` is `test`. See `sleepAlways` for a version that will run in tests.
|
|
603
|
-
|
|
604
|
-
```javascript
|
|
605
|
-
import { sleep } from "jaypie";
|
|
606
|
-
|
|
607
|
-
await sleep(2000);
|
|
608
|
-
```
|
|
609
|
-
|
|
610
|
-
_This is "bad code" because it checks `NODE_ENV` during runtime. The "right way" is to let sleep run and mock it in tests, in practice this is needless boilerplate. A fair compromise would be to mock `sleep` with `@jaypie/testkit` but not all projects include that dependency. Jaypie will trade academically incorrect for human convenience and simplicity._
|
|
611
|
-
|
|
612
|
-
#### `uuid`
|
|
613
|
-
|
|
614
|
-
The `v4` function from the `uuid` package
|
|
615
|
-
|
|
616
|
-
```javascript
|
|
617
|
-
import { uuid } from "jaypie";
|
|
618
|
-
|
|
619
|
-
const id = uuid();
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
#### `validate`
|
|
623
|
-
|
|
624
|
-
```javascript
|
|
625
|
-
import { validate, VALIDATE } from "jaypie";
|
|
626
|
-
|
|
627
|
-
validate(argument, {
|
|
628
|
-
type: VALIDATE.ANY,
|
|
629
|
-
falsy: false, // When `true`, allows "falsy" values that match the type (e.g., `0`, `""`)
|
|
630
|
-
required: true, // When `false`, allows `undefined` as a valid value
|
|
631
|
-
throws: true // When `false`, returns `false` instead of throwing error
|
|
632
|
-
});
|
|
633
|
-
```
|
|
634
|
-
|
|
635
|
-
##### Validate Convenience Functions
|
|
636
|
-
|
|
637
|
-
``` javascript
|
|
638
|
-
import { validate } from "jaypie";
|
|
639
|
-
|
|
640
|
-
validate.array(argument);
|
|
641
|
-
validate.class(argument);
|
|
642
|
-
validate.function(argument);
|
|
643
|
-
validate.null(argument);
|
|
644
|
-
validate.number(argument);
|
|
645
|
-
validate.object(argument);
|
|
646
|
-
validate.string(argument);
|
|
647
|
-
validate.undefined(argument);
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
##### Intuitive Validate Types
|
|
651
|
-
|
|
652
|
-
_Does not include any, class, or undefined_
|
|
653
|
-
|
|
654
|
-
``` javascript
|
|
655
|
-
validate(argument, {
|
|
656
|
-
// One of:
|
|
657
|
-
type: Array,
|
|
658
|
-
type: Function,
|
|
659
|
-
type: Number,
|
|
660
|
-
type: null,
|
|
661
|
-
type: Object,
|
|
662
|
-
type: String,
|
|
663
|
-
})
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
### Jaypie Handler
|
|
667
|
-
|
|
668
|
-
The Jaypie handler can be used directly but is more likely to be wrapped in a more specific handler. The Jaypie handler will call lifecycle methods and provide logging. Unhandled errors will be thrown as `UnhandledError`.
|
|
669
|
-
|
|
670
|
-
```javascript
|
|
671
|
-
import { jaypieHandler } from "jaypie";
|
|
672
|
-
|
|
673
|
-
const handler = jaypieHandler(async(...args) => {
|
|
674
|
-
// await new Promise(r => setTimeout(r, 2000));
|
|
675
|
-
// log.var({ args });
|
|
676
|
-
return "Hello World";
|
|
677
|
-
}, { name: "jaypieReference"});
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
#### Jaypie Lifecycle Methods
|
|
681
|
-
|
|
682
|
-
Each function receives the same arguments as the handler.
|
|
683
|
-
|
|
684
|
-
##### `validate: [async Function]`
|
|
685
|
-
|
|
686
|
-
Returns `true` to validate the request. Throw an error or return `false` to reject the request.
|
|
687
|
-
|
|
688
|
-
##### `setup: [async Function]`
|
|
689
|
-
|
|
690
|
-
Called before the handler (e.g., connect to a database). Throw an error to halt execution.
|
|
691
|
-
|
|
692
|
-
##### `handler: async Function`
|
|
693
|
-
|
|
694
|
-
The main function to handle the request. Throw an error to halt execution.
|
|
695
|
-
|
|
696
|
-
##### `teardown: [async Function]`
|
|
697
|
-
|
|
698
|
-
Called after the handler (e.g., disconnect from a database). Runs even if setup or handler throws errors.
|
|
699
|
-
|
|
700
|
-
### Lambda Handler
|
|
701
|
-
|
|
702
|
-
The Lambda handler wraps the Jaypie handler for AWS Lambda. It will call lifecycle methods and provide logging. Unhandled errors will be thrown as `UnhandledError`.
|
|
703
|
-
|
|
704
|
-
```javascript
|
|
705
|
-
const { lambdaHandler } = require("jaypie");
|
|
706
|
-
|
|
707
|
-
const handler = lambdaHandler(async({event}) => {
|
|
708
|
-
// await new Promise(r => setTimeout(r, 2000));
|
|
709
|
-
// log.debug("Hello World");
|
|
710
|
-
return "Hello World";
|
|
711
|
-
}, { name: "lambdaReference"});
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
### Logging
|
|
715
|
-
|
|
716
|
-
```javascript
|
|
717
|
-
import {
|
|
718
|
-
log,
|
|
719
|
-
} from "jaypie";
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
#### log
|
|
723
|
-
|
|
724
|
-
```javascript
|
|
725
|
-
import { log } from "jaypie";
|
|
726
|
-
|
|
727
|
-
log.trace();
|
|
728
|
-
log.debug();
|
|
729
|
-
log.info();
|
|
730
|
-
log.warn();
|
|
731
|
-
log.error();
|
|
732
|
-
log.fatal();
|
|
733
|
-
```
|
|
734
|
-
|
|
735
|
-
##### log.lib({ lib: "myLib" })
|
|
736
|
-
|
|
737
|
-
Uses `silent` by default. if `process.env.MODULE_LOG_LEVEL` is `true`, follows `process.env.LOG_LEVEL`. If `process.env.MODULE_LOG_LEVEL` is also set, uses that log level.
|
|
738
|
-
|
|
739
|
-
```javascript
|
|
740
|
-
import { log } from "jaypie";
|
|
741
|
-
|
|
742
|
-
log.lib().trace();
|
|
743
|
-
log.lib({ lib: "myLib" }).trace();
|
|
744
|
-
```
|
|
745
|
-
|
|
746
|
-
##### log.tag(key, value) or log.tag({ key: value })
|
|
747
|
-
|
|
748
|
-
Permanently add the key-value pair to the logger's tags, or at least until `log.untag(key)` is called.
|
|
749
|
-
|
|
750
|
-
```javascript
|
|
751
|
-
import { log } from "jaypie";
|
|
752
|
-
|
|
753
|
-
log.tag("myTag", "myValue");
|
|
754
|
-
log.tag({ myTag: "myValue" });
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
##### log.untag(key) or log.untag([key1, key2, ...])
|
|
758
|
-
|
|
759
|
-
Remove the key-value pair from the logger's tags.
|
|
760
|
-
|
|
761
|
-
```javascript
|
|
762
|
-
import { log } from "jaypie";
|
|
763
|
-
|
|
764
|
-
log.untag("myTag");
|
|
765
|
-
log.untag(["myTag1", "myTag2"]);
|
|
766
|
-
```
|
|
767
|
-
|
|
768
|
-
##### log.var(key, value) or log.var({ key: value })
|
|
769
|
-
|
|
770
|
-
Log a key-value pair. In the `json` format, the key will be tagged as `var` and the value will be the value. Logging marker variables this way can be useful for debugging.
|
|
771
|
-
|
|
772
|
-
```javascript
|
|
773
|
-
import { log } from "jaypie";
|
|
774
|
-
|
|
775
|
-
log.var("message", "Hello, world");
|
|
776
|
-
log.var({ message: "Hello, world" });
|
|
777
|
-
|
|
778
|
-
const message = "Hello, world";
|
|
779
|
-
log.var({ message });
|
|
780
|
-
```
|
|
781
|
-
|
|
782
|
-
##### log.with() - clone
|
|
783
|
-
|
|
784
|
-
Create a new log object with additional tags
|
|
785
|
-
|
|
786
|
-
```javascript
|
|
787
|
-
import { log as defaultLogger } from "jaypie";
|
|
788
|
-
|
|
789
|
-
const log = defaultLogger.with({ customProperty: "customValue" });
|
|
790
|
-
```
|
|
791
|
-
|
|
792
|
-
### Mongoose
|
|
793
|
-
|
|
794
|
-
```javascript
|
|
795
|
-
import {
|
|
796
|
-
connect,
|
|
797
|
-
connectFromSecretEnv,
|
|
798
|
-
disconnect,
|
|
799
|
-
mongoose,
|
|
800
|
-
} from "jaypie";
|
|
801
|
-
```
|
|
802
|
-
|
|
803
|
-
#### `connect`
|
|
804
|
-
|
|
805
|
-
Jaypie lifecycle method to connect to MongoDB. Uses `process.env.SECRET_MONGODB_URI` AWS Secret or `process.env.MONGODB_URI` string to connect.
|
|
806
|
-
|
|
807
|
-
```javascript
|
|
808
|
-
import { connect, disconnect, lambdaHandler, mongoose } from "jaypie";
|
|
809
|
-
|
|
810
|
-
const handler = lambdaHandler(async({event}) => {
|
|
811
|
-
// mongoose is already connected
|
|
812
|
-
return "Hello World";
|
|
813
|
-
}, {
|
|
814
|
-
name: "lambdaReference"
|
|
815
|
-
setup: [connect],
|
|
816
|
-
teardown: [disconnect],
|
|
817
|
-
});
|
|
818
|
-
```
|
|
819
|
-
|
|
820
|
-
#### `connectFromSecretEnv`
|
|
821
|
-
|
|
822
|
-
Jaypie lifecycle method to connect to MongoDB using `process.env.MONGO_CONNECTION_STRING`. Using the newer `connect` is generally preferred.
|
|
823
|
-
|
|
824
|
-
```javascript
|
|
825
|
-
import { connectFromSecretEnv, disconnect, lambdaHandler, mongoose } from "jaypie";
|
|
826
|
-
|
|
827
|
-
const handler = lambdaHandler(async({event}) => {
|
|
828
|
-
// mongoose is already connected
|
|
829
|
-
return "Hello World";
|
|
830
|
-
}, {
|
|
831
|
-
name: "lambdaReference"
|
|
832
|
-
setup: [connectFromSecretEnv],
|
|
833
|
-
teardown: [disconnect],
|
|
834
|
-
});
|
|
835
|
-
```
|
|
836
|
-
|
|
837
|
-
#### `disconnect`
|
|
838
|
-
|
|
839
|
-
Jaypie lifecycle method to disconnect from MongoDB.
|
|
840
|
-
|
|
841
|
-
```javascript
|
|
842
|
-
import { disconnect, lambdaHandler } from "jaypie";
|
|
843
|
-
|
|
844
|
-
const handler = lambdaHandler(async({event}) => {
|
|
845
|
-
// ...
|
|
846
|
-
}, {
|
|
847
|
-
teardown: [disconnect],
|
|
848
|
-
});
|
|
849
|
-
```
|
|
850
|
-
|
|
851
|
-
#### `mongoose`
|
|
852
|
-
|
|
853
|
-
`mongoose` from [NPM](https://www.npmjs.com/package/mongoose)
|
|
854
|
-
|
|
855
|
-
```javascript
|
|
856
|
-
import { mongoose } from "jaypie";
|
|
857
|
-
```
|
|
858
|
-
|
|
859
|
-
### TestKit
|
|
860
|
-
|
|
861
|
-
```bash
|
|
862
|
-
npm install --save-dev @jaypie/testkit
|
|
863
|
-
```
|
|
864
|
-
|
|
865
|
-
#### Log Spying
|
|
866
|
-
|
|
867
|
-
```javascript
|
|
868
|
-
import { restoreLog, spyLog } from "@jaypie/testkit";
|
|
869
|
-
import { log } from "@jaypie/core";
|
|
870
|
-
|
|
871
|
-
beforeEach(() => {
|
|
872
|
-
spyLog(log);
|
|
873
|
-
});
|
|
874
|
-
afterEach(() => {
|
|
875
|
-
restoreLog(log);
|
|
876
|
-
vi.clearAllMocks();
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
test("log", () => {
|
|
880
|
-
log.warn("Danger");
|
|
881
|
-
expect(log.warn).toHaveBeenCalled();
|
|
882
|
-
expect(log.error).not.toHaveBeenCalled();
|
|
883
|
-
});
|
|
884
|
-
```
|
|
885
|
-
|
|
886
|
-
👺 Logging Conventions:
|
|
887
|
-
|
|
888
|
-
* Only use `log.trace` or `log.var` during "happy path"
|
|
889
|
-
* Use `log.debug` for edge cases
|
|
890
|
-
* Now you can add an "observability" test that will fail as soon as new code triggers an unexpected edge condition
|
|
891
|
-
|
|
892
|
-
```javascript
|
|
893
|
-
describe("Observability", () => {
|
|
894
|
-
it("Does not log above trace", async () => {
|
|
895
|
-
// Arrange
|
|
896
|
-
// TODO: "happy path" setup
|
|
897
|
-
// Act
|
|
898
|
-
await myNewFunction(); // TODO: add any "happy path" parameters
|
|
899
|
-
// Assert
|
|
900
|
-
expect(log.debug).not.toHaveBeenCalled();
|
|
901
|
-
expect(log.info).not.toHaveBeenCalled();
|
|
902
|
-
expect(log.warn).not.toHaveBeenCalled();
|
|
903
|
-
expect(log.error).not.toHaveBeenCalled();
|
|
904
|
-
expect(log.fatal).not.toHaveBeenCalled();
|
|
905
|
-
});
|
|
906
|
-
});
|
|
907
|
-
```
|
|
908
|
-
|
|
909
|
-
> 👺 Follow the "arrange, act, assert" pattern
|
|
910
|
-
|
|
911
|
-
#### Test Matchers
|
|
912
|
-
|
|
913
|
-
testSetup.js
|
|
914
|
-
|
|
915
|
-
```javascript
|
|
916
|
-
import { matchers as jaypieMatchers } from "@jaypie/testkit";
|
|
917
|
-
import * as extendedMatchers from "jest-extended";
|
|
918
|
-
import { expect } from "vitest";
|
|
919
|
-
|
|
920
|
-
expect.extend(extendedMatchers);
|
|
921
|
-
expect.extend(jaypieMatchers);
|
|
922
|
-
```
|
|
923
|
-
|
|
924
|
-
test.spec.js
|
|
925
|
-
|
|
926
|
-
```javascript
|
|
927
|
-
import { ConfigurationError } from "@jaypie/core";
|
|
928
|
-
|
|
929
|
-
const error = new ConfigurationError();
|
|
930
|
-
const json = error.json();
|
|
931
|
-
expect(error).toBeJaypieError();
|
|
932
|
-
expect(json).toBeJaypieError();
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
###### `expect(subject).toBeJaypieError()`
|
|
936
|
-
|
|
937
|
-
Validates instance objects:
|
|
938
|
-
|
|
939
|
-
```javascript
|
|
940
|
-
try {
|
|
941
|
-
throw new Error("Sorpresa!");
|
|
942
|
-
} catch (error) {
|
|
943
|
-
expect(error).not.toBeJaypieError();
|
|
944
|
-
}
|
|
945
|
-
```
|
|
946
|
-
|
|
947
|
-
Validates plain old JSON:
|
|
948
|
-
|
|
949
|
-
```javascript
|
|
950
|
-
expect({ errors: [ { status, title, detail } ] }).toBeJaypieError();
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
Jaypie errors, which are `ProjectErrors`, all have a `.json()` to convert
|
|
954
|
-
|
|
955
|
-
###### `expect(subject).toBeValidSchema()`
|
|
956
|
-
|
|
957
|
-
```javascript
|
|
958
|
-
import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";
|
|
959
|
-
|
|
960
|
-
expect(jsonApiErrorSchema).toBeValidSchema();
|
|
961
|
-
expect(jsonApiSchema).toBeValidSchema();
|
|
962
|
-
expect({ project: "mayhem" }).not.toBeValidSchema();
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
From `jest-json-schema` [toBeValidSchema.js](https://github.com/americanexpress/jest-json-schema/blob/main/matchers/toBeValidSchema.js) (not documented in README)
|
|
966
|
-
|
|
967
|
-
###### `expect(subject).toMatchSchema(schema)`
|
|
968
|
-
|
|
969
|
-
```javascript
|
|
970
|
-
import { jsonApiErrorSchema, jsonApiSchema } from "@jaypie/testkit";
|
|
971
|
-
import { ConfigurationError } from "@jaypie/core";
|
|
972
|
-
|
|
973
|
-
const error = new ConfigurationError();
|
|
974
|
-
const json = error.json();
|
|
975
|
-
expect(json).toMatchSchema(jsonApiErrorSchema);
|
|
976
|
-
expect(json).not.toMatchSchema(jsonApiSchema);
|
|
977
|
-
```
|
|
978
|
-
|
|
979
|
-
From `jest-json-schema`; see [README](https://github.com/americanexpress/jest-json-schema?tab=readme-ov-file#tomatchschemaschema)
|
|
980
|
-
|
|
981
|
-
#### TestKit Sundry
|
|
982
|
-
|
|
983
|
-
```
|
|
984
|
-
import {
|
|
985
|
-
jsonApiErrorSchema,
|
|
986
|
-
jsonApiSchema,
|
|
987
|
-
mockLogFactory,
|
|
988
|
-
} from '@jaypie/testkit'
|
|
989
|
-
```
|
|
990
|
-
|
|
991
|
-
##### `jsonApiErrorSchema`
|
|
992
|
-
|
|
993
|
-
A [JSON Schema](https://json-schema.org/) validator for the [JSON:API](https://jsonapi.org/) error schema. Powers the `toBeJaypieError` matcher (via `toMatchSchema`).
|
|
994
|
-
|
|
995
|
-
##### `jsonApiSchema`
|
|
996
|
-
|
|
997
|
-
A [JSON Schema](https://json-schema.org/) validator for the [JSON:API](https://jsonapi.org/) data schema.
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
##### `mockLogFactory()`
|
|
1001
|
-
|
|
1002
|
-
Creates a mock of the `log` provided by `@jaypie/core`.
|
|
1003
|
-
|
|
1004
|
-
```javascript
|
|
1005
|
-
import { mockLogFactory } from "@jaypie/testkit";
|
|
1006
|
-
|
|
1007
|
-
const log = mockLogFactory();
|
|
1008
|
-
log.warn("Danger");
|
|
1009
|
-
expect(log.warn).toHaveBeenCalled();
|
|
1010
|
-
expect(log.error).not.toHaveBeenCalled();
|
|
1011
|
-
```
|
|
1012
|
-
|
|
1013
|
-
### `restoreLog(log)`
|
|
1014
|
-
|
|
1015
|
-
Restores the `log` provided by `@jaypie/core`, commonly performed `afterEach` with `spyLog` in `beforeEach`. See example with `spyLog`.
|
|
1016
|
-
|
|
1017
|
-
### `spyLog(log)`
|
|
1018
|
-
|
|
1019
|
-
Spies on the `log` provided by `@jaypie/core`, commonly performed `beforeEach` with `restoreLog` in `afterEach`.
|
|
1020
|
-
|
|
1021
|
-
```javascript
|
|
1022
|
-
import { restoreLog, spyLog } from "@jaypie/testkit";
|
|
1023
|
-
import { log } from "@jaypie/core";
|
|
1024
|
-
|
|
1025
|
-
beforeEach(() => {
|
|
1026
|
-
spyLog(log);
|
|
1027
|
-
});
|
|
1028
|
-
afterEach(() => {
|
|
1029
|
-
restoreLog(log);
|
|
1030
|
-
vi.clearAllMocks();
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
test("log", () => {
|
|
1034
|
-
log.warn("Danger");
|
|
1035
|
-
expect(log.warn).toHaveBeenCalled();
|
|
1036
|
-
expect(log.error).not.toHaveBeenCalled();
|
|
1037
|
-
});
|
|
1038
|
-
```
|
|
1039
|
-
|
|
1040
|
-
## 🌠 Wishlist
|
|
1041
|
-
|
|
1042
|
-
* Optional loading of Jaypie side packages
|
|
1043
|
-
* ...Nicely organized VitePress documentation 😅
|
|
5
|
+
See [Jaypie](https://github.com/finlaysonstudio/jaypie) for usage.
|
|
1044
6
|
|
|
1045
7
|
## 📝 Changelog
|
|
1046
8
|
|
|
1047
9
|
| Date | Version | Summary |
|
|
1048
10
|
| ---------- | ------- | -------------- |
|
|
11
|
+
| 10/16/2024 | 1.0.50 | Last 1.0.x release |
|
|
1049
12
|
| 5/4/2024 | 1.0.24 | Adds `@jaypie/datadog` |
|
|
1050
13
|
| 3/19/2024 | 1.0.0 | First publish with `@jaypie/core@1.0.0` |
|
|
1051
14
|
| 3/15/2024 | 0.1.0 | Initial deploy |
|
|
@@ -1053,4 +16,4 @@ test("log", () => {
|
|
|
1053
16
|
|
|
1054
17
|
## 📜 License
|
|
1055
18
|
|
|
1056
|
-
Published by Finlayson Studio
|
|
19
|
+
[MIT License](./LICENSE.txt). Published by Finlayson Studio
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaypie",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"author": "Finlayson Studio",
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"exports": {
|
|
7
8
|
".": {
|
|
@@ -17,46 +18,23 @@
|
|
|
17
18
|
"format": "npm run format:package && npm run format:lint",
|
|
18
19
|
"format:lint": "eslint --fix .",
|
|
19
20
|
"format:package": "sort-package-json ./package.json",
|
|
20
|
-
"init:deploy": "hygen jaypie workflow-npm",
|
|
21
21
|
"lint": "eslint .",
|
|
22
|
-
"new": "hygen jaypie vite",
|
|
23
22
|
"prepublish": "npm run build",
|
|
24
|
-
"test": "vitest",
|
|
23
|
+
"test": "vitest run .",
|
|
25
24
|
"test:spec:dynamicExport.function": "vitest run ./src/__tests__/dynamicExport.function.spec.js",
|
|
26
25
|
"test:spec:index": "vitest run ./src/__tests__/index.spec.js",
|
|
27
26
|
"test:spec:mongoose.package": "vitest run ./src/__tests__/mongoose.package.spec.js"
|
|
28
27
|
},
|
|
29
28
|
"dependencies": {
|
|
30
|
-
"@jaypie/aws": "^1.0
|
|
31
|
-
"@jaypie/core": "^1.0
|
|
32
|
-
"@jaypie/datadog": "^1.0
|
|
33
|
-
"@jaypie/express": "^1.0
|
|
34
|
-
"@jaypie/lambda": "^1.0
|
|
35
|
-
"@jaypie/mongoose": "^1.0
|
|
29
|
+
"@jaypie/aws": "^1.1.0",
|
|
30
|
+
"@jaypie/core": "^1.1.0",
|
|
31
|
+
"@jaypie/datadog": "^1.1.0",
|
|
32
|
+
"@jaypie/express": "^1.1.0",
|
|
33
|
+
"@jaypie/lambda": "^1.1.0",
|
|
34
|
+
"@jaypie/mongoose": "^1.1.0"
|
|
36
35
|
},
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@rollup/plugin-commonjs": "^26.0.1",
|
|
40
|
-
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
41
|
-
"eslint": "^9.7.0",
|
|
42
|
-
"eslint-config-jaypie": "^1.0.17",
|
|
43
|
-
"hygen": "^6.2.11",
|
|
44
|
-
"jest-extended": "^4.0.2",
|
|
45
|
-
"prettier": "^3.2.5",
|
|
46
|
-
"rollup": "^4.16.1",
|
|
47
|
-
"rollup-plugin-auto-external": "^2.0.0",
|
|
48
|
-
"sort-package-json": "^2.10.0",
|
|
49
|
-
"vitest": "^1.4.0"
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
50
38
|
},
|
|
51
|
-
"
|
|
52
|
-
"@jaypie/aws": {
|
|
53
|
-
"optional": true
|
|
54
|
-
},
|
|
55
|
-
"@jaypie/lambda": {
|
|
56
|
-
"optional": true
|
|
57
|
-
},
|
|
58
|
-
"@jaypie/mongoose": {
|
|
59
|
-
"optional": true
|
|
60
|
-
}
|
|
61
|
-
}
|
|
39
|
+
"gitHead": "901425686da79e25e9e4c982195bd42f043766e8"
|
|
62
40
|
}
|
package/eslint.config.mjs
DELETED