@server/next 0.49.0 → 0.49.1

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 CHANGED
@@ -203,6 +203,7 @@ type AuthOption = string | AuthFunction | AuthConfig<any> | AuthVerify<any> | Au
203
203
  type AuthContext = Pick<Context, "options" | "headers" | "cookies" | "platform" | "app">;
204
204
  type AuthEntry = {
205
205
  name: string;
206
+ providers?: string[];
206
207
  user: (ctx: AuthContext) => Promise<any>;
207
208
  routes?: () => Router;
208
209
  };
@@ -577,6 +578,10 @@ declare class Server<C extends ContextTypes = {}> extends Router<C> {
577
578
  referrerPolicy?: ReferrerPolicy;
578
579
  window?: null;
579
580
  }) => Promise<Response>;
581
+ readonly cookies: {
582
+ [k: string]: string;
583
+ };
584
+ clear: () => void;
580
585
  };
581
586
  }
582
587
  declare function server<U = AuthProfile>(options: Omit<Options, "auth"> & {
package/index.js CHANGED
@@ -245,7 +245,8 @@ function createCookies(key, val) {
245
245
  if (val.value === null) val.expires = EXPIRED;
246
246
  const { value, path, expires, maxAge, httpOnly, secure, sameSite } = val;
247
247
  let str = `${key}=${encodeURIComponent(value ?? "")};Path=${path || "/"}`;
248
- if (typeof expires !== "undefined") str += `;Expires=${normalizeExpires(expires)}`;
248
+ if (typeof expires !== "undefined")
249
+ str += `;Expires=${normalizeExpires(expires)}`;
249
250
  if (typeof maxAge === "number") str += `;Max-Age=${maxAge}`;
250
251
  if (httpOnly) str += ";HttpOnly";
251
252
  if (secure) str += ";Secure";
@@ -513,7 +514,11 @@ function serialize(body, headers2) {
513
514
  return body;
514
515
  }
515
516
  if (typeof body === "string") {
516
- fill(headers2, isHtml(body) ? mimes_default.html : mimes_default.text, Buffer.byteLength(body));
517
+ fill(
518
+ headers2,
519
+ isHtml(body) ? mimes_default.html : mimes_default.text,
520
+ Buffer.byteLength(body)
521
+ );
517
522
  return body;
518
523
  }
519
524
  if (body instanceof Uint8Array) {
@@ -676,13 +681,22 @@ var redirect = (...args) => r().redirect(...args);
676
681
  // src/body/sniff.ts
677
682
  var ascii = (text) => [...text].map((char) => char.charCodeAt(0));
678
683
  var SIGNATURES = [
679
- { type: "image/png", magic: [137, 80, 78, 71, 13, 10, 26, 10] },
684
+ {
685
+ type: "image/png",
686
+ magic: [137, 80, 78, 71, 13, 10, 26, 10]
687
+ },
680
688
  { type: "image/jpeg", magic: [255, 216, 255] },
681
689
  { type: "image/gif", magic: ascii("GIF87a") },
682
690
  { type: "image/gif", magic: ascii("GIF89a") },
683
691
  // RIFF containers: the format is at byte 8, so the whole thing is one match
684
- { type: "image/webp", magic: [...ascii("RIFF"), null, null, null, null, ...ascii("WEBP")] },
685
- { type: "audio/wav", magic: [...ascii("RIFF"), null, null, null, null, ...ascii("WAVE")] },
692
+ {
693
+ type: "image/webp",
694
+ magic: [...ascii("RIFF"), null, null, null, null, ...ascii("WEBP")]
695
+ },
696
+ {
697
+ type: "audio/wav",
698
+ magic: [...ascii("RIFF"), null, null, null, null, ...ascii("WAVE")]
699
+ },
686
700
  { type: "image/bmp", magic: ascii("BM") },
687
701
  { type: "image/tiff", magic: [73, 73, 42, 0] },
688
702
  { type: "image/tiff", magic: [77, 77, 0, 42] },
@@ -828,7 +842,11 @@ var Router = class _Router {
828
842
  }
829
843
  const base = method === "socket" ? [] : this.middleware;
830
844
  const fns = [...base, ...rest].filter((fn) => fn != null);
831
- this.handlers[method].push({ path, options, fns });
845
+ this.handlers[method].push({
846
+ path,
847
+ options,
848
+ fns
849
+ });
832
850
  return this.self();
833
851
  }
834
852
  socket(pathOrMid, optionsOrMid, ...middleware) {
@@ -1018,7 +1036,9 @@ function validate(strategy, expires, config2) {
1018
1036
  seconds(expires);
1019
1037
  const { onLogin, getUser, toPublicUser } = config2;
1020
1038
  if (onLogin && !getUser) {
1021
- throw new Error("`onLogin` needs a `getUser`: something has to resolve the id it returns.");
1039
+ throw new Error(
1040
+ "`onLogin` needs a `getUser`: something has to resolve the id it returns."
1041
+ );
1022
1042
  }
1023
1043
  if (isSigned(strategy)) {
1024
1044
  if (getUser && !toPublicUser) {
@@ -1054,7 +1074,9 @@ async function credentialPayload(config2, strategy, ctx, profile) {
1054
1074
  if (!isSigned(strategy)) return { sub: String(id) };
1055
1075
  const user = await getUser(String(id), ctx);
1056
1076
  if (user === void 0 || user === null) {
1057
- throw new Error(`getUser returned nothing for the id "${id}" that onLogin just returned`);
1077
+ throw new Error(
1078
+ `getUser returned nothing for the id "${id}" that onLogin just returned`
1079
+ );
1058
1080
  }
1059
1081
  return { user: await toPublicUser(user) };
1060
1082
  }
@@ -1450,7 +1472,12 @@ var callbackRoute = ({ name, options, provider }, redirects, finish) => async (c
1450
1472
  const pending = await readState(ctx, query.state);
1451
1473
  if (!query.code) throw errors_default.AUTH_NO_CODE();
1452
1474
  try {
1453
- const profile = await provider.exchange(ctx, options, query.code, pending);
1475
+ const profile = await provider.exchange(
1476
+ ctx,
1477
+ options,
1478
+ query.code,
1479
+ pending
1480
+ );
1454
1481
  return spendState(await finish(ctx, profile));
1455
1482
  } catch (error) {
1456
1483
  const message = failureMessage(error, name);
@@ -1477,6 +1504,7 @@ function flowEntry(config2) {
1477
1504
  };
1478
1505
  return {
1479
1506
  name: "flow",
1507
+ providers: list.map((one) => one.name),
1480
1508
  async user(ctx) {
1481
1509
  const payload = await read(ctx, strategy);
1482
1510
  if (!payload) return;
@@ -1489,11 +1517,16 @@ function flowEntry(config2) {
1489
1517
  const r2 = router();
1490
1518
  for (const one of list) {
1491
1519
  r2.get(`/auth/login/${one.name}`, SPEC, loginRoute(one));
1492
- r2.get(callbackPath(one.name), SPEC, callbackRoute(one, redirects, finish));
1520
+ r2.get(
1521
+ callbackPath(one.name),
1522
+ SPEC,
1523
+ callbackRoute(one, redirects, finish)
1524
+ );
1493
1525
  }
1494
1526
  r2.post("/auth/logout", SPEC, async (ctx) => {
1495
1527
  const payload = await read(ctx, strategy).catch(() => void 0);
1496
- if (onLogout && payload?.sub) await onLogout(payload.sub, ctx);
1528
+ const id = payload?.sub ?? payload?.user?.id;
1529
+ if (onLogout && id != null) await onLogout(String(id), ctx);
1497
1530
  const to = await target(redirects.logout, "/", null, ctx);
1498
1531
  if (!inCookie(strategy)) return status(204);
1499
1532
  return cookies(NAME, { value: null }).redirect(to);
@@ -1548,7 +1581,9 @@ function keysOf(issuer, refresh = false) {
1548
1581
  if (!algorithm) continue;
1549
1582
  out.set(
1550
1583
  jwk.kid,
1551
- await crypto.subtle.importKey("jwk", jwk, algorithm, false, ["verify"])
1584
+ await crypto.subtle.importKey("jwk", jwk, algorithm, false, [
1585
+ "verify"
1586
+ ])
1552
1587
  );
1553
1588
  }
1554
1589
  return out;
@@ -1717,7 +1752,7 @@ function toEntry(auth2) {
1717
1752
  if ("handler" in auth2) return instanceEntry(auth2);
1718
1753
  }
1719
1754
  throw new Error(
1720
- "Invalid `auth`: it takes a string, a function, `{ providers }`, `{ issuer, audience }`, a library instance, or an array of those."
1755
+ "Invalid `auth`: it takes a string, a function, `{ providers }`, `{ issuer, audience }`, or a library instance."
1721
1756
  );
1722
1757
  }
1723
1758
 
@@ -2005,7 +2040,10 @@ function config(options = {}) {
2005
2040
  settings.onError = options.onError || defaultOnError;
2006
2041
  settings.onResponse = options.onResponse;
2007
2042
  const loc = (v) => typeof v === "string" ? v : "enabled";
2008
- if (settings.auth) log.message("auth", `${settings.auth.name} enabled`);
2043
+ if (settings.auth) {
2044
+ const { name, providers: providers2 } = settings.auth;
2045
+ log.message("auth", `${providers2?.join(",") ?? name} enabled`);
2046
+ }
2009
2047
  if (settings.public) log.message("public", loc(options.public));
2010
2048
  if (settings.uploads) log.message("uploads", loc(options.uploads));
2011
2049
  if (settings.cors) {
@@ -2213,7 +2251,10 @@ var generateOpenApiPaths = async (handlers, specPath) => {
2213
2251
  const schema = await toJsonSchema(meta2.response);
2214
2252
  if (schema) {
2215
2253
  responses = {
2216
- 200: { description: "OK", content: { "application/json": { schema } } }
2254
+ 200: {
2255
+ description: "OK",
2256
+ content: { "application/json": { schema } }
2257
+ }
2217
2258
  };
2218
2259
  }
2219
2260
  }
@@ -2354,70 +2395,6 @@ async function socketUser(app, headers2, cookies2) {
2354
2395
  return app.settings.auth.user(ctx);
2355
2396
  }
2356
2397
 
2357
- // src/http/cors.ts
2358
- var localhost = /^https?:\/\/localhost(:\d+)?$/;
2359
- function cors(config2, origin = "") {
2360
- origin = origin?.toLowerCase();
2361
- if (config2 === true) return origin || null;
2362
- if (config2 === "*") return "*";
2363
- if (!origin) return null;
2364
- if (localhost.test(origin)) return origin;
2365
- const arr = typeof config2 === "string" ? config2.split(/\s*,\s*/g) : [];
2366
- if (arr.includes(origin)) return origin;
2367
- console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config2}"`);
2368
- return null;
2369
- }
2370
- function applyCors(res, ctx) {
2371
- const settings = ctx.options.cors;
2372
- if (!settings) return;
2373
- const requestOrigin = ctx.headers.origin || "";
2374
- let origin = cors(settings.origin, requestOrigin);
2375
- if (!origin) return;
2376
- if (settings.credentials && origin === "*") {
2377
- if (!requestOrigin) return;
2378
- origin = requestOrigin.toLowerCase();
2379
- }
2380
- res.headers.set("Access-Control-Allow-Origin", origin);
2381
- res.headers.set("Access-Control-Allow-Methods", settings.methods);
2382
- res.headers.set("Access-Control-Allow-Headers", settings.headers);
2383
- if (settings.credentials) {
2384
- res.headers.set("Access-Control-Allow-Credentials", "true");
2385
- }
2386
- if (origin !== "*") res.headers.append("Vary", "Origin");
2387
- if (ctx.method === "options") {
2388
- res.headers.set("Access-Control-Max-Age", "86400");
2389
- }
2390
- }
2391
-
2392
- // src/pipeline/parseResponse.ts
2393
- async function parseResponse(out, ctx) {
2394
- if (!out && typeof out !== "string") return null;
2395
- if (typeof out === "function") {
2396
- out = await out(ctx);
2397
- if (!out && typeof out !== "string") return null;
2398
- }
2399
- if (typeof out === "number") {
2400
- return new Response(null, { status: out });
2401
- }
2402
- if (!(out instanceof Response) || out.url) {
2403
- out = await send(out);
2404
- }
2405
- return out;
2406
- }
2407
- async function finalize(out, ctx) {
2408
- applyCors(out, ctx);
2409
- applySecurity(out, ctx);
2410
- out = await applyCache(out, ctx);
2411
- const stale = toClear(ctx);
2412
- if (stale) {
2413
- out.headers.append("set-cookie", clearCookie(stale));
2414
- }
2415
- if (ctx.time?.times?.length > 1) {
2416
- out.headers.set("Server-Timing", ctx.time.headers());
2417
- }
2418
- return out;
2419
- }
2420
-
2421
2398
  // src/body/bodyParts.ts
2422
2399
  var asIterable = (s) => s;
2423
2400
  function getMatching(string, regex) {
@@ -2523,7 +2500,12 @@ function openFile(part) {
2523
2500
  }
2524
2501
  });
2525
2502
  const file2 = part.bucket.file(id);
2526
- part.opened = { type: type2, file: file2, controller, write: file2.write(readable, { type: type2 }) };
2503
+ part.opened = {
2504
+ type: type2,
2505
+ file: file2,
2506
+ controller,
2507
+ write: file2.write(readable, { type: type2 })
2508
+ };
2527
2509
  }
2528
2510
  async function feedPart(part, data) {
2529
2511
  if (data.length === 0) return;
@@ -2637,7 +2619,12 @@ async function parseMultipart(stream, boundary, bucket2, limits, max = INF) {
2637
2619
  } else if (state === "headers") {
2638
2620
  const i = buf.indexOf(BREAK);
2639
2621
  if (i === -1) break;
2640
- part = startPart(buf.subarray(0, i).toString("utf-8"), bucket2, limits, budget);
2622
+ part = startPart(
2623
+ buf.subarray(0, i).toString("utf-8"),
2624
+ bucket2,
2625
+ limits,
2626
+ budget
2627
+ );
2641
2628
  buf = buf.subarray(i + BREAK.length);
2642
2629
  state = "body";
2643
2630
  advanced = true;
@@ -2745,7 +2732,8 @@ async function parseBody(input, contentType, dest, max = INF, length) {
2745
2732
  const buf = await toBuffer(input, max);
2746
2733
  return buf.length ? buf : void 0;
2747
2734
  }
2748
- if (!bucket2) throw errors_default.UPLOAD_NOT_CONFIGURED({ name: "the request body" });
2735
+ if (!bucket2)
2736
+ throw errors_default.UPLOAD_NOT_CONFIGURED({ name: "the request body" });
2749
2737
  const { maxFileSize } = limits;
2750
2738
  if (length != null && maxFileSize != null && length > parseBytes(maxFileSize)) {
2751
2739
  throw errors_default.UPLOAD_TOO_LARGE({
@@ -2804,6 +2792,21 @@ async function resolveBody(ctx, mode = "parse", max = resolveMax(void 0)) {
2804
2792
  return parsed;
2805
2793
  }
2806
2794
 
2795
+ // src/context/isValidMethod.ts
2796
+ var methods = [
2797
+ "get",
2798
+ "post",
2799
+ "put",
2800
+ "patch",
2801
+ "delete",
2802
+ "head",
2803
+ "options",
2804
+ "socket"
2805
+ ];
2806
+ function isValidMethod(method) {
2807
+ return methods.includes(method);
2808
+ }
2809
+
2807
2810
  // src/util/define.ts
2808
2811
  function define(obj, key, cb) {
2809
2812
  Object.defineProperty(obj, key, {
@@ -2820,6 +2823,70 @@ function define(obj, key, cb) {
2820
2823
  });
2821
2824
  }
2822
2825
 
2826
+ // src/http/cors.ts
2827
+ var localhost = /^https?:\/\/localhost(:\d+)?$/;
2828
+ function cors(config2, origin = "") {
2829
+ origin = origin?.toLowerCase();
2830
+ if (config2 === true) return origin || null;
2831
+ if (config2 === "*") return "*";
2832
+ if (!origin) return null;
2833
+ if (localhost.test(origin)) return origin;
2834
+ const arr = typeof config2 === "string" ? config2.split(/\s*,\s*/g) : [];
2835
+ if (arr.includes(origin)) return origin;
2836
+ console.warn(`CORS: Origin "${origin}" not allowed. Allowed "${config2}"`);
2837
+ return null;
2838
+ }
2839
+ function applyCors(res, ctx) {
2840
+ const settings = ctx.options.cors;
2841
+ if (!settings) return;
2842
+ const requestOrigin = ctx.headers.origin || "";
2843
+ let origin = cors(settings.origin, requestOrigin);
2844
+ if (!origin) return;
2845
+ if (settings.credentials && origin === "*") {
2846
+ if (!requestOrigin) return;
2847
+ origin = requestOrigin.toLowerCase();
2848
+ }
2849
+ res.headers.set("Access-Control-Allow-Origin", origin);
2850
+ res.headers.set("Access-Control-Allow-Methods", settings.methods);
2851
+ res.headers.set("Access-Control-Allow-Headers", settings.headers);
2852
+ if (settings.credentials) {
2853
+ res.headers.set("Access-Control-Allow-Credentials", "true");
2854
+ }
2855
+ if (origin !== "*") res.headers.append("Vary", "Origin");
2856
+ if (ctx.method === "options") {
2857
+ res.headers.set("Access-Control-Max-Age", "86400");
2858
+ }
2859
+ }
2860
+
2861
+ // src/pipeline/parseResponse.ts
2862
+ async function parseResponse(out, ctx) {
2863
+ if (!out && typeof out !== "string") return null;
2864
+ if (typeof out === "function") {
2865
+ out = await out(ctx);
2866
+ if (!out && typeof out !== "string") return null;
2867
+ }
2868
+ if (typeof out === "number") {
2869
+ return new Response(null, { status: out });
2870
+ }
2871
+ if (!(out instanceof Response) || out.url) {
2872
+ out = await send(out);
2873
+ }
2874
+ return out;
2875
+ }
2876
+ async function finalize(out, ctx) {
2877
+ applyCors(out, ctx);
2878
+ applySecurity(out, ctx);
2879
+ out = await applyCache(out, ctx);
2880
+ const stale = toClear(ctx);
2881
+ if (stale) {
2882
+ out.headers.append("set-cookie", clearCookie(stale));
2883
+ }
2884
+ if (ctx.time?.times?.length > 1) {
2885
+ out.headers.set("Server-Timing", ctx.time.headers());
2886
+ }
2887
+ return out;
2888
+ }
2889
+
2823
2890
  // src/errors/ValidationError.ts
2824
2891
  var ValidationError = class extends errors_default {
2825
2892
  source;
@@ -2862,21 +2929,6 @@ function replace2(target2, values) {
2862
2929
  Object.assign(target2, values);
2863
2930
  }
2864
2931
 
2865
- // src/context/isValidMethod.ts
2866
- var methods = [
2867
- "get",
2868
- "post",
2869
- "put",
2870
- "patch",
2871
- "delete",
2872
- "head",
2873
- "options",
2874
- "socket"
2875
- ];
2876
- function isValidMethod(method) {
2877
- return methods.includes(method);
2878
- }
2879
-
2880
2932
  // src/pipeline/handleRequest.ts
2881
2933
  async function handleRequest(app, ctx) {
2882
2934
  let res = await getResponse(app, ctx);
@@ -2943,6 +2995,7 @@ async function getResponse(app, ctx) {
2943
2995
  if (ctx.platform.provider === "netlify") return;
2944
2996
  throw errors_default.NOT_FOUND();
2945
2997
  } catch (error) {
2998
+ if (ctx.signal.aborted) return;
2946
2999
  return ctx.options.onError(error, ctx);
2947
3000
  }
2948
3001
  }
@@ -2984,6 +3037,11 @@ var parseHeaders_default = (raw) => {
2984
3037
 
2985
3038
  // src/context/writeResponse.ts
2986
3039
  async function writeResponse(out, response) {
3040
+ if (response.destroyed || response.writableEnded) {
3041
+ out.body?.cancel().catch(() => {
3042
+ });
3043
+ return;
3044
+ }
2987
3045
  response.writeHead(out.status || 200, parseHeaders_default(out.headers));
2988
3046
  try {
2989
3047
  if (out.body instanceof ReadableStream) {
@@ -3240,7 +3298,14 @@ function forwarded(url, headers2, trustProxy) {
3240
3298
  }
3241
3299
 
3242
3300
  // src/context/create.ts
3243
- function createContext(app, { method: rawMethod, headers: rawHeaders, url: rawUrl, signal, remoteAddress, source }) {
3301
+ function createContext(app, {
3302
+ method: rawMethod,
3303
+ headers: rawHeaders,
3304
+ url: rawUrl,
3305
+ signal,
3306
+ remoteAddress,
3307
+ source
3308
+ }) {
3244
3309
  const init = performance.now();
3245
3310
  const method = rawMethod?.toLowerCase() || "get";
3246
3311
  const headers2 = parseHeaders_default(rawHeaders);
@@ -3354,7 +3419,8 @@ var Node = async (app) => {
3354
3419
  response.end("Server Error");
3355
3420
  return;
3356
3421
  }
3357
- await writeResponse(out, response);
3422
+ if (out) await writeResponse(out, response);
3423
+ else if (!response.destroyed) response.destroy();
3358
3424
  }
3359
3425
  );
3360
3426
  await attachWebsocket(server2, app);
@@ -3384,8 +3450,27 @@ function isSerializable(body) {
3384
3450
  if (body instanceof URLSearchParams) return false;
3385
3451
  return true;
3386
3452
  }
3453
+ var deletes = (attrs) => attrs.some((attr) => {
3454
+ const [rawKey, value = ""] = attr.split("=");
3455
+ const key = rawKey.trim().toLowerCase();
3456
+ if (key === "max-age") return Number(value) <= 0;
3457
+ if (key === "expires")
3458
+ return new Date(value.trim()).getTime() <= Date.now();
3459
+ return false;
3460
+ });
3387
3461
  function ServerTest(app) {
3388
3462
  const port = app.settings.port;
3463
+ const jar = /* @__PURE__ */ new Map();
3464
+ const keep = (res) => {
3465
+ for (const line of res.headers.getSetCookie?.() ?? []) {
3466
+ const [pair, ...attrs] = line.split(";");
3467
+ const eq = pair.indexOf("=");
3468
+ if (eq === -1) continue;
3469
+ const name = pair.slice(0, eq).trim();
3470
+ if (deletes(attrs)) jar.delete(name);
3471
+ else jar.set(name, pair.slice(eq + 1).trim());
3472
+ }
3473
+ };
3389
3474
  const fetch2 = async (method, path, options = {}) => {
3390
3475
  const headers2 = new Headers(options.headers);
3391
3476
  let body = options.body;
@@ -3393,13 +3478,17 @@ function ServerTest(app) {
3393
3478
  headers2.set("content-type", "application/json");
3394
3479
  body = JSON.stringify(body);
3395
3480
  }
3481
+ if (jar.size && !headers2.has("cookie")) {
3482
+ const sent = [...jar].map(([name, value]) => `${name}=${value}`);
3483
+ headers2.set("cookie", sent.join("; "));
3484
+ }
3396
3485
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(path) && !/^https?:\/\//i.test(path)) {
3397
3486
  throw new Error(
3398
3487
  `Only http(s) URLs can be tested, received "${path}". Pass a path, or the full URL of the host the request should hit.`
3399
3488
  );
3400
3489
  }
3401
3490
  const url = /^https?:\/\//i.test(path) ? path : `http://localhost:${port}${path}`;
3402
- return await app.fetch(
3491
+ const res = await app.fetch(
3403
3492
  new Request(url, {
3404
3493
  ...options,
3405
3494
  method,
@@ -3407,6 +3496,8 @@ function ServerTest(app) {
3407
3496
  body
3408
3497
  })
3409
3498
  );
3499
+ if (res) keep(res);
3500
+ return res;
3410
3501
  };
3411
3502
  return {
3412
3503
  get: (path, options) => fetch2("get", path, options),
@@ -3415,7 +3506,12 @@ function ServerTest(app) {
3415
3506
  put: (path, body, options) => fetch2("put", path, { body, ...options }),
3416
3507
  patch: (path, body, options) => fetch2("patch", path, { body, ...options }),
3417
3508
  delete: (path, options) => fetch2("delete", path, options),
3418
- options: (path, options) => fetch2("options", path, options)
3509
+ options: (path, options) => fetch2("options", path, options),
3510
+ // The cookies the app has set so far, and a fresh session on demand
3511
+ get cookies() {
3512
+ return Object.fromEntries(jar);
3513
+ },
3514
+ clear: () => jar.clear()
3419
3515
  };
3420
3516
  }
3421
3517
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.49.0",
3
+ "version": "0.49.1",
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",
@@ -14,11 +14,11 @@
14
14
  ],
15
15
  "scripts": {
16
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
+ "postinstall": "mkdir -p node_modules/@server && ln -sfn ../.. node_modules/@server/next",
18
18
  "start": "bun test --watch",
19
- "lint": "npx tsc --noEmit && npx @biomejs/biome lint ./src --error-on-warnings",
20
- "test": "npm run test:bun && npm run lint",
21
- "test:bun": "bun test src demo"
19
+ "lint": "npm run prettier && npx tsc --noEmit && npx @biomejs/biome lint ./src --error-on-warnings",
20
+ "prettier": "prettier --write ./src",
21
+ "test": "bun test src demo"
22
22
  },
23
23
  "main": "index.js",
24
24
  "type": "module",
@@ -75,6 +75,7 @@
75
75
  "arktype": "^2.2.3",
76
76
  "bun": "^1.3.13",
77
77
  "check-dts": "^0.8.2",
78
+ "prettier": "^3.9.6",
78
79
  "tsup": "^8.5.1",
79
80
  "typescript": "^6.0.2",
80
81
  "valibot": "^1.4.2",
package/readme.md CHANGED
@@ -7,7 +7,7 @@ npm install @server/next
7
7
  ```
8
8
 
9
9
  ```js
10
- import server from '@server/next';
10
+ import server from "@server/next";
11
11
 
12
12
  export default server({ uploads: './uploads' })
13
13
  .get('/', () => 'Hello world')
@@ -15,24 +15,26 @@ export default server({ uploads: './uploads' })
15
15
  .post('/avatar', (ctx) => ctx.body.avatar.path);
16
16
  ```
17
17
 
18
- Key-value stores and file storage come included, so logins work out of the box and `uploads` takes a folder path. For Redis, S3 and the rest, pass the client straight in:
18
+ File storage comes included, so `uploads` takes a folder path or any bucket. Auth stores nothing of its own: two callbacks put the user wherever you already keep data.
19
19
 
20
20
  ```js
21
- import server, { bucket, kv } from '@server/next';
22
- import { createClient } from 'redis';
21
+ import server, { bucket } from "@server/next";
22
+ import { createClient } from "redis";
23
23
 
24
- const redis = kv(createClient({ url }));
25
- const uploads = bucket.S3('my-bucket', { id, key });
24
+ const redis = await createClient({ url }).connect();
25
+ const uploads = bucket.S3('my-bucket', { id, secret });
26
26
 
27
27
  export default server({
28
28
  uploads,
29
29
  auth: {
30
- strategy: 'cookie',
31
30
  providers: ['github'],
32
- users: redis.prefix('user:'),
33
- sessions: redis.prefix('session:'),
31
+ onLogin: async (profile) => {
32
+ await redis.set(`user:${profile.id}`, JSON.stringify(profile));
33
+ return profile.id;
34
+ },
35
+ getUser: async (id) => JSON.parse(await redis.get(`user:${id}`)),
34
36
  },
35
37
  });
36
38
  ```
37
39
 
38
- See the [full documentation](https://serverjs.io/documentation).
40
+ See the [full documentation](https://server-js.com/documentation).
@@ -47,18 +47,20 @@ const minifyCss = (str) => {
47
47
  // squeezed, then put back. Comments go first and unconditionally, so one
48
48
  // written inside a string is stripped along with the rest.
49
49
  const quoted = [];
50
- return str
51
- .replace(/\/\*[\s\S]*?\*\//g, "")
52
- .replace(
53
- /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
54
- (match) => `\0${quoted.push(match) - 1}\0`,
55
- )
56
- .replace(/\s+/g, " ")
57
- // `+` is left out on purpose for `calc(100% + 10px)`
58
- .replace(/\s*([{}:;,>~])\s*/g, "$1")
59
- .replace(/;}/g, "}")
60
- .replace(/\0(\d+)\0/g, (_, i) => quoted[i])
61
- .trim();
50
+ return (
51
+ str
52
+ .replace(/\/\*[\s\S]*?\*\//g, "")
53
+ .replace(
54
+ /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,
55
+ (match) => `\0${quoted.push(match) - 1}\0`,
56
+ )
57
+ .replace(/\s+/g, " ")
58
+ // `+` is left out on purpose for `calc(100% + 10px)`
59
+ .replace(/\s*([{}:;,>~])\s*/g, "$1")
60
+ .replace(/;}/g, "}")
61
+ .replace(/\0(\d+)\0/g, (_, i) => quoted[i])
62
+ .trim()
63
+ );
62
64
  };
63
65
 
64
66
  // React element detection (safe for custom objects too)