@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.
- package/index.d.ts +26 -23
- package/index.js +91 -142
- 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?:
|
|
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?:
|
|
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
|
-
} &
|
|
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
|
|
190
|
-
const id = `${createId()}${
|
|
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
|
|
213
|
+
const ext = getExt(originalName);
|
|
214
214
|
const mime = contentType.toLowerCase();
|
|
215
215
|
const allowed = fileType2.some(
|
|
216
|
-
(t) => t.toLowerCase() === mime || t.toLowerCase() ===
|
|
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
|
|
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
|
|
360
|
-
if (
|
|
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
|
|
774
|
-
if (!
|
|
775
|
-
const ascii =
|
|
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 (
|
|
778
|
-
return `${value}; filename*=UTF-8''${encodeExt(
|
|
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
|
|
786
|
-
return
|
|
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
|
|
821
|
-
if (
|
|
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
|
|
879
|
+
const ext = path.split(".").pop();
|
|
880
880
|
await fs.promises.access(path);
|
|
881
881
|
const stream = fs.createReadStream(path);
|
|
882
|
-
return this.type(
|
|
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
|
-
|
|
1917
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2599
|
-
const ctype =
|
|
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
|
-
"&": "&",
|
|
2665
|
-
"<": "<",
|
|
2666
|
-
">": ">",
|
|
2667
|
-
'"': """
|
|
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
|
|
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 ===
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
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
|
|
2776
|
-
description: config2.description
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
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",
|