@server/next 0.40.4 → 0.41.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 +141 -88
  2. package/index.js +102 -56
  3. package/package.json +5 -2
package/index.d.ts CHANGED
@@ -41,10 +41,14 @@ declare const redirect: (...args: Params<"redirect">) => Response;
41
41
 
42
42
  type Reply = ReturnType<typeof status>;
43
43
  type Method = "get" | "post" | "put" | "patch" | "delete" | "head" | "options" | "socket";
44
- type ServerConfig<Session = {}, User = {}> = {
45
- Session?: Session;
46
- User?: User;
44
+ type ContextTypes = {
45
+ session?: any;
46
+ user?: any;
47
+ params?: any;
48
+ query?: any;
49
+ body?: any;
47
50
  };
51
+ type Field<C, K extends keyof ContextTypes, Fallback> = K extends keyof C ? SchemaOutput<C[K], C[K]> : Fallback;
48
52
  declare namespace JSX {
49
53
  interface Element {
50
54
  type: any;
@@ -55,16 +59,49 @@ declare namespace JSX {
55
59
  }
56
60
  }
57
61
  type BodyMode = "parse" | "raw" | "stream";
58
- type BodyOption = BodyMode | {
59
- mode?: BodyMode;
60
- max?: number | string | false;
62
+ type StandardIssue = {
63
+ readonly message: string;
64
+ readonly path?: readonly (PropertyKey | {
65
+ readonly key: PropertyKey;
66
+ })[];
61
67
  };
68
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
69
+ readonly "~standard": {
70
+ readonly version: 1;
71
+ readonly vendor: string;
72
+ readonly validate: (value: unknown) => {
73
+ value: Output;
74
+ issues?: undefined;
75
+ } | {
76
+ issues: readonly StandardIssue[];
77
+ } | Promise<{
78
+ value: Output;
79
+ issues?: undefined;
80
+ } | {
81
+ issues: readonly StandardIssue[];
82
+ }>;
83
+ readonly types?: {
84
+ readonly input: Input;
85
+ readonly output: Output;
86
+ };
87
+ };
88
+ }
89
+ type SchemaOutput<S, Fallback> = [S] extends [
90
+ StandardSchemaV1<any, infer Output>
91
+ ] ? Output : Fallback;
62
92
  type CacheOption = string | number | false;
63
- type RouteOptions = {
93
+ type RouteSchema = {
64
94
  tags?: string | string[];
65
95
  title?: string;
66
96
  description?: string;
67
- body?: BodyOption;
97
+ };
98
+ type RouteOptions = {
99
+ schema?: RouteSchema;
100
+ parser?: BodyMode;
101
+ body?: StandardSchemaV1<any, any>;
102
+ query?: StandardSchemaV1<any, any>;
103
+ params?: StandardSchemaV1<any, any>;
104
+ response?: StandardSchemaV1<any, any>;
68
105
  cache?: CacheOption;
69
106
  };
70
107
  type Route = {
@@ -197,6 +234,7 @@ type SecurityOptions = {
197
234
  hsts?: boolean | string;
198
235
  xssProtection?: boolean;
199
236
  traversalProtection?: boolean;
237
+ maxBody?: number | string | false;
200
238
  csp?: boolean | string;
201
239
  coop?: boolean | string;
202
240
  corp?: boolean | string;
@@ -205,6 +243,7 @@ type SecurityOptions = {
205
243
  type SecuritySettings = {
206
244
  trustProxy: boolean;
207
245
  traversalProtection: boolean;
246
+ maxBody: number;
208
247
  headers: Record<string, string>;
209
248
  hsts: string | null;
210
249
  };
@@ -227,7 +266,7 @@ type Options = {
227
266
  log?: LogLevel | boolean;
228
267
  favicon?: string | BucketFile;
229
268
  security?: boolean | SecurityOptions;
230
- body?: BodyOption;
269
+ parser?: BodyMode;
231
270
  cache?: CacheOption;
232
271
  };
233
272
  type Settings = {
@@ -249,7 +288,7 @@ type Settings = {
249
288
  log: Logger;
250
289
  favicon?: string | BucketFile;
251
290
  security: SecuritySettings;
252
- body: BodyOption;
291
+ parser: BodyMode;
253
292
  cache?: CacheOption;
254
293
  };
255
294
  type Time = {
@@ -280,27 +319,23 @@ type BunEnv = Record<string, string> & {
280
319
  };
281
320
  interface ContextExtension {
282
321
  }
283
- type Context<Params extends Record<string, string | undefined> = Record<string, string>, O extends ServerConfig = object> = {
322
+ type Context<C extends ContextTypes = {}> = {
284
323
  method: Method;
285
324
  ip: string;
286
325
  headers: Record<string, string | string[]>;
287
326
  cookies: Record<string, string>;
288
- body?: SerializableValue | Buffer | ReadableStream;
327
+ body?: Field<C, "body", SerializableValue | Buffer | ReadableStream>;
289
328
  url: URL & {
290
- params: Params;
291
- query: Record<string, string>;
329
+ params: Field<C, "params", Record<string, any>>;
330
+ query: Field<C, "query", Record<string, any>>;
292
331
  };
293
332
  options: Settings;
294
333
  platform: Platform;
295
334
  time?: Time;
296
335
  socket?: WebSocket;
297
336
  sockets?: WebSocket[];
298
- session: O extends {
299
- Session?: infer S;
300
- } ? S extends Record<"Session", infer Inner> ? Inner : Record<string, any> : Record<string, any>;
301
- user?: O extends {
302
- User?: infer U;
303
- } ? U extends Record<"User", infer Inner> ? Inner : AuthUser : AuthUser;
337
+ session: Field<C, "session", Record<string, any>>;
338
+ user?: Field<C, "user", Record<string, any>>;
304
339
  init: number;
305
340
  req?: Request;
306
341
  res?: Response & {
@@ -313,7 +348,7 @@ type InlineReply = Response | Reply | BucketFile | {
313
348
  headers?: Headers;
314
349
  } | SerializableValue | JSX.Element | Buffer | ReadableStream;
315
350
  type Body = InlineReply;
316
- type Middleware<O extends ServerConfig = object, Params extends Record<string, string | undefined> = Record<string, string>> = (ctx: Context<Params, O>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
351
+ type Middleware<C extends ContextTypes = {}> = (ctx: Context<C>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
317
352
 
318
353
  type Variables = Record<string, string | string[]>;
319
354
  type ExtendError = string | {
@@ -336,50 +371,68 @@ declare global {
336
371
  var env: Record<string, any>;
337
372
  }
338
373
 
339
- type Mids<O extends ServerConfig, Path extends string> = Middleware<O, PathToParams<Path>>[];
340
- declare class Router<O extends ServerConfig = object> {
374
+ type Fn<C extends ContextTypes> = (ctx: Context<C>) => ReturnType<Middleware>;
375
+ type RouteCtx<C extends ContextTypes, RO extends RouteOptions, Params> = Omit<C, "params" | "query" | "body"> & {
376
+ params: [RO["params"]] extends [StandardSchemaV1<any, any>] ? RO["params"] : Params;
377
+ } & Pick<RO, keyof RO & ("query" | "body")>;
378
+ type Mids<C extends ContextTypes, Path extends string, RO extends RouteOptions = {}> = Fn<RouteCtx<C, RO, PathToParams<Path>>>[];
379
+ type Exact<RO> = RouteOptions & {
380
+ [K in Exclude<keyof RO, keyof RouteOptions>]: never;
381
+ };
382
+ declare class Router<C extends ContextTypes = {}> {
341
383
  middleware: Middleware[];
342
384
  handlers: Record<Method, Route[]>;
343
385
  self(): this;
344
386
  handle(method: Method, pathOrFn?: any, ...rest: any[]): this;
345
- socket<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
346
- socket<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
347
- socket(...middleware: Middleware<O>[]): this;
348
- socket(options: RouteOptions, ...middleware: Middleware<O>[]): this;
349
- get<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
350
- get<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
351
- get(...middleware: Middleware<O>[]): this;
352
- get(options: RouteOptions, ...middleware: Middleware<O>[]): this;
353
- head<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
354
- head<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
355
- head(...middleware: Middleware<O>[]): this;
356
- head(options: RouteOptions, ...middleware: Middleware<O>[]): this;
357
- post<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
358
- post<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
359
- post(...middleware: Middleware<O>[]): this;
360
- post(options: RouteOptions, ...middleware: Middleware<O>[]): this;
361
- put<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
362
- put<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
363
- put(...middleware: Middleware<O>[]): this;
364
- put(options: RouteOptions, ...middleware: Middleware<O>[]): this;
365
- patch<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
366
- patch<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
367
- patch(...middleware: Middleware<O>[]): this;
368
- patch(options: RouteOptions, ...middleware: Middleware<O>[]): this;
369
- delete<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
370
- delete<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
371
- delete(...middleware: Middleware<O>[]): this;
372
- delete(options: RouteOptions, ...middleware: Middleware<O>[]): this;
373
- options<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
374
- options<Path extends string>(path: Path, options: RouteOptions, ...mid: Mids<O, Path>): this;
375
- options(...middleware: Middleware<O>[]): this;
376
- options(options: RouteOptions, ...middleware: Middleware<O>[]): this;
377
- use(...middleware: Middleware[]): this;
378
- use(router: Router): this;
387
+ socket<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
388
+ socket(...middleware: Fn<C>[]): this;
389
+ socket<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
390
+ socket<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
391
+ get<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
392
+ get(...middleware: Fn<C>[]): this;
393
+ get<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
394
+ get<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
395
+ head<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
396
+ head(...middleware: Fn<C>[]): this;
397
+ head<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
398
+ head<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
399
+ post<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
400
+ post(...middleware: Fn<C>[]): this;
401
+ post<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
402
+ post<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
403
+ put<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
404
+ put(...middleware: Fn<C>[]): this;
405
+ put<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
406
+ put<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
407
+ patch<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
408
+ patch(...middleware: Fn<C>[]): this;
409
+ patch<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
410
+ patch<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
411
+ delete<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
412
+ delete(...middleware: Fn<C>[]): this;
413
+ delete<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
414
+ delete<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...middleware: Mids<C, Path, RO>): this;
415
+ options<Path extends string>(path: Path, ...middleware: Mids<C, Path>): this;
416
+ options(...middleware: Fn<C>[]): this;
417
+ options<RO extends Exact<RO>>(options: RO, ...middleware: Fn<RouteCtx<C, RO, Record<string, string>>>[]): this;
418
+ options<Path extends string, RO extends Exact<RO>>(path: Path, options: RO, ...mid: Mids<C, Path, RO>): this;
419
+ use(...middleware: Fn<C>[]): this;
420
+ use(router: Router<any>): this;
421
+ }
422
+ declare function router<C extends ContextTypes = {}>(): Router<C>;
423
+
424
+ declare class StatusError extends Error {
425
+ status: number;
426
+ constructor(msg: string, status?: number);
427
+ }
428
+
429
+ declare class ValidationError extends StatusError {
430
+ source: "body" | "query" | "params" | "response";
431
+ issues: readonly StandardIssue[];
432
+ constructor(source: "body" | "query" | "params" | "response", issues: readonly StandardIssue[]);
379
433
  }
380
- declare function router(): Router;
381
434
 
382
- declare class Server<O extends ServerConfig = {}> extends Router<O> {
435
+ declare class Server<C extends ContextTypes = {}> extends Router<C> {
383
436
  settings: Settings;
384
437
  platform: Platform;
385
438
  sockets: any[];
@@ -397,111 +450,111 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
397
450
  callback(request: Request, context: unknown): Promise<Response>;
398
451
  test(): {
399
452
  get: (path: string, options?: {
453
+ method?: string;
454
+ headers?: HeadersInit;
400
455
  cache?: RequestCache;
456
+ redirect?: RequestRedirect;
401
457
  credentials?: RequestCredentials;
402
- headers?: HeadersInit;
403
458
  integrity?: string;
404
459
  keepalive?: boolean;
405
- method?: string;
406
460
  mode?: RequestMode;
407
461
  priority?: RequestPriority;
408
- redirect?: RequestRedirect;
409
462
  referrer?: string;
410
463
  referrerPolicy?: ReferrerPolicy;
411
464
  signal?: AbortSignal | null;
412
465
  window?: null;
413
466
  }) => Promise<Response>;
414
467
  head: (path: string, options?: {
468
+ method?: string;
469
+ headers?: HeadersInit;
415
470
  cache?: RequestCache;
471
+ redirect?: RequestRedirect;
416
472
  credentials?: RequestCredentials;
417
- headers?: HeadersInit;
418
473
  integrity?: string;
419
474
  keepalive?: boolean;
420
- method?: string;
421
475
  mode?: RequestMode;
422
476
  priority?: RequestPriority;
423
- redirect?: RequestRedirect;
424
477
  referrer?: string;
425
478
  referrerPolicy?: ReferrerPolicy;
426
479
  signal?: AbortSignal | null;
427
480
  window?: null;
428
481
  }) => Promise<Response>;
429
- post: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
482
+ post: (path: string, body?: string | number | boolean | ArrayBuffer | {
430
483
  [key: string]: SerializableValue;
431
- } | SerializableValue[], options?: {
484
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
485
+ method?: string;
486
+ headers?: HeadersInit;
432
487
  cache?: RequestCache;
488
+ redirect?: RequestRedirect;
433
489
  credentials?: RequestCredentials;
434
- headers?: HeadersInit;
435
490
  integrity?: string;
436
491
  keepalive?: boolean;
437
- method?: string;
438
492
  mode?: RequestMode;
439
493
  priority?: RequestPriority;
440
- redirect?: RequestRedirect;
441
494
  referrer?: string;
442
495
  referrerPolicy?: ReferrerPolicy;
443
496
  signal?: AbortSignal | null;
444
497
  window?: null;
445
498
  }) => Promise<Response>;
446
- put: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
499
+ put: (path: string, body?: string | number | boolean | ArrayBuffer | {
447
500
  [key: string]: SerializableValue;
448
- } | SerializableValue[], options?: {
501
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
502
+ method?: string;
503
+ headers?: HeadersInit;
449
504
  cache?: RequestCache;
505
+ redirect?: RequestRedirect;
450
506
  credentials?: RequestCredentials;
451
- headers?: HeadersInit;
452
507
  integrity?: string;
453
508
  keepalive?: boolean;
454
- method?: string;
455
509
  mode?: RequestMode;
456
510
  priority?: RequestPriority;
457
- redirect?: RequestRedirect;
458
511
  referrer?: string;
459
512
  referrerPolicy?: ReferrerPolicy;
460
513
  signal?: AbortSignal | null;
461
514
  window?: null;
462
515
  }) => Promise<Response>;
463
- patch: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
516
+ patch: (path: string, body?: string | number | boolean | ArrayBuffer | {
464
517
  [key: string]: SerializableValue;
465
- } | SerializableValue[], options?: {
518
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
519
+ method?: string;
520
+ headers?: HeadersInit;
466
521
  cache?: RequestCache;
522
+ redirect?: RequestRedirect;
467
523
  credentials?: RequestCredentials;
468
- headers?: HeadersInit;
469
524
  integrity?: string;
470
525
  keepalive?: boolean;
471
- method?: string;
472
526
  mode?: RequestMode;
473
527
  priority?: RequestPriority;
474
- redirect?: RequestRedirect;
475
528
  referrer?: string;
476
529
  referrerPolicy?: ReferrerPolicy;
477
530
  signal?: AbortSignal | null;
478
531
  window?: null;
479
532
  }) => Promise<Response>;
480
533
  delete: (path: string, options?: {
534
+ method?: string;
535
+ headers?: HeadersInit;
481
536
  cache?: RequestCache;
537
+ redirect?: RequestRedirect;
482
538
  credentials?: RequestCredentials;
483
- headers?: HeadersInit;
484
539
  integrity?: string;
485
540
  keepalive?: boolean;
486
- method?: string;
487
541
  mode?: RequestMode;
488
542
  priority?: RequestPriority;
489
- redirect?: RequestRedirect;
490
543
  referrer?: string;
491
544
  referrerPolicy?: ReferrerPolicy;
492
545
  signal?: AbortSignal | null;
493
546
  window?: null;
494
547
  }) => Promise<Response>;
495
548
  options: (path: string, options?: {
549
+ method?: string;
550
+ headers?: HeadersInit;
496
551
  cache?: RequestCache;
552
+ redirect?: RequestRedirect;
497
553
  credentials?: RequestCredentials;
498
- headers?: HeadersInit;
499
554
  integrity?: string;
500
555
  keepalive?: boolean;
501
- method?: string;
502
556
  mode?: RequestMode;
503
557
  priority?: RequestPriority;
504
- redirect?: RequestRedirect;
505
558
  referrer?: string;
506
559
  referrerPolicy?: ReferrerPolicy;
507
560
  signal?: AbortSignal | null;
@@ -509,6 +562,6 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
509
562
  }) => Promise<Response>;
510
563
  };
511
564
  }
512
- declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
565
+ declare function server<C extends ContextTypes = {}>(options?: Options): Server<C>;
513
566
 
514
- 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 ContextExtension, 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 ProfileUser, type Provider, type Route, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type StoreSource, type Strategy, type Time, type UploadOptions, type UploadedFile, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
567
+ export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type ContextExtension, type ContextTypes, 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 ProfileUser, type Provider, type Route, type RouteOptions, type RouteSchema, type RouterMethod, type SchemaOutput, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, TypedServerError as ServerError, type Settings, type StandardIssue, type StandardSchemaV1, type StoreSource, type Strategy, type Time, type UploadOptions, type UploadedFile, ValidationError, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
package/index.js CHANGED
@@ -220,7 +220,7 @@ function human(bytes) {
220
220
  return `${rounded}${UNITS[i]}`;
221
221
  }
222
222
  var tooLarge = (max) => new StatusError(
223
- `Request body exceeds the ${human(max)} limit. Raise it with body: { max: '10mb' } on the route or server, or set max: false to disable.`,
223
+ `Request body exceeds the ${human(max)} limit. Raise it with security: { maxBody: '10mb' }, or maxBody: false to disable it.`,
224
224
  413
225
225
  );
226
226
 
@@ -577,11 +577,9 @@ var sources = /* @__PURE__ */ new WeakMap();
577
577
  function setBodySource(ctx, source) {
578
578
  sources.set(ctx, source);
579
579
  }
580
- async function resolveBody(ctx, body) {
580
+ async function resolveBody(ctx, mode = "parse", max = resolveMax(void 0)) {
581
581
  const source = sources.get(ctx);
582
582
  if (!source) return void 0;
583
- const mode = typeof body === "string" ? body : body?.mode ?? "parse";
584
- const max = resolveMax(typeof body === "object" ? body?.max : void 0);
585
583
  const contentType = String(ctx.headers["content-type"] || "");
586
584
  const isMultipart = /multipart\/form-data/i.test(contentType);
587
585
  const declared = Number(ctx.headers["content-length"]);
@@ -1631,6 +1629,9 @@ function resolveSecurity(security) {
1631
1629
  return {
1632
1630
  trustProxy: o.trustProxy ?? true,
1633
1631
  traversalProtection: off ? false : o.traversalProtection !== false,
1632
+ // Cap on the bytes buffered per request (see bodyLimit). `false` (or
1633
+ // turning security off entirely) resolves to Infinity, meaning no limit.
1634
+ maxBody: off ? INF : resolveMax(o.maxBody),
1634
1635
  headers: headers2,
1635
1636
  hsts: off ? null : val(o.hsts, "max-age=15552000; includeSubDomains")
1636
1637
  };
@@ -1661,6 +1662,19 @@ function applySecurity(res, ctx) {
1661
1662
  // src/helpers/config.ts
1662
1663
  function config(options = {}) {
1663
1664
  const env2 = globalThis.env;
1665
+ const opts = options;
1666
+ if (typeof opts.body === "string") {
1667
+ throw new Error(
1668
+ `The root \`body: '${opts.body}'\` option is now \`parser: '${opts.body}'\`.`
1669
+ );
1670
+ }
1671
+ for (const key of ["body", "query", "params", "response"]) {
1672
+ if (opts[key] !== void 0) {
1673
+ throw new Error(
1674
+ `\`${key}\` is a route option, not a root one; pass it per route, like .post('/', { ${key} }, handler).`
1675
+ );
1676
+ }
1677
+ }
1664
1678
  const raw = options.log ?? env2.LOG_LEVEL;
1665
1679
  const level = raw === true ? "info" : raw === false ? void 0 : raw;
1666
1680
  const log = createLogger(level);
@@ -1670,7 +1684,7 @@ function config(options = {}) {
1670
1684
  log,
1671
1685
  // How request bodies are read: parsed into ctx.body by default; `raw` keeps
1672
1686
  // the Buffer, `stream` hands the handler the unread web ReadableStream.
1673
- body: options.body ?? "parse",
1687
+ parser: options.parser ?? "parse",
1674
1688
  // Secure-by-default response headers + trustProxy for ctx.ip. `false` turns
1675
1689
  // the added headers off; see resolveSecurity for the defaults.
1676
1690
  security: resolveSecurity(options.security)
@@ -1808,7 +1822,7 @@ function applyCors(res, ctx) {
1808
1822
 
1809
1823
  // src/helpers/createWebsocket.ts
1810
1824
  function createWebsocket(sockets, handlers) {
1811
- const run = (event, socket, body) => {
1825
+ const run2 = (event, socket, body) => {
1812
1826
  const routes = handlers.socket?.filter((r2) => r2.path === event) ?? [];
1813
1827
  const user = socket.user ?? socket.data?.user;
1814
1828
  for (const route of routes) {
@@ -1818,14 +1832,14 @@ function createWebsocket(sockets, handlers) {
1818
1832
  }
1819
1833
  };
1820
1834
  return {
1821
- message: (socket, body) => run("message", socket, body),
1835
+ message: (socket, body) => run2("message", socket, body),
1822
1836
  open: (socket) => {
1823
1837
  sockets.push(socket);
1824
- run("open", socket);
1838
+ run2("open", socket);
1825
1839
  },
1826
1840
  close: (socket) => {
1827
1841
  sockets.splice(sockets.indexOf(socket), 1);
1828
- run("close", socket);
1842
+ run2("close", socket);
1829
1843
  }
1830
1844
  };
1831
1845
  }
@@ -2009,36 +2023,48 @@ function pathPattern(pattern, path) {
2009
2023
  return null;
2010
2024
  }
2011
2025
 
2012
- // src/helpers/validate.ts
2013
- function validate(ctx, schema) {
2014
- if (!schema || typeof schema !== "object") return;
2015
- let base;
2016
- try {
2017
- if (typeof schema?.body === "function") {
2018
- base = "body";
2019
- schema.body(ctx.body || {});
2020
- }
2021
- if (typeof schema?.body?.parse === "function") {
2022
- base = "body";
2023
- schema.body.parse(ctx.body || {});
2024
- }
2025
- if (typeof schema?.query === "function") {
2026
- base = "query";
2027
- schema.query(ctx.url.query || {});
2028
- }
2029
- if (typeof schema?.query?.parse === "function") {
2030
- base = "query";
2031
- schema.query.parse(ctx.url.query || {});
2032
- }
2033
- } catch (error) {
2034
- if (error.name === "ZodError" || error.constructor.name === "ZodError") {
2035
- const message = error.issues.map(
2036
- ({ path, message: message2 }) => `[${base}.${path.join(".")}]: ${message2}`
2037
- ).sort().join("\n");
2038
- throw new StatusError(message, 422);
2026
+ // src/errors/ValidationError.ts
2027
+ var ValidationError = class extends StatusError {
2028
+ source;
2029
+ issues;
2030
+ constructor(source, issues) {
2031
+ if (source === "response") {
2032
+ super("Server Error", 500);
2033
+ } else {
2034
+ super(`Invalid request ${source}`, 422);
2039
2035
  }
2040
- throw error;
2036
+ this.source = source;
2037
+ this.issues = issues;
2041
2038
  }
2039
+ };
2040
+
2041
+ // src/helpers/validate.ts
2042
+ async function run(schema, value, source) {
2043
+ const result = await schema["~standard"].validate(value);
2044
+ if (result.issues) throw new ValidationError(source, result.issues);
2045
+ return result.value;
2046
+ }
2047
+ async function validateRequest(ctx, options) {
2048
+ if (options.body) {
2049
+ ctx.body = await run(options.body, ctx.body ?? {}, "body");
2050
+ }
2051
+ if (options.query) {
2052
+ const query = await run(options.query, ctx.url.query || {}, "query");
2053
+ replace2(ctx.url.query, query);
2054
+ }
2055
+ if (options.params) {
2056
+ const params = await run(options.params, ctx.url.params || {}, "params");
2057
+ replace2(ctx.url.params, params);
2058
+ }
2059
+ }
2060
+ async function validateResponse(out, options) {
2061
+ if (!options.response) return out;
2062
+ if (out?.constructor !== Object && !Array.isArray(out)) return out;
2063
+ return await run(options.response, out, "response");
2064
+ }
2065
+ function replace2(target, values) {
2066
+ for (const key of Object.keys(target)) delete target[key];
2067
+ Object.assign(target, values);
2042
2068
  }
2043
2069
 
2044
2070
  // src/helpers/handleRequest.ts
@@ -2063,20 +2089,28 @@ async function getResponse(app, ctx) {
2063
2089
  ctx.options = { ...app.settings, ...route.options };
2064
2090
  }
2065
2091
  checkTraversal(params, ctx);
2066
- ctx.body = await resolveBody(ctx, ctx.options.body);
2092
+ ctx.body = await resolveBody(
2093
+ ctx,
2094
+ ctx.options.parser,
2095
+ ctx.options.security.maxBody
2096
+ );
2097
+ await validateRequest(ctx, route.options);
2067
2098
  for (const cb of route.fns) {
2068
- if (typeof cb === "function") {
2069
- const res = await cb(ctx);
2070
- const out = await parseResponse(res, ctx);
2071
- if (out) return out;
2072
- } else {
2073
- validate(ctx, cb);
2074
- }
2099
+ const res = await cb(ctx);
2100
+ const out = await parseResponse(
2101
+ await validateResponse(res, route.options),
2102
+ ctx
2103
+ );
2104
+ if (out) return out;
2075
2105
  }
2076
2106
  break;
2077
2107
  }
2078
2108
  if (!matched) {
2079
- ctx.body = await resolveBody(ctx, ctx.options.body);
2109
+ ctx.body = await resolveBody(
2110
+ ctx,
2111
+ ctx.options.parser,
2112
+ ctx.options.security.maxBody
2113
+ );
2080
2114
  for (const mw of app.middleware) {
2081
2115
  const out = await parseResponse(await mw(ctx), ctx);
2082
2116
  if (out) return out;
@@ -2541,8 +2575,8 @@ var generateOpenApiPaths = (handlers) => {
2541
2575
  for (const route of routes) {
2542
2576
  const path = route.path;
2543
2577
  const fn = route.fns.find((p) => typeof p === "function");
2544
- const meta = route.fns.find((p) => typeof p === "object");
2545
- const config2 = getConfig(route.options);
2578
+ const meta = route.options ?? {};
2579
+ const config2 = getConfig(route.options?.schema);
2546
2580
  if (typeof path !== "string" || path === "*" || path === "/docs" || !fn) {
2547
2581
  continue;
2548
2582
  }
@@ -2595,7 +2629,7 @@ var generateOpenApiPaths = (handlers) => {
2595
2629
  paths[normalizedPath][method] = {
2596
2630
  tags: config2.tags,
2597
2631
  summary: config2.title || getTag("@title", fn) || `${method.toUpperCase()} ${normalizedPath}`,
2598
- description: getTitle(fn) || getDescription(fn),
2632
+ description: config2.description || getTitle(fn) || getDescription(fn),
2599
2633
  requestBody,
2600
2634
  parameters,
2601
2635
  responses
@@ -3052,6 +3086,14 @@ var Netlify = async (app, request, context) => {
3052
3086
  };
3053
3087
 
3054
3088
  // src/router.ts
3089
+ function checkParserConflict(options, globalParser) {
3090
+ const parser = options.parser ?? globalParser ?? "parse";
3091
+ if (options.body && parser !== "parse") {
3092
+ throw new Error(
3093
+ `A \`parser: '${parser}'\` route never parses the body, so its \`body\` schema cannot run. Remove one, or set \`parser: 'parse'\` on the route.`
3094
+ );
3095
+ }
3096
+ }
3055
3097
  var Router = class _Router {
3056
3098
  // Cross-cutting middleware added with .use(); they run on every request
3057
3099
  middleware = [];
@@ -3085,6 +3127,7 @@ var Router = class _Router {
3085
3127
  if (rest[0] != null && typeof rest[0] !== "function") {
3086
3128
  options = rest.shift();
3087
3129
  }
3130
+ checkParserConflict(options, this.settings?.parser);
3088
3131
  const base = method === "socket" ? [] : this.middleware;
3089
3132
  const fns = [...base, ...rest].filter((fn) => fn != null);
3090
3133
  this.handlers[method].push({ path, options, fns });
@@ -3119,6 +3162,7 @@ var Router = class _Router {
3119
3162
  if (arg instanceof _Router) {
3120
3163
  for (const m of Object.keys(arg.handlers)) {
3121
3164
  for (const route of arg.handlers[m]) {
3165
+ checkParserConflict(route.options, this.settings?.parser);
3122
3166
  const base = m === "socket" ? [] : this.middleware;
3123
3167
  this.handlers[m].push({
3124
3168
  path: route.path,
@@ -3208,16 +3252,17 @@ var Server = class extends Router {
3208
3252
  } else if (this.platform.runtime === "bun") {
3209
3253
  this.settings.log.start(`http://localhost:${this.settings.port}/`);
3210
3254
  }
3211
- this.use(timer);
3212
- if (this.settings.cors) this.use(preflight);
3213
- this.use(assets);
3214
- if (this.settings.favicon) this.get("/favicon.ico", favicon);
3215
- this.use(session);
3255
+ const app = this;
3256
+ app.use(timer);
3257
+ if (this.settings.cors) app.use(preflight);
3258
+ app.use(assets);
3259
+ if (this.settings.favicon) app.get("/favicon.ico", favicon);
3260
+ app.use(session);
3216
3261
  if (this.settings.auth) {
3217
- auth(this);
3262
+ auth(app);
3218
3263
  }
3219
3264
  if (this.settings.openapi) {
3220
- this.get(this.settings.openapi.path || "/docs", openapi_default);
3265
+ app.get(this.settings.openapi.path || "/docs", openapi_default);
3221
3266
  }
3222
3267
  }
3223
3268
  self() {
@@ -3252,6 +3297,7 @@ function server(options) {
3252
3297
  export {
3253
3298
  Server,
3254
3299
  ServerError_default as ServerError,
3300
+ ValidationError,
3255
3301
  default3 as bucket,
3256
3302
  cache,
3257
3303
  cookies,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.40.4",
3
+ "version": "0.41.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",
@@ -70,9 +70,12 @@
70
70
  "devDependencies": {
71
71
  "@types/bun": "^1.3.0",
72
72
  "@types/node": "^24.10.0",
73
+ "arktype": "^2.2.3",
73
74
  "bun": "^1.3.13",
74
75
  "check-dts": "^0.8.2",
75
76
  "tsup": "^8.5.1",
76
- "typescript": "^6.0.2"
77
+ "typescript": "^6.0.2",
78
+ "valibot": "^1.4.2",
79
+ "zod": "^4.4.3"
77
80
  }
78
81
  }