@server/next 0.36.1 → 0.37.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.
Files changed (3) hide show
  1. package/index.d.ts +36 -32
  2. package/index.js +195 -151
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -15,6 +15,38 @@ declare class UploadPipeline {
15
15
  }
16
16
  declare function upload(bucket?: Bucket | string | null): UploadPipeline;
17
17
 
18
+ type CookieOptions = string | string[] | Cookie | Cookie[] | null;
19
+ interface ResponseData {
20
+ headers: Headers;
21
+ status?: number;
22
+ }
23
+ declare class Reply$1 {
24
+ res: ResponseData;
25
+ constructor();
26
+ status(status: number): this;
27
+ type(type?: string): this;
28
+ download(name?: string): this;
29
+ headers(key: string | Record<string, string>, value?: string): this;
30
+ cache(value: CacheOption): this;
31
+ cookies(key: string | Record<string, CookieOptions>, value?: CookieOptions): this;
32
+ json(body: unknown): Response;
33
+ redirect(path: string): Response;
34
+ file(path: string): Promise<Response>;
35
+ send(body?: string | Buffer | ReadableStream | any): Response;
36
+ }
37
+ type Params<K extends keyof Reply$1> = Reply$1[K] extends (...args: infer A) => any ? A : never;
38
+ declare const status: (...args: Params<"status">) => Reply$1;
39
+ declare const headers: (...args: Params<"headers">) => Reply$1;
40
+ declare const type: (...args: Params<"type">) => Reply$1;
41
+ declare const cache: (...args: Params<"cache">) => Reply$1;
42
+ declare const download: (...args: Params<"download">) => Reply$1;
43
+ declare const cookies: (...args: Params<"cookies">) => Reply$1;
44
+ declare const send: (...args: Params<"send">) => Response;
45
+ declare const json: (...args: Params<"json">) => Response;
46
+ declare const file: (...args: Params<"file">) => Promise<Response>;
47
+ declare const redirect: (...args: Params<"redirect">) => Response;
48
+
49
+ type Reply = ReturnType<typeof status>;
18
50
  type Method = "get" | "post" | "put" | "patch" | "delete" | "head" | "options" | "socket";
