nukejs 0.0.19 → 0.0.20

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.
@@ -225,7 +225,9 @@ function makeApiAdapterSource(handlerFilename) {
225
225
  return `import type { IncomingMessage, ServerResponse } from 'http';
226
226
  import * as mod from ${JSON.stringify("./" + handlerFilename)};
227
227
 
228
- function enhance(res: ServerResponse) {
228
+ const MAX_BODY_BYTES = 10 * 1024 * 1024; // 10 MB
229
+
230
+ function enhanceRes(res: ServerResponse) {
229
231
  (res as any).json = function (data: any, status = 200) {
230
232
  this.statusCode = status;
231
233
  this.setHeader('Content-Type', 'application/json');
@@ -235,26 +237,46 @@ function enhance(res: ServerResponse) {
235
237
  return res;
236
238
  }
237
239
 
238
- async function parseBody(req: IncomingMessage): Promise<any> {
239
- return new Promise((resolve, reject) => {
240
- let body = '';
241
- req.on('data', chunk => { body += chunk.toString(); });
242
- req.on('end', () => {
243
- try {
244
- resolve(body && req.headers['content-type']?.includes('application/json')
245
- ? JSON.parse(body) : body);
246
- } catch (e) { reject(e); }
247
- });
248
- req.on('error', reject);
240
+ function enhanceReq(req: IncomingMessage) {
241
+ const apiReq = req as any;
242
+ let bufferPromise: Promise<Buffer> | null = null;
243
+
244
+ const getBuffer = (): Promise<Buffer> => {
245
+ if (!bufferPromise) {
246
+ bufferPromise = new Promise((resolve, reject) => {
247
+ const chunks: Buffer[] = [];
248
+ let bytes = 0;
249
+ req.on('data', (chunk: Buffer) => {
250
+ bytes += chunk.length;
251
+ if (bytes > MAX_BODY_BYTES) { req.destroy(); return reject(new Error('Request body too large')); }
252
+ chunks.push(chunk);
253
+ });
254
+ req.on('end', () => resolve(Buffer.concat(chunks)));
255
+ req.on('error', reject);
256
+ });
257
+ }
258
+ return bufferPromise;
259
+ };
260
+
261
+ apiReq.buffer = () => getBuffer();
262
+ apiReq.text = () => getBuffer().then(buf => buf.toString('utf8'));
263
+ apiReq.json = () => getBuffer().then(buf => {
264
+ const parsed = JSON.parse(buf.toString('utf8'));
265
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
266
+ delete parsed.__proto__;
267
+ delete parsed.constructor;
268
+ }
269
+ return parsed;
249
270
  });
271
+
272
+ return apiReq;
250
273
  }
251
274
 
252
275
  export default async function handler(req: IncomingMessage, res: ServerResponse) {
253
276
  const method = (req.method || 'GET').toUpperCase();
254
- const apiRes = enhance(res);
255
- const apiReq = req as any;
277
+ const apiRes = enhanceRes(res);
278
+ const apiReq = enhanceReq(req);
256
279
 
257
- apiReq.body = await parseBody(req);
258
280
  // In production, route dynamic segments are injected as query-string keys by
259
281
  // the server entry, so params and query share the same parsed URL values.
260
282
  const qs = Object.fromEntries(new URL(req.url || '/', 'http://localhost').searchParams);
@@ -31,9 +31,9 @@ function discoverApiPrefixes(serverDir) {
31
31
  return prefixes;
32
32
  }
33
33
  const MAX_BODY_BYTES = 10 * 1024 * 1024;
34
- async function parseBody(req) {
34
+ function collectBuffer(req) {
35
35
  return new Promise((resolve, reject) => {
36
- let body = "";
36
+ const chunks = [];
37
37
  let bytes = 0;
38
38
  req.on("data", (chunk) => {
39
39
  bytes += chunk.length;
@@ -41,27 +41,31 @@ async function parseBody(req) {
41
41
  req.destroy();
42
42
  return reject(new Error("Request body too large"));
43
43
  }
44
- body += chunk.toString();
45
- });
46
- req.on("end", () => {
47
- try {
48
- if (body && req.headers["content-type"]?.includes("application/json")) {
49
- const parsed = JSON.parse(body);
50
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
51
- delete parsed.__proto__;
52
- delete parsed.constructor;
53
- }
54
- resolve(parsed);
55
- } else {
56
- resolve(body);
57
- }
58
- } catch (err) {
59
- reject(err);
60
- }
44
+ chunks.push(chunk);
61
45
  });
46
+ req.on("end", () => resolve(Buffer.concat(chunks)));
62
47
  req.on("error", reject);
63
48
  });
64
49
  }
50
+ function enhanceRequest(req) {
51
+ const apiReq = req;
52
+ let bufferPromise = null;
53
+ const getBuffer = () => {
54
+ if (!bufferPromise) bufferPromise = collectBuffer(req);
55
+ return bufferPromise;
56
+ };
57
+ apiReq.buffer = () => getBuffer();
58
+ apiReq.text = () => getBuffer().then((buf) => buf.toString("utf8"));
59
+ apiReq.json = () => getBuffer().then((buf) => {
60
+ const parsed = JSON.parse(buf.toString("utf8"));
61
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
62
+ delete parsed.__proto__;
63
+ delete parsed.constructor;
64
+ }
65
+ return parsed;
66
+ });
67
+ return apiReq;
68
+ }
65
69
  function parseQuery(url, port) {
66
70
  const query = {};
67
71
  new URL(url, `http://localhost:${port}`).searchParams.forEach((v, k) => {
@@ -145,8 +149,7 @@ function createApiHandler({ apiPrefixes, port, isDev }) {
145
149
  respondOptions(apiRes);
146
150
  return;
147
151
  }
148
- const apiReq = req;
149
- apiReq.body = await parseBody(req);
152
+ const apiReq = enhanceRequest(req);
150
153
  apiReq.params = params;
151
154
  apiReq.query = parseQuery(url, port);
152
155
  const apiModule = isDev ? await importFreshInDev(filePath) : await import(pathToFileURL(filePath).href);
@@ -165,8 +168,8 @@ function createApiHandler({ apiPrefixes, port, isDev }) {
165
168
  export {
166
169
  createApiHandler,
167
170
  discoverApiPrefixes,
171
+ enhanceRequest,
168
172
  enhanceResponse,
169
173
  matchApiPrefix,
170
- parseBody,
171
174
  parseQuery
172
175
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nukejs",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "A minimal, opinionated full-stack React framework on Node.js that server-renders everything and hydrates only interactive parts.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",