milliparsec 5.1.0 → 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 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
- - ⚡ 40% faster than body-parser and 20x faster than formidable
23
+ - ⚡ ~15% faster than body-parser, ~4x faster than formidable
24
24
 
25
25
  ## Install
26
26
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Buffer } from 'node:buffer';
2
2
  import type { ServerResponse as Response } from 'node:http';
3
- import type { LimitErrorFn, NextFunction, ParserOptions, ReqWithBody } from './types.js';
4
- export * from './types.js';
5
- 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>;
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>;
6
6
  /**
7
7
  * Parse payload with a custom function
8
8
  * @param fn
package/dist/index.js CHANGED
@@ -1,77 +1,102 @@
1
1
  import { Buffer, File } from 'node:buffer';
2
- import { checkType, hasBody } from './utils.js';
3
- export * from './types.js';
2
+ import { checkType, hasBody } from "./utils.js";
3
+ export * from "./types.js";
4
4
  const defaultPayloadLimit = 102400; // 100KiB
5
5
  const defaultErrorFn = (payloadLimit) => new Error(`Payload too large. Limit: ${payloadLimit} bytes`);
6
6
  // Main function
7
- export const p = (fn, payloadLimit = defaultPayloadLimit, payloadLimitErrorFn = defaultErrorFn) => async (req, _res, next) => {
8
- try {
9
- const body = [];
10
- for await (const chunk of req) {
11
- const totalSize = body.reduce((total, buffer) => total + buffer.byteLength, 0);
12
- if (totalSize > payloadLimit)
13
- throw payloadLimitErrorFn(payloadLimit);
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 {
14
18
  body.push(chunk);
15
19
  }
16
- return fn(Buffer.concat(body));
17
- }
18
- catch (e) {
19
- next === null || next === void 0 ? void 0 : next(e);
20
- }
21
- };
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
+ });
22
35
  /**
23
36
  * Parse payload with a custom function
24
37
  * @param fn
25
38
  */
26
- const custom = (fn, type) => async (req, _res, next) => {
27
- if (hasBody(req.method) && checkType(req, type))
28
- req.body = await p(fn)(req, _res, next);
29
- next === null || next === void 0 ? void 0 : next();
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
+ };
30
46
  };
31
47
  /**
32
48
  * Parse JSON payload
33
49
  * @param options
34
50
  */
35
- const json = ({ payloadLimit, payloadLimitErrorFn, type, reviver } = {}) => async (req, res, next) => {
36
- if (hasBody(req.method) && checkType(req, type)) {
37
- req.body = await p((x) => {
38
- const str = td.decode(x);
39
- return str ? JSON.parse(str, reviver) : {};
40
- }, payloadLimit, payloadLimitErrorFn)(req, res, next);
41
- }
42
- next === null || next === void 0 ? void 0 : next();
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
+ };
43
59
  };
44
60
  /**
45
61
  * Parse raw payload
46
62
  * @param options
47
63
  */
48
- const raw = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => async (req, _res, next) => {
49
- if (hasBody(req.method) && checkType(req, type)) {
50
- req.body = await p((x) => x, payloadLimit, payloadLimitErrorFn)(req, _res, next);
51
- }
52
- next === null || next === void 0 ? void 0 : next();
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
+ };
53
72
  };
54
- const td = new TextDecoder();
55
73
  /**
56
74
  * Stringify request payload
57
75
  * @param param0
58
76
  * @returns
59
77
  */
60
- const text = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => async (req, _res, next) => {
61
- if (hasBody(req.method) && checkType(req, type)) {
62
- req.body = await p((x) => td.decode(x), payloadLimit, payloadLimitErrorFn)(req, _res, next);
63
- }
64
- next === null || next === void 0 ? void 0 : next();
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
+ };
65
86
  };
87
+ const td = new TextDecoder();
66
88
  /**
67
89
  * Parse urlencoded payload
68
90
  * @param options
69
91
  */
70
- const urlencoded = ({ payloadLimit, payloadLimitErrorFn, type } = {}) => async (req, _res, next) => {
71
- if (hasBody(req.method) && checkType(req, type)) {
72
- req.body = await p((x) => Object.fromEntries(new URLSearchParams(x.toString()).entries()), payloadLimit, payloadLimitErrorFn)(req, _res, next);
73
- }
74
- next === null || next === void 0 ? void 0 : next();
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
+ };
75
100
  };
76
101
  const getBoundary = (contentType) => {
77
102
  const match = /boundary=(.+);?/.exec(contentType);
@@ -85,6 +110,7 @@ const parseMultipart = (body, boundary, { fileCountLimit, fileSizeLimit = defaul
85
110
  if (fileCountLimit && parts.length > fileCountLimit)
86
111
  throw new Error(`Too many files. Limit: ${fileCountLimit}`);
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
- parsedBody[name] = parsedBody[name] ? [...parsedBody[name], file] : [file];
125
+ ((_a = parsedBody[name]) !== null && _a !== void 0 ? _a : (parsedBody[name] = [])).push(file);
100
126
  return;
101
127
  }
102
- parsedBody[name] = parsedBody[name] ? [...parsedBody[name], data] : [data];
103
- return;
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, type, ...opts } = {}) => async (req, res, next) => {
114
- if (hasBody(req.method) && checkType(req, type)) {
115
- req.body = await p((x) => {
116
- const boundary = getBoundary(req.headers['content-type']);
117
- if (boundary)
118
- return parseMultipart(td.decode(x), boundary, opts);
119
- return {};
120
- }, payloadLimit, payloadLimitErrorFn)(req, res, next);
121
- }
122
- next === null || next === void 0 ? void 0 : next();
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/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { IncomingMessage } from 'node:http';
2
- import type { ParserOptions } from './types.js';
2
+ import type { ParserOptions } from './types.ts';
3
3
  export declare const hasBody: (method: string) => boolean;
4
4
  export declare const checkType: (req: IncomingMessage, type: ParserOptions["type"]) => boolean;
package/dist/utils.js CHANGED
@@ -1,2 +1,3 @@
1
- export const hasBody = (method) => ['POST', 'PUT', 'PATCH', 'DELETE'].includes(method);
1
+ const bodyMethods = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
2
+ export const hasBody = (method) => bodyMethods.has(method);
2
3
  export const checkType = (req, type) => typeof type === 'function' ? type(req) : true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "milliparsec",
3
- "version": "5.1.0",
3
+ "version": "5.1.1",
4
4
  "description": "tiniest body parser in the universe",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,20 +22,19 @@
22
22
  "exports": "./dist/index.js",
23
23
  "devDependencies": {
24
24
  "@biomejs/biome": "2.2.2",
25
- "@tinyhttp/app": "^2.4.0",
26
- "@types/express": "^5.0.3",
27
- "@types/node": "^18",
25
+ "@tinyhttp/app": "^2.5.2",
26
+ "@types/express": "^5.0.6",
27
+ "@types/node": "^18.19.130",
28
28
  "c8": "10.1.2",
29
- "express": "^5.1.0",
29
+ "express": "^5.2.1",
30
30
  "supertest-fetch": "^2.0.0",
31
- "tsx": "^4.19.1",
32
- "typescript": "^5.6.2"
31
+ "typescript": "^5.9.3"
33
32
  },
34
33
  "files": [
35
34
  "dist"
36
35
  ],
37
36
  "scripts": {
38
- "test": "tsx --test test.ts",
37
+ "test": "node --test test.ts",
39
38
  "test:coverage": "c8 --include=src pnpm test",
40
39
  "test:report": "c8 report --reporter=text-lcov > coverage.lcov",
41
40
  "build": "tsc -p tsconfig.build.json",