19
51
  type ServerConfig<Session = {}, User = {}> = {
20
52
  Session?: Session;
@@ -172,6 +204,7 @@ type SecuritySettings = {
172
204
  hsts: string | null;
173
205
  };
174
206
  type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
207
+ type OnResponse = (response: Response, ctx: Context) => Response | void | Promise<Response | void>;
175
208
  type Options = {
176
209
  port?: number;
177
210
  secret?: string;
@@ -186,6 +219,7 @@ type Options = {
186
219
  auth?: AuthOption;
187
220
  openapi?: any;
188
221
  onError?: OnError;
222
+ onResponse?: OnResponse;
189
223
  log?: LogLevel | boolean;
190
224
  favicon?: string | BucketFile;
191
225
  security?: boolean | SecurityOptions;
@@ -206,6 +240,7 @@ type Settings = {
206
240
  auth?: AuthSettings;
207
241
  openapi?: any;
208
242
  onError?: OnError;
243
+ onResponse?: OnResponse;
209
244
  log: Logger;
210
245
  favicon?: string | BucketFile;
211
246
  security: SecuritySettings;
@@ -266,7 +301,7 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
266
301
  };
267
302
  app: Server;
268
303
  };
269
- type InlineReply = Response | {
304
+ type InlineReply = Response | Reply | {
270
305
  body: string;
271
306
  headers?: Headers;
272
307
  } | SerializableValue | JSX.Element | Buffer | ReadableStream;
@@ -337,37 +372,6 @@ declare class Router<O extends ServerConfig = object> {
337
372
  }
338
373
  declare function router(): Router;
339
374
 
340
- type CookieOptions = string | string[] | Cookie | Cookie[] | null;
341
- interface ResponseData {
342
- headers: Headers;
343
- status?: number;
344
- }
345
- declare class Reply {
346
- res: ResponseData;
347
- constructor();
348
- status(status: number): this;
349
- type(type?: string): this;
350
- download(name?: string): this;
351
- headers(key: string | Record<string, string>, value?: string): this;
352
- cache(value: CacheOption): this;
353
- cookies(key: string | Record<string, CookieOptions>, value?: CookieOptions): this;
354
- json(body: unknown): Response;
355
- redirect(path: string): Response;
356
- file(path: string): Promise<Response>;
357
- send(body?: string | Buffer | ReadableStream | any): Response;
358
- }
359
- type Params<K extends keyof Reply> = Reply[K] extends (...args: infer A) => any ? A : never;
360
- declare const status: (...args: Params<"status">) => Reply;
361
- declare const headers: (...args: Params<"headers">) => Reply;
362
- declare const type: (...args: Params<"type">) => Reply;
363
- declare const cache: (...args: Params<"cache">) => Reply;
364
- declare const download: (...args: Params<"download">) => Reply;
365
- declare const cookies: (...args: Params<"cookies">) => Reply;
366
- declare const send: (...args: Params<"send">) => Response;
367
- declare const json: (...args: Params<"json">) => Response;
368
- declare const file: (...args: Params<"file">) => Promise<Response>;
369
- declare const redirect: (...args: Params<"redirect">) => Response;
370
-
371
375
  declare class Server<O extends ServerConfig = {}> extends Router<O> {
372
376
  settings: Settings;
373
377
  platform: Platform;
package/index.js CHANGED
@@ -89,127 +89,13 @@ if (typeof process !== "undefined") {
89
89
  Object.assign(globalThis.env, process.env);
90
90
  }
91
91
 
92
- // src/helpers/createId.ts
93
- var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
94
- var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
95
- var cyrb53 = (str, seed = 0) => {
96
- if (typeof str !== "string") str = String(str);
97
- let h1 = 3735928559 ^ seed;
98
- let h2 = 1103547991 ^ seed;
99
- for (let i = 0, ch; i < str.length; i++) {
100
- ch = str.charCodeAt(i);
101
- h1 = Math.imul(h1 ^ ch, 2654435761);
102
- h2 = Math.imul(h2 ^ ch, 1597334677);
103
- }
104
- h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
105
- h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
106
- h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
107
- h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
108
- return 4294967296 * (2097151 & h2) + (h1 >>> 0);
109
- };
110
- var hash = (str, size) => {
111
- let chars = "";
112
- let num = cyrb53(str);
113
- for (let i = 0; i < size; i++) {
114
- if (num < alphabet.length) num = cyrb53(str, i);
115
- chars += alphabet[num % alphabet.length];
116
- num = Math.floor(num / alphabet.length);
117
- }
118
- return chars;
119
- };
120
- var randomId = (size = 16) => {
121
- let id = "";
122
- const bytes = random(size);
123
- while (size--) {
124
- id += alphabet[bytes[size] & 61];
92
+ // src/helpers/StatusError.ts
93
+ var StatusError = class extends Error {
94
+ status;
95
+ constructor(msg, status2 = 500) {
96
+ super(msg);
97
+ this.status = status2;
125
98
  }
126
- return id;
127
- };
128
- function createId(source, size = 16) {
129
- if (source) return hash(source, size);
130
- return randomId(size);
131
- }
132
-
133
- // src/helpers/mimes.ts
134
- var mimes_default = {
135
- aac: "audio/aac",
136
- abw: "application/x-abiword",
137
- arc: "application/x-freearc",
138
- avif: "image/avif",
139
- avi: "video/x-msvideo",
140
- azw: "application/vnd.amazon.ebook",
141
- bin: "application/octet-stream",
142
- bmp: "image/bmp",
143
- bz: "application/x-bzip",
144
- bz2: "application/x-bzip2",
145
- cda: "application/x-cdf",
146
- csh: "application/x-csh",
147
- css: "text/css",
148
- csv: "text/csv",
149
- doc: "application/msword",
150
- docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
151
- eot: "application/vnd.ms-fontobject",
152
- epub: "application/epub+zip",
153
- gz: "application/gzip",
154
- gif: "image/gif",
155
- htm: "text/html",
156
- html: "text/html",
157
- ico: "image/vnd.microsoft.icon",
158
- ics: "text/calendar",
159
- jar: "application/java-archive",
160
- jpeg: "image/jpeg",
161
- jpg: "image/jpeg",
162
- js: "text/javascript",
163
- json: "application/json",
164
- jsonld: "application/ld+json",
165
- md: "text/markdown",
166
- mid: "audio/midi",
167
- midi: "audio/midi",
168
- mjs: "text/javascript",
169
- mp3: "audio/mpeg",
170
- mp4: "video/mp4",
171
- mpeg: "video/mpeg",
172
- mpkg: "application/vnd.apple.installer+xml",
173
- odp: "application/vnd.oasis.opendocument.presentation",
174
- ods: "application/vnd.oasis.opendocument.spreadsheet",
175
- odt: "application/vnd.oasis.opendocument.text",
176
- oga: "audio/ogg",
177
- ogv: "video/ogg",
178
- ogx: "application/ogg",
179
- opus: "audio/opus",
180
- otf: "font/otf",
181
- png: "image/png",
182
- pdf: "application/pdf",
183
- php: "application/x-httpd-php",
184
- ppt: "application/vnd.ms-powerpoint",
185
- pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
186
- rar: "application/vnd.rar",
187
- rtf: "application/rtf",
188
- sh: "application/x-sh",
189
- svg: "image/svg+xml",
190
- tar: "application/x-tar",
191
- text: "text/plain",
192
- tif: "image/tiff",
193
- tiff: "image/tiff",
194
- ts: "video/mp2t",
195
- ttf: "font/ttf",
196
- txt: "text/plain",
197
- vsd: "application/vnd.visio",
198
- wav: "audio/wav",
199
- weba: "audio/webm",
200
- webm: "video/webm",
201
- webp: "image/webp",
202
- woff: "font/woff",
203
- woff2: "font/woff2",
204
- xhtml: "application/xhtml+xml",
205
- xls: "application/vnd.ms-excel",
206
- xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
207
- xml: "application/xml",
208
- xul: "application/vnd.mozilla.xul+xml",
209
- zip: "application/zip",
210
- "3gp": "video/3gpp",
211
- "3g2": "video/3gpp2",
212
- "7z": "application/x-7z-compressed"
213
99
  };
214
100
 
215
101
  // src/helpers/bucket.ts
@@ -311,6 +197,47 @@ function bucket(root) {
311
197
  );
312
198
  }
313
199
 
200
+ // src/helpers/createId.ts
201
+ var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
202
+ var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
203
+ var cyrb53 = (str, seed = 0) => {
204
+ if (typeof str !== "string") str = String(str);
205
+ let h1 = 3735928559 ^ seed;
206
+ let h2 = 1103547991 ^ seed;
207
+ for (let i = 0, ch; i < str.length; i++) {
208
+ ch = str.charCodeAt(i);
209
+ h1 = Math.imul(h1 ^ ch, 2654435761);
210
+ h2 = Math.imul(h2 ^ ch, 1597334677);
211
+ }
212
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
213
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
214
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
215
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
216
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
217
+ };
218
+ var hash = (str, size) => {
219
+ let chars = "";
220
+ let num = cyrb53(str);
221
+ for (let i = 0; i < size; i++) {
222
+ if (num < alphabet.length) num = cyrb53(str, i);
223
+ chars += alphabet[num % alphabet.length];
224
+ num = Math.floor(num / alphabet.length);
225
+ }
226
+ return chars;
227
+ };
228
+ var randomId = (size = 16) => {
229
+ let id = "";
230
+ const bytes = random(size);
231
+ while (size--) {
232
+ id += alphabet[bytes[size] & 61];
233
+ }
234
+ return id;
235
+ };
236
+ function createId(source, size = 16) {
237
+ if (source) return hash(source, size);
238
+ return randomId(size);
239
+ }
240
+
314
241
  // src/helpers/upload.ts
315
242
  function parseBytes(value) {
316
243
  if (typeof value === "number") return value;
@@ -392,6 +319,108 @@ function upload(bucket2) {
392
319
  return new UploadPipeline(bucket2);
393
320
  }
394
321
 
322
+ // src/helpers/bodyLimit.ts
323
+ var INF = Number.POSITIVE_INFINITY;
324
+ var DEFAULT_MAX = "1mb";
325
+ var resolveMax = (max) => max === false ? INF : parseBytes(max == null ? DEFAULT_MAX : max);
326
+ var UNITS = ["b", "kb", "mb", "gb", "tb"];
327
+ function human(bytes) {
328
+ if (!Number.isFinite(bytes) || bytes <= 0) return `${bytes}`;
329
+ const i = Math.min(
330
+ Math.floor(Math.log(bytes) / Math.log(1024)),
331
+ UNITS.length - 1
332
+ );
333
+ const value = bytes / 1024 ** i;
334
+ const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
335
+ return `${rounded}${UNITS[i]}`;
336
+ }
337
+ var tooLarge = (max) => new StatusError(
338
+ `Request body exceeds the ${human(max)} limit. Raise it with body: { max: '10mb' } on the route or server, or set max: false to disable.`,
339
+ 413
340
+ );
341
+
342
+ // src/helpers/mimes.ts
343
+ var mimes_default = {
344
+ aac: "audio/aac",
345
+ abw: "application/x-abiword",
346
+ arc: "application/x-freearc",
347
+ avif: "image/avif",
348
+ avi: "video/x-msvideo",
349
+ azw: "application/vnd.amazon.ebook",
350
+ bin: "application/octet-stream",
351
+ bmp: "image/bmp",
352
+ bz: "application/x-bzip",
353
+ bz2: "application/x-bzip2",
354
+ cda: "application/x-cdf",
355
+ csh: "application/x-csh",
356
+ css: "text/css",
357
+ csv: "text/csv",
358
+ doc: "application/msword",
359
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
360
+ eot: "application/vnd.ms-fontobject",
361
+ epub: "application/epub+zip",
362
+ gz: "application/gzip",
363
+ gif: "image/gif",
364
+ htm: "text/html",
365
+ html: "text/html",
366
+ ico: "image/vnd.microsoft.icon",
367
+ ics: "text/calendar",
368
+ jar: "application/java-archive",
369
+ jpeg: "image/jpeg",
370
+ jpg: "image/jpeg",
371
+ js: "text/javascript",
372
+ json: "application/json",
373
+ jsonld: "application/ld+json",
374
+ md: "text/markdown",
375
+ mid: "audio/midi",
376
+ midi: "audio/midi",
377
+ mjs: "text/javascript",
378
+ mp3: "audio/mpeg",
379
+ mp4: "video/mp4",
380
+ mpeg: "video/mpeg",
381
+ mpkg: "application/vnd.apple.installer+xml",
382
+ odp: "application/vnd.oasis.opendocument.presentation",
383
+ ods: "application/vnd.oasis.opendocument.spreadsheet",
384
+ odt: "application/vnd.oasis.opendocument.text",
385
+ oga: "audio/ogg",
386
+ ogv: "video/ogg",
387
+ ogx: "application/ogg",
388
+ opus: "audio/opus",
389
+ otf: "font/otf",
390
+ png: "image/png",
391
+ pdf: "application/pdf",
392
+ php: "application/x-httpd-php",
393
+ ppt: "application/vnd.ms-powerpoint",
394
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
395
+ rar: "application/vnd.rar",
396
+ rtf: "application/rtf",
397
+ sh: "application/x-sh",
398
+ svg: "image/svg+xml",
399
+ tar: "application/x-tar",
400
+ text: "text/plain",
401
+ tif: "image/tiff",
402
+ tiff: "image/tiff",
403
+ ts: "video/mp2t",
404
+ ttf: "font/ttf",
405
+ txt: "text/plain",
406
+ vsd: "application/vnd.visio",
407
+ wav: "audio/wav",
408
+ weba: "audio/webm",
409
+ webm: "video/webm",
410
+ webp: "image/webp",
411
+ woff: "font/woff",
412
+ woff2: "font/woff2",
413
+ xhtml: "application/xhtml+xml",
414
+ xls: "application/vnd.ms-excel",
415
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
416
+ xml: "application/xml",
417
+ xul: "application/vnd.mozilla.xul+xml",
418
+ zip: "application/zip",
419
+ "3gp": "video/3gpp",
420
+ "3g2": "video/3gpp2",
421
+ "7z": "application/x-7z-compressed"
422
+ };
423
+
395
424
  // src/helpers/parseBody.ts
396
425
  function getBoundary(header) {
397
426
  if (!header) return null;
@@ -438,10 +467,18 @@ function toStream(input) {
438
467
  }
439
468
  });
440
469
  }
441
- async function toBuffer(input) {
442
- if (!(input instanceof ReadableStream)) return input;
470
+ async function toBuffer(input, max = INF) {
471
+ if (!(input instanceof ReadableStream)) {
472
+ if (input.length > max) throw tooLarge(max);
473
+ return input;
474
+ }
443
475
  const chunks = [];
444
- for await (const chunk of asIterable(input)) chunks.push(Buffer.from(chunk));
476
+ let total = 0;
477
+ for await (const chunk of asIterable(input)) {
478
+ total += chunk.byteLength;
479
+ if (total > max) throw tooLarge(max);
480
+ chunks.push(Buffer.from(chunk));
481
+ }
445
482
  return Buffer.concat(chunks);
446
483
  }
447
484
  function parseUrlEncoded(text) {
@@ -522,13 +559,21 @@ async function endPart(part, body) {
522
559
  }
523
560
  }
524
561
  var BREAK = Buffer.from("\r\n\r\n");
525
- async function parseMultipart(stream, boundary, dest) {
562
+ async function parseMultipart(stream, boundary, dest, max = INF) {
526
563
  const delim = Buffer.from(`\r
527
564
  --${boundary}`);
528
565
  const body = {};
529
566
  let buf = Buffer.from("\r\n");
530
567
  let state = "boundary";
531
568
  let part = null;
569
+ let textBytes = 0;
570
+ const feed = (p, data) => {
571
+ if (p.kind === "text") {
572
+ textBytes += data.length;
573
+ if (textBytes > max) throw tooLarge(max);
574
+ }
575
+ feedPart(p, data);
576
+ };
532
577
  for await (const chunk of asIterable(stream)) {
533
578
  buf = Buffer.concat([buf, Buffer.from(chunk)]);
534
579
  let advanced = true;
@@ -560,13 +605,13 @@ async function parseMultipart(stream, boundary, dest) {
560
605
  if (i === -1) {
561
606
  const safe = buf.length - (delim.length - 1);
562
607
  if (safe > 0 && part) {
563
- feedPart(part, buf.subarray(0, safe));
608
+ feed(part, buf.subarray(0, safe));
564
609
  buf = buf.subarray(safe);
565
610
  }
566
611
  break;
567
612
  }
568
613
  if (part) {
569
- feedPart(part, buf.subarray(0, i));
614
+ feed(part, buf.subarray(0, i));
570
615
  await endPart(part, body);
571
616
  part = null;
572
617
  }
@@ -599,24 +644,24 @@ async function streamToBucket(stream, type2, bucket2) {
599
644
  if (!size) return void 0;
600
645
  return { name: id, id, path: file2.path, type: type2, size };
601
646
  }
602
- async function parseBody(input, contentType, dest) {
647
+ async function parseBody(input, contentType, dest, max = INF) {
603
648
  const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
604
649
  const boundary = type2 && /multipart\/form-data/i.test(type2) ? getBoundary(type2) : null;
605
- if (boundary) return parseMultipart(toStream(input), boundary, dest);
650
+ if (boundary) return parseMultipart(toStream(input), boundary, dest, max);
606
651
  if (!type2 || /^text\//i.test(type2)) {
607
- const buf = await toBuffer(input);
652
+ const buf = await toBuffer(input, max);
608
653
  return buf.length ? buf.toString("utf-8") : void 0;
609
654
  }
610
655
  if (/application\/json/i.test(type2)) {
611
- const buf = await toBuffer(input);
656
+ const buf = await toBuffer(input, max);
612
657
  return buf.length ? JSON.parse(buf.toString("utf-8")) : void 0;
613
658
  }
614
659
  if (/application\/x-www-form-urlencoded/i.test(type2)) {
615
- const buf = await toBuffer(input);
660
+ const buf = await toBuffer(input, max);
616
661
  return buf.length ? parseUrlEncoded(buf.toString("utf-8")) : void 0;
617
662
  }
618
663
  if (!dest) {
619
- const buf = await toBuffer(input);
664
+ const buf = await toBuffer(input, max);
620
665
  return buf.length ? buf : void 0;
621
666
  }
622
667
  if (dest instanceof UploadPipeline) {
@@ -626,19 +671,7 @@ async function parseBody(input, contentType, dest) {
626
671
  return streamToBucket(toStream(input), type2, dest);
627
672
  }
628
673
 
629
- // src/helpers/StatusError.ts
630
- var StatusError = class extends Error {
631
- status;
632
- constructor(msg, status2 = 500) {
633
- super(msg);
634
- this.status = status2;
635
- }
636
- };
637
-
638
674
  // src/helpers/body.ts
639
- var INF = Number.POSITIVE_INFINITY;
640
- var resolveMax = (max) => max === false || max == null ? INF : parseBytes(max);
641
- var tooLarge = (max) => new StatusError(`Request body exceeds the ${max}-byte limit`, 413);
642
675
  var sources = /* @__PURE__ */ new WeakMap();
643
676
  function setBodySource(ctx, source) {
644
677
  sources.set(ctx, source);
@@ -648,8 +681,11 @@ async function resolveBody(ctx, body) {
648
681
  if (!source) return void 0;
649
682
  const mode = typeof body === "string" ? body : body?.mode ?? "parse";
650
683
  const max = resolveMax(typeof body === "object" ? body?.max : void 0);
684
+ const contentType = String(ctx.headers["content-type"] || "");
685
+ const isMultipart = /multipart\/form-data/i.test(contentType);
651
686
  const declared = Number(ctx.headers["content-length"]);
652
- if (max !== INF && declared > max) throw tooLarge(max);
687
+ const trustDeclared = !isMultipart && !ctx.options.uploads;
688
+ if (max !== INF && trustDeclared && declared > max) throw tooLarge(max);
653
689
  if (mode === "stream") return source.getStream();
654
690
  if (mode === "raw") {
655
691
  const raw = await source.getBuffer();
@@ -667,7 +703,6 @@ async function resolveBody(ctx, body) {
667
703
  new TransformStream({
668
704
  transform(chunk, controller) {
669
705
  size += chunk.byteLength;
670
- if (size > max) return controller.error(tooLarge(max));
671
706
  controller.enqueue(chunk);
672
707
  }
673
708
  })
@@ -675,7 +710,8 @@ async function resolveBody(ctx, body) {
675
710
  const parsed = await parseBody(
676
711
  counted,
677
712
  ctx.headers["content-type"],
678
- ctx.options.uploads
713
+ ctx.options.uploads,
714
+ max
679
715
  );
680
716
  if (size && !ctx.headers["content-length"]) {
681
717
  ctx.headers["content-length"] = String(size);
@@ -1543,16 +1579,16 @@ var STATUS_TEXT = {
1543
1579
  502: "Bad Gateway",
1544
1580
  503: "Service Unavailable"
1545
1581
  };
1546
- var UNITS = ["b", "kb", "mb", "gb", "tb"];
1582
+ var UNITS2 = ["b", "kb", "mb", "gb", "tb"];
1547
1583
  function formatBytes(bytes) {
1548
1584
  if (!bytes || bytes < 0) return "0b";
1549
1585
  const i = Math.min(
1550
1586
  Math.floor(Math.log(bytes) / Math.log(1024)),
1551
- UNITS.length - 1
1587
+ UNITS2.length - 1
1552
1588
  );
1553
1589
  const value = bytes / 1024 ** i;
1554
1590
  const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
1555
- return `${rounded}${UNITS[i]}`;
1591
+ return `${rounded}${UNITS2[i]}`;
1556
1592
  }
1557
1593
  var SCOPE_COLORS = {
1558
1594
  start: "green",
@@ -1714,6 +1750,7 @@ function config(options = {}) {
1714
1750
  status: error.status || 500
1715
1751
  });
1716
1752
  });
1753
+ settings.onResponse = options.onResponse;
1717
1754
  const loc = (v) => typeof v === "string" ? v : "enabled";
1718
1755
  if (settings.auth) {
1719
1756
  log.message("auth", `${settings.auth.providers.join(", ")} auth enabled`);
@@ -1836,6 +1873,9 @@ async function parseResponse(out, ctx) {
1836
1873
  if (typeof out === "function") {
1837
1874
  out = await out(ctx);
1838
1875
  }
1876
+ if (out && typeof out.send === "function" && out.res?.headers instanceof Headers) {
1877
+ out = out.send();
1878
+ }
1839
1879
  if (out instanceof Blob) {
1840
1880
  out = new Response(out, { headers: { "Content-Type": out.type } });
1841
1881
  }
@@ -1996,7 +2036,11 @@ function validate(ctx, schema) {
1996
2036
 
1997
2037
  // src/helpers/handleRequest.ts
1998
2038
  async function handleRequest(app, ctx) {
1999
- const res = await getResponse(app, ctx);
2039
+ let res = await getResponse(app, ctx);
2040
+ if (res && ctx.options.onResponse) {
2041
+ const replaced = await ctx.options.onResponse(res, ctx);
2042
+ if (replaced) res = replaced;
2043
+ }
2000
2044
  if (res) ctx.options.log.request(ctx, res);
2001
2045
  return res;
2002
2046
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.36.1",
3
+ "version": "0.37.0",
4
4
  "description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
5
5
  "homepage": "https://server-js.com/",
6
6
  "repository": "github:franciscop/server-next",