@server/next 0.40.3 → 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 +144 -89
  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 = {
@@ -278,40 +317,38 @@ type BunEnv = Record<string, string> & {
278
317
  data?: any;
279
318
  }) => boolean;
280
319
  };
281
- type Context<Params extends Record<string, string | undefined> = Record<string, string>, O extends ServerConfig = object> = {
320
+ interface ContextExtension {
321
+ }
322
+ type Context<C extends ContextTypes = {}> = {
282
323
  method: Method;
283
324
  ip: string;
284
325
  headers: Record<string, string | string[]>;
285
326
  cookies: Record<string, string>;
286
- body?: SerializableValue | Buffer | ReadableStream;
327
+ body?: Field<C, "body", SerializableValue | Buffer | ReadableStream>;
287
328
  url: URL & {
288
- params: Params;
289
- query: Record<string, string>;
329
+ params: Field<C, "params", Record<string, any>>;
330
+ query: Field<C, "query", Record<string, any>>;
290
331
  };
291
332
  options: Settings;
292
333
  platform: Platform;
293
334
  time?: Time;
294
335
  socket?: WebSocket;
295
336
  sockets?: WebSocket[];
296
- session: O extends {
297
- Session?: infer S;
298
- } ? S extends Record<"Session", infer Inner> ? Inner : Record<string, any> : Record<string, any>;
299
- user?: O extends {
300
- User?: infer U;
301
- } ? 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>>;
302
339
  init: number;
303
340
  req?: Request;
304
341
  res?: Response & {
305
342
  cookies?: Record<string, string>;
306
343
  };
307
344
  app: Server;
308
- };
345
+ } & ContextExtension;
309
346
  type InlineReply = Response | Reply | BucketFile | {
310
347
  body: string;
311
348
  headers?: Headers;
312
349
  } | SerializableValue | JSX.Element | Buffer | ReadableStream;
313
350
  type Body = InlineReply;
314
- 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>;
315
352
 
316
353
  type Variables = Record<string, string | string[]>;
317
354
  type ExtendError = string | {
@@ -334,50 +371,68 @@ declare global {
334
371
  var env: Record<string, any>;
335
372
  }
336
373
 
337
- type Mids<O extends ServerConfig, Path extends string> = Middleware<O, PathToParams<Path>>[];
338
- 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 = {}> {
339
383
  middleware: Middleware[];
340
384
  handlers: Record<Method, Route[]>;
341
385
  self(): this;
342
386
  handle(method: Method, pathOrFn?: any, ...rest: any[]): this;
343
- socket<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
344
- socket<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
345
- socket(...middleware: Middleware<O>[]): this;
346
- socket(options: RouteOptions, ...middleware: Middleware<O>[]): this;
347
- get<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
348
- get<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
349
- get(...middleware: Middleware<O>[]): this;
350
- get(options: RouteOptions, ...middleware: Middleware<O>[]): this;
351
- head<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
352
- head<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
353
- head(...middleware: Middleware<O>[]): this;
354
- head(options: RouteOptions, ...middleware: Middleware<O>[]): this;
355
- post<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
356
- post<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
357
- post(...middleware: Middleware<O>[]): this;
358
- post(options: RouteOptions, ...middleware: Middleware<O>[]): this;
359
- put<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
360
- put<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
361
- put(...middleware: Middleware<O>[]): this;
362
- put(options: RouteOptions, ...middleware: Middleware<O>[]): this;
363
- patch<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
364
- patch<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
365
- patch(...middleware: Middleware<O>[]): this;
366
- patch(options: RouteOptions, ...middleware: Middleware<O>[]): this;
367
- delete<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
368
- delete<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
369
- delete(...middleware: Middleware<O>[]): this;
370
- delete(options: RouteOptions, ...middleware: Middleware<O>[]): this;
371
- options<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
372
- options<Path extends string>(path: Path, options: RouteOptions, ...mid: Mids<O, Path>): this;
373
- options(...middleware: Middleware<O>[]): this;
374
- options(options: RouteOptions, ...middleware: Middleware<O>[]): this;
375
- use(...middleware: Middleware[]): this;
376
- 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);
377
427
  }
378
- declare function router(): Router;
379
428
 
