@server/next 0.43.0 → 0.44.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 +26 -23
  2. package/index.js +91 -142
  3. package/package.json +5 -3
package/index.d.ts CHANGED
@@ -96,7 +96,7 @@ type RouteSchema = {
96
96
  description?: string;
97
97
  };
98
98
  type RouteOptions = {
99
- schema?: RouteSchema;
99
+ schema?: RouteSchema | false;
100
100
  parser?: BodyMode;
101
101
  body?: StandardSchemaV1<any, any>;
102
102
  query?: StandardSchemaV1<any, any>;
@@ -259,11 +259,15 @@ type Options = {
259
259
  sessions?: StoreSource;
260
260
  cors?: CorsOptions;
261
261
  auth?: AuthOption;
262
- openapi?: any;
262
+ openapi?: boolean | string | {
263
+ path?: string;
264
+ title?: string;
265
+ description?: string;
266
+ version?: string;
267
+ };
263
268
  onError?: OnError;
264
269
  onResponse?: OnResponse;
265
270
  log?: LogLevel | boolean;
266
- favicon?: string | BucketFile;
267
271
  security?: boolean | SecurityOptions;
268
272
  parser?: BodyMode;
269
273
  cache?: CacheOption;
@@ -279,11 +283,15 @@ type Settings = {
279
283
  sessionsDefault?: boolean;
280
284
  cors?: CorsSettings;
281
285
  auth?: AuthSettings;
282
- openapi?: any;
286
+ openapi?: {
287
+ path: string;
288
+ title?: string;
289
+ description?: string;
290
+ version?: string;
291
+ };
283
292
  onError?: OnError;
284
293
  onResponse?: OnResponse;
285
294
  log: Logger;
286
- favicon?: string | BucketFile;
287
295
  security: SecuritySettings;
288
296
  parser: BodyMode;
289
297
  cache?: CacheOption;
@@ -319,9 +327,9 @@ interface ContextExtension {
319
327
  type Context<C extends ContextTypes = {}> = {
320
328
  method: Method;
321
329
  ip: string;
330
+ signal: AbortSignal;
322
331
  headers: Record<string, string | string[]>;
323
332
  cookies: Record<string, string>;
324
- body?: Field<C, "body", SerializableValue | Buffer | ReadableStream>;
325
333
  url: URL & {
326
334
  params: Field<C, "params", Record<string, any>>;
327
335
  query: Field<C, "query", Record<string, any>>;
@@ -334,12 +342,12 @@ type Context<C extends ContextTypes = {}> = {
334
342
  session: Field<C, "session", Record<string, any>>;
335
343
  user?: Field<C, "user", Record<string, any>>;
336
344
  init: number;
337
- req?: Request;
338
- res?: Response & {
339
- cookies?: Record<string, string>;
340
- };
341
345
  app: Server;
342
- } & ContextExtension;
346
+ } & ("body" extends keyof C ? {
347
+ body: Field<C, "body", never>;
348
+ } : {
349
+ body?: SerializableValue | Buffer | ReadableStream;
350
+ }) & ContextExtension;
343
351
  type InlineReply = Response | Reply | BucketFile | {
344
352
  body: string;
345
353
  headers?: Headers;
@@ -434,11 +442,6 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
434
442
  platform: Platform;
435
443
  sockets: any[];
436
444
  websocket: any;
437
- faviconCache?: {
438
- bytes: Buffer;
439
- type: string;
440
- etag: string;
441
- } | null;
442
445
  port?: number;
443
446
  constructor(options?: Options);
444
447
  self(): this;
@@ -448,6 +451,7 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
448
451
  test(): {
449
452
  get: (path: string, options?: {
450
453
  method?: string;
454
+ signal?: AbortSignal | null;
451
455
  headers?: HeadersInit;
452
456
  cache?: RequestCache;
453
457
  redirect?: RequestRedirect;
@@ -458,11 +462,11 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
458
462
  priority?: RequestPriority;
459
463
  referrer?: string;
460
464
  referrerPolicy?: ReferrerPolicy;
461
- signal?: AbortSignal | null;
462
465
  window?: null;
463
466
  }) => Promise<Response>;
464
467
  head: (path: string, options?: {
465
468
  method?: string;
469
+ signal?: AbortSignal | null;
466
470
  headers?: HeadersInit;
467
471
  cache?: RequestCache;
468
472
  redirect?: RequestRedirect;
@@ -473,13 +477,13 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
473
477
  priority?: RequestPriority;
474
478
  referrer?: string;
475
479
  referrerPolicy?: ReferrerPolicy;
476
- signal?: AbortSignal | null;
477
480
  window?: null;
478
481
  }) => Promise<Response>;
479
482
  post: (path: string, body?: string | number | boolean | ArrayBuffer | {
480
483
  [key: string]: SerializableValue;
481
484
  } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
482
485
  method?: string;
486
+ signal?: AbortSignal | null;
483
487
  headers?: HeadersInit;
484
488
  cache?: RequestCache;
485
489
  redirect?: RequestRedirect;
@@ -490,13 +494,13 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
490
494
  priority?: RequestPriority;
491
495
  referrer?: string;
492
496
  referrerPolicy?: ReferrerPolicy;
493
- signal?: AbortSignal | null;
494
497
  window?: null;
495
498
  }) => Promise<Response>;
496
499
  put: (path: string, body?: string | number | boolean | ArrayBuffer | {
497
500
  [key: string]: SerializableValue;
498
501
  } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
499
502
  method?: string;
503
+ signal?: AbortSignal | null;
500
504
  headers?: HeadersInit;
501
505
  cache?: RequestCache;
502
506
  redirect?: RequestRedirect;
@@ -507,13 +511,13 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
507
511
  priority?: RequestPriority;
508
512
  referrer?: string;
509
513
  referrerPolicy?: ReferrerPolicy;
510
- signal?: AbortSignal | null;
511
514
  window?: null;
512
515
  }) => Promise<Response>;
513
516
  patch: (path: string, body?: string | number | boolean | ArrayBuffer | {
514
517
  [key: string]: SerializableValue;
515
518
  } | SerializableValue[] | ReadableStream<any> | Blob | ArrayBufferView<ArrayBuffer> | FormData | URLSearchParams, options?: {
516
519
  method?: string;
520
+ signal?: AbortSignal | null;
517
521
  headers?: HeadersInit;
518
522
  cache?: RequestCache;
519
523
  redirect?: RequestRedirect;
@@ -524,11 +528,11 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
524
528
  priority?: RequestPriority;
525
529
  referrer?: string;
526
530
  referrerPolicy?: ReferrerPolicy;
527
- signal?: AbortSignal | null;
528
531
  window?: null;
529
532
  }) => Promise<Response>;
530
533
  delete: (path: string, options?: {
531
534
  method?: string;
535
+ signal?: AbortSignal | null;
532
536
  headers?: HeadersInit;
533
537
  cache?: RequestCache;
534
538
  redirect?: RequestRedirect;
@@ -539,11 +543,11 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
539
543
  priority?: RequestPriority;
540
544
  referrer?: string;
541
545
  referrerPolicy?: ReferrerPolicy;
542
- signal?: AbortSignal | null;
543
546
  window?: null;
544
547
  }) => Promise<Response>;
545
548
  options: (path: string, options?: {
546
549
  method?: string;
550
+ signal?: AbortSignal | null;
547
551
  headers?: HeadersInit;
548
552
  cache?: RequestCache;
549
553
  redirect?: RequestRedirect;
@@ -554,7 +558,6 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
554
558
  priority?: RequestPriority;
555
559
  referrer?: string;
556
560
  referrerPolicy?: ReferrerPolicy;
557
- signal?: AbortSignal | null;
558
561
  window?: null;
559
562
  }) => Promise<Response>;
560
563
  };
package/index.js CHANGED
@@ -186,8 +186,8 @@ function getExt(filename) {
186
186
  return filename.slice(i).toLowerCase();
187
187
  }
188
188
  async function saveFileToBucket(originalName, data, bucket2, contentType) {
189
- const ext2 = getExt(originalName);
190
- const id = `${createId()}${ext2}`;
189
+ const ext = getExt(originalName);
190
+ const id = `${createId()}${ext}`;
191
191
  const file2 = bucket2.file(id);
192
192
  await file2.write(data, { type: contentType });
193
193
  return {
@@ -210,10 +210,10 @@ function validateFile(originalName, data, contentType, limits) {
210
210
  );
211
211
  }
212
212
  if (fileType2 && fileType2.length > 0) {
213
- const ext2 = getExt(originalName);
213
+ const ext = getExt(originalName);
214
214
  const mime = contentType.toLowerCase();
215
215
  const allowed = fileType2.some(
216
- (t) => t.toLowerCase() === mime || t.toLowerCase() === ext2
216
+ (t) => t.toLowerCase() === mime || t.toLowerCase() === ext
217
217
  );
218
218
  if (!allowed) {
219
219
  throw new Error(
@@ -353,11 +353,11 @@ function isProbablyText(buffer) {
353
353
  return true;
354
354
  }
355
355
  var extByMime = {};
356
- for (const ext2 in mimes_default) extByMime[mimes_default[ext2]] = ext2;
356
+ for (const ext in mimes_default) extByMime[mimes_default[ext]] = ext;
357
357
  function extFromType(type2) {
358
358
  const base = (type2 || "").split(";")[0].trim().toLowerCase();
359
- const ext2 = extByMime[base];
360
- if (ext2) return `.${ext2}`;
359
+ const ext = extByMime[base];
360
+ if (ext) return `.${ext}`;
361
361
  const sub = base.split("/")[1];
362
362
  return sub && /^[a-z0-9]+$/.test(sub) ? `.${sub}` : ".bin";
363
363
  }
@@ -770,20 +770,20 @@ var encodeExt = (name) => encodeURIComponent(name).replace(
770
770
  );
771
771
  function disposition(name) {
772
772
  if (!name) return "attachment";
773
- const clean = name.replace(/[\r\n]/g, "").split(/[\\/]/).pop() || "";
774
- if (!clean) return "attachment";
775
- const ascii = clean.replace(/[^\x20-\x7e]/g, "?");
773
+ const clean2 = name.replace(/[\r\n]/g, "").split(/[\\/]/).pop() || "";
774
+ if (!clean2) return "attachment";
775
+ const ascii = clean2.replace(/[^\x20-\x7e]/g, "?");
776
776
  const value = `attachment; filename="${ascii.replace(/["\\]/g, "\\$&")}"`;
777
- if (clean === ascii) return value;
778
- return `${value}; filename*=UTF-8''${encodeExt(clean)}`;
777
+ if (clean2 === ascii) return value;
778
+ return `${value}; filename*=UTF-8''${encodeExt(clean2)}`;
779
779
  }
780
780
 
781
781
  // src/helpers/fileType.ts
782
782
  function fileType(file2) {
783
783
  if (file2.type) return file2.type;
784
784
  const name = file2.path || file2.name || "";
785
- const ext2 = name.split(".").pop()?.toLowerCase();
786
- return ext2 ? mimes_default[ext2] : void 0;
785
+ const ext = name.split(".").pop()?.toLowerCase();
786
+ return ext ? mimes_default[ext] : void 0;
787
787
  }
788
788
 
789
789
  // src/helpers/isHtml.ts
@@ -817,8 +817,8 @@ var Reply = class {
817
817
  return this;
818
818
  }
819
819
  download(name) {
820
- const ext2 = name?.split(".").pop();
821
- if (ext2 && !this.res.headers.get("content-type")) this.type(ext2);
820
+ const ext = name?.split(".").pop();
821
+ if (ext && !this.res.headers.get("content-type")) this.type(ext);
822
822
  return this.headers("content-disposition", disposition(name));
823
823
  }
824
824
  headers(key, value) {
@@ -876,10 +876,10 @@ var Reply = class {
876
876
  if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(path)) return this.status(404).send();
877
877
  try {
878
878
  const fs = await import("fs");
879
- const ext2 = path.split(".").pop();
879
+ const ext = path.split(".").pop();
880
880
  await fs.promises.access(path);
881
881
  const stream = fs.createReadStream(path);
882
- return this.type(ext2).send(stream);
882
+ return this.type(ext).send(stream);
883
883
  } catch (error) {
884
884
  if (error.code === "ENOENT" || error.code === "EISDIR") {
885
885
  return this.status(404).send();
@@ -1884,8 +1884,6 @@ function config(options = {}) {
1884
1884
  const publicDir = options.public || env2.PUBLIC;
1885
1885
  settings.public = publicDir ? bucket(publicDir) : null;
1886
1886
  settings.uploads = resolveUploads(options.uploads);
1887
- const favicon2 = options.favicon || env2.FAVICON;
1888
- if (favicon2) settings.favicon = favicon2;
1889
1887
  const production = env2.NODE_ENV === "production";
1890
1888
  const defaulted = options.sessions == null;
1891
1889
  settings.sessionsDefault = defaulted;
@@ -1913,9 +1911,10 @@ function config(options = {}) {
1913
1911
  );
1914
1912
  }
1915
1913
  if (options.openapi) {
1916
- if (options.openapi === true) {
1917
- settings.openapi = {};
1918
- }
1914
+ const o = options.openapi;
1915
+ if (o === true) settings.openapi = { path: "/openapi.json" };
1916
+ else if (typeof o === "string") settings.openapi = { path: o };
1917
+ else settings.openapi = { path: "/openapi.json", ...o };
1919
1918
  }
1920
1919
  settings.onError = options.onError || ((error) => {
1921
1920
  return new Response(error.message || "Server Error", {
@@ -1934,9 +1933,8 @@ function config(options = {}) {
1934
1933
  const origin = settings.cors.origin === true ? "*" : String(settings.cors.origin);
1935
1934
  log.message("cors", origin);
1936
1935
  }
1937
- if (settings.favicon) log.message("favicon", loc(settings.favicon));
1938
1936
  if (settings.cache !== void 0) log.message("cache", loc(options.cache));
1939
- if (settings.openapi) log.message("openapi", settings.openapi.path || "/docs");
1937
+ if (settings.openapi) log.message("openapi", settings.openapi.path);
1940
1938
  return settings;
1941
1939
  }
1942
1940
 
@@ -2136,11 +2134,6 @@ async function parseResponse(out, ctx) {
2136
2134
  }
2137
2135
  ctx.options.sessions.set(id, ctx.session);
2138
2136
  }
2139
- if (ctx?.res?.headers) {
2140
- for (const key in ctx.res.headers) {
2141
- out.headers[key] = ctx.res.headers[key];
2142
- }
2143
- }
2144
2137
  return out;
2145
2138
  }
2146
2139
 
@@ -2532,31 +2525,32 @@ function auth(app) {
2532
2525
  app.use(async function middle(ctx) {
2533
2526
  ctx.user = await getUser(ctx);
2534
2527
  });
2535
- app.post("/auth/logout", logout);
2528
+ const spec = { schema: { tags: "auth" } };
2529
+ app.post("/auth/logout", spec, logout);
2536
2530
  const enabled = app.settings.auth.providers;
2537
2531
  for (const name of oauth2) {
2538
2532
  if (!enabled.includes(name)) continue;
2539
2533
  const key = name.toUpperCase();
2540
2534
  if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
2541
2535
  if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
2542
- app.get(`/auth/login/${name}`, providers_default[name].login);
2543
- app.get(`/auth/callback/${name}`, providers_default[name].callback);
2544
- app.post(`/auth/verify/${name}`, providers_default[name].verify);
2536
+ app.get(`/auth/login/${name}`, spec, providers_default[name].login);
2537
+ app.get(`/auth/callback/${name}`, spec, providers_default[name].callback);
2538
+ app.post(`/auth/verify/${name}`, spec, providers_default[name].verify);
2545
2539
  }
2546
2540
  if (enabled.includes("apple")) {
2547
2541
  const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
2548
2542
  for (const key of keys) {
2549
2543
  if (!env[key]) throw new Error(`${key} not defined`);
2550
2544
  }
2551
- app.get("/auth/login/apple", providers_default.apple.login);
2552
- app.post("/auth/callback/apple", providers_default.apple.callback);
2553
- app.post("/auth/verify/apple", providers_default.apple.verify);
2545
+ app.get("/auth/login/apple", spec, providers_default.apple.login);
2546
+ app.post("/auth/callback/apple", spec, providers_default.apple.callback);
2547
+ app.post("/auth/verify/apple", spec, providers_default.apple.verify);
2554
2548
  }
2555
2549
  if (enabled.includes("email")) {
2556
- app.post("/auth/register/email", providers_default.email.register);
2557
- app.post("/auth/login/email", providers_default.email.login);
2558
- app.put("/auth/password/email", providers_default.email.password);
2559
- app.put("/auth/reset/email", providers_default.email.reset);
2550
+ app.post("/auth/register/email", spec, providers_default.email.register);
2551
+ app.post("/auth/login/email", spec, providers_default.email.login);
2552
+ app.put("/auth/password/email", spec, providers_default.email.password);
2553
+ app.put("/auth/reset/email", spec, providers_default.email.reset);
2560
2554
  }
2561
2555
  }
2562
2556
 
@@ -2595,8 +2589,8 @@ async function assets(ctx) {
2595
2589
  const info = file2.info?.bind(file2);
2596
2590
  const meta = info ? await info() : null;
2597
2591
  if (info ? !meta : !await file2.exists()) return;
2598
- const ext2 = ctx.url.pathname.split(".").pop()?.toLowerCase();
2599
- const ctype = ext2 && mimes_default[ext2] || meta?.type || ext2;
2592
+ const ext = ctx.url.pathname.split(".").pop()?.toLowerCase();
2593
+ const ctype = ext && mimes_default[ext] || meta?.type || ext;
2600
2594
  const headers2 = { "cache-control": CACHE_CONTROL };
2601
2595
  let tag;
2602
2596
  if (meta) {
@@ -2631,46 +2625,8 @@ async function assets(ctx) {
2631
2625
  }
2632
2626
  }
2633
2627
 
2634
- // src/middle/favicon.ts
2635
- var CACHE_CONTROL2 = "public, max-age=86400";
2636
- var ext = (name) => name.split(".").pop() || "ico";
2637
- async function loadFavicon(fav) {
2638
- try {
2639
- const type2 = ext(typeof fav === "string" ? fav : fav?.name);
2640
- const bytes = typeof fav === "string" ? await (await import("fs/promises")).readFile(fav) : Buffer.from(await fav.bytes());
2641
- return { bytes, type: type2, etag: etag(bytes) };
2642
- } catch {
2643
- return null;
2644
- }
2645
- }
2646
- async function favicon(ctx) {
2647
- const fav = ctx.options.favicon;
2648
- if (!fav) return;
2649
- if (ctx.app.faviconCache === void 0) {
2650
- ctx.app.faviconCache = await loadFavicon(fav);
2651
- }
2652
- const entry = ctx.app.faviconCache;
2653
- if (!entry) return 204;
2654
- const headers2 = { "cache-control": CACHE_CONTROL2, etag: entry.etag };
2655
- if (ctx.headers["if-none-match"] === entry.etag) {
2656
- return status(304).headers(headers2).send();
2657
- }
2658
- return type(entry.type).headers(headers2).send(entry.bytes);
2659
- }
2660
-
2661
2628
  // src/middle/openapi.ts
2662
2629
  import * as fsp from "fs/promises";
2663
- var entities = {
2664
- "&": "&amp;",
2665
- "<": "&lt;",
2666
- ">": "&gt;",
2667
- '"': "&quot;"
2668
- };
2669
- var encode = (str = "") => {
2670
- if (typeof str === "number") str = String(str);
2671
- if (typeof str !== "string") return "";
2672
- return str.replace(/[&<>"]/g, (tag) => entities[tag]);
2673
- };
2674
2630
  var getConfig = (options = {}) => {
2675
2631
  const config2 = { ...options };
2676
2632
  if (config2.tags) {
@@ -2684,6 +2640,25 @@ var getConfig = (options = {}) => {
2684
2640
  }
2685
2641
  return config2;
2686
2642
  };
2643
+ var clean = ({ $schema, ...schema }) => schema;
2644
+ async function toJsonSchema(schema) {
2645
+ try {
2646
+ if (typeof schema?.toJsonSchema === "function") {
2647
+ return clean(schema.toJsonSchema());
2648
+ }
2649
+ const vendor = schema?.["~standard"]?.vendor;
2650
+ if (vendor === "zod") {
2651
+ const mod = await import("zod");
2652
+ return clean((mod.toJSONSchema ?? mod.z.toJSONSchema)(schema));
2653
+ }
2654
+ if (vendor === "valibot") {
2655
+ const mod = await import("@valibot/to-json-schema");
2656
+ return clean(mod.toJsonSchema(schema));
2657
+ }
2658
+ } catch {
2659
+ }
2660
+ return zodToSchema(schema);
2661
+ }
2687
2662
  function zodToSchema(schema) {
2688
2663
  const type2 = schema?.def?.type || "string";
2689
2664
  if (type2 === "object") {
@@ -2706,48 +2681,31 @@ function zodToSchema(schema) {
2706
2681
  return { type: type2 };
2707
2682
  }
2708
2683
  var pkgProm = fsp.readFile("package.json", "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
2709
- var getTag = (name, fn) => {
2710
- const found = fn.toString().split("\n").filter((l) => /\s+\/\/\s/.test(l)).map((l) => l.trim().replace("// ", "")).find((l) => l.startsWith(name));
2711
- if (!found) return "";
2712
- return encode(found.replace(name, "").trim());
2713
- };
2714
- var getDescription = (fn) => getTag("@description", fn) || "";
2715
- var getReturn = (fn) => getTag("@returns", fn) || "OK";
2716
- var generateOpenApiPaths = (handlers) => {
2684
+ var generateOpenApiPaths = async (handlers, specPath) => {
2717
2685
  const paths = {};
2718
2686
  for (const [method, routes] of Object.entries(handlers)) {
2719
2687
  for (const route of routes) {
2720
2688
  const path = route.path;
2721
- const fn = route.fns.find((p) => typeof p === "function");
2722
2689
  const meta = route.options ?? {};
2723
2690
  const config2 = getConfig(route.options?.schema);
2724
- if (typeof path !== "string" || path === "*" || path === "/docs" || !fn) {
2691
+ if (typeof path !== "string" || path === "*" || path === specPath) {
2725
2692
  continue;
2726
2693
  }
2694
+ if (route.options?.schema === false) continue;
2727
2695
  const normalizedPath = path.replace(/\(\w+\)/gi, "").replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
2728
2696
  if (!paths[normalizedPath]) {
2729
2697
  paths[normalizedPath] = {};
2730
2698
  }
2731
- const getTitle = (fn2) => {
2732
- if (!fn2.name) return null;
2733
- const wrongNames = ["default"];
2734
- if (wrongNames.includes(fn2.name)) return null;
2735
- if (fn2.name.length <= 3) return null;
2736
- if (fn2.name.includes("_")) return fn2.name.replace(/_/g, " ");
2737
- const name = fn2.name.split(/(?=[A-Z])/).join(" ").toLowerCase();
2738
- return name[0].toUpperCase() + name.slice(1);
2739
- };
2740
2699
  let requestBody;
2741
2700
  if (meta?.body) {
2742
- const schema = zodToSchema(meta.body);
2701
+ const schema = await toJsonSchema(meta.body);
2743
2702
  requestBody = { content: { "application/json": { schema } } };
2744
2703
  }
2745
2704
  let responses;
2746
2705
  if (meta?.response) {
2747
- const schema = zodToSchema(meta.response);
2748
- const description = getReturn(fn);
2706
+ const schema = await toJsonSchema(meta.response);
2749
2707
  responses = {
2750
- 200: { description, content: { "application/json": { schema } } }
2708
+ 200: { description: "OK", content: { "application/json": { schema } } }
2751
2709
  };
2752
2710
  }
2753
2711
  const parameters = [];
@@ -2762,18 +2720,20 @@ var generateOpenApiPaths = (handlers) => {
2762
2720
  });
2763
2721
  });
2764
2722
  if (meta?.query) {
2765
- Object.entries(meta.query).map(([key, value]) => ({
2766
- name: key,
2767
- in: "query",
2768
- required: false,
2769
- schema: { type: typeof value },
2770
- example: value
2771
- }));
2723
+ const schema = await toJsonSchema(meta.query);
2724
+ for (const [name, prop] of Object.entries(schema.properties ?? {})) {
2725
+ parameters.push({
2726
+ name,
2727
+ in: "query",
2728
+ required: schema.required?.includes(name) ?? false,
2729
+ schema: prop
2730
+ });
2731
+ }
2772
2732
  }
2773
2733
  paths[normalizedPath][method] = {
2774
2734
  tags: config2.tags,
2775
- summary: config2.title || getTag("@title", fn) || `${method.toUpperCase()} ${normalizedPath}`,
2776
- description: config2.description || getTitle(fn) || getDescription(fn),
2735
+ summary: config2.title,
2736
+ description: config2.description,
2777
2737
  requestBody,
2778
2738
  parameters,
2779
2739
  responses
@@ -2784,34 +2744,21 @@ var generateOpenApiPaths = (handlers) => {
2784
2744
  };
2785
2745
  var openapi_default = async (ctx) => {
2786
2746
  const pkg = await pkgProm;
2747
+ const { title, description, version } = ctx.options.openapi ?? {};
2787
2748
  const domain = pkg.homepage || ctx.url.origin;
2788
- const openApi = {
2749
+ return {
2789
2750
  openapi: "3.0.0",
2790
2751
  info: {
2791
- title: pkg.name || "API Documentation",
2792
- version: pkg.version || "1.0.0",
2793
- description: pkg.description || ""
2752
+ title: title || pkg.name || "API Documentation",
2753
+ version: version || pkg.version || "1.0.0",
2754
+ description: description ?? (pkg.description || "")
2794
2755
  },
2795
2756
  servers: domain ? [{ url: domain }] : [],
2796
- paths: generateOpenApiPaths(ctx.app.handlers)
2757
+ paths: await generateOpenApiPaths(
2758
+ ctx.app.handlers,
2759
+ ctx.options.openapi?.path ?? ""
2760
+ )
2797
2761
  };
2798
- const configuration = ctx.options.openapi?.scalar || {};
2799
- return `
2800
- <!doctype html>
2801
- <html>
2802
- <head>
2803
- <title>API Reference</title>
2804
- <meta charset="utf-8" />
2805
- <meta
2806
- name="viewport"
2807
- content="width=device-width, initial-scale=1" />
2808
- <style>.open-api-client-button {display: none!important;}</style>
2809
- </head>
2810
- <body>
2811
- <script id="api-reference" type="application/json" data-configuration="${encode(JSON.stringify(configuration))}">${JSON.stringify(openApi, null, 2)}</script>
2812
- <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
2813
- </body>
2814
- </html> `;
2815
2762
  };
2816
2763
 
2817
2764
  // src/middle/preflight.ts
@@ -3046,7 +2993,7 @@ function isValidMethod(method) {
3046
2993
 
3047
2994
  // src/context/node.ts
3048
2995
  var chunkArray = (arr) => arr.length > 2 ? [[arr[0], arr[1]], ...chunkArray(arr.slice(2))] : [arr];
3049
- async function createNode(req, app) {
2996
+ async function createNode(req, app, signal = new AbortController().signal) {
3050
2997
  const init = performance.now();
3051
2998
  const method = req.method?.toLowerCase() || "get";
3052
2999
  if (!isValidMethod(method)) {
@@ -3080,6 +3027,7 @@ async function createNode(req, app) {
3080
3027
  body: void 0,
3081
3028
  headers: headers2,
3082
3029
  cookies: cookies2,
3030
+ signal,
3083
3031
  session: {},
3084
3032
  init,
3085
3033
  app,
@@ -3120,6 +3068,7 @@ async function createWinter(req, app, server2) {
3120
3068
  body: void 0,
3121
3069
  headers: headers2,
3122
3070
  cookies: cookies2,
3071
+ signal: req.signal,
3123
3072
  session: {},
3124
3073
  init,
3125
3074
  app,
@@ -3157,7 +3106,11 @@ var Node = async (app) => {
3157
3106
  const http = await import("http");
3158
3107
  const server2 = http.createServer(
3159
3108
  async (request, response) => {
3160
- const ctx = await createNode(request, app);
3109
+ const controller = new AbortController();
3110
+ response.on("close", () => {
3111
+ if (!response.writableFinished) controller.abort();
3112
+ });
3113
+ const ctx = await createNode(request, app, controller.signal);
3161
3114
  if ("error" in ctx) throw ctx.error;
3162
3115
  const out = await handleRequest(app, ctx);
3163
3116
  response.writeHead(out.status || 200, parseHeaders_default(out.headers));
@@ -3348,9 +3301,6 @@ var Server = class extends Router {
3348
3301
  platform;
3349
3302
  sockets;
3350
3303
  websocket;
3351
- // Lazily-loaded favicon bytes, cached per server until restart (see favicon
3352
- // middleware). `undefined` = not loaded yet; `null` = configured but missing.
3353
- faviconCache;
3354
3304
  port;
3355
3305
  constructor(options = {}) {
3356
3306
  super();
@@ -3370,13 +3320,12 @@ var Server = class extends Router {
3370
3320
  app.use(timer);
3371
3321
  if (this.settings.cors) app.use(preflight);
3372
3322
  app.use(assets);
3373
- if (this.settings.favicon) app.get("/favicon.ico", favicon);
3374
3323
  app.use(session);
3375
3324
  if (this.settings.auth) {
3376
3325
  auth(app);
3377
3326
  }
3378
3327
  if (this.settings.openapi) {
3379
- app.get(this.settings.openapi.path || "/docs", openapi_default);
3328
+ app.get(this.settings.openapi.path, openapi_default);
3380
3329
  }
3381
3330
  }
3382
3331
  self() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.43.0",
3
+ "version": "0.44.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",
@@ -13,7 +13,8 @@
13
13
  "server.js"
14
14
  ],
15
15
  "scripts": {
16
- "build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24",
16
+ "build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24 --external zod --external @valibot/to-json-schema",
17
+ "prepare": "mkdir -p node_modules/@server && ln -sfn ../.. node_modules/@server/next",
17
18
  "start": "bun test --watch",
18
19
  "lint": "npx tsc --noEmit && npx @biomejs/biome lint ./src --skip=lint/suspicious/noExplicitAny --skip=lint/style/noParameterAssign --skip=lint/suspicious/noConfusingVoidType --skip=lint/complexity/noBannedTypes",
19
20
  "test": "npm run test:bun && tsc --noEmit",
@@ -54,7 +55,7 @@
54
55
  "Reply": "docs/4. Reply.md",
55
56
  "Authentication": "docs/5. Authentication.md",
56
57
  "Testing": "docs/6. Testing.md",
57
- "Platforms": "docs/7. Platforms.md",
58
+ "Concepts": "docs/7. Concepts.md",
58
59
  "FAQ": "docs/8. FAQ.md"
59
60
  },
60
61
  "tutorials": "docs/tutorials"
@@ -66,6 +67,7 @@
66
67
  "devDependencies": {
67
68
  "@types/bun": "^1.3.0",
68
69
  "@types/node": "^24.10.0",
70
+ "@valibot/to-json-schema": "^1.7.1",
69
71
  "arktype": "^2.2.3",
70
72
  "bun": "^1.3.13",
71
73
  "check-dts": "^0.8.2",