@wooksjs/event-http 0.5.25 → 0.6.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/dist/index.mjs CHANGED
@@ -1,9 +1,12 @@
1
- import { attachHook, createAsyncEventContext, useAsyncEventContext, useEventId, useEventLogger, useRouteParams } from "@wooksjs/event-core";
1
+ import { attachHook, createAsyncEventContext, useAsyncEventContext, useEventId, useEventLogger, useEventLogger as useEventLogger$1, useRouteParams } from "@wooksjs/event-core";
2
2
  import { Buffer as Buffer$1 } from "buffer";
3
+ import { Readable, pipeline } from "node:stream";
4
+ import { promisify } from "node:util";
5
+ import { createBrotliCompress, createBrotliDecompress, createDeflate, createGunzip, createGzip, createInflate } from "node:zlib";
3
6
  import { URLSearchParams } from "url";
4
7
  import http from "http";
5
8
  import { WooksAdapterBase } from "wooks";
6
- import { Readable } from "stream";
9
+ import { Readable as Readable$1 } from "stream";
7
10
 
8
11
  //#region packages/event-http/src/event-http.ts
9
12
  function createHttpContext(data, options) {
@@ -15,6 +18,10 @@ function createHttpContext(data, options) {
15
18
  options
16
19
  });
17
20
  }
21
+ /**
22
+ * Wrapper on useEventContext with HTTP event types
23
+ * @returns set of hooks { getCtx, restoreCtx, clearCtx, hookStore, getStore, setStore }
24
+ */
18
25
  function useHttpContext() {
19
26
  return useAsyncEventContext("HTTP");
20
27
  }
