@server/next 0.29.0 → 0.31.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 +15 -9
  2. package/index.js +563 -280
  3. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -32,10 +32,19 @@ type RouteOptions = {
32
32
  title?: string;
33
33
  description?: string;
34
34
  };
35
+ type Route = {
36
+ path: string;
37
+ options: RouteOptions;
38
+ fns: Middleware[];
39
+ };
35
40
  type Cookie = {
36
- value?: string;
41
+ value?: string | null;
37
42
  path?: string;
38
43
  expires?: number | string | Date;
44
+ maxAge?: number;
45
+ httpOnly?: boolean;
46
+ secure?: boolean;
47
+ sameSite?: "Strict" | "Lax" | "None";
39
48
  };
40
49
  type RouterMethod = "*" | Method;
41
50
  type Bucket = {
@@ -77,7 +86,7 @@ type KVStore = {
77
86
  del: (key: string) => Promise<void | string>;
78
87
  keys: () => Promise<string[]>;
79
88
  };
80
- type Provider = "email" | "github";
89
+ type Provider = "email" | "github" | "google" | "microsoft" | "discord" | "facebook" | "apple";
81
90
  type Strategy = "cookie" | "jwt" | "token";
82
91
  type AuthSession = {
83
92
  id: string;
@@ -247,12 +256,11 @@ declare global {
247
256
  }
248
257
 
249
258
  type Mids<O extends ServerConfig, Path extends string> = Middleware<O, PathToParams<Path>>[];
250
- type PathOrMiddle<O extends ServerConfig = object> = string | Middleware<O>;
251
- type FullRoute = [RouterMethod, string, ...Middleware[]][];
252
259
  declare class Router<O extends ServerConfig = object> {
253
- handlers: Record<Method, FullRoute>;
260
+ middleware: Middleware[];
261
+ handlers: Record<Method, Route[]>;
254
262
  self(): this;
255
- handle(method: RouterMethod, path: PathOrMiddle<O>, ...middleware: Middleware<O>[]): this;
263
+ handle(method: Method, pathOrFn?: any, ...rest: any[]): this;
256
264
  socket<Path extends string>(path: Path, ...middleware: Mids<O, Path>): this;
257
265
  socket<Path extends string>(path: Path, options: RouteOptions, ...middleware: Mids<O, Path>): this;
258
266
  socket(...middleware: Middleware<O>[]): this;
@@ -286,9 +294,7 @@ declare class Router<O extends ServerConfig = object> {
286
294
  options(...middleware: Middleware<O>[]): this;
287
295
  options(options: RouteOptions, ...middleware: Middleware<O>[]): this;
288
296
  use(...middleware: Middleware[]): this;
289
- use(path: string, ...middleware: Middleware[]): this;
290
297
  use(router: Router): this;
291
- use(path: string, router: Router): this;
292
298
  }
293
299
  declare function router(): Router;
294
300
 
@@ -448,4 +454,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
448
454
  }
449
455
  declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
450
456
 
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 };
457
+ 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 Route, 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
@@ -43,12 +43,22 @@ ServerError_default.extend({
43
43
  NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
44
44
  NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
45
45
  AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
46
- AUTH_INVALID_TOKEN: "Invalid Authorization token",
47
- AUTH_INVALID_COOKIE: "Invalid Authorization cookie",
48
- AUTH_INVALID_HEADER: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)",
49
- AUTH_INVALID_STRATEGY: "Invalid Authorization type '{strategy}', valid one is '{valid}'",
46
+ AUTH_INVALID_TOKEN: { status: 401, message: "Invalid Authorization token" },
47
+ AUTH_INVALID_COOKIE: { status: 401, message: "Invalid Authorization cookie" },
48
+ AUTH_INVALID_HEADER: {
49
+ status: 401,
50
+ message: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)"
51
+ },
52
+ AUTH_INVALID_STRATEGY: {
53
+ status: 401,
54
+ message: "Invalid Authorization type '{strategy}', valid one is '{valid}'"
55
+ },
56
+ AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
50
57
  AUTH_NO_PROVIDER: "No provider passed to the option 'auth.provider'",
51
- AUTH_INVALID_PROVIDER: "Invalid provider '{provider}', valid ones are: '{valid}'",
58
+ AUTH_INVALID_PROVIDER: {
59
+ status: 401,
60
+ message: "Invalid provider '{provider}', valid ones are: '{valid}'"
61
+ },
52
62
  AUTH_NO_SESSION: { status: 401, message: "Invalid session" },
53
63
  AUTH_NO_USER: {
54
64
  status: 401,
@@ -97,98 +107,6 @@ function clientIp(headers2, opts = {}) {
97
107
  return normalize(remoteAddress);
98
108
  }
99
109
 
100
- // src/auth/updateUser.ts
101
- async function updateUser(user, auth2, store) {
102
- if (auth2.provider === "email") {
103
- return await store.set(auth2.email, user);
104
- }
105
- }
106
-
107
- // src/auth/providers/email.ts
108
- var createSession = async (user, ctx) => {
109
- const { strategy, session: session2, cleanUser, redirect: redirect2 = "/user" } = ctx.options.auth;
110
- user = await cleanUser(user);
111
- const id = createId();
112
- const provider = "email";
113
- ctx.user = {
114
- id,
115
- strategy,
116
- provider,
117
- email: user.email
118
- };
119
- await session2.set(
120
- id,
121
- { id, strategy, provider, user: user.email },
122
- { expires: "1w" }
123
- );
124
- if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
125
- if (strategy.includes("token")) {
126
- return status(201).json({ ...user, token: id });
127
- }
128
- if (strategy.includes("cookie")) {
129
- return status(302).cookies({ authentication: id }).redirect(redirect2);
130
- }
131
- if (strategy.includes("jwt")) {
132
- throw new Error("JWT auth not supported yet");
133
- }
134
- if (strategy.includes("key")) {
135
- throw new Error("Key auth not supported yet");
136
- }
137
- throw new Error("Unknown auth type");
138
- };
139
- async function emailLogin(ctx) {
140
- const { email, password } = ctx.body;
141
- if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
142
- if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
143
- if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
144
- if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
145
- const store = ctx.options.auth.store;
146
- if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
147
- const user = await store.get(email);
148
- const isValid = await verify(password, user.password);
149
- if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
150
- return createSession(user, ctx);
151
- }
152
- async function emailRegister(ctx) {
153
- const { email, password, ...data } = ctx.body;
154
- if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
155
- if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
156
- if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
157
- if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
158
- const store = ctx.options.auth.store;
159
- if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
160
- const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
161
- const user = {
162
- id: createId(email),
163
- strategy: ctx.options.auth.strategy,
164
- provider: "email",
165
- email,
166
- password: await hash(password),
167
- time,
168
- ...data
169
- };
170
- await store.set(email, user);
171
- return createSession(user, ctx);
172
- }
173
- async function emailResetPassword() {
174
- }
175
- async function emailUpdatePassword(ctx) {
176
- const passwords = ctx.body;
177
- const fullUser = await ctx.options.auth.store.get(ctx.user.email);
178
- if (!fullUser) throw ServerError_default.AUTH_NO_USER();
179
- const isValid = await verify(passwords.previous, fullUser.password);
180
- if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
181
- fullUser.password = await hash(passwords.updated);
182
- await updateUser(fullUser, ctx.user, ctx.options.auth.store);
183
- return 200;
184
- }
185
- var email_default = {
186
- login: emailLogin,
187
- register: emailRegister,
188
- reset: emailResetPassword,
189
- password: emailUpdatePassword
190
- };
191
-
192
110
  // src/helpers/isReadableStream.ts
193
111
  function isReadableStream(obj) {
194
112
  return obj !== null && typeof obj === "object" && typeof obj.pipe === "function" && typeof obj.read === "function" && typeof obj.on === "function";
@@ -314,6 +232,379 @@ var json = (...args) => r().json(...args);
314
232
  var file = (...args) => r().file(...args);
315
233
  var redirect = (...args) => r().redirect(...args);
316
234
 
235
+ // src/auth/finishLogin.ts
236
+ async function finishLogin(ctx, input) {
237
+ const settings = ctx.options.auth;
238
+ const { strategy, cleanUser } = settings;
239
+ const key = String(input.key);
240
+ const auth2 = {
241
+ id: createId(),
242
+ strategy,
243
+ provider: input.provider,
244
+ user: key,
245
+ email: input.email,
246
+ time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
247
+ };
248
+ let user = input.user;
249
+ if (input.store !== false) {
250
+ const existing = await settings.store.get(key);
251
+ user = { ...existing ?? {}, ...input.user };
252
+ }
253
+ user = await cleanUser(user);
254
+ if (input.store !== false) await settings.store.set(key, user);
255
+ await settings.session.set(auth2.id, auth2, { expires: "1w" });
256
+ if (strategy.includes("token")) {
257
+ return status(201).json({ ...user, token: auth2.id });
258
+ }
259
+ if (strategy.includes("cookie")) {
260
+ return cookies("authentication", {
261
+ value: auth2.id,
262
+ path: "/",
263
+ httpOnly: true,
264
+ secure: ctx.platform.production,
265
+ sameSite: "Lax"
266
+ }).redirect(settings.redirect);
267
+ }
268
+ if (strategy.includes("jwt")) throw new Error("JWT auth not supported yet");
269
+ if (strategy.includes("key")) throw new Error("Key auth not supported yet");
270
+ throw new Error("Unknown auth type");
271
+ }
272
+
273
+ // src/helpers/createCookies.ts
274
+ var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
275
+ var times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
276
+ parse.millisecond = parse.ms = 1e-3;
277
+ parse.second = parse.sec = parse.s = parse[""] = 1;
278
+ parse.minute = parse.min = parse.m = parse.s * 60;
279
+ parse.hour = parse.hr = parse.h = parse.m * 60;
280
+ parse.day = parse.d = parse.h * 24;
281
+ parse.week = parse.wk = parse.w = parse.d * 7;
282
+ parse.year = parse.yr = parse.y = parse.d * 365.25;
283
+ parse.month = parse.b = parse.y / 12;
284
+ function parse(str) {
285
+ if (str === null || str === void 0) return null;
286
+ if (typeof str === "number") return str;
287
+ if (typeof str !== "string") {
288
+ throw new Error(`Not a string: ${str} (${typeof str})`);
289
+ }
290
+ str = str.toLowerCase().replace(/[,_]/g, "");
291
+ const [_, value, units] = times.exec(str) || [];
292
+ if (!units) return null;
293
+ const unitValue = parse[units] || parse[units.replace(/s$/, "")];
294
+ if (!unitValue) return null;
295
+ const result = unitValue * parseFloat(value);
296
+ return Math.abs(Math.round(result * 1e3));
297
+ }
298
+ function normalizeExpires(expires) {
299
+ if (expires === null || expires === void 0) return void 0;
300
+ if (expires === 0) return EXPIRED2;
301
+ if (typeof expires === "string") {
302
+ if (/^[\d._]+\w+$/.test(expires)) {
303
+ return new Date(Date.now() + parse(expires)).toUTCString();
304
+ } else {
305
+ return expires;
306
+ }
307
+ }
308
+ if (typeof expires === "number") {
309
+ return new Date(Date.now() + expires).toUTCString();
310
+ }
311
+ if (expires instanceof Date) {
312
+ return expires.toUTCString();
313
+ }
314
+ return void 0;
315
+ }
316
+ function createCookies(key, val) {
317
+ if (val.value === null) val.expires = EXPIRED2;
318
+ const { value, path: path2, expires, maxAge, httpOnly, secure, sameSite } = val;
319
+ let str = `${key}=${value || ""};Path=${path2 || "/"}`;
320
+ if (typeof expires !== "undefined") str += `;Expires=${normalizeExpires(expires)}`;
321
+ if (typeof maxAge === "number") str += `;Max-Age=${maxAge}`;
322
+ if (httpOnly) str += ";HttpOnly";
323
+ if (secure) str += ";Secure";
324
+ if (sameSite) str += `;SameSite=${sameSite}`;
325
+ return str;
326
+ }
327
+
328
+ // src/auth/state.ts
329
+ var NAME = "oauth_state";
330
+ function startState(ctx, crossSite = false) {
331
+ const state = createId();
332
+ return {
333
+ state,
334
+ cookie: {
335
+ value: state,
336
+ path: "/",
337
+ expires: "10m",
338
+ httpOnly: true,
339
+ secure: crossSite || ctx.platform.production,
340
+ sameSite: crossSite ? "None" : "Lax"
341
+ }
342
+ };
343
+ }
344
+ function checkState(ctx, received) {
345
+ const expected = ctx.cookies[NAME];
346
+ if (!expected || !received || expected !== received) {
347
+ throw ServerError_default.AUTH_INVALID_STATE();
348
+ }
349
+ }
350
+ function clearState() {
351
+ return createCookies(NAME, { value: null });
352
+ }
353
+
354
+ // src/auth/providers/apple.ts
355
+ var AUTHORIZE = "https://appleid.apple.com/auth/authorize";
356
+ var TOKEN = "https://appleid.apple.com/auth/token";
357
+ var b64url = (data) => {
358
+ const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
359
+ let bin = "";
360
+ for (const byte of bytes) bin += String.fromCharCode(byte);
361
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
362
+ };
363
+ var b64urlJson = (segment) => {
364
+ let b64 = segment.replace(/-/g, "+").replace(/_/g, "/");
365
+ b64 += "=".repeat((4 - b64.length % 4) % 4);
366
+ const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
367
+ return JSON.parse(new TextDecoder().decode(bytes));
368
+ };
369
+ var clientSecret = async () => {
370
+ const now = Math.floor(Date.now() / 1e3);
371
+ const header = { alg: "ES256", kid: env.APPLE_KEY_ID, typ: "JWT" };
372
+ const payload = {
373
+ iss: env.APPLE_TEAM_ID,
374
+ iat: now,
375
+ exp: now + 3600,
376
+ aud: "https://appleid.apple.com",
377
+ sub: env.APPLE_ID
378
+ };
379
+ const data = `${b64url(JSON.stringify(header))}.${b64url(JSON.stringify(payload))}`;
380
+ const pem = String(env.APPLE_PRIVATE_KEY).replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
381
+ const der = Uint8Array.from(atob(pem), (c) => c.charCodeAt(0));
382
+ const key = await crypto.subtle.importKey(
383
+ "pkcs8",
384
+ der,
385
+ { name: "ECDSA", namedCurve: "P-256" },
386
+ false,
387
+ ["sign"]
388
+ );
389
+ const sig = await crypto.subtle.sign(
390
+ { name: "ECDSA", hash: "SHA-256" },
391
+ key,
392
+ new TextEncoder().encode(data)
393
+ );
394
+ return `${data}.${b64url(new Uint8Array(sig))}`;
395
+ };
396
+ var login = (ctx) => {
397
+ const { state, cookie } = startState(ctx, true);
398
+ const params = new URLSearchParams({
399
+ client_id: env.APPLE_ID,
400
+ redirect_uri: `${ctx.url.origin}/auth/callback/apple`,
401
+ response_type: "code",
402
+ scope: "name email",
403
+ // Requesting scopes forces Apple to POST the result back (form_post)
404
+ response_mode: "form_post",
405
+ state
406
+ });
407
+ return cookies("oauth_state", cookie).redirect(`${AUTHORIZE}?${params}`);
408
+ };
409
+ var callback = async (ctx) => {
410
+ const body = ctx.body || {};
411
+ checkState(ctx, body.state);
412
+ const tokenRes = await fetch(TOKEN, {
413
+ method: "POST",
414
+ headers: {
415
+ accept: "application/json",
416
+ "content-type": "application/x-www-form-urlencoded"
417
+ },
418
+ body: new URLSearchParams({
419
+ client_id: env.APPLE_ID,
420
+ client_secret: await clientSecret(),
421
+ code: body.code,
422
+ grant_type: "authorization_code",
423
+ redirect_uri: `${ctx.url.origin}/auth/callback/apple`
424
+ })
425
+ });
426
+ if (!tokenRes.ok) throw new Error("apple: token exchange failed");
427
+ const token = await tokenRes.json();
428
+ const claims = b64urlJson(token.id_token.split(".")[1]);
429
+ let name;
430
+ if (body.user) {
431
+ const parsed = JSON.parse(body.user).name;
432
+ if (parsed) name = `${parsed.firstName} ${parsed.lastName}`.trim();
433
+ }
434
+ const res = await finishLogin(ctx, {
435
+ provider: "apple",
436
+ key: claims.sub,
437
+ email: claims.email,
438
+ user: { id: claims.sub, name, email: claims.email }
439
+ });
440
+ res.headers.append("set-cookie", clearState());
441
+ return res;
442
+ };
443
+ var apple_default = { login, callback };
444
+
445
+ // src/auth/providers/oauth.ts
446
+ function oauthProvider(config2) {
447
+ const KEY = config2.name.toUpperCase();
448
+ const callbackUrl = (ctx) => `${ctx.url.origin}/auth/callback/${config2.name}`;
449
+ const login3 = (ctx) => {
450
+ const { state, cookie } = startState(ctx);
451
+ const params = new URLSearchParams({
452
+ client_id: env[`${KEY}_ID`],
453
+ redirect_uri: callbackUrl(ctx),
454
+ response_type: "code",
455
+ scope: config2.scope,
456
+ state
457
+ });
458
+ return cookies("oauth_state", cookie).redirect(
459
+ `${config2.authorizeUrl}?${params}`
460
+ );
461
+ };
462
+ const callback3 = async (ctx) => {
463
+ checkState(ctx, ctx.url.query.state);
464
+ const tokenRes = await fetch(config2.tokenUrl, {
465
+ method: "POST",
466
+ headers: {
467
+ accept: "application/json",
468
+ "content-type": "application/x-www-form-urlencoded"
469
+ },
470
+ body: new URLSearchParams({
471
+ client_id: env[`${KEY}_ID`],
472
+ client_secret: env[`${KEY}_SECRET`],
473
+ code: ctx.url.query.code,
474
+ grant_type: "authorization_code",
475
+ redirect_uri: callbackUrl(ctx)
476
+ })
477
+ });
478
+ if (!tokenRes.ok) throw new Error(`${config2.name}: token exchange failed`);
479
+ const token = await tokenRes.json();
480
+ const profileRes = await fetch(config2.profileUrl, {
481
+ headers: {
482
+ accept: "application/json",
483
+ authorization: `Bearer ${token.access_token}`
484
+ }
485
+ });
486
+ if (!profileRes.ok) throw new Error(`${config2.name}: profile fetch failed`);
487
+ const profile = config2.profile(await profileRes.json());
488
+ const res = await finishLogin(ctx, {
489
+ provider: config2.name,
490
+ key: profile.id,
491
+ email: profile.email,
492
+ user: {
493
+ id: profile.id,
494
+ name: profile.name,
495
+ email: profile.email,
496
+ picture: profile.picture
497
+ }
498
+ });
499
+ res.headers.append("set-cookie", clearState());
500
+ return res;
501
+ };
502
+ return { login: login3, callback: callback3 };
503
+ }
504
+
505
+ // src/auth/providers/discord.ts
506
+ var discord_default = oauthProvider({
507
+ name: "discord",
508
+ authorizeUrl: "https://discord.com/oauth2/authorize",
509
+ tokenUrl: "https://discord.com/api/oauth2/token",
510
+ profileUrl: "https://discord.com/api/users/@me",
511
+ scope: "identify email",
512
+ profile: (p) => ({
513
+ id: p.id,
514
+ email: p.email,
515
+ name: p.global_name || p.username,
516
+ picture: p.avatar ? `https://cdn.discordapp.com/avatars/${p.id}/${p.avatar}.png` : void 0
517
+ })
518
+ });
519
+
520
+ // src/auth/updateUser.ts
521
+ async function updateUser(user, auth2, store) {
522
+ if (auth2.provider === "email") {
523
+ return await store.set(auth2.email, user);
524
+ }
525
+ }
526
+
527
+ // src/auth/providers/email.ts
528
+ async function emailLogin(ctx) {
529
+ const { email, password } = ctx.body;
530
+ if (!email) throw ServerError_default.LOGIN_NO_EMAIL();
531
+ if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
532
+ if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
533
+ if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
534
+ const store = ctx.options.auth.store;
535
+ if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
536
+ const user = await store.get(email);
537
+ const isValid = await verify(password, user.password);
538
+ if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
539
+ return finishLogin(ctx, {
540
+ provider: "email",
541
+ key: user.email,
542
+ email: user.email,
543
+ user,
544
+ store: false
545
+ });
546
+ }
547
+ async function emailRegister(ctx) {
548
+ const { email, password, ...data } = ctx.body;
549
+ if (!email) throw ServerError_default.REGISTER_NO_EMAIL();
550
+ if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
551
+ if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
552
+ if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
553
+ const store = ctx.options.auth.store;
554
+ if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
555
+ const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
556
+ const user = {
557
+ id: createId(email),
558
+ strategy: ctx.options.auth.strategy,
559
+ provider: "email",
560
+ email,
561
+ password: await hash(password),
562
+ time,
563
+ ...data
564
+ };
565
+ await store.set(email, user);
566
+ return finishLogin(ctx, {
567
+ provider: "email",
568
+ key: email,
569
+ email,
570
+ user,
571
+ store: false
572
+ });
573
+ }
574
+ async function emailResetPassword() {
575
+ }
576
+ async function emailUpdatePassword(ctx) {
577
+ const passwords = ctx.body;
578
+ const fullUser = await ctx.options.auth.store.get(ctx.user.email);
579
+ if (!fullUser) throw ServerError_default.AUTH_NO_USER();
580
+ const isValid = await verify(passwords.previous, fullUser.password);
581
+ if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
582
+ fullUser.password = await hash(passwords.updated);
583
+ await updateUser(fullUser, ctx.user, ctx.options.auth.store);
584
+ return 200;
585
+ }
586
+ var email_default = {
587
+ login: emailLogin,
588
+ register: emailRegister,
589
+ reset: emailResetPassword,
590
+ password: emailUpdatePassword
591
+ };
592
+
593
+ // src/auth/providers/facebook.ts
594
+ var facebook_default = oauthProvider({
595
+ name: "facebook",
596
+ authorizeUrl: "https://www.facebook.com/v18.0/dialog/oauth",
597
+ tokenUrl: "https://graph.facebook.com/v18.0/oauth/access_token",
598
+ profileUrl: "https://graph.facebook.com/me?fields=id,name,email,picture",
599
+ scope: "email public_profile",
600
+ profile: (p) => ({
601
+ id: p.id,
602
+ email: p.email,
603
+ name: p.name,
604
+ picture: p.picture?.data?.url
605
+ })
606
+ });
607
+
317
608
  // src/auth/providers/github.ts
318
609
  var oauth = async (code) => {
319
610
  const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
@@ -337,9 +628,15 @@ var oauth = async (code) => {
337
628
  });
338
629
  };
339
630
  };
340
- var login = function githubLogin() {
341
- return redirect(
342
- `https://github.com/login/oauth/authorize?client_id=${env.GITHUB_ID}&scope=user:email`
631
+ var login2 = (ctx) => {
632
+ const { state, cookie } = startState(ctx);
633
+ const params = new URLSearchParams({
634
+ client_id: env.GITHUB_ID,
635
+ scope: "user:email",
636
+ state
637
+ });
638
+ return cookies("oauth_state", cookie).redirect(
639
+ `https://github.com/login/oauth/authorize?${params}`
343
640
  );
344
641
  };
345
642
  var getUserProfile = async (code) => {
@@ -351,47 +648,67 @@ var getUserProfile = async (code) => {
351
648
  const email = emails.sort((a) => a.primary ? -1 : 1)[0]?.email;
352
649
  return { ...profile, email };
353
650
  };
354
- var callback = async (ctx) => {
355
- const { strategy, cleanUser, store, session: session2, redirect: redirect2 } = ctx.options.auth;
651
+ var callback2 = async (ctx) => {
652
+ checkState(ctx, ctx.url.query.state);
356
653
  const profile = await getUserProfile(ctx.url.query.code);
357
- const auth2 = {
358
- id: createId(),
359
- strategy,
654
+ const res = await finishLogin(ctx, {
360
655
  provider: "github",
361
- user: String(profile.id),
656
+ key: profile.id,
362
657
  email: profile.email,
363
- time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
364
- };
365
- const existing = await store.get(String(profile.id));
366
- const user = cleanUser({
367
- ...existing ?? {},
368
- id: profile.id,
369
- name: profile.name,
370
- email: profile.email,
371
- picture: profile.avatar_url,
372
- location: profile.location,
373
- created: profile.created_at
658
+ user: {
659
+ id: profile.id,
660
+ name: profile.name,
661
+ email: profile.email,
662
+ picture: profile.avatar_url,
663
+ location: profile.location,
664
+ created: profile.created_at
665
+ }
374
666
  });
375
- await store.set(auth2.user, user);
376
- await session2.set(auth2.id, auth2, { expires: "1w" });
377
- if (auth2.strategy.includes("token")) {
378
- return status(201).json({ ...user, token: auth2.id });
379
- }
380
- if (auth2.strategy.includes("cookie")) {
381
- return status(302).cookies({ authentication: auth2.id }).redirect(redirect2);
382
- }
383
- if (auth2.strategy.includes("jwt")) {
384
- throw new Error("JWT auth not supported yet");
385
- }
386
- if (auth2.strategy.includes("key")) {
387
- throw new Error("Key auth not supported yet");
388
- }
389
- throw new Error("Unknown auth type");
667
+ res.headers.append("set-cookie", clearState());
668
+ return res;
390
669
  };
391
- var github_default = { login, callback };
670
+ var github_default = { login: login2, callback: callback2 };
671
+
672
+ // src/auth/providers/google.ts
673
+ var google_default = oauthProvider({
674
+ name: "google",
675
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
676
+ tokenUrl: "https://oauth2.googleapis.com/token",
677
+ profileUrl: "https://openidconnect.googleapis.com/v1/userinfo",
678
+ scope: "openid email profile",
679
+ profile: (p) => ({
680
+ id: p.sub,
681
+ email: p.email,
682
+ name: p.name,
683
+ picture: p.picture
684
+ })
685
+ });
686
+
687
+ // src/auth/providers/microsoft.ts
688
+ var microsoft_default = oauthProvider({
689
+ name: "microsoft",
690
+ authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
691
+ tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
692
+ profileUrl: "https://graph.microsoft.com/v1.0/me",
693
+ scope: "openid email profile User.Read",
694
+ profile: (p) => ({
695
+ // Personal accounts expose `userPrincipalName` rather than `mail`
696
+ id: p.id,
697
+ email: p.mail || p.userPrincipalName,
698
+ name: p.displayName
699
+ })
700
+ });
392
701
 
393
702
  // src/auth/providers/index.ts
394
- var providers_default = { email: email_default, github: github_default };
703
+ var providers_default = {
704
+ apple: apple_default,
705
+ discord: discord_default,
706
+ email: email_default,
707
+ facebook: facebook_default,
708
+ github: github_default,
709
+ google: google_default,
710
+ microsoft: microsoft_default
711
+ };
395
712
 
396
713
  // src/auth/parseAuthOptions.ts
397
714
  var defaultRedirect = "/user";
@@ -884,71 +1201,25 @@ function applyCors(res, ctx) {
884
1201
  }
885
1202
  }
886
1203
 
887
- // src/helpers/createCookies.ts
888
- var EXPIRED2 = (/* @__PURE__ */ new Date(0)).toUTCString();
889
- var times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
890
- parse.millisecond = parse.ms = 1e-3;
891
- parse.second = parse.sec = parse.s = parse[""] = 1;
892
- parse.minute = parse.min = parse.m = parse.s * 60;
893
- parse.hour = parse.hr = parse.h = parse.m * 60;
894
- parse.day = parse.d = parse.h * 24;
895
- parse.week = parse.wk = parse.w = parse.d * 7;
896
- parse.year = parse.yr = parse.y = parse.d * 365.25;
897
- parse.month = parse.b = parse.y / 12;
898
- function parse(str) {
899
- if (str === null || str === void 0) return null;
900
- if (typeof str === "number") return str;
901
- if (typeof str !== "string") {
902
- throw new Error(`Not a string: ${str} (${typeof str})`);
903
- }
904
- str = str.toLowerCase().replace(/[,_]/g, "");
905
- const [_, value, units] = times.exec(str) || [];
906
- if (!units) return null;
907
- const unitValue = parse[units] || parse[units.replace(/s$/, "")];
908
- if (!unitValue) return null;
909
- const result = unitValue * parseFloat(value);
910
- return Math.abs(Math.round(result * 1e3));
911
- }
912
- function normalizeExpires(expires) {
913
- if (expires === null || expires === void 0) return void 0;
914
- if (expires === 0) return EXPIRED2;
915
- if (typeof expires === "string") {
916
- if (/^[\d._]+\w+$/.test(expires)) {
917
- return new Date(Date.now() + parse(expires)).toUTCString();
918
- } else {
919
- return expires;
920
- }
921
- }
922
- if (typeof expires === "number") {
923
- return new Date(Date.now() + expires).toUTCString();
924
- }
925
- if (expires instanceof Date) {
926
- return expires.toUTCString();
927
- }
928
- return void 0;
929
- }
930
- function createCookies(key, val) {
931
- if (val.value === null) val.expires = EXPIRED2;
932
- const { value, path: path2, expires } = val;
933
- const pathPart = `;Path=${path2 || "/"}`;
934
- const expiresStr = normalizeExpires(expires);
935
- const expiresPart = typeof expires !== "undefined" ? `;Expires=${expiresStr}` : "";
936
- return `${key}=${value || ""}${pathPart}${expiresPart}`;
937
- }
938
-
939
1204
  // src/helpers/createWebsocket.ts
940
1205
  function createWebsocket(sockets, handlers) {
1206
+ const run = (event, socket, body) => {
1207
+ const routes = handlers.socket?.filter((r2) => r2.path === event) ?? [];
1208
+ for (const route of routes) {
1209
+ for (const fn of route.fns) {
1210
+ fn({ socket, sockets, body });
1211
+ }
1212
+ }
1213
+ };
941
1214
  return {
942
- message: async (socket, body) => {
943
- handlers.socket?.filter((s) => s[1] === "message")?.map((s) => s[2]({ socket, sockets, body }));
944
- },
1215
+ message: (socket, body) => run("message", socket, body),
945
1216
  open: (socket) => {
946
1217
  sockets.push(socket);
947
- handlers.socket?.filter((s) => s[1] === "open")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
1218
+ run("open", socket);
948
1219
  },
949
1220
  close: (socket) => {
950
1221
  sockets.splice(sockets.indexOf(socket), 1);
951
- handlers.socket?.filter((s) => s[1] === "close")?.map((s) => s[2]({ socket, sockets, body: void 0 }));
1222
+ run("close", socket);
952
1223
  }
953
1224
  };
954
1225
  }
@@ -1155,18 +1426,23 @@ function validate(ctx, schema) {
1155
1426
  }
1156
1427
 
1157
1428
  // src/helpers/handleRequest.ts
1158
- async function handleRequest(handlers, ctx) {
1159
- const res = await getResponse(handlers, ctx);
1429
+ async function handleRequest(app, ctx) {
1430
+ const res = await getResponse(app, ctx);
1160
1431
  if (res) ctx.options.log.request(ctx, res);
1161
1432
  return res;
1162
1433
  }
1163
- async function getResponse(handlers, ctx) {
1434
+ async function getResponse(app, ctx) {
1164
1435
  try {
1165
- for (const [method, matcher, ...cbs] of handlers[ctx.method]) {
1166
- const match = pathPattern(matcher, ctx.url.pathname || "/");
1167
- if (!match) continue;
1168
- define(ctx.url, "params", () => match);
1169
- for (const cb of cbs) {
1436
+ let matched = false;
1437
+ for (const route of app.handlers[ctx.method]) {
1438
+ const params = pathPattern(route.path, ctx.url.pathname || "/");
1439
+ if (!params) continue;
1440
+ matched = true;
1441
+ define(ctx.url, "params", () => params);
1442
+ if (Object.keys(route.options).length) {
1443
+ ctx.options = { ...app.settings, ...route.options };
1444
+ }
1445
+ for (const cb of route.fns) {
1170
1446
  if (typeof cb === "function") {
1171
1447
  const res = await cb(ctx);
1172
1448
  const out = await parseResponse(res, ctx);
@@ -1175,7 +1451,13 @@ async function getResponse(handlers, ctx) {
1175
1451
  validate(ctx, cb);
1176
1452
  }
1177
1453
  }
1178
- if (method !== "*") break;
1454
+ break;
1455
+ }
1456
+ if (!matched) {
1457
+ for (const mw of app.middleware) {
1458
+ const out = await parseResponse(await mw(ctx), ctx);
1459
+ if (out) return out;
1460
+ }
1179
1461
  }
1180
1462
  if (ctx.platform.provider === "netlify") return;
1181
1463
  throw new ServerError_default("NOT_FOUND", 404, "Not Found");
@@ -1594,7 +1876,7 @@ async function logout(ctx) {
1594
1876
  return { token: null };
1595
1877
  }
1596
1878
  if (strategy.includes("cookie")) {
1597
- return cookies({ authorization: null }).redirect("/");
1879
+ return cookies({ authentication: null }).redirect("/");
1598
1880
  }
1599
1881
  if (strategy.includes("jwt")) {
1600
1882
  throw new Error("JWT auth not supported yet");
@@ -1606,18 +1888,37 @@ async function logout(ctx) {
1606
1888
  }
1607
1889
 
1608
1890
  // src/auth/index.ts
1891
+ var oauth2 = [
1892
+ "github",
1893
+ "google",
1894
+ "microsoft",
1895
+ "discord",
1896
+ "facebook"
1897
+ ];
1609
1898
  function auth(app) {
1610
1899
  app.use(async function middle(ctx) {
1611
1900
  ctx.user = await getUser(ctx);
1612
1901
  });
1613
- if (app.settings.auth.provider.includes("github")) {
1614
- if (!env.GITHUB_ID) throw new Error("GITHUB_ID not defined");
1615
- if (!env.GITHUB_SECRET) throw new Error("GITHUB_SECRET not defined");
1902
+ const enabled = app.settings.auth.provider;
1903
+ for (const name of oauth2) {
1904
+ if (!enabled.includes(name)) continue;
1905
+ const key = name.toUpperCase();
1906
+ if (!env[`${key}_ID`]) throw new Error(`${key}_ID not defined`);
1907
+ if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
1616
1908
  app.get("/auth/logout", logout);
1617
- app.get("/auth/login/github", providers_default.github.login);
1618
- app.get("/auth/callback/github", providers_default.github.callback);
1909
+ app.get(`/auth/login/${name}`, providers_default[name].login);
1910
+ app.get(`/auth/callback/${name}`, providers_default[name].callback);
1619
1911
  }
1620
- if (app.settings.auth.provider.includes("email")) {
1912
+ if (enabled.includes("apple")) {
1913
+ const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
1914
+ for (const key of keys) {
1915
+ if (!env[key]) throw new Error(`${key} not defined`);
1916
+ }
1917
+ app.get("/auth/logout", logout);
1918
+ app.get("/auth/login/apple", providers_default.apple.login);
1919
+ app.post("/auth/callback/apple", providers_default.apple.callback);
1920
+ }
1921
+ if (enabled.includes("email")) {
1621
1922
  app.post("/auth/logout", logout);
1622
1923
  app.post("/auth/register/email", providers_default.email.register);
1623
1924
  app.post("/auth/login/email", providers_default.email.login);
@@ -1650,7 +1951,7 @@ async function favicon(ctx) {
1650
1951
  return icon ? type("ico").send(icon) : 204;
1651
1952
  }
1652
1953
  const handled = ctx.app.handlers.get.some(
1653
- ([method, matcher]) => method !== "*" && pathPattern(matcher, "/favicon.ico")
1954
+ (route) => pathPattern(route.path, "/favicon.ico")
1654
1955
  );
1655
1956
  if (handled) return;
1656
1957
  return 204;
@@ -1669,11 +1970,8 @@ var encode = (str = "") => {
1669
1970
  if (typeof str !== "string") return "";
1670
1971
  return str.replace(/[&<>"]/g, (tag) => entities[tag]);
1671
1972
  };
1672
- var getConfig = (routes) => {
1673
- const config2 = routes.find(
1674
- (r2) => typeof r2 !== "string" && typeof r2 !== "function" && typeof r2 === "object"
1675
- );
1676
- if (!config2) return {};
1973
+ var getConfig = (options = {}) => {
1974
+ const config2 = { ...options };
1677
1975
  if (config2.tags) {
1678
1976
  if (typeof config2.tags === "string") {
1679
1977
  config2.tags = config2.tags.split(/\s*,\s*/g);
@@ -1718,13 +2016,10 @@ var generateOpenApiPaths = (handlers) => {
1718
2016
  const paths = {};
1719
2017
  for (const [method, routes] of Object.entries(handlers)) {
1720
2018
  for (const route of routes) {
1721
- const [_, path2, fn, meta] = [
1722
- route[0],
1723
- route[1],
1724
- route.find((p) => typeof p === "function"),
1725
- route.find((p) => typeof p === "object")
1726
- ];
1727
- const config2 = getConfig(route);
2019
+ const path2 = route.path;
2020
+ const fn = route.fns.find((p) => typeof p === "function");
2021
+ const meta = route.fns.find((p) => typeof p === "object");
2022
+ const config2 = getConfig(route.options);
1728
2023
  if (typeof path2 !== "string" || path2 === "*" || path2 === "/docs" || !fn) {
1729
2024
  continue;
1730
2025
  }
@@ -1823,7 +2118,7 @@ function preflight(ctx) {
1823
2118
  if (ctx.method !== "options") return;
1824
2119
  if (!ctx.headers["access-control-request-method"]) return;
1825
2120
  const handled = ctx.app.handlers.options.some(
1826
- ([method, matcher]) => method !== "*" && pathPattern(matcher, ctx.url.pathname)
2121
+ (route) => pathPattern(route.path, ctx.url.pathname)
1827
2122
  );
1828
2123
  if (handled) return;
1829
2124
  return 204;
@@ -1885,9 +2180,9 @@ import { TLSSocket } from "tls";
1885
2180
  // src/context/createEvents.ts
1886
2181
  function createEvents() {
1887
2182
  const events = {};
1888
- events.on = (name, callback2) => {
2183
+ events.on = (name, callback3) => {
1889
2184
  events[name] = events[name] || [];
1890
- events[name].push(callback2);
2185
+ events[name].push(callback3);
1891
2186
  };
1892
2187
  events.trigger = (name, data) => {
1893
2188
  if (!events[name]) return;
@@ -2008,7 +2303,7 @@ var Winter = async (app, request, env2) => {
2008
2303
  if (env2?.upgrade(request)) return;
2009
2304
  Object.assign(globalThis.env, env2);
2010
2305
  const ctx = await createWinter(request, app, env2);
2011
- const res = await handleRequest(app.handlers, ctx);
2306
+ const res = await handleRequest(app, ctx);
2012
2307
  ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
2013
2308
  return res;
2014
2309
  };
@@ -2017,7 +2312,7 @@ var Node = async (app) => {
2017
2312
  http.createServer(async (request, response) => {
2018
2313
  const ctx = await createNode(request, app);
2019
2314
  if ("error" in ctx) throw ctx.error;
2020
- const out = await handleRequest(app.handlers, ctx);
2315
+ const out = await handleRequest(app, ctx);
2021
2316
  response.writeHead(out.status || 200, parseHeaders_default(out.headers));
2022
2317
  if (out.body instanceof ReadableStream) {
2023
2318
  await iterate(out.body, (chunk) => response.write(chunk));
@@ -2035,16 +2330,16 @@ var Netlify = async (app, request, context) => {
2035
2330
  throw new Error("Netlify doesn't exist");
2036
2331
  }
2037
2332
  const ctx = await createWinter(request, app);
2038
- const res = await handleRequest(app.handlers, ctx);
2333
+ const res = await handleRequest(app, ctx);
2039
2334
  ctx.events.trigger("finish", { ...ctx, res, end: performance.now() });
2040
2335
  return res;
2041
2336
  };
2042
2337
 
2043
2338
  // src/router.ts
2044
- function isMiddleware(x) {
2045
- return typeof x === "function";
2046
- }
2047
2339
  var Router = class _Router {
2340
+ // Cross-cutting middleware added with .use(); they run on every request
2341
+ middleware = [];
2342
+ // Routes per method, each carrying its own (already-flattened) chain of fns
2048
2343
  handlers = {
2049
2344
  socket: [],
2050
2345
  get: [],
@@ -2060,79 +2355,67 @@ var Router = class _Router {
2060
2355
  self() {
2061
2356
  return this;
2062
2357
  }
2063
- handle(method, path2, ...middleware) {
2064
- if (typeof path2 !== "string") {
2065
- middleware.unshift(path2);
2066
- path2 = "*";
2067
- }
2068
- const methods2 = method === "*" ? Object.keys(this.handlers) : [method];
2069
- for (const m of methods2) {
2070
- this.handlers[m].push([method, path2, ...middleware]);
2071
- }
2358
+ // Registers one route: bakes the current middleware + the route's own
2359
+ // functions into a single flat `fns` list. A plain options object may sit
2360
+ // between the path and the handlers, and it's pulled out here.
2361
+ handle(method, pathOrFn, ...rest) {
2362
+ let path2 = "*";
2363
+ if (typeof pathOrFn === "string") {
2364
+ path2 = pathOrFn;
2365
+ } else if (pathOrFn != null) {
2366
+ rest.unshift(pathOrFn);
2367
+ }
2368
+ let options = {};
2369
+ if (rest[0] != null && typeof rest[0] !== "function") {
2370
+ options = rest.shift();
2371
+ }
2372
+ const base = method === "socket" ? [] : this.middleware;
2373
+ const fns = [...base, ...rest].filter((fn) => fn != null);
2374
+ this.handlers[method].push({ path: path2, options, fns });
2072
2375
  return this.self();
2073
2376
  }
2074
2377
  socket(pathOrMid, optionsOrMid, ...middleware) {
2075
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2076
- return this.handle("socket", pathOrMid, optionsOrMid, ...middleware);
2077
- }
2078
- return this.handle("socket", pathOrMid, ...middleware);
2378
+ return this.handle("socket", pathOrMid, optionsOrMid, ...middleware);
2079
2379
  }
2080
2380
  get(pathOrMid, optionsOrMid, ...middleware) {
2081
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2082
- return this.handle("get", pathOrMid, optionsOrMid, ...middleware);
2083
- }
2084
- return this.handle("get", pathOrMid, ...middleware);
2381
+ return this.handle("get", pathOrMid, optionsOrMid, ...middleware);
2085
2382
  }
2086
2383
  head(pathOrMid, optionsOrMid, ...middleware) {
2087
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2088
- return this.handle("head", pathOrMid, optionsOrMid, ...middleware);
2089
- }
2090
- return this.handle("head", pathOrMid, ...middleware);
2384
+ return this.handle("head", pathOrMid, optionsOrMid, ...middleware);
2091
2385
  }
2092
2386
  post(pathOrMid, optionsOrMid, ...middleware) {
2093
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2094
- return this.handle("post", pathOrMid, optionsOrMid, ...middleware);
2095
- }
2096
- return this.handle("post", pathOrMid, ...middleware);
2387
+ return this.handle("post", pathOrMid, optionsOrMid, ...middleware);
2097
2388
  }
2098
2389
  put(pathOrMid, optionsOrMid, ...middleware) {
2099
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2100
- return this.handle("put", pathOrMid, optionsOrMid, ...middleware);
2101
- }
2102
- return this.handle("put", pathOrMid, ...middleware);
2390
+ return this.handle("put", pathOrMid, optionsOrMid, ...middleware);
2103
2391
  }
2104
2392
  patch(pathOrMid, optionsOrMid, ...middleware) {
2105
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2106
- return this.handle("patch", pathOrMid, optionsOrMid, ...middleware);
2107
- }
2108
- return this.handle("patch", pathOrMid, ...middleware);
2393
+ return this.handle("patch", pathOrMid, optionsOrMid, ...middleware);
2109
2394
  }
2110
2395
  delete(pathOrMid, optionsOrMid, ...middleware) {
2111
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2112
- return this.handle("delete", pathOrMid, optionsOrMid, ...middleware);
2113
- }
2114
- return this.handle("delete", pathOrMid, ...middleware);
2396
+ return this.handle("delete", pathOrMid, optionsOrMid, ...middleware);
2115
2397
  }
2116
2398
  options(pathOrMid, optionsOrMid, ...middleware) {
2117
- if (typeof pathOrMid === "string" && isMiddleware(optionsOrMid)) {
2118
- return this.handle("options", pathOrMid, optionsOrMid, ...middleware);
2119
- }
2120
- return this.handle("options", pathOrMid, ...middleware);
2399
+ return this.handle("options", pathOrMid, optionsOrMid, ...middleware);
2121
2400
  }
2122
2401
  use(...args) {
2123
- const path2 = typeof args[0] === "string" ? args.shift() : "*";
2124
- if (args[0] instanceof _Router) {
2125
- const basePath = `/${path2.replace(/\*$/, "")}/`.replace(/^\/+/, "/").replace(/\/+$/, "/");
2126
- const handlers = args[0].handlers;
2127
- for (const m in handlers) {
2128
- for (const [method, path3, ...middleware] of handlers[m]) {
2129
- const fullPath = basePath + path3.replace(/^\//, "");
2130
- this.handlers[m].push([method, fullPath, ...middleware]);
2402
+ for (const arg of args) {
2403
+ if (arg instanceof _Router) {
2404
+ for (const m of Object.keys(arg.handlers)) {
2405
+ for (const route of arg.handlers[m]) {
2406
+ const base = m === "socket" ? [] : this.middleware;
2407
+ this.handlers[m].push({
2408
+ path: route.path,
2409
+ options: route.options,
2410
+ fns: [...base, ...route.fns]
2411
+ });
2412
+ }
2131
2413
  }
2414
+ } else {
2415
+ this.middleware.push(arg);
2132
2416
  }
2133
- return this.self();
2134
2417
  }
2135
- return this.handle("*", path2, ...args);
2418
+ return this.self();
2136
2419
  }
2137
2420
  };
2138
2421
  function router() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.29.0",
3
+ "version": "0.31.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",