380
- declare class Server<O extends ServerConfig = {}> extends Router<O> {
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[]);
433
+ }
434
+
435
+ declare class Server<C extends ContextTypes = {}> extends Router<C> {
381
436
  settings: Settings;
382
437
  platform: Platform;
383
438
  sockets: any[];
@@ -395,111 +450,111 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
395
450
  callback(request: Request, context: unknown): Promise<Response>;
396
451
  test(): {
397
452
  get: (path: string, options?: {
453
+ method?: string;
454
+ headers?: HeadersInit;
398
455
  cache?: RequestCache;
456
+ redirect?: RequestRedirect;
399
457
  credentials?: RequestCredentials;
400
- headers?: HeadersInit;
401
458
  integrity?: string;
402
459
  keepalive?: boolean;
403
- method?: string;
404
460
  mode?: RequestMode;
405
461
  priority?: RequestPriority;
406
- redirect?: RequestRedirect;
407
462
  referrer?: string;
408
463
  referrerPolicy?: ReferrerPolicy;
409
464
  signal?: AbortSignal | null;
410
465
  window?: null;
411
466
  }) => Promise<Response>;
412
467
  head: (path: string, options?: {
468
+ method?: string;
469
+ headers?: HeadersInit;
413
470
  cache?: RequestCache;
471
+ redirect?: RequestRedirect;
414
472
  credentials?: RequestCredentials;
415
- headers?: HeadersInit;
416
473
  integrity?: string;
417
474
  keepalive?: boolean;
418
- method?: string;
419
475
  mode?: RequestMode;
420
476
  priority?: RequestPriority;
421
- redirect?: RequestRedirect;
422
477
  referrer?: string;
423
478
  referrerPolicy?: ReferrerPolicy;
424
479
  signal?: AbortSignal | null;
425
480
  window?: null;
426
481
  }) => Promise<Response>;
427
- post: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
482
+ post: (path: string, body?: string | number | boolean | ArrayBuffer | {
428
483
  [key: string]: SerializableValue;
429
- } | SerializableValue[], options?: {
484
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
485
+ method?: string;
486
+ headers?: HeadersInit;
430
487
  cache?: RequestCache;
488
+ redirect?: RequestRedirect;
431
489
  credentials?: RequestCredentials;
432
- headers?: HeadersInit;
433
490
  integrity?: string;
434
491
  keepalive?: boolean;
435
- method?: string;
436
492
  mode?: RequestMode;
437
493
  priority?: RequestPriority;
438
- redirect?: RequestRedirect;
439
494
  referrer?: string;
440
495
  referrerPolicy?: ReferrerPolicy;
441
496
  signal?: AbortSignal | null;
442
497
  window?: null;
443
498
  }) => Promise<Response>;
444
- put: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
499
+ put: (path: string, body?: string | number | boolean | ArrayBuffer | {
445
500
  [key: string]: SerializableValue;
446
- } | SerializableValue[], options?: {
501
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
502
+ method?: string;
503
+ headers?: HeadersInit;
447
504
  cache?: RequestCache;
505
+ redirect?: RequestRedirect;
448
506
  credentials?: RequestCredentials;
449
- headers?: HeadersInit;
450
507
  integrity?: string;
451
508
  keepalive?: boolean;
452
- method?: string;
453
509
  mode?: RequestMode;
454
510
  priority?: RequestPriority;
455
- redirect?: RequestRedirect;
456
511
  referrer?: string;
457
512
  referrerPolicy?: ReferrerPolicy;
458
513
  signal?: AbortSignal | null;
459
514
  window?: null;
460
515
  }) => Promise<Response>;
461
- patch: (path: string, body?: string | number | boolean | ArrayBuffer | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams | {
516
+ patch: (path: string, body?: string | number | boolean | ArrayBuffer | {
462
517
  [key: string]: SerializableValue;
463
- } | SerializableValue[], options?: {
518
+ } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
519
+ method?: string;
520
+ headers?: HeadersInit;
464
521
  cache?: RequestCache;
522
+ redirect?: RequestRedirect;
465
523
  credentials?: RequestCredentials;
466
- headers?: HeadersInit;
467
524
  integrity?: string;
468
525
  keepalive?: boolean;
469
- method?: string;
470
526
  mode?: RequestMode;
471
527
  priority?: RequestPriority;
472
- redirect?: RequestRedirect;
473
528
  referrer?: string;
474
529
  referrerPolicy?: ReferrerPolicy;
475
530
  signal?: AbortSignal | null;
476
531
  window?: null;
477
532
  }) => Promise<Response>;
478
533
  delete: (path: string, options?: {
534
+ method?: string;
535
+ headers?: HeadersInit;
479
536
  cache?: RequestCache;
537
+ redirect?: RequestRedirect;
480
538
  credentials?: RequestCredentials;
481
- headers?: HeadersInit;
482
539
  integrity?: string;
483
540
  keepalive?: boolean;
484
- method?: string;
485
541
  mode?: RequestMode;
486
542
  priority?: RequestPriority;
487
- redirect?: RequestRedirect;
488
543
  referrer?: string;
489
544
  referrerPolicy?: ReferrerPolicy;
490
545
  signal?: AbortSignal | null;
491
546
  window?: null;
492
547
  }) => Promise<Response>;
493
548
  options: (path: string, options?: {
549
+ method?: string;
550
+ headers?: HeadersInit;
494
551
  cache?: RequestCache;
552
+ redirect?: RequestRedirect;
495
553
  credentials?: RequestCredentials;
496
- headers?: HeadersInit;
497
554
  integrity?: string;
498
555
  keepalive?: boolean;
499
- method?: string;
500
556
  mode?: RequestMode;
501
557
  priority?: RequestPriority;
502
- redirect?: RequestRedirect;
503
558
  referrer?: string;
504
559
  referrerPolicy?: ReferrerPolicy;
505
560
  signal?: AbortSignal | null;
@@ -507,6 +562,6 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
507
562
  }) => Promise<Response>;
508
563
  };
509
564
  }
510
- 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>;
511
566
 
512
- 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 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.3",
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
  }