cpeak 2.6.0 → 2.8.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 +195 -67
- package/dist/index.d.ts +73 -42
- package/dist/index.js +503 -118
- package/dist/index.js.map +1 -1
- package/lib/index.ts +177 -123
- package/lib/internal/compression.ts +180 -0
- package/lib/internal/errors.ts +35 -0
- package/lib/internal/mimeTypes.ts +22 -0
- package/lib/internal/router.ts +259 -0
- package/lib/internal/types.ts +10 -0
- package/lib/types.ts +31 -20
- package/lib/utils/auth.ts +1 -23
- package/lib/utils/cookieParser.ts +1 -11
- package/lib/utils/cors.ts +109 -0
- package/lib/utils/index.ts +3 -4
- package/lib/utils/render.ts +18 -6
- package/lib/utils/serveStatic.ts +29 -28
- package/lib/utils/types.ts +51 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/cpeak)
|
|
4
4
|
|
|
5
|
-
Cpeak is a minimal and fast Node.js framework inspired by Express.js.
|
|
5
|
+
Cpeak is a minimal and fast Node.js framework inspired by Express.js & Fastify.
|
|
6
6
|
|
|
7
|
-
This project is designed to be improved until it's ready for use in complex production applications, aiming to be more performant and
|
|
7
|
+
This project is designed to be improved until it's ready for use in complex production applications, aiming to be more performant and have a cleaner structure than Express.js and other common frameworks.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Cpeak replaces Express.js, body-parser, cookie-parser, compression, CORS, Passport and more, in one zero-dependency package. You can use it as a drop-in replacement for Express.js, and many npm packages that work with Express.js will also work with Cpeak.
|
|
10
|
+
|
|
11
|
+
Cpeak currently has almost all the features of Express.js, with roughly the same performance as Fastify. Additionally, it has a codebase that you can easily navigate, audit and change if you want. *Benchmarks and test results, and videos on navigating and understanding the codebase will be added soon.*
|
|
12
|
+
|
|
13
|
+
While Cpeak is being built for production, it is also an educational project that was started as part of the [Understanding Node.js: Core Concepts](https://www.udemy.com/course/understanding-nodejs-core-concepts/?referralCode=0BC21AC4DD6958AE6A95) course. If you want to learn how to build a framework like this, and get to a point where you can build things like this yourself and beyond, check out this course!
|
|
10
14
|
|
|
11
15
|
## Why Cpeak?
|
|
12
16
|
|
|
@@ -25,9 +29,12 @@ This is an educational project that was started as part of the [Understanding No
|
|
|
25
29
|
- [Middleware](#middleware)
|
|
26
30
|
- [Route Handling](#route-handling)
|
|
27
31
|
- [Route Middleware](#route-middleware)
|
|
32
|
+
- [Fallback Handler](#fallback-handler)
|
|
28
33
|
- [URL Variables & Parameters](#url-variables--parameters)
|
|
29
34
|
- [Sending Files](#sending-files)
|
|
30
35
|
- [Redirecting](#redirecting)
|
|
36
|
+
- [MIME Types](#mime-types)
|
|
37
|
+
- [Compression](#compression)
|
|
31
38
|
- [Error Handling](#error-handling)
|
|
32
39
|
- [Listening](#listening)
|
|
33
40
|
- [Util Functions](#util-functions)
|
|
@@ -37,6 +44,7 @@ This is an educational project that was started as part of the [Understanding No
|
|
|
37
44
|
- [cookieParser](#cookieparser)
|
|
38
45
|
- [swagger](#swagger)
|
|
39
46
|
- [auth](#auth)
|
|
47
|
+
- [cors](#cors)
|
|
40
48
|
- [Complete Example](#complete-example)
|
|
41
49
|
- [Versioning Notice](#versioning-notice)
|
|
42
50
|
|
|
@@ -58,7 +66,7 @@ import cpeak from "cpeak";
|
|
|
58
66
|
const server = cpeak();
|
|
59
67
|
|
|
60
68
|
server.route("get", "/", (req, res) => {
|
|
61
|
-
res.json({ message: "Hi there!" });
|
|
69
|
+
return res.json({ message: "Hi there!" });
|
|
62
70
|
});
|
|
63
71
|
|
|
64
72
|
server.listen(3000, () => {
|
|
@@ -125,13 +133,13 @@ server.beforeEach((req, res, next) => {
|
|
|
125
133
|
You can also add middleware functions for a particular route handler like this:
|
|
126
134
|
|
|
127
135
|
```javascript
|
|
128
|
-
const requireAuth = (req, res, next
|
|
136
|
+
const requireAuth = (req, res, next) => {
|
|
129
137
|
// Check if user is logged in, if so then:
|
|
130
138
|
req.test = "this is a test value";
|
|
131
139
|
next();
|
|
132
140
|
|
|
133
141
|
// If user is not logged in:
|
|
134
|
-
|
|
142
|
+
throw { status: 401, message: "Unauthorized" };
|
|
135
143
|
};
|
|
136
144
|
|
|
137
145
|
server.route("get", "/profile", requireAuth, (req, res) => {
|
|
@@ -166,6 +174,20 @@ server.route("patch", "/the-path-you-want", (req, res) => {
|
|
|
166
174
|
|
|
167
175
|
First add the HTTP method name you want to handle, then the path, and finally, the callback. The `req` and `res` object types are the same as in the Node.js HTTP module (`http.IncomingMessage` and `http.ServerResponse`). You can read more about them in the [official Node.js documentation](https://nodejs.org/docs/latest/api/http.html).
|
|
168
176
|
|
|
177
|
+
Under the hood, Cpeak stores your routes in a radix tree, so finding the right route for an incoming request is roughly O(log n) in your total route count. In plain terms, that means matching stays fast even when your app has hundreds or thousands of routes. You won't pay a linear scan per request as your route table grows.
|
|
178
|
+
|
|
179
|
+
### Fallback Handler
|
|
180
|
+
|
|
181
|
+
If no route or middleware matches an incoming request, Cpeak returns a default `404` message. You can replace it with your own handler using `server.fallback`:
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
server.fallback((req, res) => {
|
|
185
|
+
return res.status(404).json({ error: "not found" });
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Use this for custom 404 pages, JSON error envelopes, or serving an SPA's `index.html` for unknown paths. Static, param, and `*` wildcard routes still win when they match, the fallback only fires once the router has no match.
|
|
190
|
+
|
|
169
191
|
### URL Variables & Parameters
|
|
170
192
|
|
|
171
193
|
To be more consistent with the broader Node.js community and frameworks, we call the HTTP URL parameters (query strings) '**query**', and the path variables (route parameters) '**params**'.
|
|
@@ -196,7 +218,15 @@ server.route("get", "/testing", (req, res) => {
|
|
|
196
218
|
});
|
|
197
219
|
```
|
|
198
220
|
|
|
199
|
-
The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the `path` module for better compatibility)
|
|
221
|
+
The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the `path` module for better compatibility).
|
|
222
|
+
|
|
223
|
+
The MIME type argument is optional. When omitted, Cpeak infers it from the file extension using its built-in MIME registry (see [MIME Types](#mime-types)):
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
return res.status(200).sendFile("./images/sun.jpeg");
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Passing the MIME type explicitly is the fastest path, Cpeak skips the extension lookup entirely.
|
|
200
230
|
|
|
201
231
|
### Redirecting
|
|
202
232
|
|
|
@@ -206,50 +236,134 @@ If you want to redirect to a new URL, you can simply do:
|
|
|
206
236
|
res.redirect("https://whatever.com");
|
|
207
237
|
```
|
|
208
238
|
|
|
209
|
-
###
|
|
239
|
+
### MIME Types
|
|
210
240
|
|
|
211
|
-
|
|
241
|
+
`serveStatic`, `res.sendFile`, and `res.render` all share a single MIME type registry. Out of the box it covers the common web types:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
html: "text/html",
|
|
245
|
+
css: "text/css",
|
|
246
|
+
js: "application/javascript",
|
|
247
|
+
jpg: "image/jpeg",
|
|
248
|
+
jpeg: "image/jpeg",
|
|
249
|
+
png: "image/png",
|
|
250
|
+
svg: "image/svg+xml",
|
|
251
|
+
gif: "image/gif",
|
|
252
|
+
ico: "image/x-icon",
|
|
253
|
+
txt: "text/plain",
|
|
254
|
+
json: "application/json",
|
|
255
|
+
webmanifest: "application/manifest+json",
|
|
256
|
+
eot: "application/vnd.ms-fontobject",
|
|
257
|
+
otf: "font/otf",
|
|
258
|
+
ttf: "font/ttf",
|
|
259
|
+
woff: "font/woff",
|
|
260
|
+
woff2: "font/woff2"
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
If you need to serve something else, register it once on the `cpeak()` constructor:
|
|
212
264
|
|
|
213
265
|
```javascript
|
|
214
|
-
server
|
|
215
|
-
|
|
266
|
+
const server = cpeak({
|
|
267
|
+
mimeTypes: { mp3: "audio/mpeg", m4a: "audio/mp4" }
|
|
268
|
+
});
|
|
269
|
+
```
|
|
216
270
|
|
|
217
|
-
|
|
271
|
+
We keep this list small on purpose. Cpeak isn't going to load the [thousands of MIME types from the IANA registry](https://www.iana.org/assignments/media-types/media-types.xhtml) into your process memory on the off chance you might serve an `.apk` or a `.fla` one day. You tell us what you serve, we keep memory tight.
|
|
218
272
|
|
|
219
|
-
|
|
273
|
+
### Compression
|
|
274
|
+
|
|
275
|
+
You can enable HTTP response compression at construction time. Once enabled, `serveStatic`, `res.json()` and `res.sendFile()` will compress eligible responses automatically, and you also get a `res.compress()` method on the response for custom payloads.
|
|
276
|
+
|
|
277
|
+
Fire it up with the defaults like this:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
const server = cpeak({ compression: true });
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Or pass options to tune the behavior:
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
const server = cpeak({
|
|
287
|
+
compression: {
|
|
288
|
+
threshold: 1024, // bytes — responses smaller than this are sent uncompressed. Default: 1024
|
|
289
|
+
brotli: {}, // node:zlib BrotliOptions
|
|
290
|
+
gzip: {}, // node:zlib ZlibOptions
|
|
291
|
+
deflate: {} // node:zlib ZlibOptions
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
For arbitrary payloads, like a `Buffer`, `string`, or `Readable` stream, use `res.compress`:
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
server.route("get", "/report", async (req, res) => {
|
|
300
|
+
const csv = await buildCsvReport();
|
|
301
|
+
await res.compress("text/csv", csv);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
When you're streaming, you can pass a known size as the third argument. Cpeak will use it to decide eligibility against `threshold`, and to set `Content-Length` if the body ends up being sent uncompressed:
|
|
306
|
+
|
|
307
|
+
```javascript
|
|
308
|
+
import { Readable } from "node:stream";
|
|
309
|
+
|
|
310
|
+
server.route("get", "/proxy/feed", async (req, res) => {
|
|
311
|
+
const upstream = await fetch("https://example.com/feed.xml");
|
|
312
|
+
const size = Number(upstream.headers.get("content-length"));
|
|
313
|
+
await res.compress("application/xml", Readable.fromWeb(upstream.body), size);
|
|
220
314
|
});
|
|
221
315
|
```
|
|
222
316
|
|
|
223
|
-
You
|
|
317
|
+
You must first enable compression at construction time to use `res.compress`.
|
|
318
|
+
|
|
319
|
+
### Error Handling
|
|
320
|
+
|
|
321
|
+
If anywhere in your route functions or route middleware functions you want to return an error, you can just throw the error and let the automatic error handler catch it:
|
|
224
322
|
|
|
225
323
|
```javascript
|
|
226
|
-
server.route("get", "/api/document/:title", (req, res
|
|
324
|
+
server.route("get", "/api/document/:title", (req, res) => {
|
|
227
325
|
const title = req.params.title;
|
|
228
326
|
|
|
229
|
-
if (title.length > 500)
|
|
230
|
-
return handleErr({ status: 400, message: "Title too long." });
|
|
327
|
+
if (title.length > 500) throw { status: 400, message: "Title too long." };
|
|
231
328
|
|
|
232
329
|
// The rest of your logic...
|
|
233
330
|
});
|
|
234
331
|
```
|
|
235
332
|
|
|
236
|
-
**Make sure** to call
|
|
333
|
+
**Make sure** to call `server.handleErr` and pass a function like this to have the automatic error handler work properly:
|
|
237
334
|
|
|
238
335
|
```javascript
|
|
239
336
|
server.handleErr((error, req, res) => {
|
|
240
337
|
if (error && error.status) {
|
|
241
|
-
res.status(error.status).json({ error: error.message });
|
|
338
|
+
return res.status(error.status).json({ error: error.message });
|
|
242
339
|
} else {
|
|
243
340
|
// Log the unexpected errors somewhere so you can keep track of them...
|
|
244
341
|
console.error(error);
|
|
245
|
-
res.status(500).json({
|
|
342
|
+
return res.status(500).json({
|
|
246
343
|
error: "Sorry, something unexpected happened on our side."
|
|
247
344
|
});
|
|
248
345
|
}
|
|
249
346
|
});
|
|
250
347
|
```
|
|
251
348
|
|
|
252
|
-
_The error object is the object that you threw
|
|
349
|
+
_The error object is the object that you threw earlier in your routes or middleware._
|
|
350
|
+
|
|
351
|
+
One thing to keep in mind: `res.sendFile`, `res.render`, `res.compress`, and `res.json` all return a `Promise`. **Return** that promise (or `await` it in an `async` handler) so the framework can route any rejection to your `handleErr`:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
server.route("get", "/file", (req, res) => {
|
|
355
|
+
return res.sendFile("./images/sun.jpeg", "image/jpeg");
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Or, in an `async` handler that does other work alongside the send:
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
server.route("get", "/avatars/:id", async (req, res) => {
|
|
363
|
+
const path = await db.lookupAvatarPath(req.params.id);
|
|
364
|
+
await res.sendFile(path);
|
|
365
|
+
});
|
|
366
|
+
```
|
|
253
367
|
|
|
254
368
|
### Listening
|
|
255
369
|
|
|
@@ -261,6 +375,8 @@ server.listen(3000, () => {
|
|
|
261
375
|
});
|
|
262
376
|
```
|
|
263
377
|
|
|
378
|
+
`server.listen` accepts the same arguments as Node.js's [http.Server.listen](https://nodejs.org/docs/latest/api/net.html#serverlistenoptions-callback), so you can also pass a host, an options object, or a Unix socket path.
|
|
379
|
+
|
|
264
380
|
### Util Functions
|
|
265
381
|
|
|
266
382
|
There are utility functions that you can include and use as middleware functions. These are meant to make it easier for you to build HTTP applications. In the future, many more will be added, and you only move them into memory once you include them. No need to have many npm dependencies for simple applications!
|
|
@@ -273,6 +389,7 @@ The list of utility functions as of now:
|
|
|
273
389
|
- cookieParser
|
|
274
390
|
- swagger
|
|
275
391
|
- auth
|
|
392
|
+
- cors
|
|
276
393
|
|
|
277
394
|
Including any one of them is done like this:
|
|
278
395
|
|
|
@@ -285,44 +402,26 @@ import cpeak, { utilName } from "cpeak";
|
|
|
285
402
|
With this middleware function, you can automatically set a folder in your project to be served by Cpeak. Here’s how to set it up:
|
|
286
403
|
|
|
287
404
|
```javascript
|
|
288
|
-
server.beforeEach(
|
|
289
|
-
serveStatic("./public", {
|
|
290
|
-
mp3: "audio/mpeg"
|
|
291
|
-
})
|
|
292
|
-
);
|
|
405
|
+
server.beforeEach(serveStatic("./public"));
|
|
293
406
|
```
|
|
294
407
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
```
|
|
298
|
-
html: "text/html",
|
|
299
|
-
css: "text/css",
|
|
300
|
-
js: "application/javascript",
|
|
301
|
-
jpg: "image/jpeg",
|
|
302
|
-
jpeg: "image/jpeg",
|
|
303
|
-
png: "image/png",
|
|
304
|
-
svg: "image/svg+xml",
|
|
305
|
-
gif: "image/gif",
|
|
306
|
-
ico: "image/x-icon",
|
|
307
|
-
txt: "text/plain",
|
|
308
|
-
json: "application/json",
|
|
309
|
-
webmanifest: "application/manifest+json",
|
|
310
|
-
eot: "application/vnd.ms-fontobject",
|
|
311
|
-
otf: "font/otf",
|
|
312
|
-
ttf: "font/ttf",
|
|
313
|
-
woff: "font/woff",
|
|
314
|
-
woff2: "font/woff2"
|
|
315
|
-
```
|
|
408
|
+
`serveStatic` infers each file's MIME type from its extension. If your folder contains types that aren't in the built-in registry, register them on the `cpeak()` constructor (see [MIME Types](#mime-types)).
|
|
316
409
|
|
|
317
|
-
You can also serve your static files under a URL prefix by passing a
|
|
410
|
+
You can also serve your static files under a URL prefix by passing a `prefix` option. This is useful when you want all static assets to live under a specific path like `/static`:
|
|
318
411
|
|
|
319
412
|
```javascript
|
|
320
413
|
server.beforeEach(
|
|
321
|
-
serveStatic("./public",
|
|
414
|
+
serveStatic("./public", { prefix: "/static" })
|
|
322
415
|
);
|
|
323
416
|
```
|
|
324
417
|
|
|
325
|
-
With this setup, a file at `./public/app.js` would be served at `/static/app.js` instead of `/app.js`.
|
|
418
|
+
With this setup, a file at `./public/app.js` would be served at `/static/app.js` instead of `/app.js`.
|
|
419
|
+
|
|
420
|
+
If file names in the served folder change while the server is running (e.g. during development), pass `live: true` so the middleware stats the disk on each request instead of caching the file map at startup:
|
|
421
|
+
|
|
422
|
+
```javascript
|
|
423
|
+
server.beforeEach(serveStatic("./public", { live: true }));
|
|
424
|
+
```
|
|
326
425
|
|
|
327
426
|
#### parseJSON
|
|
328
427
|
|
|
@@ -344,7 +443,7 @@ server.route("put", "/api/user", (req, res) => {
|
|
|
344
443
|
// rest of your logic...
|
|
345
444
|
|
|
346
445
|
// Sending JSON in the HTTP response:
|
|
347
|
-
res.status(201).json({ message: "Something was created..." });
|
|
446
|
+
return res.status(201).json({ message: "Something was created..." });
|
|
348
447
|
});
|
|
349
448
|
```
|
|
350
449
|
|
|
@@ -373,6 +472,8 @@ server.route("get", "/", (req, res, next) => {
|
|
|
373
472
|
});
|
|
374
473
|
```
|
|
375
474
|
|
|
475
|
+
The third argument (the MIME type) is optional. When omitted, Cpeak infers it from the file extension using the registry on the `cpeak()` constructor.
|
|
476
|
+
|
|
376
477
|
You can then inject the variables into your file in {{ variable_name }} like this:
|
|
377
478
|
|
|
378
479
|
```HTML
|
|
@@ -412,7 +513,7 @@ server.route("get", "/dashboard", (req, res) => {
|
|
|
412
513
|
// Signed cookies — returns false if the signature is invalid or the value was tampered with
|
|
413
514
|
const userId = req.signedCookies.userId;
|
|
414
515
|
|
|
415
|
-
res.status(200).json({ theme, userId });
|
|
516
|
+
return res.status(200).json({ theme, userId });
|
|
416
517
|
});
|
|
417
518
|
```
|
|
418
519
|
|
|
@@ -426,7 +527,7 @@ server.route("post", "/login", (req, res) => {
|
|
|
426
527
|
// A signed cookie
|
|
427
528
|
res.cookie("userId", "abc123", { signed: true, httpOnly: true, secure: true });
|
|
428
529
|
|
|
429
|
-
res.status(200).json({ message: "Logged in" });
|
|
530
|
+
return res.status(200).json({ message: "Logged in" });
|
|
430
531
|
});
|
|
431
532
|
```
|
|
432
533
|
|
|
@@ -571,20 +672,40 @@ For complete working examples, see:
|
|
|
571
672
|
- [`examples/auth-localstorage.js`](examples/auth-localstorage.js) — token sent via the `Authorization` header (suited for SPAs and mobile clients)
|
|
572
673
|
- [`examples/auth-cookies.js`](examples/auth-cookies.js) — token stored in an `httpOnly` cookie (protects against XSS)
|
|
573
674
|
|
|
675
|
+
#### cors
|
|
676
|
+
The CORS middleware allows you to enable Cross-Origin Resource Sharing in your application.
|
|
677
|
+
|
|
678
|
+
```javascript
|
|
679
|
+
server.beforeEach(cors({
|
|
680
|
+
origin: "http://localhost:3000", // string, string[], RegExp, boolean, or async (origin) => boolean. Default: "*" (all origins)
|
|
681
|
+
methods: "GET,POST,PUT,DELETE", // allowed HTTP methods. Default: "GET,HEAD,PUT,PATCH,POST,DELETE"
|
|
682
|
+
allowedHeaders: "Content-Type", // headers the browser may send. Default: echoes request headers for origin:"*", else "Content-Type, Authorization"
|
|
683
|
+
exposedHeaders: "X-Request-Id", // response headers the browser may read. Default: none
|
|
684
|
+
credentials: true, // adds Access-Control-Allow-Credentials: true. Default: false
|
|
685
|
+
maxAge: 3600, // seconds to cache preflight result in the browser. Default: 86400
|
|
686
|
+
preflightContinue: false, // pass OPTIONS preflight to next middleware instead of auto-responding. Default: false
|
|
687
|
+
optionsSuccessStatus: 204 // status code for successful preflight responses. Default: 204
|
|
688
|
+
}));
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Or if you don't care and want to allow everything with the default settings, just do:
|
|
692
|
+
|
|
693
|
+
```javascript
|
|
694
|
+
server.beforeEach(cors());
|
|
695
|
+
```
|
|
696
|
+
|
|
574
697
|
## Complete Example
|
|
575
698
|
|
|
576
699
|
Here you can see all the features that Cpeak offers (excluding the authentication features), in one small piece of code:
|
|
577
700
|
|
|
578
701
|
```javascript
|
|
579
|
-
import cpeak, { serveStatic, parseJSON, render, cookieParser } from "cpeak";
|
|
702
|
+
import cpeak, { serveStatic, parseJSON, render, cookieParser, cors } from "cpeak";
|
|
580
703
|
|
|
581
|
-
const server = cpeak(
|
|
704
|
+
const server = cpeak({
|
|
705
|
+
mimeTypes: { mp3: "audio/mpeg" }
|
|
706
|
+
});
|
|
582
707
|
|
|
583
|
-
server.beforeEach(
|
|
584
|
-
serveStatic("./public", {
|
|
585
|
-
mp3: "audio/mpeg"
|
|
586
|
-
})
|
|
587
|
-
);
|
|
708
|
+
server.beforeEach(serveStatic("./public"));
|
|
588
709
|
|
|
589
710
|
server.beforeEach(render());
|
|
590
711
|
|
|
@@ -594,6 +715,13 @@ server.beforeEach(parseJSON());
|
|
|
594
715
|
// For reading and setting cookies
|
|
595
716
|
server.beforeEach(cookieParser({ secret: "your-secret-key" }));
|
|
596
717
|
|
|
718
|
+
// For enabling CORS
|
|
719
|
+
server.beforeEach(cors({
|
|
720
|
+
origin: "http://localhost:3000",
|
|
721
|
+
credentials: true,
|
|
722
|
+
methods: "GET,POST,PUT,DELETE"
|
|
723
|
+
}));
|
|
724
|
+
|
|
597
725
|
// Adding custom middleware functions
|
|
598
726
|
server.beforeEach((req, res, next) => {
|
|
599
727
|
req.custom = "This is some string";
|
|
@@ -601,11 +729,11 @@ server.beforeEach((req, res, next) => {
|
|
|
601
729
|
});
|
|
602
730
|
|
|
603
731
|
// A middleware function that can be specified to run before some particular routes
|
|
604
|
-
const testRouteMiddleware = (req, res, next
|
|
732
|
+
const testRouteMiddleware = (req, res, next) => {
|
|
605
733
|
req.whatever = "some calculated value maybe";
|
|
606
734
|
|
|
607
735
|
if (req.params.test !== "something special") {
|
|
608
|
-
|
|
736
|
+
throw { status: 400, message: "an error message" };
|
|
609
737
|
}
|
|
610
738
|
|
|
611
739
|
next();
|
|
@@ -642,7 +770,7 @@ server.route("get", "/api/document/:title", testRouteMiddleware, (req, res) => {
|
|
|
642
770
|
throw { status: 400, message: "Invalid property." };
|
|
643
771
|
|
|
644
772
|
// Sending a JSON response
|
|
645
|
-
res.status(200).json({ message: "This is a test response" });
|
|
773
|
+
return res.status(200).json({ message: "This is a test response" });
|
|
646
774
|
});
|
|
647
775
|
|
|
648
776
|
// Reading and setting cookies
|
|
@@ -652,22 +780,22 @@ server.route("post", "/login", (req, res) => {
|
|
|
652
780
|
|
|
653
781
|
// Set a signed session cookie
|
|
654
782
|
res.cookie("sessionId", "abc123", { signed: true, httpOnly: true, secure: true });
|
|
655
|
-
res.status(200).json({ message: "Logged in" });
|
|
783
|
+
return res.status(200).json({ message: "Logged in" });
|
|
656
784
|
});
|
|
657
785
|
|
|
658
786
|
// Sending a file response
|
|
659
787
|
server.route("get", "/file", (req, res) => {
|
|
660
788
|
// Make sure to specify a correct path and MIME type...
|
|
661
|
-
res.status(200).sendFile("<path-to-file-relative-to-cwd>", "<mime-type>");
|
|
789
|
+
return res.status(200).sendFile("<path-to-file-relative-to-cwd>", "<mime-type>");
|
|
662
790
|
});
|
|
663
791
|
|
|
664
792
|
// Handle all the errors that could happen in the routes
|
|
665
793
|
server.handleErr((error, req, res) => {
|
|
666
794
|
if (error && error.status) {
|
|
667
|
-
res.status(error.status).json({ error: error.message });
|
|
795
|
+
return res.status(error.status).json({ error: error.message });
|
|
668
796
|
} else {
|
|
669
797
|
console.error(error);
|
|
670
|
-
res.status(500).json({
|
|
798
|
+
return res.status(500).json({
|
|
671
799
|
error: "Sorry, something unexpected happened from our side."
|
|
672
800
|
});
|
|
673
801
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import http, { IncomingMessage, ServerResponse, Server } from 'node:http';
|
|
2
|
+
import net from 'node:net';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { Buffer } from 'node:buffer';
|
|
5
|
+
import * as node_zlib from 'node:zlib';
|
|
3
6
|
|
|
7
|
+
declare function frameworkError(message: string, skipFn: Function, code?: string, status?: number): Error & {
|
|
8
|
+
code?: string;
|
|
9
|
+
cpeak_err?: boolean;
|
|
10
|
+
};
|
|
11
|
+
declare enum ErrorCode {
|
|
12
|
+
MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
|
|
13
|
+
FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
|
|
14
|
+
NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
|
|
15
|
+
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
|
|
16
|
+
INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
|
|
17
|
+
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
|
|
18
|
+
WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET",
|
|
19
|
+
COMPRESSION_NOT_ENABLED = "CPEAK_ERR_COMPRESSION_NOT_ENABLED",
|
|
20
|
+
DUPLICATE_ROUTE = "CPEAK_ERR_DUPLICATE_ROUTE",
|
|
21
|
+
INVALID_ROUTE = "CPEAK_ERR_INVALID_ROUTE"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CompressionOptions {
|
|
25
|
+
threshold?: number;
|
|
26
|
+
brotli?: node_zlib.BrotliOptions;
|
|
27
|
+
gzip?: node_zlib.ZlibOptions;
|
|
28
|
+
deflate?: node_zlib.ZlibOptions;
|
|
29
|
+
}
|
|
30
|
+
type ResolvedCompressionConfig = Required<CompressionOptions>;
|
|
31
|
+
|
|
32
|
+
type CpeakHttpServer = Server<typeof CpeakIncomingMessage, typeof CpeakServerResponse>;
|
|
33
|
+
interface CpeakOptions {
|
|
34
|
+
compression?: boolean | CompressionOptions;
|
|
35
|
+
mimeTypes?: StringMap;
|
|
36
|
+
}
|
|
4
37
|
type StringMap = Record<string, string>;
|
|
5
38
|
interface CpeakRequest<ReqBody = any, ReqQueries = any> extends IncomingMessage {
|
|
6
39
|
params: StringMap;
|
|
@@ -11,40 +44,32 @@ interface CpeakRequest<ReqBody = any, ReqQueries = any> extends IncomingMessage
|
|
|
11
44
|
[key: string]: any;
|
|
12
45
|
}
|
|
13
46
|
interface CpeakResponse extends ServerResponse {
|
|
14
|
-
sendFile: (path: string, mime
|
|
47
|
+
sendFile: (path: string, mime?: string) => Promise<void>;
|
|
15
48
|
status: (code: number) => CpeakResponse;
|
|
16
49
|
attachment: (filename?: string) => CpeakResponse;
|
|
17
50
|
cookie: (name: string, value: string, options?: any) => CpeakResponse;
|
|
18
51
|
redirect: (location: string) => void;
|
|
19
|
-
json: (data: any) => void
|
|
52
|
+
json: (data: any) => Promise<void>;
|
|
53
|
+
compress: (mime: string, body: Buffer | string | Readable, size?: number) => Promise<void>;
|
|
20
54
|
[key: string]: any;
|
|
21
55
|
}
|
|
22
56
|
type Next = (err?: any) => void;
|
|
23
|
-
type HandleErr = (err: any) => void;
|
|
24
57
|
type Middleware<ReqBody = any, ReqParams = any> = (req: CpeakRequest<ReqBody, ReqParams>, res: CpeakResponse, next: Next) => void;
|
|
25
|
-
type RouteMiddleware<ReqBody = any, ReqParams = any> = (req: CpeakRequest<ReqBody, ReqParams>, res: CpeakResponse, next: Next
|
|
26
|
-
type Handler<ReqBody = any, ReqParams = any> = (req: CpeakRequest<ReqBody, ReqParams>, res: CpeakResponse
|
|
27
|
-
interface Route {
|
|
28
|
-
path: string;
|
|
29
|
-
regex: RegExp;
|
|
30
|
-
middleware: RouteMiddleware[];
|
|
31
|
-
cb: Handler;
|
|
32
|
-
}
|
|
33
|
-
interface RoutesMap {
|
|
34
|
-
[method: string]: Route[];
|
|
35
|
-
}
|
|
58
|
+
type RouteMiddleware<ReqBody = any, ReqParams = any> = (req: CpeakRequest<ReqBody, ReqParams>, res: CpeakResponse, next: Next) => void | Promise<void>;
|
|
59
|
+
type Handler<ReqBody = any, ReqParams = any> = (req: CpeakRequest<ReqBody, ReqParams>, res: CpeakResponse) => void | Promise<void>;
|
|
36
60
|
|
|
37
61
|
declare const parseJSON: (options?: {
|
|
38
62
|
limit?: number;
|
|
39
63
|
}) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
40
64
|
|
|
41
|
-
declare const serveStatic: (folderPath: string,
|
|
65
|
+
declare const serveStatic: (folderPath: string, options?: {
|
|
42
66
|
prefix?: string;
|
|
67
|
+
live?: boolean;
|
|
43
68
|
}) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void | Promise<void>;
|
|
44
69
|
|
|
45
70
|
declare const render: () => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
46
71
|
|
|
47
|
-
declare const swagger: (spec: object, prefix?: string) => (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
72
|
+
declare const swagger: (spec: object, prefix?: string) => (req: CpeakRequest, res: CpeakResponse, next: Next) => Promise<void> | undefined;
|
|
48
73
|
|
|
49
74
|
interface PbkdfOptions {
|
|
50
75
|
iterations?: number;
|
|
@@ -64,10 +89,6 @@ interface AuthOptions extends PbkdfOptions {
|
|
|
64
89
|
tokenIdSize?: number;
|
|
65
90
|
revokeToken?: (tokenId: string) => Promise<void>;
|
|
66
91
|
}
|
|
67
|
-
declare function hashPassword(password: string, options?: PbkdfOptions): Promise<string>;
|
|
68
|
-
declare function verifyPassword(password: string, stored: string): Promise<boolean>;
|
|
69
|
-
declare function auth(options: AuthOptions): Middleware;
|
|
70
|
-
|
|
71
92
|
interface CookieOptions {
|
|
72
93
|
signed?: boolean;
|
|
73
94
|
httpOnly?: boolean;
|
|
@@ -78,23 +99,28 @@ interface CookieOptions {
|
|
|
78
99
|
path?: string;
|
|
79
100
|
domain?: string;
|
|
80
101
|
}
|
|
102
|
+
type OriginInput = string | string[] | RegExp | boolean | ((origin: string | undefined) => boolean | Promise<boolean>);
|
|
103
|
+
interface CorsOptions {
|
|
104
|
+
origin?: OriginInput;
|
|
105
|
+
methods?: string | string[];
|
|
106
|
+
allowedHeaders?: string | string[];
|
|
107
|
+
exposedHeaders?: string | string[];
|
|
108
|
+
credentials?: boolean;
|
|
109
|
+
maxAge?: number;
|
|
110
|
+
preflightContinue?: boolean;
|
|
111
|
+
optionsSuccessStatus?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
declare function hashPassword(password: string, options?: PbkdfOptions): Promise<string>;
|
|
115
|
+
declare function verifyPassword(password: string, stored: string): Promise<boolean>;
|
|
116
|
+
declare function auth(options: AuthOptions): Middleware;
|
|
117
|
+
|
|
81
118
|
declare function cookieParser(options?: {
|
|
82
119
|
secret?: string;
|
|
83
120
|
}): (req: CpeakRequest, res: CpeakResponse, next: Next) => void;
|
|
84
121
|
|
|
85
|
-
declare
|
|
86
|
-
|
|
87
|
-
cpeak_err?: boolean;
|
|
88
|
-
};
|
|
89
|
-
declare enum ErrorCode {
|
|
90
|
-
MISSING_MIME = "CPEAK_ERR_MISSING_MIME",
|
|
91
|
-
FILE_NOT_FOUND = "CPEAK_ERR_FILE_NOT_FOUND",
|
|
92
|
-
NOT_A_FILE = "CPEAK_ERR_NOT_A_FILE",
|
|
93
|
-
SEND_FILE_FAIL = "CPEAK_ERR_SEND_FILE_FAIL",
|
|
94
|
-
INVALID_JSON = "CPEAK_ERR_INVALID_JSON",
|
|
95
|
-
PAYLOAD_TOO_LARGE = "CPEAK_ERR_PAYLOAD_TOO_LARGE",
|
|
96
|
-
WEAK_SECRET = "CPEAK_ERR_WEAK_SECRET"
|
|
97
|
-
}
|
|
122
|
+
declare const cors: (options?: CorsOptions) => (req: CpeakRequest, res: CpeakResponse, next: Next) => Promise<void>;
|
|
123
|
+
|
|
98
124
|
declare class CpeakIncomingMessage extends http.IncomingMessage {
|
|
99
125
|
#private;
|
|
100
126
|
body: any;
|
|
@@ -102,23 +128,28 @@ declare class CpeakIncomingMessage extends http.IncomingMessage {
|
|
|
102
128
|
get query(): StringMap;
|
|
103
129
|
}
|
|
104
130
|
declare class CpeakServerResponse extends http.ServerResponse<CpeakIncomingMessage> {
|
|
105
|
-
|
|
131
|
+
_compression?: ResolvedCompressionConfig;
|
|
132
|
+
sendFile(path: string, mime?: string): Promise<void>;
|
|
106
133
|
status(code: number): this;
|
|
107
134
|
attachment(filename?: string): this;
|
|
108
135
|
redirect(location: string): void;
|
|
109
|
-
json(data: any): void
|
|
136
|
+
json(data: any): Promise<void>;
|
|
137
|
+
compress(mime: string, body: Buffer | string | Readable, size?: number): Promise<void>;
|
|
110
138
|
}
|
|
111
139
|
declare class Cpeak {
|
|
112
140
|
#private;
|
|
113
|
-
constructor();
|
|
141
|
+
constructor(options?: CpeakOptions);
|
|
114
142
|
route(method: string, path: string, ...args: (RouteMiddleware | Handler)[]): void;
|
|
115
143
|
beforeEach(cb: Middleware): void;
|
|
116
144
|
handleErr(cb: (err: unknown, req: CpeakRequest, res: CpeakResponse) => void): void;
|
|
117
|
-
listen(port: number, cb?: () => void):
|
|
145
|
+
listen(port: number, cb?: () => void): CpeakHttpServer;
|
|
146
|
+
listen(port: number, host: string, cb?: () => void): CpeakHttpServer;
|
|
147
|
+
listen(options: net.ListenOptions, cb?: () => void): CpeakHttpServer;
|
|
118
148
|
address(): string | net.AddressInfo | null;
|
|
119
|
-
close(cb?: (err?: Error) => void):
|
|
149
|
+
close(cb?: (err?: Error) => void): CpeakHttpServer;
|
|
150
|
+
get server(): CpeakHttpServer;
|
|
120
151
|
}
|
|
121
152
|
|
|
122
|
-
declare function cpeak(): Cpeak;
|
|
153
|
+
declare function cpeak(options?: CpeakOptions): Cpeak;
|
|
123
154
|
|
|
124
|
-
export { type AuthOptions, type CookieOptions, Cpeak, CpeakIncomingMessage, type CpeakRequest, type CpeakResponse, CpeakServerResponse, ErrorCode, type
|
|
155
|
+
export { type AuthOptions, type CompressionOptions, type CookieOptions, type CorsOptions, Cpeak, type CpeakHttpServer, CpeakIncomingMessage, type CpeakOptions, type CpeakRequest, type CpeakResponse, CpeakServerResponse, ErrorCode, type Handler, type Middleware, type Next, type PbkdfOptions, type RouteMiddleware, auth, cookieParser, cors, cpeak as default, frameworkError, hashPassword, parseJSON, render, serveStatic, swagger, verifyPassword };
|