@server/next 0.36.2 → 0.37.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.
Files changed (3) hide show
  1. package/index.d.ts +11 -6
  2. package/index.js +148 -88
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -10,10 +10,8 @@ declare class UploadPipeline {
10
10
  private _limits;
11
11
  constructor(bucket?: Bucket | string | null);
12
12
  limit(options: LimitOptions): this;
13
- store(bucket: Bucket | string): this;
14
13
  processFile(originalName: string, data: Buffer, contentType: string): Promise<UploadedFile>;
15
14
  }
16
- declare function upload(bucket?: Bucket | string | null): UploadPipeline;
17
15
 
18
16
  type CookieOptions = string | string[] | Cookie | Cookie[] | null;
19
17
  interface ResponseData {
@@ -31,7 +29,7 @@ declare class Reply$1 {
31
29
  cookies(key: string | Record<string, CookieOptions>, value?: CookieOptions): this;
32
30
  json(body: unknown): Response;
33
31
  redirect(path: string): Response;
34
- file(path: string): Promise<Response>;
32
+ file(path: string | BucketFile): Promise<Response>;
35
33
  send(body?: string | Buffer | ReadableStream | any): Response;
36
34
  }
37
35
  type Params<K extends keyof Reply$1> = Reply$1[K] extends (...args: infer A) => any ? A : never;
@@ -99,6 +97,7 @@ type BucketFile = {
99
97
  readonly path: string;
100
98
  readonly id: string;
101
99
  readonly name: string;
100
+ readonly type?: string;
102
101
  exists(): Promise<boolean>;
103
102
  info?(): Promise<FileInfo>;
104
103
  write(content: string | Buffer | ReadableStream, options?: {
@@ -120,6 +119,9 @@ type UploadedFile = {
120
119
  type: string;
121
120
  size: number;
122
121
  };
122
+ type UploadOptions = LimitOptions & {
123
+ bucket: string | Bucket;
124
+ };
123
125
  type CorsSettings = {
124
126
  origin: string | boolean;
125
127
  methods: string;
@@ -204,11 +206,12 @@ type SecuritySettings = {
204
206
  hsts: string | null;
205
207
  };
206
208
  type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
209
+ type OnResponse = (response: Response, ctx: Context) => Response | void | Promise<Response | void>;
207
210
  type Options = {
208
211
  port?: number;
209
212
  secret?: string;
210
213
  public?: string | Bucket;
211
- uploads?: string | Bucket | UploadPipeline;
214
+ uploads?: string | Bucket | UploadOptions;
212
215
  store?: KVStore;
213
216
  cookies?: KVStore;
214
217
  session?: KVStore | {
@@ -218,6 +221,7 @@ type Options = {
218
221
  auth?: AuthOption;
219
222
  openapi?: any;
220
223
  onError?: OnError;
224
+ onResponse?: OnResponse;
221
225
  log?: LogLevel | boolean;
222
226
  favicon?: string | BucketFile;
223
227
  security?: boolean | SecurityOptions;
@@ -238,6 +242,7 @@ type Settings = {
238
242
  auth?: AuthSettings;
239
243
  openapi?: any;
240
244
  onError?: OnError;
245
+ onResponse?: OnResponse;
241
246
  log: Logger;
242
247
  favicon?: string | BucketFile;
243
248
  security: SecuritySettings;
@@ -298,7 +303,7 @@ type Context<Params extends Record<string, string | undefined> = Record<string,
298
303
  };
299
304
  app: Server;
300
305
  };
301
- type InlineReply = Response | Reply | {
306
+ type InlineReply = Response | Reply | BucketFile | {
302
307
  body: string;
303
308
  headers?: Headers;
304
309
  } | SerializableValue | JSX.Element | Buffer | ReadableStream;
@@ -501,4 +506,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
501
506
  }
502
507
  declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
503
508
 
504
- export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type BodyOption, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type FileInfo, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
509
+ export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type BodyOption, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type FileInfo, type InferParamType, type InlineReply, type KVStore, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, type UploadOptions, type UploadedFile, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
package/index.js CHANGED
@@ -89,46 +89,19 @@ 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
99
  };
128
- function createId(source, size = 16) {
129
- if (source) return hash(source, size);
130
- return randomId(size);
131
- }
100
+
101
+ // src/helpers/bucket.ts
102
+ import * as fs from "fs";
103
+ import * as fsp from "fs/promises";
104
+ import * as path from "path";
132
105
 
133
106
  // src/helpers/mimes.ts
134
107
  var mimes_default = {
@@ -213,9 +186,6 @@ var mimes_default = {
213
186
  };
214
187
 
215
188
  // src/helpers/bucket.ts
216
- import * as fs from "fs";
217
- import * as fsp from "fs/promises";
218
- import * as path from "path";
219
189
  function localBucket(root) {
220
190
  const base = path.resolve(root);
221
191
  const resolveKey = (name) => {
@@ -228,6 +198,7 @@ function localBucket(root) {
228
198
  };
229
199
  const file2 = (name, win) => {
230
200
  const full = resolveKey(name);
201
+ const type2 = mimes_default[path.extname(name).slice(1).toLowerCase()];
231
202
  const read = () => {
232
203
  let opts;
233
204
  if (win) {
@@ -250,6 +221,7 @@ function localBucket(root) {
250
221
  path: full,
251
222
  id: name.replace(/^\/+/, ""),
252
223
  name: path.basename(name),
224
+ type: type2,
253
225
  async exists() {
254
226
  const stats = await fsp.stat(full).catch(() => null);
255
227
  return !!stats?.isFile();
@@ -259,7 +231,7 @@ function localBucket(root) {
259
231
  const exists = !!stats?.isFile();
260
232
  const total = stats?.size ?? 0;
261
233
  const size = win ? Math.max(0, Math.min(win.end, total) - win.start) : total;
262
- return { exists, size, date: stats?.mtime ?? null };
234
+ return { exists, size, date: stats?.mtime ?? null, type: type2 };
263
235
  },
264
236
  // Read-only view of [start, end), composed relative to the current window.
265
237
  slice(start, end) {
@@ -311,6 +283,47 @@ function bucket(root) {
311
283
  );
312
284
  }
313
285
 
286
+ // src/helpers/createId.ts
287
+ var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
288
+ var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
289
+ var cyrb53 = (str, seed = 0) => {
290
+ if (typeof str !== "string") str = String(str);
291
+ let h1 = 3735928559 ^ seed;
292
+ let h2 = 1103547991 ^ seed;
293
+ for (let i = 0, ch; i < str.length; i++) {
294
+ ch = str.charCodeAt(i);
295
+ h1 = Math.imul(h1 ^ ch, 2654435761);
296
+ h2 = Math.imul(h2 ^ ch, 1597334677);
297
+ }
298
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
299
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
300
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
301
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
302
+ return 4294967296 * (2097151 & h2) + (h1 >>> 0);
303
+ };
304
+ var hash = (str, size) => {
305
+ let chars = "";
306
+ let num = cyrb53(str);
307
+ for (let i = 0; i < size; i++) {
308
+ if (num < alphabet.length) num = cyrb53(str, i);
309
+ chars += alphabet[num % alphabet.length];
310
+ num = Math.floor(num / alphabet.length);
311
+ }
312
+ return chars;
313
+ };
314
+ var randomId = (size = 16) => {
315
+ let id = "";
316
+ const bytes = random(size);
317
+ while (size--) {
318
+ id += alphabet[bytes[size] & 61];
319
+ }
320
+ return id;
321
+ };
322
+ function createId(source, size = 16) {
323
+ if (source) return hash(source, size);
324
+ return randomId(size);
325
+ }
326
+
314
327
  // src/helpers/upload.ts
315
328
  function parseBytes(value) {
316
329
  if (typeof value === "number") return value;
@@ -352,10 +365,6 @@ var UploadPipeline = class {
352
365
  this._limits = { ...this._limits, ...options };
353
366
  return this;
354
367
  }
355
- store(bucket2) {
356
- this._bucket = bucket(bucket2);
357
- return this;
358
- }
359
368
  async processFile(originalName, data, contentType) {
360
369
  const { maxSize, minSize, fileType } = this._limits;
361
370
  if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
@@ -381,16 +390,31 @@ var UploadPipeline = class {
381
390
  }
382
391
  }
383
392
  if (!this._bucket) {
384
- throw new Error(
385
- `No destination configured. Pass a bucket to upload() or call .store()`
386
- );
393
+ throw new Error(`No upload destination configured (missing bucket)`);
387
394
  }
388
395
  return saveFileToBucket(originalName, data, this._bucket, contentType);
389
396
  }
390
397
  };
391
- function upload(bucket2) {
392
- return new UploadPipeline(bucket2);
398
+
399
+ // src/helpers/bodyLimit.ts
400
+ var INF = Number.POSITIVE_INFINITY;
401
+ var DEFAULT_MAX = "1mb";
402
+ var resolveMax = (max) => max === false ? INF : parseBytes(max == null ? DEFAULT_MAX : max);
403
+ var UNITS = ["b", "kb", "mb", "gb", "tb"];
404
+ function human(bytes) {
405
+ if (!Number.isFinite(bytes) || bytes <= 0) return `${bytes}`;
406
+ const i = Math.min(
407
+ Math.floor(Math.log(bytes) / Math.log(1024)),
408
+ UNITS.length - 1
409
+ );
410
+ const value = bytes / 1024 ** i;
411
+ const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
412
+ return `${rounded}${UNITS[i]}`;
393
413
  }
414
+ var tooLarge = (max) => new StatusError(
415
+ `Request body exceeds the ${human(max)} limit. Raise it with body: { max: '10mb' } on the route or server, or set max: false to disable.`,
416
+ 413
417
+ );
394
418
 
395
419
  // src/helpers/parseBody.ts
396
420
  function getBoundary(header) {
@@ -438,10 +462,18 @@ function toStream(input) {
438
462
  }
439
463
  });
440
464
  }
441
- async function toBuffer(input) {
442
- if (!(input instanceof ReadableStream)) return input;
465
+ async function toBuffer(input, max = INF) {
466
+ if (!(input instanceof ReadableStream)) {
467
+ if (input.length > max) throw tooLarge(max);
468
+ return input;
469
+ }
443
470
  const chunks = [];
444
- for await (const chunk of asIterable(input)) chunks.push(Buffer.from(chunk));
471
+ let total = 0;
472
+ for await (const chunk of asIterable(input)) {
473
+ total += chunk.byteLength;
474
+ if (total > max) throw tooLarge(max);
475
+ chunks.push(Buffer.from(chunk));
476
+ }
445
477
  return Buffer.concat(chunks);
446
478
  }
447
479
  function parseUrlEncoded(text) {
@@ -522,13 +554,21 @@ async function endPart(part, body) {
522
554
  }
523
555
  }
524
556
  var BREAK = Buffer.from("\r\n\r\n");
525
- async function parseMultipart(stream, boundary, dest) {
557
+ async function parseMultipart(stream, boundary, dest, max = INF) {
526
558
  const delim = Buffer.from(`\r
527
559
  --${boundary}`);
528
560
  const body = {};
529
561
  let buf = Buffer.from("\r\n");
530
562
  let state = "boundary";
531
563
  let part = null;
564
+ let textBytes = 0;
565
+ const feed = (p, data) => {
566
+ if (p.kind === "text") {
567
+ textBytes += data.length;
568
+ if (textBytes > max) throw tooLarge(max);
569
+ }
570
+ feedPart(p, data);
571
+ };
532
572
  for await (const chunk of asIterable(stream)) {
533
573
  buf = Buffer.concat([buf, Buffer.from(chunk)]);
534
574
  let advanced = true;
@@ -560,13 +600,13 @@ async function parseMultipart(stream, boundary, dest) {
560
600
  if (i === -1) {
561
601
  const safe = buf.length - (delim.length - 1);
562
602
  if (safe > 0 && part) {
563
- feedPart(part, buf.subarray(0, safe));
603
+ feed(part, buf.subarray(0, safe));
564
604
  buf = buf.subarray(safe);
565
605
  }
566
606
  break;
567
607
  }
568
608
  if (part) {
569
- feedPart(part, buf.subarray(0, i));
609
+ feed(part, buf.subarray(0, i));
570
610
  await endPart(part, body);
571
611
  part = null;
572
612
  }
@@ -599,24 +639,24 @@ async function streamToBucket(stream, type2, bucket2) {
599
639
  if (!size) return void 0;
600
640
  return { name: id, id, path: file2.path, type: type2, size };
601
641
  }
602
- async function parseBody(input, contentType, dest) {
642
+ async function parseBody(input, contentType, dest, max = INF) {
603
643
  const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
604
644
  const boundary = type2 && /multipart\/form-data/i.test(type2) ? getBoundary(type2) : null;
605
- if (boundary) return parseMultipart(toStream(input), boundary, dest);
645
+ if (boundary) return parseMultipart(toStream(input), boundary, dest, max);
606
646
  if (!type2 || /^text\//i.test(type2)) {
607
- const buf = await toBuffer(input);
647
+ const buf = await toBuffer(input, max);
608
648
  return buf.length ? buf.toString("utf-8") : void 0;
609
649
  }
610
650
  if (/application\/json/i.test(type2)) {
611
- const buf = await toBuffer(input);
651
+ const buf = await toBuffer(input, max);
612
652
  return buf.length ? JSON.parse(buf.toString("utf-8")) : void 0;
613
653
  }
614
654
  if (/application\/x-www-form-urlencoded/i.test(type2)) {
615
- const buf = await toBuffer(input);
655
+ const buf = await toBuffer(input, max);
616
656
  return buf.length ? parseUrlEncoded(buf.toString("utf-8")) : void 0;
617
657
  }
618
658
  if (!dest) {
619
- const buf = await toBuffer(input);
659
+ const buf = await toBuffer(input, max);
620
660
  return buf.length ? buf : void 0;
621
661
  }
622
662
  if (dest instanceof UploadPipeline) {
@@ -626,19 +666,7 @@ async function parseBody(input, contentType, dest) {
626
666
  return streamToBucket(toStream(input), type2, dest);
627
667
  }
628
668
 
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
669
  // 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
670
  var sources = /* @__PURE__ */ new WeakMap();
643
671
  function setBodySource(ctx, source) {
644
672
  sources.set(ctx, source);
@@ -648,8 +676,11 @@ async function resolveBody(ctx, body) {
648
676
  if (!source) return void 0;
649
677
  const mode = typeof body === "string" ? body : body?.mode ?? "parse";
650
678
  const max = resolveMax(typeof body === "object" ? body?.max : void 0);
679
+ const contentType = String(ctx.headers["content-type"] || "");
680
+ const isMultipart = /multipart\/form-data/i.test(contentType);
651
681
  const declared = Number(ctx.headers["content-length"]);
652
- if (max !== INF && declared > max) throw tooLarge(max);
682
+ const trustDeclared = !isMultipart && !ctx.options.uploads;
683
+ if (max !== INF && trustDeclared && declared > max) throw tooLarge(max);
653
684
  if (mode === "stream") return source.getStream();
654
685
  if (mode === "raw") {
655
686
  const raw = await source.getBuffer();
@@ -667,7 +698,6 @@ async function resolveBody(ctx, body) {
667
698
  new TransformStream({
668
699
  transform(chunk, controller) {
669
700
  size += chunk.byteLength;
670
- if (size > max) return controller.error(tooLarge(max));
671
701
  controller.enqueue(chunk);
672
702
  }
673
703
  })
@@ -675,7 +705,8 @@ async function resolveBody(ctx, body) {
675
705
  const parsed = await parseBody(
676
706
  counted,
677
707
  ctx.headers["content-type"],
678
- ctx.options.uploads
708
+ ctx.options.uploads,
709
+ max
679
710
  );
680
711
  if (size && !ctx.headers["content-length"]) {
681
712
  ctx.headers["content-length"] = String(size);
@@ -861,6 +892,10 @@ var Reply = class {
861
892
  return this.headers("location", path2).status(302).send();
862
893
  }
863
894
  async file(path2) {
895
+ if (typeof path2 !== "string") {
896
+ if (!await path2.exists()) return this.status(404).send();
897
+ return this.type(path2.type).send(path2.stream());
898
+ }
864
899
  try {
865
900
  const fs2 = await import("fs");
866
901
  const ext2 = path2.split(".").pop();
@@ -889,7 +924,7 @@ var Reply = class {
889
924
  return new Response(body, { status: status2, headers: headers2 });
890
925
  }
891
926
  const name = body?.constructor?.name;
892
- if (name === "Buffer") {
927
+ if (body instanceof Uint8Array) {
893
928
  if (!headers2.has("content-length")) {
894
929
  headers2.set("content-length", String(body.length));
895
930
  }
@@ -1543,16 +1578,16 @@ var STATUS_TEXT = {
1543
1578
  502: "Bad Gateway",
1544
1579
  503: "Service Unavailable"
1545
1580
  };
1546
- var UNITS = ["b", "kb", "mb", "gb", "tb"];
1581
+ var UNITS2 = ["b", "kb", "mb", "gb", "tb"];
1547
1582
  function formatBytes(bytes) {
1548
1583
  if (!bytes || bytes < 0) return "0b";
1549
1584
  const i = Math.min(
1550
1585
  Math.floor(Math.log(bytes) / Math.log(1024)),
1551
- UNITS.length - 1
1586
+ UNITS2.length - 1
1552
1587
  );
1553
1588
  const value = bytes / 1024 ** i;
1554
1589
  const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
1555
- return `${rounded}${UNITS[i]}`;
1590
+ return `${rounded}${UNITS2[i]}`;
1556
1591
  }
1557
1592
  var SCOPE_COLORS = {
1558
1593
  start: "green",
@@ -1685,9 +1720,20 @@ function config(options = {}) {
1685
1720
  }
1686
1721
  settings.cors = cors2;
1687
1722
  }
1688
- settings.public = options.public ? bucket(options.public) : null;
1689
- settings.uploads = options.uploads instanceof UploadPipeline ? options.uploads : options.uploads ? bucket(options.uploads) : null;
1690
- if (options.favicon) settings.favicon = options.favicon;
1723
+ const publicDir = options.public || env2.PUBLIC;
1724
+ settings.public = publicDir ? bucket(publicDir) : null;
1725
+ const up = options.uploads;
1726
+ if (!up) {
1727
+ settings.uploads = null;
1728
+ } else if (typeof up === "object" && "bucket" in up) {
1729
+ const { bucket: bucket2, maxSize, minSize, fileType } = up;
1730
+ const hasLimits = maxSize != null || minSize != null || fileType != null;
1731
+ settings.uploads = hasLimits ? new UploadPipeline(bucket2).limit({ maxSize, minSize, fileType }) : bucket(bucket2);
1732
+ } else {
1733
+ settings.uploads = bucket(up);
1734
+ }
1735
+ const favicon2 = options.favicon || env2.FAVICON;
1736
+ if (favicon2) settings.favicon = favicon2;
1691
1737
  settings.store = options.store ?? null;
1692
1738
  settings.cookies = options.cookies ?? null;
1693
1739
  if (options.session) {
@@ -1714,6 +1760,7 @@ function config(options = {}) {
1714
1760
  status: error.status || 500
1715
1761
  });
1716
1762
  });
1763
+ settings.onResponse = options.onResponse;
1717
1764
  const loc = (v) => typeof v === "string" ? v : "enabled";
1718
1765
  if (settings.auth) {
1719
1766
  log.message("auth", `${settings.auth.providers.join(", ")} auth enabled`);
@@ -1842,6 +1889,16 @@ async function parseResponse(out, ctx) {
1842
1889
  if (out instanceof Blob) {
1843
1890
  out = new Response(out, { headers: { "Content-Type": out.type } });
1844
1891
  }
1892
+ if (out && typeof out.stream === "function" && typeof out.bytes === "function" && typeof out.exists === "function" && typeof out.name === "string") {
1893
+ if (!await out.exists()) {
1894
+ out = new Response(null, { status: 404 });
1895
+ } else {
1896
+ out = new Response(
1897
+ out.stream(),
1898
+ out.type ? { headers: { "content-type": out.type } } : void 0
1899
+ );
1900
+ }
1901
+ }
1845
1902
  if (out instanceof ReadableStream) {
1846
1903
  out = new Response(out);
1847
1904
  }
@@ -1999,7 +2056,11 @@ function validate(ctx, schema) {
1999
2056
 
2000
2057
  // src/helpers/handleRequest.ts
2001
2058
  async function handleRequest(app, ctx) {
2002
- const res = await getResponse(app, ctx);
2059
+ let res = await getResponse(app, ctx);
2060
+ if (res && ctx.options.onResponse) {
2061
+ const replaced = await ctx.options.onResponse(res, ctx);
2062
+ if (replaced) res = replaced;
2063
+ }
2003
2064
  if (res) ctx.options.log.request(ctx, res);
2004
2065
  return res;
2005
2066
  }
@@ -3223,6 +3284,5 @@ export {
3223
3284
  router,
3224
3285
  send,
3225
3286
  status,
3226
- type,
3227
- upload
3287
+ type
3228
3288
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.36.2",
3
+ "version": "0.37.1",
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",