@server/next 0.28.15 → 0.29.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 +25 -3
- package/index.js +260 -25
- package/package.json +19 -25
- package/readme.md +1 -1
- package/src/jsx/jsx-runtime.d.ts +12 -0
- package/src/jsx/jsx-runtime.js +1 -1
package/index.d.ts
CHANGED
|
@@ -54,11 +54,13 @@ type CorsSettings = {
|
|
|
54
54
|
origin: string | boolean;
|
|
55
55
|
methods: string;
|
|
56
56
|
headers: string;
|
|
57
|
+
credentials?: boolean;
|
|
57
58
|
};
|
|
58
59
|
type CorsOptions = boolean | string | string[] | {
|
|
59
60
|
origin?: string | string[];
|
|
60
61
|
methods?: string | Method[];
|
|
61
62
|
headers?: string | string[];
|
|
63
|
+
credentials?: boolean;
|
|
62
64
|
};
|
|
63
65
|
type BasicValue = string | number | boolean | null;
|
|
64
66
|
type SerializableValue = BasicValue | {
|
|
@@ -106,6 +108,19 @@ type AuthSettings = {
|
|
|
106
108
|
cleanUser: <T = AuthUser>(user: T) => T | Promise<T>;
|
|
107
109
|
redirect: string;
|
|
108
110
|
};
|
|
111
|
+
type LogLevel = "info";
|
|
112
|
+
type Logger = {
|
|
113
|
+
level?: LogLevel;
|
|
114
|
+
message: (scope: string, message: string) => void;
|
|
115
|
+
start: (url: string) => void;
|
|
116
|
+
request: (ctx: Context, res: Response) => void;
|
|
117
|
+
};
|
|
118
|
+
type SecurityOptions = {
|
|
119
|
+
trustProxy?: boolean;
|
|
120
|
+
};
|
|
121
|
+
type SecuritySettings = {
|
|
122
|
+
trustProxy: boolean;
|
|
123
|
+
};
|
|
109
124
|
type OnError = (error: Error, ctx: Context) => Response | Promise<Response>;
|
|
110
125
|
type Options = {
|
|
111
126
|
port?: number;
|
|
@@ -122,6 +137,9 @@ type Options = {
|
|
|
122
137
|
auth?: AuthOption;
|
|
123
138
|
openapi?: any;
|
|
124
139
|
onError?: OnError;
|
|
140
|
+
log?: LogLevel | boolean;
|
|
141
|
+
favicon?: string | Bucket;
|
|
142
|
+
security?: SecurityOptions;
|
|
125
143
|
};
|
|
126
144
|
type Settings = {
|
|
127
145
|
port: number;
|
|
@@ -138,6 +156,9 @@ type Settings = {
|
|
|
138
156
|
auth?: AuthSettings;
|
|
139
157
|
openapi?: any;
|
|
140
158
|
onError?: OnError;
|
|
159
|
+
log: Logger;
|
|
160
|
+
favicon?: string | Bucket;
|
|
161
|
+
security: SecuritySettings;
|
|
141
162
|
};
|
|
142
163
|
type Time = {
|
|
143
164
|
(name: string): void;
|
|
@@ -168,8 +189,9 @@ type Events = Record<string, EventCallback[]> & {
|
|
|
168
189
|
on?: (key: string, cb: (value?: Context & SerializableValue) => void) => void;
|
|
169
190
|
trigger?: (key: string, value?: Partial<Context & SerializableValue>) => void;
|
|
170
191
|
};
|
|
171
|
-
type Context<Params extends Record<string, string> = Record<string, string>, O extends ServerConfig = object> = {
|
|
192
|
+
type Context<Params extends Record<string, string | undefined> = Record<string, string>, O extends ServerConfig = object> = {
|
|
172
193
|
method: Method;
|
|
194
|
+
ip: string;
|
|
173
195
|
headers: Record<string, string | string[]>;
|
|
174
196
|
cookies: Record<string, string>;
|
|
175
197
|
body?: SerializableValue;
|
|
@@ -201,7 +223,7 @@ type InlineReply = Response | {
|
|
|
201
223
|
headers?: Headers;
|
|
202
224
|
} | SerializableValue | JSX.Element;
|
|
203
225
|
type Body = InlineReply;
|
|
204
|
-
type Middleware<O extends ServerConfig = object, Params extends Record<string, string> = Record<string, string>> = (ctx: Context<Params, O>) => InlineReply | Promise<InlineReply> | void | Promise<void>;
|
|
226
|
+
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>;
|
|
205
227
|
|
|
206
228
|
type Variables = Record<string, string | string[]>;
|
|
207
229
|
type ExtendError = string | {
|
|
@@ -426,4 +448,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
|
|
|
426
448
|
}
|
|
427
449
|
declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
|
|
428
450
|
|
|
429
|
-
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type RouteOptions, type RouterMethod, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
|
|
451
|
+
export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type Bucket, type BunEnv, type Context, type Cookie, type CorsSettings, type EventCallback, type ExtractPathParams, type InferParamType, type InlineReply, type KVStore, type LimitOptions, type LogLevel, type Logger, type Method, type Middleware, type Options, type ParamTypeMap, type ParamsToObject, type PathToParams, type Platform, type Provider, type RouteOptions, type RouterMethod, type SecurityOptions, type SecuritySettings, type SerializableValue, Server, type ServerConfig, TypedServerError as ServerError, type Settings, type Strategy, type Time, UploadPipeline, type UploadedFile, cookies, server as default, download, file, headers, json, redirect, router, send, status, type, upload };
|
package/index.js
CHANGED
|
@@ -79,6 +79,24 @@ if (typeof process !== "undefined") {
|
|
|
79
79
|
Object.assign(globalThis.env, process.env);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// src/helpers/clientIp.ts
|
|
83
|
+
var first = (v) => (Array.isArray(v) ? v[0] : v) || "";
|
|
84
|
+
var normalize = (ip) => ip.replace(/^::ffff:/, "");
|
|
85
|
+
function clientIp(headers2, opts = {}) {
|
|
86
|
+
const { remoteAddress = "", trustProxy = false } = opts;
|
|
87
|
+
const cf = first(headers2["cf-connecting-ip"]);
|
|
88
|
+
if (cf) return normalize(cf);
|
|
89
|
+
const nf = first(headers2["x-nf-client-connection-ip"]);
|
|
90
|
+
if (nf) return normalize(nf);
|
|
91
|
+
if (trustProxy) {
|
|
92
|
+
const xff = first(headers2["x-forwarded-for"]);
|
|
93
|
+
if (xff) return normalize(xff.split(",")[0].trim());
|
|
94
|
+
const real = first(headers2["x-real-ip"]);
|
|
95
|
+
if (real) return normalize(real);
|
|
96
|
+
}
|
|
97
|
+
return normalize(remoteAddress);
|
|
98
|
+
}
|
|
99
|
+
|
|
82
100
|
// src/auth/updateUser.ts
|
|
83
101
|
async function updateUser(user, auth2, store) {
|
|
84
102
|
if (auth2.provider === "email") {
|
|
@@ -254,10 +272,16 @@ var Reply = class {
|
|
|
254
272
|
const isHtml = body.trim().startsWith("<");
|
|
255
273
|
headers2.set("content-type", isHtml ? "text/html" : "text/plain");
|
|
256
274
|
}
|
|
275
|
+
if (!headers2.has("content-length")) {
|
|
276
|
+
headers2.set("content-length", String(Buffer.byteLength(body)));
|
|
277
|
+
}
|
|
257
278
|
return new Response(body, { status: status2, headers: headers2 });
|
|
258
279
|
}
|
|
259
280
|
const name = body?.constructor?.name;
|
|
260
281
|
if (name === "Buffer") {
|
|
282
|
+
if (!headers2.has("content-length")) {
|
|
283
|
+
headers2.set("content-length", String(body.length));
|
|
284
|
+
}
|
|
261
285
|
return new Response(body, { status: status2, headers: headers2 });
|
|
262
286
|
}
|
|
263
287
|
if (typeof body?.getReader === "function") {
|
|
@@ -272,7 +296,11 @@ var Reply = class {
|
|
|
272
296
|
if (!headers2.get("content-type")) {
|
|
273
297
|
headers2.set("content-type", "application/json");
|
|
274
298
|
}
|
|
275
|
-
|
|
299
|
+
const payload = JSON.stringify(body);
|
|
300
|
+
if (!headers2.has("content-length")) {
|
|
301
|
+
headers2.set("content-length", String(Buffer.byteLength(payload)));
|
|
302
|
+
}
|
|
303
|
+
return new Response(payload, { status: status2, headers: headers2 });
|
|
276
304
|
}
|
|
277
305
|
};
|
|
278
306
|
var r = () => new Reply();
|
|
@@ -398,7 +426,7 @@ function parseAuthOptions(auth2, all) {
|
|
|
398
426
|
throw new Error("Auth options needs a strategy");
|
|
399
427
|
}
|
|
400
428
|
const strategy = auth2.strategy;
|
|
401
|
-
if (!auth2.provider
|
|
429
|
+
if (!auth2.provider?.length) {
|
|
402
430
|
throw new Error("Auth options needs a provider");
|
|
403
431
|
}
|
|
404
432
|
const provider = getProviders(auth2.provider);
|
|
@@ -439,7 +467,7 @@ function thinLocalBucket(root) {
|
|
|
439
467
|
read: async (name) => {
|
|
440
468
|
const fullPath = absolute(name);
|
|
441
469
|
const stats = await fsp.stat(fullPath).catch(() => null);
|
|
442
|
-
if (!stats
|
|
470
|
+
if (!stats?.isFile()) return null;
|
|
443
471
|
const nodeStream = fs.createReadStream(fullPath);
|
|
444
472
|
return new ReadableStream({
|
|
445
473
|
start(controller) {
|
|
@@ -544,6 +572,115 @@ function createId(source, size = 16) {
|
|
|
544
572
|
return randomId(size);
|
|
545
573
|
}
|
|
546
574
|
|
|
575
|
+
// src/helpers/color.ts
|
|
576
|
+
var map = {
|
|
577
|
+
reset: 0,
|
|
578
|
+
bright: 1,
|
|
579
|
+
dim: 2,
|
|
580
|
+
under: 4,
|
|
581
|
+
blink: 5,
|
|
582
|
+
reverse: 7,
|
|
583
|
+
black: 30,
|
|
584
|
+
red: 31,
|
|
585
|
+
green: 32,
|
|
586
|
+
yellow: 33,
|
|
587
|
+
blue: 34,
|
|
588
|
+
magenta: 35,
|
|
589
|
+
cyan: 36,
|
|
590
|
+
white: 37,
|
|
591
|
+
bgblack: 40,
|
|
592
|
+
bgred: 41,
|
|
593
|
+
bggreen: 42,
|
|
594
|
+
bgyellow: 43,
|
|
595
|
+
bgblue: 44,
|
|
596
|
+
bgmagenta: 45,
|
|
597
|
+
bgcyan: 46,
|
|
598
|
+
bgwhite: 47
|
|
599
|
+
};
|
|
600
|
+
var replace = (k) => {
|
|
601
|
+
if (process.env.NO_COLOR) return "";
|
|
602
|
+
if (!(k in map)) throw new Error(`"{${k}}" is not a valid color`);
|
|
603
|
+
return `\x1B[${map[k]}m`;
|
|
604
|
+
};
|
|
605
|
+
function color(str, ...vals) {
|
|
606
|
+
if (typeof str === "string") {
|
|
607
|
+
return str.replace(/\{(\w+)\}/g, (_m, k) => replace(k)).replace(/\{\/\w*\}/g, () => replace("reset"));
|
|
608
|
+
}
|
|
609
|
+
return color(str[0] + vals.map((v, i) => v + str[i + 1]).join(""));
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/helpers/logger.ts
|
|
613
|
+
var STATUS_TEXT = {
|
|
614
|
+
200: "OK",
|
|
615
|
+
201: "Created",
|
|
616
|
+
202: "Accepted",
|
|
617
|
+
204: "No Content",
|
|
618
|
+
301: "Moved Permanently",
|
|
619
|
+
302: "Found",
|
|
620
|
+
303: "See Other",
|
|
621
|
+
304: "Not Modified",
|
|
622
|
+
307: "Temporary Redirect",
|
|
623
|
+
308: "Permanent Redirect",
|
|
624
|
+
400: "Bad Request",
|
|
625
|
+
401: "Unauthorized",
|
|
626
|
+
403: "Forbidden",
|
|
627
|
+
404: "Not Found",
|
|
628
|
+
405: "Method Not Allowed",
|
|
629
|
+
409: "Conflict",
|
|
630
|
+
413: "Payload Too Large",
|
|
631
|
+
422: "Unprocessable Entity",
|
|
632
|
+
429: "Too Many Requests",
|
|
633
|
+
500: "Internal Server Error",
|
|
634
|
+
502: "Bad Gateway",
|
|
635
|
+
503: "Service Unavailable"
|
|
636
|
+
};
|
|
637
|
+
var UNITS = ["b", "kb", "mb", "gb", "tb"];
|
|
638
|
+
function formatBytes(bytes) {
|
|
639
|
+
if (!bytes || bytes < 0) return "0b";
|
|
640
|
+
const i = Math.min(
|
|
641
|
+
Math.floor(Math.log(bytes) / Math.log(1024)),
|
|
642
|
+
UNITS.length - 1
|
|
643
|
+
);
|
|
644
|
+
const value = bytes / 1024 ** i;
|
|
645
|
+
const rounded = i === 0 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
646
|
+
return `${rounded}${UNITS[i]}`;
|
|
647
|
+
}
|
|
648
|
+
var SCOPE_COLORS = {
|
|
649
|
+
start: "green",
|
|
650
|
+
api: "cyan"
|
|
651
|
+
};
|
|
652
|
+
var MODULE_COLOR = "magenta";
|
|
653
|
+
var paint = (name, text) => `${color(`{${name}}`)}${text}${color("{/}")}`;
|
|
654
|
+
function createLogger(level) {
|
|
655
|
+
const enabled = !!level;
|
|
656
|
+
const message = (scope, msg) => {
|
|
657
|
+
if (!enabled) return;
|
|
658
|
+
const c = SCOPE_COLORS[scope] || MODULE_COLOR;
|
|
659
|
+
console.log(paint(c, `[server:${scope}] ${msg}`));
|
|
660
|
+
};
|
|
661
|
+
const request = (ctx, res) => {
|
|
662
|
+
if (!enabled) return;
|
|
663
|
+
const method = ctx.method.toUpperCase();
|
|
664
|
+
const path2 = ctx.url.pathname;
|
|
665
|
+
const reqLen = Number(ctx.headers["content-length"]) || 0;
|
|
666
|
+
const resLen = Number(res.headers.get("content-length")) || 0;
|
|
667
|
+
const status2 = res.status;
|
|
668
|
+
const text = STATUS_TEXT[status2] || "";
|
|
669
|
+
const reqSize = reqLen ? ` ${formatBytes(reqLen)}` : "";
|
|
670
|
+
const resSize = resLen ? ` ${formatBytes(resLen)}` : "";
|
|
671
|
+
let line = `${method} ${path2}${reqSize} \u2192 ${status2}${text ? ` ${text}` : ""}${resSize}`;
|
|
672
|
+
const location = res.headers.get("location");
|
|
673
|
+
if (location) line += ` \u2192 ${location}`;
|
|
674
|
+
message("api", line);
|
|
675
|
+
};
|
|
676
|
+
return {
|
|
677
|
+
level,
|
|
678
|
+
message,
|
|
679
|
+
start: (url) => message("start", url),
|
|
680
|
+
request
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
547
684
|
// src/helpers/upload.ts
|
|
548
685
|
function parseBytes(value) {
|
|
549
686
|
if (typeof value === "number") return value;
|
|
@@ -608,7 +745,7 @@ var UploadPipeline = class {
|
|
|
608
745
|
}
|
|
609
746
|
if (!this._bucket) {
|
|
610
747
|
throw new Error(
|
|
611
|
-
`No destination configured
|
|
748
|
+
`No destination configured. Pass a bucket to upload() or call .store()`
|
|
612
749
|
);
|
|
613
750
|
}
|
|
614
751
|
return saveFileToBucket(originalName, data, this._bucket, contentType);
|
|
@@ -621,9 +758,18 @@ function upload(bucket) {
|
|
|
621
758
|
// src/helpers/config.ts
|
|
622
759
|
function config(options = {}) {
|
|
623
760
|
const env2 = globalThis.env;
|
|
761
|
+
const raw = options.log ?? env2.LOG_LEVEL;
|
|
762
|
+
const level = raw === true ? "info" : raw === false ? void 0 : raw;
|
|
763
|
+
const log = createLogger(level);
|
|
624
764
|
const settings = {
|
|
625
765
|
port: options.port || env2.PORT || 3e3,
|
|
626
|
-
secret: options.secret || env2.SECRET || `unsafe-${createId()}
|
|
766
|
+
secret: options.secret || env2.SECRET || `unsafe-${createId()}`,
|
|
767
|
+
log,
|
|
768
|
+
// Trust X-Forwarded-* headers for ctx.ip (on by default; set it to false
|
|
769
|
+
// when clients connect directly so a client can't spoof its IP).
|
|
770
|
+
security: {
|
|
771
|
+
trustProxy: options.security?.trustProxy ?? true
|
|
772
|
+
}
|
|
627
773
|
};
|
|
628
774
|
options.cors = options.cors || env2.CORS || null;
|
|
629
775
|
if (options.cors) {
|
|
@@ -652,6 +798,9 @@ function config(options = {}) {
|
|
|
652
798
|
if ("headers" in options.cors) {
|
|
653
799
|
cors2.headers = Array.isArray(options.cors.headers) ? options.cors.headers.join(",") : options.cors.headers;
|
|
654
800
|
}
|
|
801
|
+
if (options.cors.credentials) {
|
|
802
|
+
cors2.credentials = true;
|
|
803
|
+
}
|
|
655
804
|
}
|
|
656
805
|
if (typeof cors2.origin === "string") {
|
|
657
806
|
cors2.origin = cors2.origin.toLowerCase();
|
|
@@ -661,6 +810,7 @@ function config(options = {}) {
|
|
|
661
810
|
settings.views = options.views ? bucket_default(options.views) : null;
|
|
662
811
|
settings.public = options.public ? bucket_default(options.public) : null;
|
|
663
812
|
settings.uploads = options.uploads instanceof UploadPipeline ? options.uploads : options.uploads ? bucket_default(options.uploads) : null;
|
|
813
|
+
if (options.favicon) settings.favicon = options.favicon;
|
|
664
814
|
settings.store = options.store ?? null;
|
|
665
815
|
settings.cookies = options.cookies ?? null;
|
|
666
816
|
if (options.session) {
|
|
@@ -682,6 +832,20 @@ function config(options = {}) {
|
|
|
682
832
|
status: error.status || 500
|
|
683
833
|
});
|
|
684
834
|
});
|
|
835
|
+
const loc = (v) => typeof v === "string" ? v : "enabled";
|
|
836
|
+
if (settings.auth) {
|
|
837
|
+
log.message("auth", `${settings.auth.provider.join(", ")} auth enabled`);
|
|
838
|
+
}
|
|
839
|
+
if (settings.public) log.message("public", loc(options.public));
|
|
840
|
+
if (settings.views) log.message("views", loc(options.views));
|
|
841
|
+
if (settings.uploads) log.message("uploads", loc(options.uploads));
|
|
842
|
+
if (settings.session) log.message("session", "enabled");
|
|
843
|
+
if (settings.cors) {
|
|
844
|
+
const origin = settings.cors.origin === true ? "*" : String(settings.cors.origin);
|
|
845
|
+
log.message("cors", origin);
|
|
846
|
+
}
|
|
847
|
+
if (settings.favicon) log.message("favicon", loc(settings.favicon));
|
|
848
|
+
if (settings.openapi) log.message("openapi", settings.openapi.path || "/docs");
|
|
685
849
|
return settings;
|
|
686
850
|
}
|
|
687
851
|
|
|
@@ -698,6 +862,27 @@ function cors(config2, origin = "") {
|
|
|
698
862
|
console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config2}"`);
|
|
699
863
|
return null;
|
|
700
864
|
}
|
|
865
|
+
function applyCors(res, ctx) {
|
|
866
|
+
const settings = ctx.options.cors;
|
|
867
|
+
if (!settings) return;
|
|
868
|
+
const requestOrigin = ctx.headers.origin || "";
|
|
869
|
+
let origin = cors(settings.origin, requestOrigin);
|
|
870
|
+
if (!origin) return;
|
|
871
|
+
if (settings.credentials && origin === "*") {
|
|
872
|
+
if (!requestOrigin) return;
|
|
873
|
+
origin = requestOrigin.toLowerCase();
|
|
874
|
+
}
|
|
875
|
+
res.headers.set("Access-Control-Allow-Origin", origin);
|
|
876
|
+
res.headers.set("Access-Control-Allow-Methods", settings.methods);
|
|
877
|
+
res.headers.set("Access-Control-Allow-Headers", settings.headers);
|
|
878
|
+
if (settings.credentials) {
|
|
879
|
+
res.headers.set("Access-Control-Allow-Credentials", "true");
|
|
880
|
+
}
|
|
881
|
+
if (origin !== "*") res.headers.append("Vary", "Origin");
|
|
882
|
+
if (ctx.method === "options") {
|
|
883
|
+
res.headers.set("Access-Control-Max-Age", "86400");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
701
886
|
|
|
702
887
|
// src/helpers/createCookies.ts
|
|
703
888
|
var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
|
|
@@ -825,7 +1010,12 @@ async function parseResponse(out, ctx) {
|
|
|
825
1010
|
}
|
|
826
1011
|
if (typeof out === "string") {
|
|
827
1012
|
const type2 = /^\s*</.test(out) ? "text/html" : "text/plain";
|
|
828
|
-
out = new Response(out, {
|
|
1013
|
+
out = new Response(out, {
|
|
1014
|
+
headers: {
|
|
1015
|
+
"content-type": type2,
|
|
1016
|
+
"content-length": String(Buffer.byteLength(out))
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
829
1019
|
}
|
|
830
1020
|
if (out?.constructor === Object || Array.isArray(out)) {
|
|
831
1021
|
out = json(out);
|
|
@@ -848,17 +1038,7 @@ async function parseResponse(out, ctx) {
|
|
|
848
1038
|
if (!(out instanceof Response)) {
|
|
849
1039
|
throw new Error(`Invalid response type ${out}`);
|
|
850
1040
|
}
|
|
851
|
-
|
|
852
|
-
const origin = cors(ctx.options.cors.origin, ctx.headers.origin);
|
|
853
|
-
if (origin) {
|
|
854
|
-
out.headers.set("Access-Control-Allow-Origin", origin);
|
|
855
|
-
out.headers.set("Access-Control-Allow-Methods", ctx.options.cors.methods);
|
|
856
|
-
out.headers.set("Access-Control-Allow-Headers", ctx.options.cors.headers);
|
|
857
|
-
if (ctx.options.cors.credentials) {
|
|
858
|
-
out.headers.set("Access-Control-Allow-Credentials", "true");
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
1041
|
+
applyCors(out, ctx);
|
|
862
1042
|
if (ctx.time?.times?.length > 1) {
|
|
863
1043
|
out.headers.set("Server-Timing", ctx.time.headers());
|
|
864
1044
|
}
|
|
@@ -976,6 +1156,11 @@ function validate(ctx, schema) {
|
|
|
976
1156
|
|
|
977
1157
|
// src/helpers/handleRequest.ts
|
|
978
1158
|
async function handleRequest(handlers, ctx) {
|
|
1159
|
+
const res = await getResponse(handlers, ctx);
|
|
1160
|
+
if (res) ctx.options.log.request(ctx, res);
|
|
1161
|
+
return res;
|
|
1162
|
+
}
|
|
1163
|
+
async function getResponse(handlers, ctx) {
|
|
979
1164
|
try {
|
|
980
1165
|
for (const [method, matcher, ...cbs] of handlers[ctx.method]) {
|
|
981
1166
|
const match = pathPattern(matcher, ctx.url.pathname || "/");
|
|
@@ -995,7 +1180,9 @@ async function handleRequest(handlers, ctx) {
|
|
|
995
1180
|
if (ctx.platform.provider === "netlify") return;
|
|
996
1181
|
throw new ServerError_default("NOT_FOUND", 404, "Not Found");
|
|
997
1182
|
} catch (error) {
|
|
998
|
-
|
|
1183
|
+
const res = await ctx.options.onError(error, ctx);
|
|
1184
|
+
applyCors(res, ctx);
|
|
1185
|
+
return res;
|
|
999
1186
|
}
|
|
1000
1187
|
}
|
|
1001
1188
|
|
|
@@ -1342,10 +1529,10 @@ async function verify(password, hash3) {
|
|
|
1342
1529
|
// src/auth/findSessionId.ts
|
|
1343
1530
|
var validateToken = (authorization) => {
|
|
1344
1531
|
const [type2, id] = authorization.trim().split(" ");
|
|
1345
|
-
if (
|
|
1532
|
+
if (type2?.toLowerCase() !== "bearer") {
|
|
1346
1533
|
throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
|
|
1347
1534
|
}
|
|
1348
|
-
if (
|
|
1535
|
+
if (id?.length !== 16) {
|
|
1349
1536
|
throw ServerError_default.AUTH_INVALID_TOKEN();
|
|
1350
1537
|
}
|
|
1351
1538
|
return id;
|
|
@@ -1452,6 +1639,23 @@ async function assets(ctx) {
|
|
|
1452
1639
|
}
|
|
1453
1640
|
}
|
|
1454
1641
|
|
|
1642
|
+
// src/middle/favicon.ts
|
|
1643
|
+
async function favicon(ctx) {
|
|
1644
|
+
if (ctx.method !== "get") return;
|
|
1645
|
+
if (ctx.url.pathname !== "/favicon.ico") return;
|
|
1646
|
+
const fav = ctx.options.favicon;
|
|
1647
|
+
if (fav) {
|
|
1648
|
+
if (typeof fav === "string") return file(fav);
|
|
1649
|
+
const icon = await fav.read("favicon.ico");
|
|
1650
|
+
return icon ? type("ico").send(icon) : 204;
|
|
1651
|
+
}
|
|
1652
|
+
const handled = ctx.app.handlers.get.some(
|
|
1653
|
+
([method, matcher]) => method !== "*" && pathPattern(matcher, "/favicon.ico")
|
|
1654
|
+
);
|
|
1655
|
+
if (handled) return;
|
|
1656
|
+
return 204;
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1455
1659
|
// src/middle/openapi.ts
|
|
1456
1660
|
import * as fsp2 from "fs/promises";
|
|
1457
1661
|
var entities = {
|
|
@@ -1614,6 +1818,17 @@ var openapi_default = async (ctx) => {
|
|
|
1614
1818
|
</html> `;
|
|
1615
1819
|
};
|
|
1616
1820
|
|
|
1821
|
+
// src/middle/preflight.ts
|
|
1822
|
+
function preflight(ctx) {
|
|
1823
|
+
if (ctx.method !== "options") return;
|
|
1824
|
+
if (!ctx.headers["access-control-request-method"]) return;
|
|
1825
|
+
const handled = ctx.app.handlers.options.some(
|
|
1826
|
+
([method, matcher]) => method !== "*" && pathPattern(matcher, ctx.url.pathname)
|
|
1827
|
+
);
|
|
1828
|
+
if (handled) return;
|
|
1829
|
+
return 204;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1617
1832
|
// src/middle/NoSession.ts
|
|
1618
1833
|
var NoSession = class {
|
|
1619
1834
|
};
|
|
@@ -1723,6 +1938,9 @@ async function createNode(req, app) {
|
|
|
1723
1938
|
const body2 = [];
|
|
1724
1939
|
req.on("data", (chunk) => body2.push(chunk)).on("end", () => resolve2(Buffer.concat(body2))).on("error", reject);
|
|
1725
1940
|
});
|
|
1941
|
+
if (rawBody.length && !headers2["content-length"]) {
|
|
1942
|
+
headers2["content-length"] = String(rawBody.length);
|
|
1943
|
+
}
|
|
1726
1944
|
const body = rawBody ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
1727
1945
|
const events = createEvents();
|
|
1728
1946
|
return {
|
|
@@ -1736,12 +1954,16 @@ async function createNode(req, app) {
|
|
|
1736
1954
|
session: {},
|
|
1737
1955
|
init,
|
|
1738
1956
|
events,
|
|
1739
|
-
app
|
|
1957
|
+
app,
|
|
1958
|
+
ip: clientIp(headers2, {
|
|
1959
|
+
remoteAddress: req.socket.remoteAddress || "",
|
|
1960
|
+
trustProxy: app.settings.security.trustProxy
|
|
1961
|
+
})
|
|
1740
1962
|
};
|
|
1741
1963
|
}
|
|
1742
1964
|
|
|
1743
1965
|
// src/context/winter.ts
|
|
1744
|
-
async function createWinter(req, app) {
|
|
1966
|
+
async function createWinter(req, app, server2) {
|
|
1745
1967
|
const init = performance.now();
|
|
1746
1968
|
const method = req.method.toLowerCase();
|
|
1747
1969
|
if (!isValidMethod(method)) {
|
|
@@ -1757,6 +1979,9 @@ async function createWinter(req, app) {
|
|
|
1757
1979
|
(url2) => Object.fromEntries(url2.searchParams.entries())
|
|
1758
1980
|
);
|
|
1759
1981
|
const rawBody = Buffer.from(await req.arrayBuffer());
|
|
1982
|
+
if (rawBody.length && !headers2["content-length"]) {
|
|
1983
|
+
headers2["content-length"] = String(rawBody.length);
|
|
1984
|
+
}
|
|
1760
1985
|
const body = req.body ? await parseBody(rawBody, headers2["content-type"], app.settings.uploads) : void 0;
|
|
1761
1986
|
const events = createEvents();
|
|
1762
1987
|
return {
|
|
@@ -1770,7 +1995,11 @@ async function createWinter(req, app) {
|
|
|
1770
1995
|
session: {},
|
|
1771
1996
|
init,
|
|
1772
1997
|
events,
|
|
1773
|
-
app
|
|
1998
|
+
app,
|
|
1999
|
+
ip: clientIp(headers2, {
|
|
2000
|
+
remoteAddress: server2?.requestIP?.(req)?.address || "",
|
|
2001
|
+
trustProxy: app.settings.security.trustProxy
|
|
2002
|
+
})
|
|
1774
2003
|
};
|
|
1775
2004
|
}
|
|
1776
2005
|
|
|
@@ -1778,7 +2007,7 @@ async function createWinter(req, app) {
|
|
|
1778
2007
|
var Winter = async (app, request, env2) => {
|
|
1779
2008
|
if (env2?.upgrade(request)) return;
|
|
1780
2009
|
Object.assign(globalThis.env, env2);
|
|
1781
|
-
const ctx = await createWinter(request, app);
|
|
2010
|
+
const ctx = await createWinter(request, app, env2);
|
|
1782
2011
|
const res = await handleRequest(app.handlers, ctx);
|
|
1783
2012
|
ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
|
|
1784
2013
|
return res;
|
|
@@ -1796,7 +2025,9 @@ var Node = async (app) => {
|
|
|
1796
2025
|
response.write(out.body || "");
|
|
1797
2026
|
}
|
|
1798
2027
|
response.end();
|
|
1799
|
-
}).listen(app.settings.port)
|
|
2028
|
+
}).listen(app.settings.port, () => {
|
|
2029
|
+
app.settings.log.start(`http://localhost:${app.settings.port}/`);
|
|
2030
|
+
});
|
|
1800
2031
|
};
|
|
1801
2032
|
var Netlify = async (app, request, context) => {
|
|
1802
2033
|
request.context = context;
|
|
@@ -1964,9 +2195,13 @@ var Server = class extends Router {
|
|
|
1964
2195
|
this.websocket = createWebsocket(this.sockets, this.handlers);
|
|
1965
2196
|
if (this.platform.runtime === "node") {
|
|
1966
2197
|
this.node();
|
|
2198
|
+
} else if (this.platform.runtime === "bun") {
|
|
2199
|
+
this.settings.log.start(`http://localhost:${this.settings.port}/`);
|
|
1967
2200
|
}
|
|
1968
2201
|
this.use(timer);
|
|
2202
|
+
if (this.settings.cors) this.use(preflight);
|
|
1969
2203
|
this.use(assets);
|
|
2204
|
+
this.use(favicon);
|
|
1970
2205
|
this.use(session);
|
|
1971
2206
|
if (this.settings.auth) {
|
|
1972
2207
|
auth(this);
|
package/package.json
CHANGED
|
@@ -1,37 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@server/next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.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
|
-
"repository": "
|
|
7
|
-
"bugs": "https://github.com/franciscop/server-next/issues",
|
|
8
|
-
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
6
|
+
"repository": "github:franciscop/server-next",
|
|
9
7
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
8
|
+
"funding": "https://www.paypal.me/franciscopresencia/19",
|
|
10
9
|
"license": "UNLICENSED",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
"Github": "https://github.com/franciscop/server-next"
|
|
17
|
-
}
|
|
18
|
-
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"server",
|
|
12
|
+
"node",
|
|
13
|
+
"server.js"
|
|
14
|
+
],
|
|
19
15
|
"scripts": {
|
|
20
16
|
"build": "bunx tsup src/index.ts --format esm --dts --out-dir . --target node24",
|
|
21
17
|
"start": "bun test --watch",
|
|
22
|
-
"lint": "npx @biomejs/biome lint ./src --skip=lint/suspicious/noExplicitAny --skip=lint/style/noParameterAssign
|
|
23
|
-
"types": "npx tsc --noEmit",
|
|
18
|
+
"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",
|
|
24
19
|
"test": "npm run test:bun && tsc --noEmit",
|
|
25
20
|
"test:bun": "bun test",
|
|
26
21
|
"test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
27
22
|
},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"server",
|
|
30
|
-
"node",
|
|
31
|
-
"server.js"
|
|
32
|
-
],
|
|
33
|
-
"type": "module",
|
|
34
23
|
"main": "index.js",
|
|
24
|
+
"type": "module",
|
|
35
25
|
"types": "index.d.ts",
|
|
36
26
|
"files": [
|
|
37
27
|
"index.d.ts",
|
|
@@ -49,6 +39,14 @@
|
|
|
49
39
|
"default": "./src/jsx/jsx-dev-runtime.js"
|
|
50
40
|
}
|
|
51
41
|
},
|
|
42
|
+
"documentation.page": {
|
|
43
|
+
"title": "Server JS - A modern web server for Bun and Node.js",
|
|
44
|
+
"home": "./docs/index.html",
|
|
45
|
+
"menu": {
|
|
46
|
+
"Documentation": "/documentation",
|
|
47
|
+
"Github": "https://github.com/franciscop/server-next"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
52
50
|
"devDependencies": {
|
|
53
51
|
"@types/bun": "^1.3.0",
|
|
54
52
|
"@types/jest": "^30.0.0",
|
|
@@ -58,12 +56,8 @@
|
|
|
58
56
|
"jest": "^29.7.0",
|
|
59
57
|
"polystore": "^0.21.1",
|
|
60
58
|
"tsup": "^8.5.1",
|
|
61
|
-
"typescript": "^
|
|
62
|
-
},
|
|
63
|
-
"engines": {
|
|
64
|
-
"node": ">=24.11.0"
|
|
59
|
+
"typescript": "^6.0.2"
|
|
65
60
|
},
|
|
66
|
-
"engineStrict": true,
|
|
67
61
|
"jest": {
|
|
68
62
|
"testEnvironment": "jest-environment-node",
|
|
69
63
|
"transform": {}
|
package/readme.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
# Server JS [](https://www.npmjs.com/package/@server/next) [](https://github.com/franciscop/server-next/actions)
|
package/src/jsx/jsx-runtime.d.ts
CHANGED
package/src/jsx/jsx-runtime.js
CHANGED
|
@@ -34,7 +34,7 @@ const encode = (str = "") => {
|
|
|
34
34
|
return str.replace(/[&<>"']/g, (tag) => ENTITIES[tag]);
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
// valid primitives only
|
|
37
|
+
// valid primitives only: null, undefined, false, true are all skipped
|
|
38
38
|
const isValidChild = (child) =>
|
|
39
39
|
child != null && child !== false && child !== true;
|
|
40
40
|
|