@@ -49,12 +56,12 @@ function convertTime(time, unit = "ms") {
49
56
  const units = {
50
57
  ms: 1,
51
58
  s: 1e3,
52
- m: 6e4,
53
- h: 36e5,
54
- d: 864e5,
55
- w: 6048e5,
56
- M: 2592e6,
57
- Y: 31536e6
59
+ m: 1e3 * 60,
60
+ h: 1e3 * 60 * 60,
61
+ d: 1e3 * 60 * 60 * 24,
62
+ w: 1e3 * 60 * 60 * 24 * 7,
63
+ M: 1e3 * 60 * 60 * 24 * 30,
64
+ Y: 1e3 * 60 * 60 * 24 * 365
58
65
  };
59
66
 
60
67
  //#endregion
@@ -80,35 +87,426 @@ const cookieAttrFunc = {
80
87
  sameSite: (v) => v ? `SameSite=${typeof v === "string" ? v : "Strict"}` : ""
81
88
  };
82
89
 
90
+ //#endregion
91
+ //#region packages/event-http/src/compressor/body-compressor.ts
92
+ const compressors = { identity: {
93
+ compress: (v) => v,
94
+ uncompress: (v) => v,
95
+ stream: {
96
+ compress: (data) => data,
97
+ uncompress: (data) => data
98
+ }
99
+ } };
100
+ function encodingSupportsStream(encodings) {
101
+ return encodings.every((enc) => compressors[enc]?.stream);
102
+ }
103
+ async function uncompressBody(encodings, compressed) {
104
+ let buf = compressed;
105
+ for (const enc of encodings.slice().reverse()) {
106
+ const c = compressors[enc];
107
+ if (!c) throw new Error(`Unsupported compression type "${enc}".`);
108
+ buf = await c.uncompress(buf);
109
+ }
110
+ return buf;
111
+ }
112
+ async function uncompressBodyStream(encodings, src) {
113
+ if (!encodingSupportsStream(encodings)) throw new Error("Some encodings lack a streaming decompressor");
114
+ let out = src;
115
+ for (const enc of Array.from(encodings).reverse()) out = await compressors[enc].stream.uncompress(out);
116
+ return out;
117
+ }
118
+
119
+ //#endregion
120
+ //#region packages/event-http/src/compressor/zlib-compressors.ts
121
+ const pipeline$1 = pipeline;
122
+ function iterableToReadable(src) {
123
+ return Readable.from(src, { objectMode: false });
124
+ }
125
+ function pump(src, transform) {
126
+ pipeline$1(iterableToReadable(src), transform, (err) => {
127
+ if (err) transform.destroy(err);
128
+ });
129
+ return transform;
130
+ }
131
+ function addStreamCodec(name, createDeflater, createInflater) {
132
+ const c = compressors[name] ?? (compressors[name] = {
133
+ compress: (v) => v,
134
+ uncompress: (v) => v
135
+ });
136
+ c.stream = {
137
+ compress: async (src) => pump(src, createDeflater()),
138
+ uncompress: async (src) => pump(src, createInflater())
139
+ };
140
+ }
141
+ addStreamCodec("gzip", createGzip, createGunzip);
142
+ addStreamCodec("deflate", createDeflate, createInflate);
143
+ addStreamCodec("br", createBrotliCompress, createBrotliDecompress);
144
+ let zp;
145
+ async function zlib() {
146
+ if (!zp) {
147
+ const { gzip, gunzip, deflate, inflate, brotliCompress, brotliDecompress } = await import("node:zlib");
148
+ zp = {
149
+ gzip: promisify(gzip),
150
+ gunzip: promisify(gunzip),
151
+ deflate: promisify(deflate),
152
+ inflate: promisify(inflate),
153
+ brotliCompress: promisify(brotliCompress),
154
+ brotliDecompress: promisify(brotliDecompress)
155
+ };
156
+ }
157
+ return zp;
158
+ }
159
+ compressors.gzip.compress = async (b) => (await zlib()).gzip(b);
160
+ compressors.gzip.uncompress = async (b) => (await zlib()).gunzip(b);
161
+ compressors.deflate.compress = async (b) => (await zlib()).deflate(b);
162
+ compressors.deflate.uncompress = async (b) => (await zlib()).inflate(b);
163
+ compressors.br.compress = async (b) => (await zlib()).brotliCompress(b);
164
+ compressors.br.uncompress = async (b) => (await zlib()).brotliDecompress(b);
165
+
166
+ //#endregion
167
+ //#region packages/event-http/src/response/renderer.ts
168
+ var BaseHttpResponseRenderer = class {
169
+ render(response) {
170
+ if (typeof response.body === "string" || typeof response.body === "boolean" || typeof response.body === "number") {
171
+ if (!response.getContentType()) response.setContentType("text/plain");
172
+ return response.body.toString();
173
+ }
174
+ if (response.body === void 0) return "";
175
+ if (response.body instanceof Uint8Array) return response.body;
176
+ if (typeof response.body === "object") {
177
+ if (!response.getContentType()) response.setContentType("application/json");
178
+ return JSON.stringify(response.body);
179
+ }
180
+ throw new Error(`Unsupported body format "${typeof response.body}"`);
181
+ }
182
+ };
183
+
184
+ //#endregion
185
+ //#region packages/event-http/src/utils/status-codes.ts
186
+ const httpStatusCodes = {
187
+ 100: "Continue",
188
+ 101: "Switching protocols",
189
+ 102: "Processing",
190
+ 103: "Early Hints",
191
+ 200: "OK",
192
+ 201: "Created",
193
+ 202: "Accepted",
194
+ 203: "Non-Authoritative Information",
195
+ 204: "No Content",
196
+ 205: "Reset Content",
197
+ 206: "Partial Content",
198
+ 207: "Multi-Status",
199
+ 208: "Already Reported",
200
+ 226: "IM Used",
201
+ 300: "Multiple Choices",
202
+ 301: "Moved Permanently",
203
+ 302: "Found (Previously \"Moved Temporarily\")",
204
+ 303: "See Other",
205
+ 304: "Not Modified",
206
+ 305: "Use Proxy",
207
+ 306: "Switch Proxy",
208
+ 307: "Temporary Redirect",
209
+ 308: "Permanent Redirect",
210
+ 400: "Bad Request",
211
+ 401: "Unauthorized",
212
+ 402: "Payment Required",
213
+ 403: "Forbidden",
214
+ 404: "Not Found",
215
+ 405: "Method Not Allowed",
216
+ 406: "Not Acceptable",
217
+ 407: "Proxy Authentication Required",
218
+ 408: "Request Timeout",
219
+ 409: "Conflict",
220
+ 410: "Gone",
221
+ 411: "Length Required",
222
+ 412: "Precondition Failed",
223
+ 413: "Payload Too Large",
224
+ 414: "URI Too Long",
225
+ 415: "Unsupported Media Type",
226
+ 416: "Range Not Satisfiable",
227
+ 417: "Expectation Failed",
228
+ 418: "I'm a Teapot",
229
+ 421: "Misdirected Request",
230
+ 422: "Unprocessable Entity",
231
+ 423: "Locked",
232
+ 424: "Failed Dependency",
233
+ 425: "Too Early",
234
+ 426: "Upgrade Required",
235
+ 428: "Precondition Required",
236
+ 429: "Too Many Requests",
237
+ 431: "Request Header Fields Too Large",
238
+ 451: "Unavailable For Legal Reasons",
239
+ 500: "Internal Server Error",
240
+ 501: "Not Implemented",
241
+ 502: "Bad Gateway",
242
+ 503: "Service Unavailable",
243
+ 504: "Gateway Timeout",
244
+ 505: "HTTP Version Not Supported",
245
+ 506: "Variant Also Negotiates",
246
+ 507: "Insufficient Storage",
247
+ 508: "Loop Detected",
248
+ 510: "Not Extended",
249
+ 511: "Network Authentication Required"
250
+ };
251
+ let EHttpStatusCode = /* @__PURE__ */ function(EHttpStatusCode$1) {
252
+ EHttpStatusCode$1[EHttpStatusCode$1["Continue"] = 100] = "Continue";
253
+ EHttpStatusCode$1[EHttpStatusCode$1["SwitchingProtocols"] = 101] = "SwitchingProtocols";
254
+ EHttpStatusCode$1[EHttpStatusCode$1["Processing"] = 102] = "Processing";
255
+ EHttpStatusCode$1[EHttpStatusCode$1["EarlyHints"] = 103] = "EarlyHints";
256
+ EHttpStatusCode$1[EHttpStatusCode$1["OK"] = 200] = "OK";
257
+ EHttpStatusCode$1[EHttpStatusCode$1["Created"] = 201] = "Created";
258
+ EHttpStatusCode$1[EHttpStatusCode$1["Accepted"] = 202] = "Accepted";
259
+ EHttpStatusCode$1[EHttpStatusCode$1["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
260
+ EHttpStatusCode$1[EHttpStatusCode$1["NoContent"] = 204] = "NoContent";
261
+ EHttpStatusCode$1[EHttpStatusCode$1["ResetContent"] = 205] = "ResetContent";
262
+ EHttpStatusCode$1[EHttpStatusCode$1["PartialContent"] = 206] = "PartialContent";
263
+ EHttpStatusCode$1[EHttpStatusCode$1["MultiStatus"] = 207] = "MultiStatus";
264
+ EHttpStatusCode$1[EHttpStatusCode$1["AlreadyReported"] = 208] = "AlreadyReported";
265
+ EHttpStatusCode$1[EHttpStatusCode$1["IMUsed"] = 226] = "IMUsed";
266
+ EHttpStatusCode$1[EHttpStatusCode$1["MultipleChoices"] = 300] = "MultipleChoices";
267
+ EHttpStatusCode$1[EHttpStatusCode$1["MovedPermanently"] = 301] = "MovedPermanently";
268
+ EHttpStatusCode$1[EHttpStatusCode$1["Found"] = 302] = "Found";
269
+ EHttpStatusCode$1[EHttpStatusCode$1["SeeOther"] = 303] = "SeeOther";
270
+ EHttpStatusCode$1[EHttpStatusCode$1["NotModified"] = 304] = "NotModified";
271
+ EHttpStatusCode$1[EHttpStatusCode$1["UseProxy"] = 305] = "UseProxy";
272
+ EHttpStatusCode$1[EHttpStatusCode$1["SwitchProxy"] = 306] = "SwitchProxy";
273
+ EHttpStatusCode$1[EHttpStatusCode$1["TemporaryRedirect"] = 307] = "TemporaryRedirect";
274
+ EHttpStatusCode$1[EHttpStatusCode$1["PermanentRedirect"] = 308] = "PermanentRedirect";
275
+ EHttpStatusCode$1[EHttpStatusCode$1["BadRequest"] = 400] = "BadRequest";
276
+ EHttpStatusCode$1[EHttpStatusCode$1["Unauthorized"] = 401] = "Unauthorized";
277
+ EHttpStatusCode$1[EHttpStatusCode$1["PaymentRequired"] = 402] = "PaymentRequired";
278
+ EHttpStatusCode$1[EHttpStatusCode$1["Forbidden"] = 403] = "Forbidden";
279
+ EHttpStatusCode$1[EHttpStatusCode$1["NotFound"] = 404] = "NotFound";
280
+ EHttpStatusCode$1[EHttpStatusCode$1["MethodNotAllowed"] = 405] = "MethodNotAllowed";
281
+ EHttpStatusCode$1[EHttpStatusCode$1["NotAcceptable"] = 406] = "NotAcceptable";
282
+ EHttpStatusCode$1[EHttpStatusCode$1["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
283
+ EHttpStatusCode$1[EHttpStatusCode$1["RequestTimeout"] = 408] = "RequestTimeout";
284
+ EHttpStatusCode$1[EHttpStatusCode$1["Conflict"] = 409] = "Conflict";
285
+ EHttpStatusCode$1[EHttpStatusCode$1["Gone"] = 410] = "Gone";
286
+ EHttpStatusCode$1[EHttpStatusCode$1["LengthRequired"] = 411] = "LengthRequired";
287
+ EHttpStatusCode$1[EHttpStatusCode$1["PreconditionFailed"] = 412] = "PreconditionFailed";
288
+ EHttpStatusCode$1[EHttpStatusCode$1["PayloadTooLarge"] = 413] = "PayloadTooLarge";
289
+ EHttpStatusCode$1[EHttpStatusCode$1["URITooLong"] = 414] = "URITooLong";
290
+ EHttpStatusCode$1[EHttpStatusCode$1["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
291
+ EHttpStatusCode$1[EHttpStatusCode$1["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
292
+ EHttpStatusCode$1[EHttpStatusCode$1["ExpectationFailed"] = 417] = "ExpectationFailed";
293
+ EHttpStatusCode$1[EHttpStatusCode$1["ImATeapot"] = 418] = "ImATeapot";
294
+ EHttpStatusCode$1[EHttpStatusCode$1["MisdirectedRequest"] = 421] = "MisdirectedRequest";
295
+ EHttpStatusCode$1[EHttpStatusCode$1["UnprocessableEntity"] = 422] = "UnprocessableEntity";
296
+ EHttpStatusCode$1[EHttpStatusCode$1["Locked"] = 423] = "Locked";
297
+ EHttpStatusCode$1[EHttpStatusCode$1["FailedDependency"] = 424] = "FailedDependency";
298
+ EHttpStatusCode$1[EHttpStatusCode$1["TooEarly"] = 425] = "TooEarly";
299
+ EHttpStatusCode$1[EHttpStatusCode$1["UpgradeRequired"] = 426] = "UpgradeRequired";
300
+ EHttpStatusCode$1[EHttpStatusCode$1["PreconditionRequired"] = 428] = "PreconditionRequired";
301
+ EHttpStatusCode$1[EHttpStatusCode$1["TooManyRequests"] = 429] = "TooManyRequests";
302
+ EHttpStatusCode$1[EHttpStatusCode$1["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
303
+ EHttpStatusCode$1[EHttpStatusCode$1["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
304
+ EHttpStatusCode$1[EHttpStatusCode$1["InternalServerError"] = 500] = "InternalServerError";
305
+ EHttpStatusCode$1[EHttpStatusCode$1["NotImplemented"] = 501] = "NotImplemented";
306
+ EHttpStatusCode$1[EHttpStatusCode$1["BadGateway"] = 502] = "BadGateway";
307
+ EHttpStatusCode$1[EHttpStatusCode$1["ServiceUnavailable"] = 503] = "ServiceUnavailable";
308
+ EHttpStatusCode$1[EHttpStatusCode$1["GatewayTimeout"] = 504] = "GatewayTimeout";
309
+ EHttpStatusCode$1[EHttpStatusCode$1["HTTPVersionNotSupported"] = 505] = "HTTPVersionNotSupported";
310
+ EHttpStatusCode$1[EHttpStatusCode$1["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
311
+ EHttpStatusCode$1[EHttpStatusCode$1["InsufficientStorage"] = 507] = "InsufficientStorage";
312
+ EHttpStatusCode$1[EHttpStatusCode$1["LoopDetected"] = 508] = "LoopDetected";
313
+ EHttpStatusCode$1[EHttpStatusCode$1["NotExtended"] = 510] = "NotExtended";
314
+ EHttpStatusCode$1[EHttpStatusCode$1["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
315
+ return EHttpStatusCode$1;
316
+ }({});
317
+
318
+ //#endregion
319
+ //#region packages/event-http/src/errors/error-renderer.ts
320
+ const preStyles = "font-family: monospace;width: 100%;max-width: 900px;padding: 10px;margin: 20px auto;border-radius: 8px;background-color: #494949;box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);";
321
+ var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
322
+ renderHtml(response) {
323
+ const data = response.body || {};
324
+ response.setContentType("text/html");
325
+ const keys = Object.keys(data).filter((key) => ![
326
+ "statusCode",
327
+ "error",
328
+ "message"
329
+ ].includes(key));
330
+ return `<html style="background-color: #333; color: #bbb;"><head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head><body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center><center><h4>${data.message}</h1></center><hr color="#666"><center style="color: #666;"> Wooks v0.6.0 </center>${keys.length > 0 ? `<pre style="${preStyles}">${JSON.stringify({
331
+ ...data,
332
+ statusCode: void 0,
333
+ message: void 0,
334
+ error: void 0
335
+ }, null, " ")}</pre>` : ""}</body></html>`;
336
+ }
337
+ renderText(response) {
338
+ const data = response.body || {};
339
+ response.setContentType("text/plain");
340
+ const keys = Object.keys(data).filter((key) => ![
341
+ "statusCode",
342
+ "error",
343
+ "message"
344
+ ].includes(key));
345
+ return `${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}\n\n${keys.length > 0 ? `${JSON.stringify({
346
+ ...data,
347
+ statusCode: void 0,
348
+ message: void 0,
349
+ error: void 0
350
+ }, null, " ")}` : ""}`;
351
+ }
352
+ renderJson(response) {
353
+ const data = response.body || {};
354
+ response.setContentType("application/json");
355
+ const keys = Object.keys(data).filter((key) => ![
356
+ "statusCode",
357
+ "error",
358
+ "message"
359
+ ].includes(key));
360
+ return `{"statusCode":${escapeQuotes(data.statusCode)},"error":"${escapeQuotes(data.error)}","message":"${escapeQuotes(data.message)}"${keys.length > 0 ? `,${keys.map((k) => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`).join(",")}` : ""}}`;
361
+ }
362
+ render(response) {
363
+ const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
364
+ response.status = response.body?.statusCode || 500;
365
+ if (acceptsJson()) return this.renderJson(response);
366
+ else if (acceptsHtml()) return this.renderHtml(response);
367
+ else if (acceptsText()) return this.renderText(response);
368
+ else return this.renderJson(response);
369
+ }
370
+ };
371
+ function escapeQuotes(s) {
372
+ return (typeof s === "number" ? s : s || "").toString().replace(/"/gu, "\\\"");
373
+ }
374
+
375
+ //#endregion
376
+ //#region packages/event-http/src/errors/http-error.ts
377
+ var HttpError = class extends Error {
378
+ name = "HttpError";
379
+ constructor(code = 500, _body = "") {
380
+ super(typeof _body === "string" ? _body : _body.message);
381
+ this.code = code;
382
+ this._body = _body;
383
+ }
384
+ get body() {
385
+ return typeof this._body === "string" ? {
386
+ statusCode: this.code,
387
+ message: this.message,
388
+ error: httpStatusCodes[this.code]
389
+ } : {
390
+ ...this._body,
391
+ statusCode: this.code,
392
+ message: this.message,
393
+ error: httpStatusCodes[this.code]
394
+ };
395
+ }
396
+ renderer;
397
+ attachRenderer(renderer) {
398
+ this.renderer = renderer;
399
+ }
400
+ getRenderer() {
401
+ return this.renderer;
402
+ }
403
+ };
404
+
83
405
  //#endregion
84
406
  //#region packages/event-http/src/composables/request.ts
85
407
  const xForwardedFor = "x-forwarded-for";
408
+ const DEFAULT_LIMITS = {
409
+ maxCompressed: 1 * 1024 * 1024,
410
+ maxInflated: 10 * 1024 * 1024,
411
+ maxRatio: 100,
412
+ readTimeoutMs: 1e4
413
+ };
86
414
  function useRequest() {
87
415
  const { store } = useHttpContext();
88
- const { init } = store("request");
416
+ const { init, get, set } = store("request");
89
417
  const event = store("event");
90
418
  const req = event.get("req");
91
- const rawBody = () => init("rawBody", () => new Promise((resolve, reject) => {
92
- let body = Buffer$1.from("");
93
- req.on("data", (chunk) => {
94
- body = Buffer$1.concat([body, chunk]);
95
- });
96
- req.on("error", (err) => {
97
- reject(err);
98
- });
99
- req.on("end", () => {
100
- resolve(body);
101
- });
102
- }));
419
+ const contentEncoding = req.headers["content-encoding"];
420
+ const contentEncodings = () => init("contentEncodings", () => (contentEncoding || "").split(",").map((p) => p.trim()).filter((p) => !!p));
421
+ const isCompressed = () => init("isCompressed", () => {
422
+ const parts = contentEncodings();
423
+ for (const p of parts) if ([
424
+ "deflate",
425
+ "gzip",
426
+ "br"
427
+ ].includes(p)) return true;
428
+ return false;
429
+ });
430
+ const getMaxCompressed = () => get("maxCompressed") ?? DEFAULT_LIMITS.maxCompressed;
431
+ const setMaxCompressed = (limit) => set("maxCompressed", limit);
432
+ const getReadTimeoutMs = () => get("readTimeoutMs") ?? DEFAULT_LIMITS.readTimeoutMs;
433
+ const setReadTimeoutMs = (limit) => set("readTimeoutMs", limit);
434
+ const getMaxInflated = () => get("maxInflated") ?? DEFAULT_LIMITS.maxInflated;
435
+ const setMaxInflated = (limit) => set("maxInflated", limit);
436
+ const rawBody = () => init("rawBody", async () => {
437
+ const encs = contentEncodings();
438
+ const isZip = isCompressed();
439
+ const streamable = isZip && encodingSupportsStream(encs);
440
+ const maxCompressed = getMaxCompressed();
441
+ const maxInflated = getMaxInflated();
442
+ const timeoutMs = getReadTimeoutMs();
443
+ const cl = Number(req.headers["content-length"] ?? 0);
444
+ const upfrontLimit = isZip ? maxCompressed : maxInflated;
445
+ if (cl && cl > upfrontLimit) throw new HttpError(413, "Payload Too Large");
446
+ for (const enc of encs) if (!compressors[enc]) throw new HttpError(415, `Unsupported Content-Encoding "${enc}"`);
447
+ let timer = null;
448
+ function resetTimer() {
449
+ if (timeoutMs === 0) return;
450
+ clearTimer();
451
+ timer = setTimeout(() => {
452
+ clearTimer();
453
+ req.destroy();
454
+ }, timeoutMs);
455
+ }
456
+ function clearTimer() {
457
+ if (timer) {
458
+ clearTimeout(timer);
459
+ timer = null;
460
+ }
461
+ }
462
+ let rawBytes = 0;
463
+ async function* limitedCompressed() {
464
+ resetTimer();
465
+ try {
466
+ for await (const chunk of req) {
467
+ rawBytes += chunk.length;
468
+ if (rawBytes > upfrontLimit) {
469
+ req.destroy();
470
+ throw new HttpError(413, "Payload Too Large");
471
+ }
472
+ resetTimer();
473
+ yield chunk;
474
+ }
475
+ } finally {
476
+ clearTimer();
477
+ }
478
+ }
479
+ let stream = limitedCompressed();
480
+ if (streamable) stream = await uncompressBodyStream(encs, stream);
481
+ const chunks = [];
482
+ let inflatedBytes = 0;
483
+ try {
484
+ for await (const chunk of stream) {
485
+ inflatedBytes += chunk.length;
486
+ if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
487
+ chunks.push(chunk);
488
+ }
489
+ } catch (error) {
490
+ if (error instanceof HttpError) throw error;
491
+ throw new HttpError(408, "Request body timeout");
492
+ }
493
+ let body = Buffer$1.concat(chunks);
494
+ if (!streamable && isZip) {
495
+ body = await uncompressBody(encs, body);
496
+ inflatedBytes = body.byteLength;
497
+ if (inflatedBytes > maxInflated) throw new HttpError(413, "Inflated body too large");
498
+ }
499
+ return body;
500
+ });
103
501
  const reqId = useEventId().getId;
104
502
  const forwardedIp = () => init("forwardedIp", () => {
105
503
  if (typeof req.headers[xForwardedFor] === "string" && req.headers[xForwardedFor]) return req.headers[xForwardedFor].split(",").shift()?.trim();
106
- else return "";
504
+ else return "";
107
505
  });
108
506
  const remoteIp = () => init("remoteIp", () => req.socket.remoteAddress || req.connection.remoteAddress || "");
109
507
  function getIp(options) {
110
508
  if (options?.trustProxy) return forwardedIp() || getIp();
111
- else return remoteIp();
509
+ else return remoteIp();
112
510
  }
113
511
  const getIpList = () => init("ipList", () => ({
114
512
  remoteIp: req.socket.remoteAddress || req.connection.remoteAddress || "",
@@ -122,7 +520,14 @@ else return remoteIp();
122
520
  rawBody,
123
521
  reqId,
124
522
  getIp,
125
- getIpList
523
+ getIpList,
524
+ isCompressed,
525
+ getMaxCompressed,
526
+ setMaxCompressed,
527
+ getReadTimeoutMs,
528
+ setReadTimeoutMs,
529
+ getMaxInflated,
530
+ setMaxInflated
126
531
  };
127
532
  }
128
533
 
@@ -282,7 +687,7 @@ function useAuthorization() {
282
687
  function renderCacheControl(data) {
283
688
  let attrs = "";
284
689
  for (const [a, v] of Object.entries(data)) {
285
- if (v === undefined) continue;
690
+ if (v === void 0) continue;
286
691
  const func = cacheControlFunc[a];
287
692
  if (typeof func === "function") {
288
693
  const val = func(v);
@@ -363,11 +768,15 @@ function useStatus() {
363
768
  //#region packages/event-http/src/utils/url-search-params.ts
364
769
  var WooksURLSearchParams = class extends URLSearchParams {
365
770
  toJson() {
366
- const json = {};
771
+ const json = Object.create(null);
367
772
  for (const [key, value] of this.entries()) if (isArrayParam(key)) {
368
773
  const a = json[key] = json[key] || [];
369
774
  a.push(value);
370
- } else json[key] = value;
775
+ } else {
776
+ if (key === "__proto__") throw new HttpError(400, `Illegal key name "${key}"`);
777
+ if (key in json) throw new HttpError(400, `Duplicate key "${key}"`);
778
+ json[key] = value;
779
+ }
371
780
  return json;
372
781
  }
373
782
  };
@@ -393,245 +802,6 @@ function useSearchParams() {
393
802
  };
394
803
  }
395
804
 
396
- //#endregion
397
- //#region packages/event-http/src/response/renderer.ts
398
- var BaseHttpResponseRenderer = class {
399
- render(response) {
400
- if (typeof response.body === "string" || typeof response.body === "boolean" || typeof response.body === "number") {
401
- if (!response.getContentType()) response.setContentType("text/plain");
402
- return response.body.toString();
403
- }
404
- if (response.body === undefined) return "";
405
- if (response.body instanceof Uint8Array) return response.body;
406
- if (typeof response.body === "object") {
407
- if (!response.getContentType()) response.setContentType("application/json");
408
- return JSON.stringify(response.body);
409
- }
410
- throw new Error(`Unsupported body format "${typeof response.body}"`);
411
- }
412
- };
413
-
414
- //#endregion
415
- //#region packages/event-http/src/utils/status-codes.ts
416
- const httpStatusCodes = {
417
- 100: "Continue",
418
- 101: "Switching protocols",
419
- 102: "Processing",
420
- 103: "Early Hints",
421
- 200: "OK",
422
- 201: "Created",
423
- 202: "Accepted",
424
- 203: "Non-Authoritative Information",
425
- 204: "No Content",
426
- 205: "Reset Content",
427
- 206: "Partial Content",
428
- 207: "Multi-Status",
429
- 208: "Already Reported",
430
- 226: "IM Used",
431
- 300: "Multiple Choices",
432
- 301: "Moved Permanently",
433
- 302: "Found (Previously \"Moved Temporarily\")",
434
- 303: "See Other",
435
- 304: "Not Modified",
436
- 305: "Use Proxy",
437
- 306: "Switch Proxy",
438
- 307: "Temporary Redirect",
439
- 308: "Permanent Redirect",
440
- 400: "Bad Request",
441
- 401: "Unauthorized",
442
- 402: "Payment Required",
443
- 403: "Forbidden",
444
- 404: "Not Found",
445
- 405: "Method Not Allowed",
446
- 406: "Not Acceptable",
447
- 407: "Proxy Authentication Required",
448
- 408: "Request Timeout",
449
- 409: "Conflict",
450
- 410: "Gone",
451
- 411: "Length Required",
452
- 412: "Precondition Failed",
453
- 413: "Payload Too Large",
454
- 414: "URI Too Long",
455
- 415: "Unsupported Media Type",
456
- 416: "Range Not Satisfiable",
457
- 417: "Expectation Failed",
458
- 418: "I'm a Teapot",
459
- 421: "Misdirected Request",
460
- 422: "Unprocessable Entity",
461
- 423: "Locked",
462
- 424: "Failed Dependency",
463
- 425: "Too Early",
464
- 426: "Upgrade Required",
465
- 428: "Precondition Required",
466
- 429: "Too Many Requests",
467
- 431: "Request Header Fields Too Large",
468
- 451: "Unavailable For Legal Reasons",
469
- 500: "Internal Server Error",
470
- 501: "Not Implemented",
471
- 502: "Bad Gateway",
472
- 503: "Service Unavailable",
473
- 504: "Gateway Timeout",
474
- 505: "HTTP Version Not Supported",
475
- 506: "Variant Also Negotiates",
476
- 507: "Insufficient Storage",
477
- 508: "Loop Detected",
478
- 510: "Not Extended",
479
- 511: "Network Authentication Required"
480
- };
481
- let EHttpStatusCode = function(EHttpStatusCode$1) {
482
- EHttpStatusCode$1[EHttpStatusCode$1["Continue"] = 100] = "Continue";
483
- EHttpStatusCode$1[EHttpStatusCode$1["SwitchingProtocols"] = 101] = "SwitchingProtocols";
484
- EHttpStatusCode$1[EHttpStatusCode$1["Processing"] = 102] = "Processing";
485
- EHttpStatusCode$1[EHttpStatusCode$1["EarlyHints"] = 103] = "EarlyHints";
486
- EHttpStatusCode$1[EHttpStatusCode$1["OK"] = 200] = "OK";
487
- EHttpStatusCode$1[EHttpStatusCode$1["Created"] = 201] = "Created";
488
- EHttpStatusCode$1[EHttpStatusCode$1["Accepted"] = 202] = "Accepted";
489
- EHttpStatusCode$1[EHttpStatusCode$1["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
490
- EHttpStatusCode$1[EHttpStatusCode$1["NoContent"] = 204] = "NoContent";
491
- EHttpStatusCode$1[EHttpStatusCode$1["ResetContent"] = 205] = "ResetContent";
492
- EHttpStatusCode$1[EHttpStatusCode$1["PartialContent"] = 206] = "PartialContent";
493
- EHttpStatusCode$1[EHttpStatusCode$1["MultiStatus"] = 207] = "MultiStatus";
494
- EHttpStatusCode$1[EHttpStatusCode$1["AlreadyReported"] = 208] = "AlreadyReported";
495
- EHttpStatusCode$1[EHttpStatusCode$1["IMUsed"] = 226] = "IMUsed";
496
- EHttpStatusCode$1[EHttpStatusCode$1["MultipleChoices"] = 300] = "MultipleChoices";
497
- EHttpStatusCode$1[EHttpStatusCode$1["MovedPermanently"] = 301] = "MovedPermanently";
498
- EHttpStatusCode$1[EHttpStatusCode$1["Found"] = 302] = "Found";
499
- EHttpStatusCode$1[EHttpStatusCode$1["SeeOther"] = 303] = "SeeOther";
500
- EHttpStatusCode$1[EHttpStatusCode$1["NotModified"] = 304] = "NotModified";
501
- EHttpStatusCode$1[EHttpStatusCode$1["UseProxy"] = 305] = "UseProxy";
502
- EHttpStatusCode$1[EHttpStatusCode$1["SwitchProxy"] = 306] = "SwitchProxy";
503
- EHttpStatusCode$1[EHttpStatusCode$1["TemporaryRedirect"] = 307] = "TemporaryRedirect";
504
- EHttpStatusCode$1[EHttpStatusCode$1["PermanentRedirect"] = 308] = "PermanentRedirect";
505
- EHttpStatusCode$1[EHttpStatusCode$1["BadRequest"] = 400] = "BadRequest";
506
- EHttpStatusCode$1[EHttpStatusCode$1["Unauthorized"] = 401] = "Unauthorized";
507
- EHttpStatusCode$1[EHttpStatusCode$1["PaymentRequired"] = 402] = "PaymentRequired";
508
- EHttpStatusCode$1[EHttpStatusCode$1["Forbidden"] = 403] = "Forbidden";
509
- EHttpStatusCode$1[EHttpStatusCode$1["NotFound"] = 404] = "NotFound";
510
- EHttpStatusCode$1[EHttpStatusCode$1["MethodNotAllowed"] = 405] = "MethodNotAllowed";
511
- EHttpStatusCode$1[EHttpStatusCode$1["NotAcceptable"] = 406] = "NotAcceptable";
512
- EHttpStatusCode$1[EHttpStatusCode$1["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
513
- EHttpStatusCode$1[EHttpStatusCode$1["RequestTimeout"] = 408] = "RequestTimeout";
514
- EHttpStatusCode$1[EHttpStatusCode$1["Conflict"] = 409] = "Conflict";
515
- EHttpStatusCode$1[EHttpStatusCode$1["Gone"] = 410] = "Gone";
516
- EHttpStatusCode$1[EHttpStatusCode$1["LengthRequired"] = 411] = "LengthRequired";
517
- EHttpStatusCode$1[EHttpStatusCode$1["PreconditionFailed"] = 412] = "PreconditionFailed";
518
- EHttpStatusCode$1[EHttpStatusCode$1["PayloadTooLarge"] = 413] = "PayloadTooLarge";
519
- EHttpStatusCode$1[EHttpStatusCode$1["URITooLong"] = 414] = "URITooLong";
520
- EHttpStatusCode$1[EHttpStatusCode$1["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
521
- EHttpStatusCode$1[EHttpStatusCode$1["RangeNotSatisfiable"] = 416] = "RangeNotSatisfiable";
522
- EHttpStatusCode$1[EHttpStatusCode$1["ExpectationFailed"] = 417] = "ExpectationFailed";
523
- EHttpStatusCode$1[EHttpStatusCode$1["ImATeapot"] = 418] = "ImATeapot";
524
- EHttpStatusCode$1[EHttpStatusCode$1["MisdirectedRequest"] = 421] = "MisdirectedRequest";
525
- EHttpStatusCode$1[EHttpStatusCode$1["UnprocessableEntity"] = 422] = "UnprocessableEntity";
526
- EHttpStatusCode$1[EHttpStatusCode$1["Locked"] = 423] = "Locked";
527
- EHttpStatusCode$1[EHttpStatusCode$1["FailedDependency"] = 424] = "FailedDependency";
528
- EHttpStatusCode$1[EHttpStatusCode$1["TooEarly"] = 425] = "TooEarly";
529
- EHttpStatusCode$1[EHttpStatusCode$1["UpgradeRequired"] = 426] = "UpgradeRequired";
530
- EHttpStatusCode$1[EHttpStatusCode$1["PreconditionRequired"] = 428] = "PreconditionRequired";
531
- EHttpStatusCode$1[EHttpStatusCode$1["TooManyRequests"] = 429] = "TooManyRequests";
532
- EHttpStatusCode$1[EHttpStatusCode$1["RequestHeaderFieldsTooLarge"] = 431] = "RequestHeaderFieldsTooLarge";
533
- EHttpStatusCode$1[EHttpStatusCode$1["UnavailableForLegalReasons"] = 451] = "UnavailableForLegalReasons";
534
- EHttpStatusCode$1[EHttpStatusCode$1["InternalServerError"] = 500] = "InternalServerError";
535
- EHttpStatusCode$1[EHttpStatusCode$1["NotImplemented"] = 501] = "NotImplemented";
536
- EHttpStatusCode$1[EHttpStatusCode$1["BadGateway"] = 502] = "BadGateway";
537
- EHttpStatusCode$1[EHttpStatusCode$1["ServiceUnavailable"] = 503] = "ServiceUnavailable";
538
- EHttpStatusCode$1[EHttpStatusCode$1["GatewayTimeout"] = 504] = "GatewayTimeout";
539
- EHttpStatusCode$1[EHttpStatusCode$1["HTTPVersionNotSupported"] = 505] = "HTTPVersionNotSupported";
540
- EHttpStatusCode$1[EHttpStatusCode$1["VariantAlsoNegotiates"] = 506] = "VariantAlsoNegotiates";
541
- EHttpStatusCode$1[EHttpStatusCode$1["InsufficientStorage"] = 507] = "InsufficientStorage";
542
- EHttpStatusCode$1[EHttpStatusCode$1["LoopDetected"] = 508] = "LoopDetected";
543
- EHttpStatusCode$1[EHttpStatusCode$1["NotExtended"] = 510] = "NotExtended";
544
- EHttpStatusCode$1[EHttpStatusCode$1["NetworkAuthenticationRequired"] = 511] = "NetworkAuthenticationRequired";
545
- return EHttpStatusCode$1;
546
- }({});
547
-
548
- //#endregion
549
- //#region packages/event-http/src/errors/error-renderer.ts
550
- const preStyles = "font-family: monospace;width: 100%;max-width: 900px;padding: 10px;margin: 20px auto;border-radius: 8px;background-color: #494949;box-shadow: 0px 0px 3px 2px rgb(255 255 255 / 20%);";
551
- var HttpErrorRenderer = class extends BaseHttpResponseRenderer {
552
- renderHtml(response) {
553
- const data = response.body || {};
554
- response.setContentType("text/html");
555
- const keys = Object.keys(data).filter((key) => ![
556
- "statusCode",
557
- "error",
558
- "message"
559
- ].includes(key));
560
- return "<html style=\"background-color: #333; color: #bbb;\">" + `<head><title>${data.statusCode} ${httpStatusCodes[data.statusCode]}</title></head>` + `<body><center><h1>${data.statusCode} ${httpStatusCodes[data.statusCode]}</h1></center>` + `<center><h4>${data.message}</h1></center><hr color="#666">` + `<center style="color: #666;"> Wooks v${"0.5.24"} </center>` + `${keys.length > 0 ? `<pre style="${preStyles}">${JSON.stringify({
561
- ...data,
562
- statusCode: undefined,
563
- message: undefined,
564
- error: undefined
565
- }, null, " ")}</pre>` : ""}` + "</body></html>";
566
- }
567
- renderText(response) {
568
- const data = response.body || {};
569
- response.setContentType("text/plain");
570
- const keys = Object.keys(data).filter((key) => ![
571
- "statusCode",
572
- "error",
573
- "message"
574
- ].includes(key));
575
- return `${data.statusCode} ${httpStatusCodes[data.statusCode]}\n${data.message}` + `\n\n${keys.length > 0 ? `${JSON.stringify({
576
- ...data,
577
- statusCode: undefined,
578
- message: undefined,
579
- error: undefined
580
- }, null, " ")}` : ""}`;
581
- }
582
- renderJson(response) {
583
- const data = response.body || {};
584
- response.setContentType("application/json");
585
- const keys = Object.keys(data).filter((key) => ![
586
- "statusCode",
587
- "error",
588
- "message"
589
- ].includes(key));
590
- return `{"statusCode":${escapeQuotes(data.statusCode)},` + `"error":"${escapeQuotes(data.error)}",` + `"message":"${escapeQuotes(data.message)}"` + `${keys.length > 0 ? `,${keys.map((k) => `"${escapeQuotes(k)}":${JSON.stringify(data[k])}`).join(",")}` : ""}}`;
591
- }
592
- render(response) {
593
- const { acceptsJson, acceptsText, acceptsHtml } = useAccept();
594
- response.status = response.body?.statusCode || 500;
595
- if (acceptsJson()) return this.renderJson(response);
596
- else if (acceptsHtml()) return this.renderHtml(response);
597
- else if (acceptsText()) return this.renderText(response);
598
- else return this.renderJson(response);
599
- }
600
- };
601
- function escapeQuotes(s) {
602
- return (typeof s === "number" ? s : s || "").toString().replace(/"/gu, "\\\"");
603
- }
604
-
605
- //#endregion
606
- //#region packages/event-http/src/errors/http-error.ts
607
- var HttpError = class extends Error {
608
- name = "HttpError";
609
- constructor(code = 500, _body = "") {
610
- super(typeof _body === "string" ? _body : _body.message);
611
- this.code = code;
612
- this._body = _body;
613
- }
614
- get body() {
615
- return typeof this._body === "string" ? {
616
- statusCode: this.code,
617
- message: this.message,
618
- error: httpStatusCodes[this.code]
619
- } : {
620
- ...this._body,
621
- statusCode: this.code,
622
- message: this.message,
623
- error: httpStatusCodes[this.code]
624
- };
625
- }
626
- renderer;
627
- attachRenderer(renderer) {
628
- this.renderer = renderer;
629
- }
630
- getRenderer() {
631
- return this.renderer;
632
- }
633
- };
634
-
635
805
  //#endregion
636
806
  //#region packages/event-http/src/response/core.ts
637
807
  const defaultStatus = {
@@ -738,11 +908,11 @@ var BaseHttpResponse = class {
738
908
  async respond() {
739
909
  const { rawResponse, hasResponded } = useResponse();
740
910
  const { method, rawRequest } = useRequest();
741
- const logger = useEventLogger("http-response") || console;
911
+ const logger = useEventLogger$1("http-response") || console;
742
912
  if (hasResponded()) this.panic("The response was already sent.", logger);
743
913
  this.mergeHeaders();
744
914
  const res = rawResponse();
745
- if (this.body instanceof Readable) {
915
+ if (this.body instanceof Readable$1) {
746
916
  const stream = this.body;
747
917
  this.mergeStatus("ok");
748
918
  res.writeHead(this.status, { ...this._headers });
@@ -760,14 +930,14 @@ var BaseHttpResponse = class {
760
930
  });
761
931
  stream.on("close", () => {
762
932
  stream.destroy();
763
- resolve(undefined);
933
+ resolve(void 0);
764
934
  });
765
935
  stream.pipe(res);
766
936
  });
767
937
  } else if (globalThis.Response && this.body instanceof Response) {
768
938
  this.mergeFetchStatus(this.body.status);
769
939
  if (method === "HEAD") res.end();
770
- else {
940
+ else {
771
941
  const additionalHeaders = {};
772
942
  if (this.body.headers.get("content-length")) additionalHeaders["content-length"] = this.body.headers.get("content-length");
773
943
  if (this.body.headers.get("content-type")) additionalHeaders["content-type"] = this.body.headers.get("content-type");
@@ -804,11 +974,11 @@ function createWooksResponder(renderer = new BaseHttpResponseRenderer(), errorRe
804
974
  const r = new BaseHttpResponse(errorRenderer);
805
975
  let httpError;
806
976
  if (data instanceof HttpError) httpError = data;
807
- else httpError = new HttpError(500, data.message);
977
+ else httpError = new HttpError(500, data.message);
808
978
  r.setBody(httpError.body);
809
979
  return r;
810
980
  } else if (data instanceof BaseHttpResponse) return data;
811
- else return new BaseHttpResponse(renderer).setBody(data);
981
+ else return new BaseHttpResponse(renderer).setBody(data);
812
982
  }
813
983
  return {
814
984
  createResponse,
@@ -823,7 +993,7 @@ var WooksHttp = class extends WooksAdapterBase {
823
993
  constructor(opts, wooks) {
824
994
  super(wooks, opts?.logger, opts?.router);
825
995
  this.opts = opts;
826
- this.logger = opts?.logger || this.getLogger(`${"\x1B[96m"}[wooks-http]`);
996
+ this.logger = opts?.logger || this.getLogger(`[wooks-http]`);
827
997
  }
828
998
  all(path, handler) {
829
999
  return this.on("*", path, handler);
@@ -861,7 +1031,7 @@ var WooksHttp = class extends WooksAdapterBase {
861
1031
  backlog,
862
1032
  listeningListener
863
1033
  ];
864
- const ui = args.indexOf(undefined);
1034
+ const ui = args.indexOf(void 0);
865
1035
  if (ui >= 0) args = args.slice(0, ui);
866
1036
  server.listen(...args);
867
1037
  });
@@ -903,7 +1073,7 @@ var WooksHttp = class extends WooksAdapterBase {
903
1073
  }
904
1074
  responder = createWooksResponder();
905
1075
  respond(data) {
906
- void this.responder.respond(data)?.catch((e) => {
1076
+ this.responder.respond(data)?.catch((e) => {
907
1077
  this.logger.error("Uncaught response exception", e);
908
1078
  });
909
1079
  }
@@ -934,7 +1104,7 @@ var WooksHttp = class extends WooksAdapterBase {
934
1104
  this.respond(error);
935
1105
  return error;
936
1106
  }
937
- else {
1107
+ else {
938
1108
  this.logger.debug(`404 Not found (${req.method})${req.url}`);
939
1109
  const error = new HttpError(404);
940
1110
  this.respond(error);
@@ -962,9 +1132,15 @@ else {
962
1132
  }
963
1133
  }
964
1134
  };
1135
+ /**
1136
+ * Factory for WooksHttp App
1137
+ * @param opts TWooksHttpOptions
1138
+ * @param wooks Wooks | WooksAdapterBase
1139
+ * @returns WooksHttp
1140
+ */
965
1141
  function createHttpApp(opts, wooks) {
966
1142
  return new WooksHttp(opts, wooks);
967
1143
  }
968
1144
 
969
1145
  //#endregion
970
- export { BaseHttpResponse, BaseHttpResponseRenderer, EHttpStatusCode, HttpError, HttpErrorRenderer, WooksHttp, WooksURLSearchParams, createHttpApp, createHttpContext, createWooksResponder, httpStatusCodes, renderCacheControl, useAccept, useAuthorization, useCookies, useEventLogger, useHeaders, useHttpContext, useRequest, useResponse, useRouteParams, useSearchParams, useSetCacheControl, useSetCookie, useSetCookies, useSetHeader, useSetHeaders, useStatus };
1146
+ export { BaseHttpResponse, BaseHttpResponseRenderer, DEFAULT_LIMITS, EHttpStatusCode, HttpError, HttpErrorRenderer, WooksHttp, WooksURLSearchParams, createHttpApp, createHttpContext, createWooksResponder, httpStatusCodes, renderCacheControl, useAccept, useAuthorization, useCookies, useEventLogger, useHeaders, useHttpContext, useRequest, useResponse, useRouteParams, useSearchParams, useSetCacheControl, useSetCookie, useSetCookies, useSetHeader, useSetHeaders, useStatus };