milliparsec 5.0.2 → 5.1.1
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 +1 -1
- package/dist/index.d.ts +12 -28
- package/dist/index.js +87 -58
- package/dist/types.d.ts +24 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +3 -0
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Check out [deno-libs/parsec](https://github.com/deno-libs/parsec) for Deno port.
|
|
|
20
20
|
- 📦 tiny package size (8KB dist size)
|
|
21
21
|
- 🔥 no dependencies
|
|
22
22
|
- ✨ [tinyhttp](https://github.com/tinyhttp/tinyhttp) and Express support
|
|
23
|
-
- ⚡
|
|
23
|
+
- ⚡ ~15% faster than body-parser, ~4x faster than formidable
|
|
24
24
|
|
|
25
25
|
## Install
|
|
26
26
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,52 +1,36 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
import type {
|
|
3
|
-
type NextFunction
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*/
|
|
7
|
-
export type ReqWithBody<T = any> = IncomingMessage & {
|
|
8
|
-
body?: T;
|
|
9
|
-
};
|
|
10
|
-
export declare const hasBody: (method: string) => boolean;
|
|
11
|
-
export type LimitErrorFn = (limit: number) => Error;
|
|
12
|
-
export type ParserOptions = Partial<{
|
|
13
|
-
/**
|
|
14
|
-
* Limit payload size (in bytes)
|
|
15
|
-
* @default '100KB'
|
|
16
|
-
*/
|
|
17
|
-
payloadLimit: number;
|
|
18
|
-
/**
|
|
19
|
-
* Custom error function for payload limit
|
|
20
|
-
*/
|
|
21
|
-
payloadLimitErrorFn: LimitErrorFn;
|
|
22
|
-
}>;
|
|
23
|
-
export declare const p: <T = any>(fn: (body: Buffer) => void, payloadLimit?: number, payloadLimitErrorFn?: LimitErrorFn) => (req: ReqWithBody<T>, _res: Response, next?: (err?: any) => void) => Promise<void>;
|
|
2
|
+
import type { ServerResponse as Response } from 'node:http';
|
|
3
|
+
import type { LimitErrorFn, NextFunction, ParserOptions, ReqWithBody } from './types.ts';
|
|
4
|
+
export * from './types.ts';
|
|
5
|
+
export declare const p: <T = any>(fn: (body: Buffer, req: ReqWithBody<T>) => void, payloadLimit?: number, payloadLimitErrorFn?: LimitErrorFn) => (req: ReqWithBody<T>, _res: Response, next?: (err?: any) => void) => Promise<T | undefined>;
|
|
24
6
|
/**
|
|
25
7
|
* Parse payload with a custom function
|
|
26
8
|
* @param fn
|
|
27
9
|
*/
|
|
28
|
-
declare const custom: <T = any>(fn: (body: Buffer) => any) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
10
|
+
declare const custom: <T = any>(fn: (body: Buffer) => any, type?: ParserOptions["type"]) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
29
11
|
/**
|
|
30
12
|
* Parse JSON payload
|
|
31
13
|
* @param options
|
|
32
14
|
*/
|
|
33
|
-
declare const json: ({ payloadLimit, payloadLimitErrorFn
|
|
15
|
+
declare const json: ({ payloadLimit, payloadLimitErrorFn, type, reviver }?: ParserOptions<{
|
|
16
|
+
reviver?: (this: any, key: string, value: any) => any;
|
|
17
|
+
}>) => (req: ReqWithBody, res: Response, next?: NextFunction) => Promise<void>;
|
|
34
18
|
/**
|
|
35
19
|
* Parse raw payload
|
|
36
20
|
* @param options
|
|
37
21
|
*/
|
|
38
|
-
declare const raw: ({ payloadLimit, payloadLimitErrorFn }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
22
|
+
declare const raw: ({ payloadLimit, payloadLimitErrorFn, type }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
39
23
|
/**
|
|
40
24
|
* Stringify request payload
|
|
41
25
|
* @param param0
|
|
42
26
|
* @returns
|
|
43
27
|
*/
|
|
44
|
-
declare const text: ({ payloadLimit, payloadLimitErrorFn }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
28
|
+
declare const text: ({ payloadLimit, payloadLimitErrorFn, type }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
45
29
|
/**
|
|
46
30
|
* Parse urlencoded payload
|
|
47
31
|
* @param options
|
|
48
32
|
*/
|
|
49
|
-
declare const urlencoded: ({ payloadLimit, payloadLimitErrorFn }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
33
|
+
declare const urlencoded: ({ payloadLimit, payloadLimitErrorFn, type }?: ParserOptions) => (req: ReqWithBody, _res: Response, next?: NextFunction) => Promise<void>;
|
|
50
34
|
type MultipartOptions = Partial<{
|
|
51
35
|
/**
|
|
52
36
|
* Limit number of files
|
|
@@ -67,5 +51,5 @@ type MultipartOptions = Partial<{
|
|
|
67
51
|
* Does not restrict total payload size by default.
|
|
68
52
|
* @param options
|
|
69
53
|
*/
|
|
70
|
-
declare const multipart: ({ payloadLimit, payloadLimitErrorFn, ...opts }?: MultipartOptions & ParserOptions) => (req: ReqWithBody, res: Response, next?: NextFunction) => Promise<void>;
|
|
54
|
+
declare const multipart: ({ payloadLimit, payloadLimitErrorFn, type, ...opts }?: MultipartOptions & ParserOptions) => (req: ReqWithBody, res: Response, next?: NextFunction) => Promise<void>;
|
|
71
55
|
export { custom, json, raw, text, urlencoded, multipart };
|
package/dist/index.js
CHANGED
|
@@ -1,90 +1,116 @@
|
|
|
1
1
|
import { Buffer, File } from 'node:buffer';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { checkType, hasBody } from "./utils.js";
|
|
3
|
+
export * from "./types.js";
|
|
4
|
+
const defaultPayloadLimit = 102400; // 100KiB
|
|
4
5
|
const defaultErrorFn = (payloadLimit) => new Error(`Payload too large. Limit: ${payloadLimit} bytes`);
|
|
5
6
|
// Main function
|
|
6
|
-
export const p = (fn, payloadLimit = defaultPayloadLimit, payloadLimitErrorFn = defaultErrorFn) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
export const p = (fn, payloadLimit = defaultPayloadLimit, payloadLimitErrorFn = defaultErrorFn) => (req, _res, next) => new Promise((resolve) => {
|
|
8
|
+
const body = [];
|
|
9
|
+
let totalSize = 0;
|
|
10
|
+
req.on('data', (chunk) => {
|
|
11
|
+
totalSize += chunk.byteLength;
|
|
12
|
+
if (totalSize > payloadLimit) {
|
|
13
|
+
req.removeAllListeners();
|
|
14
|
+
next === null || next === void 0 ? void 0 : next(payloadLimitErrorFn(payloadLimit));
|
|
15
|
+
resolve(undefined);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
13
18
|
body.push(chunk);
|
|
14
19
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
});
|
|
21
|
+
req.on('end', () => {
|
|
22
|
+
try {
|
|
23
|
+
resolve(fn(body.length === 1 ? body[0] : Buffer.concat(body), req));
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
next === null || next === void 0 ? void 0 : next(e);
|
|
27
|
+
resolve(undefined);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
req.on('error', (err) => {
|
|
31
|
+
next === null || next === void 0 ? void 0 : next(err);
|
|
32
|
+
resolve(undefined);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
21
35
|
/**
|
|
22
36
|
* Parse payload with a custom function
|
|
23
37
|
* @param fn
|
|
24
38
|
*/
|
|
25
|
-
const custom = (fn
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
const custom = (fn, type) => {
|
|
40
|
+
const parse = p(fn);
|
|
41
|
+
return async (req, _res, next) => {
|
|
42
|
+
if (hasBody(req.method) && checkType(req, type))
|
|
43
|
+
req.body = await parse(req, _res, next);
|
|
44
|
+
next === null || next === void 0 ? void 0 : next();
|
|
45
|
+
};
|
|
29
46
|
};
|
|
30
47
|
/**
|
|
31
48
|
* Parse JSON payload
|
|
32
49
|
* @param options
|
|
33
50
|
*/
|
|
34
|
-
const json = ({ payloadLimit, payloadLimitErrorFn } = {}) =>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
51
|
+
const json = ({ payloadLimit, payloadLimitErrorFn, type, reviver } = {}) => {
|
|
52
|
+
const parse = p((x) => (x.length === 0 ? {} : JSON.parse(x.toString(), reviver)), payloadLimit, payloadLimitErrorFn);
|
|
53
|
+
return async (req, res, next) => {
|
|
54
|
+
if (hasBody(req.method) && checkType(req, type)) {
|
|
55
|
+
req.body = await parse(req, res, next);
|
|
56
|
+
}
|
|
57
|
+
next === null || next === void 0 ? void 0 : next();
|
|
58
|
+
};
|
|
42
59
|
};
|
|
43
60
|
/**
|
|
44
61
|
* Parse raw payload
|
|
45
62
|
* @param options
|
|
46
63
|
*/
|
|
47
|
-
const raw = ({ payloadLimit, payloadLimitErrorFn } = {}) =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
64
|
+
const raw = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => {
|
|
65
|
+
const parse = p((x) => x, payloadLimit, payloadLimitErrorFn);
|
|
66
|
+
return async (req, _res, next) => {
|
|
67
|
+
if (hasBody(req.method) && checkType(req, type)) {
|
|
68
|
+
req.body = await parse(req, _res, next);
|
|
69
|
+
}
|
|
70
|
+
next === null || next === void 0 ? void 0 : next();
|
|
71
|
+
};
|
|
52
72
|
};
|
|
53
|
-
const td = new TextDecoder();
|
|
54
73
|
/**
|
|
55
74
|
* Stringify request payload
|
|
56
75
|
* @param param0
|
|
57
76
|
* @returns
|
|
58
77
|
*/
|
|
59
|
-
const text = ({ payloadLimit, payloadLimitErrorFn } = {}) =>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
const text = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => {
|
|
79
|
+
const parse = p((x) => x.toString(), payloadLimit, payloadLimitErrorFn);
|
|
80
|
+
return async (req, _res, next) => {
|
|
81
|
+
if (hasBody(req.method) && checkType(req, type)) {
|
|
82
|
+
req.body = await parse(req, _res, next);
|
|
83
|
+
}
|
|
84
|
+
next === null || next === void 0 ? void 0 : next();
|
|
85
|
+
};
|
|
64
86
|
};
|
|
87
|
+
const td = new TextDecoder();
|
|
65
88
|
/**
|
|
66
89
|
* Parse urlencoded payload
|
|
67
90
|
* @param options
|
|
68
91
|
*/
|
|
69
|
-
const urlencoded = ({ payloadLimit, payloadLimitErrorFn } = {}) =>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
92
|
+
const urlencoded = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => {
|
|
93
|
+
const parse = p((x) => Object.fromEntries(new URLSearchParams(x.toString()).entries()), payloadLimit, payloadLimitErrorFn);
|
|
94
|
+
return async (req, _res, next) => {
|
|
95
|
+
if (hasBody(req.method) && checkType(req, type)) {
|
|
96
|
+
req.body = await parse(req, _res, next);
|
|
97
|
+
}
|
|
98
|
+
next === null || next === void 0 ? void 0 : next();
|
|
99
|
+
};
|
|
74
100
|
};
|
|
75
101
|
const getBoundary = (contentType) => {
|
|
76
102
|
const match = /boundary=(.+);?/.exec(contentType);
|
|
77
103
|
return match ? `--${match[1]}` : null;
|
|
78
104
|
};
|
|
79
105
|
const defaultFileSizeLimitErrorFn = (limit) => new Error(`File too large. Limit: ${limit} bytes`);
|
|
80
|
-
const defaultFileSizeLimit = 200 * 1024 * 1024;
|
|
106
|
+
const defaultFileSizeLimit = 200 * 1024 * 1024; // 200MiB
|
|
81
107
|
const parseMultipart = (body, boundary, { fileCountLimit, fileSizeLimit = defaultFileSizeLimit, fileSizeLimitErrorFn = defaultFileSizeLimitErrorFn }) => {
|
|
82
108
|
const parts = body.split(new RegExp(`${boundary}(--)?`)).filter((part) => !!part && /content-disposition/i.test(part));
|
|
83
109
|
const parsedBody = {};
|
|
84
110
|
if (fileCountLimit && parts.length > fileCountLimit)
|
|
85
111
|
throw new Error(`Too many files. Limit: ${fileCountLimit}`);
|
|
86
|
-
// biome-ignore lint/complexity/noForEach: for...of fails
|
|
87
112
|
parts.forEach((part) => {
|
|
113
|
+
var _a, _b;
|
|
88
114
|
const [headers, ...lines] = part.split('\r\n').filter((part) => !!part);
|
|
89
115
|
const data = lines.join('\r\n').trim();
|
|
90
116
|
if (data.length > fileSizeLimit)
|
|
@@ -96,11 +122,11 @@ const parseMultipart = (body, boundary, { fileCountLimit, fileSizeLimit = defaul
|
|
|
96
122
|
const contentTypeMatch = /Content-Type: (.+)/i.exec(data);
|
|
97
123
|
const fileContent = data.slice(contentTypeMatch[0].length + 2);
|
|
98
124
|
const file = new File([fileContent], filename[1], { type: contentTypeMatch[1] });
|
|
99
|
-
|
|
125
|
+
((_a = parsedBody[name]) !== null && _a !== void 0 ? _a : (parsedBody[name] = [])).push(file);
|
|
100
126
|
return;
|
|
101
127
|
}
|
|
102
|
-
|
|
103
|
-
|
|
128
|
+
;
|
|
129
|
+
((_b = parsedBody[name]) !== null && _b !== void 0 ? _b : (parsedBody[name] = [])).push(data);
|
|
104
130
|
});
|
|
105
131
|
return parsedBody;
|
|
106
132
|
};
|
|
@@ -110,15 +136,18 @@ const parseMultipart = (body, boundary, { fileCountLimit, fileSizeLimit = defaul
|
|
|
110
136
|
* Does not restrict total payload size by default.
|
|
111
137
|
* @param options
|
|
112
138
|
*/
|
|
113
|
-
const multipart = ({ payloadLimit = Number.POSITIVE_INFINITY, payloadLimitErrorFn, ...opts } = {}) =>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
139
|
+
const multipart = ({ payloadLimit = Number.POSITIVE_INFINITY, payloadLimitErrorFn, type, ...opts } = {}) => {
|
|
140
|
+
const parse = p((x, req) => {
|
|
141
|
+
const boundary = getBoundary(req.headers['content-type']);
|
|
142
|
+
if (boundary)
|
|
143
|
+
return parseMultipart(td.decode(x), boundary, opts);
|
|
144
|
+
return {};
|
|
145
|
+
}, payloadLimit, payloadLimitErrorFn);
|
|
146
|
+
return async (req, res, next) => {
|
|
147
|
+
if (hasBody(req.method) && checkType(req, type)) {
|
|
148
|
+
req.body = await parse(req, res, next);
|
|
149
|
+
}
|
|
150
|
+
next === null || next === void 0 ? void 0 : next();
|
|
151
|
+
};
|
|
123
152
|
};
|
|
124
153
|
export { custom, json, raw, text, urlencoded, multipart };
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
/**
|
|
3
|
+
* Request extension with a body
|
|
4
|
+
*/
|
|
5
|
+
export type ReqWithBody<T = any> = IncomingMessage & {
|
|
6
|
+
body?: T;
|
|
7
|
+
};
|
|
8
|
+
export type LimitErrorFn = (limit: number) => Error;
|
|
9
|
+
export type ParserOptions<T extends Record<string, any> = Record<string, any>> = Partial<{
|
|
10
|
+
/**
|
|
11
|
+
* Limit payload size (in bytes)
|
|
12
|
+
* @default 102400
|
|
13
|
+
*/
|
|
14
|
+
payloadLimit: number;
|
|
15
|
+
/**
|
|
16
|
+
* Custom error function for payload limit
|
|
17
|
+
*/
|
|
18
|
+
payloadLimitErrorFn: LimitErrorFn;
|
|
19
|
+
/**
|
|
20
|
+
* Middleware content type
|
|
21
|
+
*/
|
|
22
|
+
type: (req: IncomingMessage) => boolean;
|
|
23
|
+
}> & T;
|
|
24
|
+
export type NextFunction = (err?: any) => void;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "milliparsec",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "tiniest body parser in the universe",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,25 +21,25 @@
|
|
|
21
21
|
},
|
|
22
22
|
"exports": "./dist/index.js",
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@biomejs/biome": "
|
|
25
|
-
"@tinyhttp/app": "^2.
|
|
26
|
-
"@types/
|
|
24
|
+
"@biomejs/biome": "2.2.2",
|
|
25
|
+
"@tinyhttp/app": "^2.5.2",
|
|
26
|
+
"@types/express": "^5.0.6",
|
|
27
|
+
"@types/node": "^18.19.130",
|
|
27
28
|
"c8": "10.1.2",
|
|
29
|
+
"express": "^5.2.1",
|
|
28
30
|
"supertest-fetch": "^2.0.0",
|
|
29
|
-
"
|
|
30
|
-
"typescript": "^5.6.2"
|
|
31
|
+
"typescript": "^5.9.3"
|
|
31
32
|
},
|
|
32
33
|
"files": [
|
|
33
34
|
"dist"
|
|
34
35
|
],
|
|
35
|
-
"publishConfig": {
|
|
36
|
-
"provenance": true
|
|
37
|
-
},
|
|
38
36
|
"scripts": {
|
|
39
|
-
"test": "
|
|
37
|
+
"test": "node --test test.ts",
|
|
40
38
|
"test:coverage": "c8 --include=src pnpm test",
|
|
41
39
|
"test:report": "c8 report --reporter=text-lcov > coverage.lcov",
|
|
42
40
|
"build": "tsc -p tsconfig.build.json",
|
|
41
|
+
"prepublishOnly": "pnpm build && pnpm test",
|
|
43
42
|
"check": "biome check --write"
|
|
44
|
-
}
|
|
45
|
-
|
|
43
|
+
},
|
|
44
|
+
"packageManager": "pnpm@10.11.0"
|
|
45
|
+
}
|