@server/next 0.41.0 → 0.43.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 (4) hide show
  1. package/index.d.ts +13 -16
  2. package/index.js +362 -248
  3. package/package.json +11 -15
  4. package/readme.md +6 -5
package/index.d.ts CHANGED
@@ -103,10 +103,13 @@ type RouteOptions = {
103
103
  params?: StandardSchemaV1<any, any>;
104
104
  response?: StandardSchemaV1<any, any>;
105
105
  cache?: CacheOption;
106
+ uploads?: string | Bucket | UploadOptions | false;
106
107
  };
107
108
  type Route = {
108
109
  path: string;
109
- options: RouteOptions;
110
+ options: Omit<RouteOptions, "uploads"> & {
111
+ uploads?: Settings["uploads"];
112
+ };
110
113
  fns: Middleware[];
111
114
  };
112
115
  type Cookie = {
@@ -182,10 +185,9 @@ type KVStore = {
182
185
  type Provider = "email" | "github" | "google" | "microsoft" | "discord" | "facebook" | "apple";
183
186
  type Strategy = "cookie" | "jwt" | "token";
184
187
  type AuthSession = {
185
- id: string;
186
- provider: Provider;
187
- strategy: Strategy;
188
188
  user: string;
189
+ provider: Provider;
190
+ created: string;
189
191
  };
190
192
  type AuthUser<T = Record<string, any>> = T & {
191
193
  id: string | number;
@@ -200,22 +202,22 @@ type ProfileUser = {
200
202
  type AuthOption = `${Strategy}:${Provider}` | {
201
203
  strategy: Strategy;
202
204
  providers?: Provider | Provider[];
203
- session?: StoreSource;
204
- store?: StoreSource;
205
+ users?: StoreSource;
205
206
  redirect?: string;
206
207
  onProfile?: (raw: any, provider: Provider) => ProfileUser | Promise<ProfileUser>;
207
208
  onLogin?: (loginUser: AuthUser, existingUser: AuthUser | null, ctx: Context) => ProfileUser | Promise<ProfileUser>;
208
209
  onUser?: <T = AuthUser>(user: T, ctx: Context) => T | Promise<T>;
210
+ onToken?: (user: AuthUser, ctx: Context) => ProfileUser | Promise<ProfileUser>;
209
211
  onLogout?: (ctx: Context) => unknown;
210
212
  };
211
213
  type AuthSettings = {
212
214
  providers: Provider[];
213
215
  strategy: Strategy;
214
- store: KVStore;
215
- session: KVStore;
216
+ users: KVStore;
216
217
  onProfile?: (raw: any, provider: Provider) => ProfileUser | Promise<ProfileUser>;
217
218
  onLogin?: (loginUser: AuthUser, existingUser: AuthUser | null, ctx: Context) => ProfileUser | Promise<ProfileUser>;
218
219
  onUser: <T = AuthUser>(user: T, ctx: Context) => T | Promise<T>;
220
+ onToken: (user: AuthUser, ctx: Context) => ProfileUser | Promise<ProfileUser>;
219
221
  onLogout?: (ctx: Context) => unknown;
220
222
  redirect: string;
221
223
  };
@@ -254,10 +256,7 @@ type Options = {
254
256
  secret?: string;
255
257
  public?: string | Bucket;
256
258
  uploads?: string | Bucket | UploadOptions;
257
- store?: StoreSource;
258
- session?: StoreSource | {
259
- store: StoreSource;
260
- };
259
+ sessions?: StoreSource;
261
260
  cors?: CorsOptions;
262
261
  auth?: AuthOption;
263
262
  openapi?: any;
@@ -276,10 +275,8 @@ type Settings = {
276
275
  uploads?: ({
277
276
  bucket: Bucket;
278
277
  } & LimitOptions) | null;
279
- store?: KVStore;
280
- session?: {
281
- store: KVStore;
282
- };
278
+ sessions: KVStore;
279
+ sessionsDefault?: boolean;
283
280
  cors?: CorsSettings;
284
281
  auth?: AuthSettings;
285
282
  openapi?: any;
package/index.js CHANGED
@@ -43,20 +43,18 @@ ServerError_default.extend({
43
43
  status: 400,
44
44
  message: "The route param '{param}' tries to climb the path ('{value}'). If this route legitimately receives paths, set security: { traversalProtection: false }"
45
45
  },
46
- NO_STORE: "You need a 'store' to write 'ctx.session'",
47
- NO_STORE_WRITE: "You need a 'store' to write 'ctx.session.{key}'",
48
- NO_STORE_READ: "You need a 'store' to read 'ctx.session.{key}'",
49
46
  AUTH_ARGON_NEEDED: "Argon2 is needed for the auth module, please install it with 'npm i argon2'",
50
47
  AUTH_INVALID_TOKEN: { status: 401, message: "Invalid Authorization token" },
51
- AUTH_INVALID_COOKIE: { status: 401, message: "Invalid Authorization cookie" },
48
+ AUTH_NO_CODE: {
49
+ status: 400,
50
+ message: "Missing the OAuth 'code' in the request body"
51
+ },
52
+ SESSION_JWT: "The `jwt` strategy is stateless, so there is no `ctx.session` (tried '{key}'). Use the `token` strategy for server-side sessions, or `cookie` for browsers",
53
+ SESSION_GUEST: "No `ctx.session` for this request (tried '{key}'): the `token` strategy carries the session in the Authorization header, and this request has none. Sign in first, or use the `cookie` strategy for guest sessions",
52
54
  AUTH_INVALID_HEADER: {
53
55
  status: 401,
54
56
  message: "Invalid authorization header {type}, must send 'Bearer {TOKEN}' (with space)"
55
57
  },
56
- AUTH_INVALID_STRATEGY: {
57
- status: 401,
58
- message: "Invalid Authorization type '{strategy}', valid one is '{valid}'"
59
- },
60
58
  AUTH_INVALID_STATE: { status: 403, message: "Invalid OAuth state" },
61
59
  AUTH_NO_PROVIDER: "No provider passed to the option 'auth.providers'",
62
60
  AUTH_INVALID_PROVIDER: {
@@ -107,6 +105,17 @@ var StatusError = class extends Error {
107
105
  }
108
106
  };
109
107
 
108
+ // src/helpers/bucket.ts
109
+ import FileSystem from "bucket/fs";
110
+ function bucket(root) {
111
+ if (!root) return null;
112
+ if (typeof root === "string") return FileSystem(root);
113
+ if (typeof root.file === "function") return root;
114
+ throw new Error(
115
+ "Invalid bucket: pass a directory path or a `bucket` instance (with .file())"
116
+ );
117
+ }
118
+
110
119
  // src/helpers/createId.ts
111
120
  var alphabet = "useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
112
121
  var random = (bytes) => crypto.getRandomValues(new Uint8Array(bytes));
@@ -149,6 +158,16 @@ function createId(source, size = 16) {
149
158
  }
150
159
 
151
160
  // src/helpers/upload.ts
161
+ function resolveUploads(up) {
162
+ if (!up) return null;
163
+ if (typeof up === "object" && "bucket" in up) {
164
+ const { bucket: bucket2, maxSize, minSize, fileType: fileType2 } = up;
165
+ if (maxSize != null) parseBytes(maxSize);
166
+ if (minSize != null) parseBytes(minSize);
167
+ return { bucket: bucket(bucket2), maxSize, minSize, fileType: fileType2 };
168
+ }
169
+ return { bucket: bucket(up) };
170
+ }
152
171
  function parseBytes(value) {
153
172
  if (typeof value === "number") return value;
154
173
  const units = {
@@ -729,13 +748,20 @@ function clientIp(headers2, opts = {}) {
729
748
 
730
749
  // src/helpers/store.ts
731
750
  import kv from "polystore";
732
- function toStore(source) {
751
+ function isStore(source) {
733
752
  const store = source;
734
- if (store && typeof store.prefix === "function" && typeof store.get === "function" && typeof store.set === "function") {
735
- return store;
736
- }
753
+ return Boolean(
754
+ store && typeof store.prefix === "function" && typeof store.get === "function" && typeof store.set === "function"
755
+ );
756
+ }
757
+ function toStore(source) {
758
+ if (isStore(source)) return source;
737
759
  return kv(source);
738
760
  }
761
+ function toStoreExpiring(source, expires) {
762
+ if (isStore(source)) return source;
763
+ return kv(source).expires(expires);
764
+ }
739
765
 
740
766
  // src/helpers/disposition.ts
741
767
  var encodeExt = (name) => encodeURIComponent(name).replace(
@@ -994,50 +1020,117 @@ async function verifyJwt(token, secret) {
994
1020
  return payload;
995
1021
  }
996
1022
 
1023
+ // src/auth/findSessionId.ts
1024
+ var validateToken = (authorization) => {
1025
+ const [type2, id] = authorization.trim().split(" ");
1026
+ if (type2?.toLowerCase() !== "bearer") {
1027
+ throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
1028
+ }
1029
+ if (id?.length !== 16) {
1030
+ throw ServerError_default.AUTH_INVALID_TOKEN();
1031
+ }
1032
+ return id;
1033
+ };
1034
+ function findSessionId(ctx) {
1035
+ if (ctx.options.auth?.strategy.includes("token")) {
1036
+ if (!ctx.headers.authorization) return;
1037
+ return validateToken(ctx.headers.authorization);
1038
+ }
1039
+ return ctx.cookies.session || void 0;
1040
+ }
1041
+
1042
+ // src/middle/session.ts
1043
+ var loaded = /* @__PURE__ */ new WeakMap();
1044
+ function noSession(error) {
1045
+ const target = {};
1046
+ return new Proxy(target, {
1047
+ get(target2, key) {
1048
+ if (typeof key === "symbol" || key === "then") return target2[key];
1049
+ throw error(String(key));
1050
+ },
1051
+ set(target2, key, value) {
1052
+ if (typeof key === "symbol") {
1053
+ target2[key] = value;
1054
+ return true;
1055
+ }
1056
+ throw error(String(key));
1057
+ }
1058
+ });
1059
+ }
1060
+ async function session(ctx) {
1061
+ const strategy = ctx.options.auth?.strategy;
1062
+ if (strategy?.includes("jwt")) {
1063
+ ctx.session = noSession((key) => ServerError_default.SESSION_JWT({ key }));
1064
+ return;
1065
+ }
1066
+ const id = findSessionId(ctx);
1067
+ if (!id && strategy?.includes("token")) {
1068
+ ctx.session = noSession((key) => ServerError_default.SESSION_GUEST({ key }));
1069
+ return;
1070
+ }
1071
+ ctx.session = id && await ctx.options.sessions.get(id) || {};
1072
+ loaded.set(ctx, { id, data: JSON.stringify(ctx.session) });
1073
+ }
1074
+
997
1075
  // src/auth/finishLogin.ts
998
- async function finishLogin(ctx, input) {
1076
+ async function finishLogin(ctx, input, opts = {}) {
999
1077
  const settings = ctx.options.auth;
1000
- const { strategy, onLogin, onUser } = settings;
1078
+ const { strategy, onLogin, onUser, onToken } = settings;
1001
1079
  const key = String(input.key);
1080
+ if (!strategy.includes("jwt") && !loaded.has(ctx)) {
1081
+ ctx.session = {};
1082
+ loaded.set(ctx, { id: void 0, data: "{}" });
1083
+ }
1002
1084
  const auth2 = {
1003
- id: createId(),
1004
- strategy,
1005
- provider: input.provider,
1006
1085
  user: key,
1007
- email: input.email,
1008
- time: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
1086
+ provider: input.provider,
1087
+ created: (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "")
1009
1088
  };
1010
1089
  const loginUser = {
1011
1090
  ...input.user,
1012
1091
  provider: input.provider,
1013
1092
  strategy
1014
1093
  };
1015
- const existingUser = await settings.store.get(key) ?? null;
1094
+ const existingUser = await settings.users.get(key) ?? null;
1016
1095
  const user = onLogin ? await onLogin(loginUser, existingUser, ctx) : { ...existingUser ?? {}, ...loginUser };
1017
1096
  assertUser(user, "onLogin");
1018
- await settings.store.set(key, user);
1019
- if (!strategy.includes("jwt")) {
1020
- await settings.session.set(auth2.id, auth2, { expires: "1w" });
1021
- }
1097
+ await settings.users.set(key, user);
1022
1098
  if (strategy.includes("jwt")) {
1023
- const token = await signJwt(auth2, ctx.options.secret, 7 * 24 * 60 * 60);
1024
- const exposed = await onUser(user, ctx);
1099
+ const payload = {
1100
+ ...await onToken(user, ctx),
1101
+ provider: input.provider
1102
+ };
1103
+ assertUser(payload, "onToken");
1104
+ const token = await signJwt(payload, ctx.options.secret, 7 * 24 * 60 * 60);
1105
+ const exposed = await onUser(payload, ctx);
1025
1106
  assertUser(exposed, "onUser");
1026
1107
  return status(201).json({ ...exposed, token });
1027
1108
  }
1109
+ const prev = loaded.get(ctx);
1110
+ if (prev?.id) await ctx.options.sessions.del(prev.id);
1111
+ const id = createId();
1112
+ Object.assign(ctx.session, auth2);
1113
+ await ctx.options.sessions.set(id, ctx.session);
1114
+ loaded.set(ctx, { id, data: JSON.stringify(ctx.session) });
1028
1115
  if (strategy.includes("token")) {
1029
1116
  const exposed = await onUser(user, ctx);
1030
1117
  assertUser(exposed, "onUser");
1031
- return status(201).json({ ...exposed, token: auth2.id });
1118
+ return status(201).json({ ...exposed, token: id });
1032
1119
  }
1033
1120
  if (strategy.includes("cookie")) {
1034
- return cookies("authentication", {
1035
- value: auth2.id,
1121
+ const reply = cookies("session", {
1122
+ value: id,
1036
1123
  path: "/",
1037
1124
  httpOnly: true,
1038
1125
  secure: ctx.platform.production,
1039
1126
  sameSite: "Lax"
1040
- }).redirect(settings.redirect);
1127
+ });
1128
+ if (opts.json) {
1129
+ const exposed = await onUser(user, ctx);
1130
+ assertUser(exposed, "onUser");
1131
+ return reply.status(201).json(exposed);
1132
+ }
1133
+ return reply.redirect(settings.redirect);
1041
1134
  }
1042
1135
  throw new Error("Unknown auth type");
1043
1136
  }
@@ -1123,78 +1216,111 @@ var login = (ctx) => {
1123
1216
  });
1124
1217
  return cookies("oauth_state", cookie).redirect(`${AUTHORIZE}?${params}`);
1125
1218
  };
1126
- var callback = async (ctx) => {
1127
- const body = ctx.body || {};
1128
- checkState(ctx, body.state);
1219
+ var exchange = async (code, redirectUri, user) => {
1220
+ const params = new URLSearchParams({
1221
+ client_id: env.APPLE_ID,
1222
+ client_secret: await clientSecret(),
1223
+ code: code ?? "",
1224
+ grant_type: "authorization_code"
1225
+ });
1226
+ if (redirectUri) params.set("redirect_uri", redirectUri);
1129
1227
  const tokenRes = await fetch(TOKEN, {
1130
1228
  method: "POST",
1131
1229
  headers: {
1132
1230
  accept: "application/json",
1133
1231
  "content-type": "application/x-www-form-urlencoded"
1134
1232
  },
1135
- body: new URLSearchParams({
1136
- client_id: env.APPLE_ID,
1137
- client_secret: await clientSecret(),
1138
- code: body.code,
1139
- grant_type: "authorization_code",
1140
- redirect_uri: `${ctx.url.origin}/auth/callback/apple`
1141
- })
1233
+ body: params
1142
1234
  });
1143
1235
  if (!tokenRes.ok) throw new Error("apple: token exchange failed");
1144
1236
  const token = await tokenRes.json();
1145
1237
  const claims = b64urlJson(token.id_token.split(".")[1]);
1146
1238
  let name;
1147
- if (body.user) {
1148
- const parsed = JSON.parse(body.user).name;
1239
+ if (user) {
1240
+ const parsed = JSON.parse(user).name;
1149
1241
  if (parsed) name = `${parsed.firstName} ${parsed.lastName}`.trim();
1150
1242
  }
1151
- const raw = { ...claims, name };
1243
+ return { ...claims, name };
1244
+ };
1245
+ var finish = async (ctx, raw, opts) => {
1152
1246
  const { onProfile } = ctx.options.auth;
1153
1247
  const profile = onProfile ? await onProfile(raw, "apple") : { id: raw.sub, name: raw.name, email: raw.email };
1154
1248
  assertUser(profile, "onProfile");
1155
- const res = await finishLogin(ctx, {
1156
- provider: "apple",
1157
- key: profile.id,
1158
- email: profile.email,
1159
- user: profile
1160
- });
1249
+ return finishLogin(
1250
+ ctx,
1251
+ {
1252
+ provider: "apple",
1253
+ key: profile.id,
1254
+ email: profile.email,
1255
+ user: profile
1256
+ },
1257
+ opts
1258
+ );
1259
+ };
1260
+ var callback = async (ctx) => {
1261
+ const body = ctx.body || {};
1262
+ checkState(ctx, body.state);
1263
+ const url = `${ctx.url.origin}/auth/callback/apple`;
1264
+ const raw = await exchange(body.code, url, body.user);
1265
+ const res = await finish(ctx, raw);
1161
1266
  res.headers.append("set-cookie", clearState());
1162
1267
  return res;
1163
1268
  };
1164
- var apple_default = { login, callback };
1269
+ var verify = async (ctx) => {
1270
+ const { code, redirect_uri, user } = ctx.body ?? {};
1271
+ if (!code) throw ServerError_default.AUTH_NO_CODE();
1272
+ const raw = await exchange(code, redirect_uri, user);
1273
+ return finish(ctx, raw, { json: true });
1274
+ };
1275
+ var apple_default = { login, callback, verify };
1165
1276
 
1166
1277
  // src/auth/providers/oauth.ts
1278
+ var wantsJson = (ctx) => String(ctx.headers.accept || "").includes("application/json");
1279
+ var clientParams = (source) => ({
1280
+ redirect_uri: source.redirect_uri,
1281
+ state: source.state,
1282
+ code_challenge: source.code_challenge,
1283
+ code_challenge_method: source.code_challenge ? "S256" : void 0
1284
+ });
1167
1285
  function oauthProvider(config2) {
1168
1286
  const KEY = config2.name.toUpperCase();
1169
1287
  const callbackUrl = (ctx) => `${ctx.url.origin}/auth/callback/${config2.name}`;
1170
- const login3 = (ctx) => {
1171
- const { state, cookie } = startState(ctx);
1172
- const params = new URLSearchParams({
1288
+ const authorizeUrl2 = (params) => {
1289
+ const search = new URLSearchParams({
1173
1290
  client_id: env[`${KEY}_ID`],
1174
- redirect_uri: callbackUrl(ctx),
1175
1291
  response_type: "code",
1176
- scope: config2.scope,
1177
- state
1292
+ scope: config2.scope
1178
1293
  });
1179
- return cookies("oauth_state", cookie).redirect(
1180
- `${config2.authorizeUrl}?${params}`
1181
- );
1294
+ for (const [key, value] of Object.entries(params)) {
1295
+ if (value) search.set(key, value);
1296
+ }
1297
+ return `${config2.authorizeUrl}?${search}`;
1182
1298
  };
1183
- const callback3 = async (ctx) => {
1184
- checkState(ctx, ctx.url.query.state);
1299
+ const login3 = (ctx) => {
1300
+ if (wantsJson(ctx)) {
1301
+ return json({ url: authorizeUrl2(clientParams(ctx.url.query)) });
1302
+ }
1303
+ const { state, cookie } = startState(ctx);
1304
+ const url = authorizeUrl2({ redirect_uri: callbackUrl(ctx), state });
1305
+ return cookies("oauth_state", cookie).redirect(url);
1306
+ };
1307
+ const exchange2 = async (ctx, code, extra) => {
1308
+ const body = new URLSearchParams({
1309
+ client_id: env[`${KEY}_ID`],
1310
+ client_secret: env[`${KEY}_SECRET`],
1311
+ code,
1312
+ grant_type: "authorization_code"
1313
+ });
1314
+ for (const [key, value] of Object.entries(extra)) {
1315
+ if (value) body.set(key, value);
1316
+ }
1185
1317
  const tokenRes = await fetch(config2.tokenUrl, {
1186
1318
  method: "POST",
1187
1319
  headers: {
1188
1320
  accept: "application/json",
1189
1321
  "content-type": "application/x-www-form-urlencoded"
1190
1322
  },
1191
- body: new URLSearchParams({
1192
- client_id: env[`${KEY}_ID`],
1193
- client_secret: env[`${KEY}_SECRET`],
1194
- code: ctx.url.query.code,
1195
- grant_type: "authorization_code",
1196
- redirect_uri: callbackUrl(ctx)
1197
- })
1323
+ body
1198
1324
  });
1199
1325
  if (!tokenRes.ok) throw new Error(`${config2.name}: token exchange failed`);
1200
1326
  const token = await tokenRes.json();
@@ -1205,20 +1331,39 @@ function oauthProvider(config2) {
1205
1331
  }
1206
1332
  });
1207
1333
  if (!profileRes.ok) throw new Error(`${config2.name}: profile fetch failed`);
1208
- const raw = await profileRes.json();
1334
+ return profileRes.json();
1335
+ };
1336
+ const finish3 = async (ctx, raw, opts) => {
1209
1337
  const { onProfile } = ctx.options.auth;
1210
1338
  const profile = onProfile ? await onProfile(raw, config2.name) : config2.profile(raw);
1211
1339
  assertUser(profile, "onProfile");
1212
- const res = await finishLogin(ctx, {
1213
- provider: config2.name,
1214
- key: profile.id,
1215
- email: profile.email,
1216
- user: profile
1340
+ return finishLogin(
1341
+ ctx,
1342
+ {
1343
+ provider: config2.name,
1344
+ key: profile.id,
1345
+ email: profile.email,
1346
+ user: profile
1347
+ },
1348
+ opts
1349
+ );
1350
+ };
1351
+ const callback3 = async (ctx) => {
1352
+ checkState(ctx, ctx.url.query.state);
1353
+ const raw = await exchange2(ctx, ctx.url.query.code, {
1354
+ redirect_uri: callbackUrl(ctx)
1217
1355
  });
1356
+ const res = await finish3(ctx, raw);
1218
1357
  res.headers.append("set-cookie", clearState());
1219
1358
  return res;
1220
1359
  };
1221
- return { login: login3, callback: callback3 };
1360
+ const verify4 = async (ctx) => {
1361
+ const { code, redirect_uri, code_verifier } = ctx.body ?? {};
1362
+ if (!code) throw ServerError_default.AUTH_NO_CODE();
1363
+ const raw = await exchange2(ctx, code, { redirect_uri, code_verifier });
1364
+ return finish3(ctx, raw, { json: true });
1365
+ };
1366
+ return { login: login3, callback: callback3, verify: verify4 };
1222
1367
  }
1223
1368
 
1224
1369
  // src/auth/providers/discord.ts
@@ -1250,10 +1395,10 @@ async function emailLogin(ctx) {
1250
1395
  if (!/@/.test(email)) throw ServerError_default.LOGIN_INVALID_EMAIL();
1251
1396
  if (!password) throw ServerError_default.LOGIN_NO_PASSWORD();
1252
1397
  if (password.length < 8) throw ServerError_default.LOGIN_INVALID_PASSWORD();
1253
- const store = ctx.options.auth.store;
1254
- if (!await store.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
1255
- const user = await store.get(email);
1256
- const isValid = await verify(password, user.password);
1398
+ const users = ctx.options.auth.users;
1399
+ if (!await users.has(email)) throw ServerError_default.LOGIN_WRONG_EMAIL();
1400
+ const user = await users.get(email);
1401
+ const isValid = await verify2(password, user.password);
1257
1402
  if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
1258
1403
  return finishLogin(ctx, {
1259
1404
  provider: "email",
@@ -1268,8 +1413,8 @@ async function emailRegister(ctx) {
1268
1413
  if (!/@/.test(email)) throw ServerError_default.REGISTER_INVALID_EMAIL();
1269
1414
  if (!password) throw ServerError_default.REGISTER_NO_PASSWORD();
1270
1415
  if (password.length < 8) throw ServerError_default.REGISTER_INVALID_PASSWORD();
1271
- const store = ctx.options.auth.store;
1272
- if (await store.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
1416
+ const users = ctx.options.auth.users;
1417
+ if (await users.has(email)) throw ServerError_default.REGISTER_EMAIL_EXISTS();
1273
1418
  const time = (/* @__PURE__ */ new Date()).toISOString().replace(/\.[0-9]*/, "");
1274
1419
  const user = {
1275
1420
  id: createId(email),
@@ -1291,12 +1436,12 @@ async function emailResetPassword() {
1291
1436
  }
1292
1437
  async function emailUpdatePassword(ctx) {
1293
1438
  const passwords = ctx.body;
1294
- const fullUser = await ctx.options.auth.store.get(ctx.user.email);
1439
+ const fullUser = await ctx.options.auth.users.get(ctx.user.email);
1295
1440
  if (!fullUser) throw ServerError_default.AUTH_NO_USER();
1296
- const isValid = await verify(passwords.previous, fullUser.password);
1441
+ const isValid = await verify2(passwords.previous, fullUser.password);
1297
1442
  if (!isValid) throw ServerError_default.LOGIN_WRONG_PASSWORD();
1298
1443
  fullUser.password = await hash2(passwords.updated);
1299
- await updateUser(fullUser, ctx.user, ctx.options.auth.store);
1444
+ await updateUser(fullUser, ctx.user, ctx.options.auth.users);
1300
1445
  return 200;
1301
1446
  }
1302
1447
  var email_default = {
@@ -1322,7 +1467,8 @@ var facebook_default = oauthProvider({
1322
1467
  });
1323
1468
 
1324
1469
  // src/auth/providers/github.ts
1325
- var oauth = async (code) => {
1470
+ var AUTHORIZE2 = "https://github.com/login/oauth/authorize";
1471
+ var oauth = async (code, extra) => {
1326
1472
  const fch = async (url, { body, headers: headers2 = {}, ...rest } = {}) => {
1327
1473
  headers2.accept = "application/json";
1328
1474
  headers2["content-type"] = "application/json";
@@ -1330,13 +1476,17 @@ var oauth = async (code) => {
1330
1476
  if (!res2.ok) throw new Error("Invalid request");
1331
1477
  return res2.json();
1332
1478
  };
1479
+ const params = {
1480
+ client_id: env.GITHUB_ID,
1481
+ client_secret: env.GITHUB_SECRET,
1482
+ code
1483
+ };
1484
+ for (const [key, value] of Object.entries(extra)) {
1485
+ if (value) params[key] = value;
1486
+ }
1333
1487
  const res = await fch("https://github.com/login/oauth/access_token", {
1334
1488
  method: "post",
1335
- body: JSON.stringify({
1336
- client_id: env.GITHUB_ID,
1337
- client_secret: env.GITHUB_SECRET,
1338
- code
1339
- })
1489
+ body: JSON.stringify(params)
1340
1490
  });
1341
1491
  return (path) => {
1342
1492
  return fch(`https://api.github.com${path}`, {
@@ -1344,19 +1494,25 @@ var oauth = async (code) => {
1344
1494
  });
1345
1495
  };
1346
1496
  };
1347
- var login2 = (ctx) => {
1348
- const { state, cookie } = startState(ctx);
1349
- const params = new URLSearchParams({
1497
+ var authorizeUrl = (params) => {
1498
+ const search = new URLSearchParams({
1350
1499
  client_id: env.GITHUB_ID,
1351
- scope: "user:email",
1352
- state
1500
+ scope: "user:email"
1353
1501
  });
1354
- return cookies("oauth_state", cookie).redirect(
1355
- `https://github.com/login/oauth/authorize?${params}`
1356
- );
1502
+ for (const [key, value] of Object.entries(params)) {
1503
+ if (value) search.set(key, value);
1504
+ }
1505
+ return `${AUTHORIZE2}?${search}`;
1357
1506
  };
1358
- var getUserProfile = async (code) => {
1359
- const api = await oauth(code);
1507
+ var login2 = (ctx) => {
1508
+ if (wantsJson(ctx)) {
1509
+ return json({ url: authorizeUrl(clientParams(ctx.url.query)) });
1510
+ }
1511
+ const { state, cookie } = startState(ctx);
1512
+ return cookies("oauth_state", cookie).redirect(authorizeUrl({ state }));
1513
+ };
1514
+ var getUserProfile = async (code, extra = {}) => {
1515
+ const api = await oauth(code, extra);
1360
1516
  const [profile, emails] = await Promise.all([
1361
1517
  api("/user"),
1362
1518
  api("/user/emails")
@@ -1372,22 +1528,35 @@ var defaultProfile = (raw) => ({
1372
1528
  location: raw.location,
1373
1529
  created: raw.created_at
1374
1530
  });
1375
- var callback2 = async (ctx) => {
1376
- checkState(ctx, ctx.url.query.state);
1377
- const raw = await getUserProfile(ctx.url.query.code);
1531
+ var finish2 = async (ctx, raw, opts) => {
1378
1532
  const { onProfile } = ctx.options.auth;
1379
1533
  const profile = onProfile ? await onProfile(raw, "github") : defaultProfile(raw);
1380
1534
  assertUser(profile, "onProfile");
1381
- const res = await finishLogin(ctx, {
1382
- provider: "github",
1383
- key: profile.id,
1384
- email: profile.email,
1385
- user: profile
1386
- });
1535
+ return finishLogin(
1536
+ ctx,
1537
+ {
1538
+ provider: "github",
1539
+ key: profile.id,
1540
+ email: profile.email,
1541
+ user: profile
1542
+ },
1543
+ opts
1544
+ );
1545
+ };
1546
+ var callback2 = async (ctx) => {
1547
+ checkState(ctx, ctx.url.query.state);
1548
+ const raw = await getUserProfile(ctx.url.query.code);
1549
+ const res = await finish2(ctx, raw);
1387
1550
  res.headers.append("set-cookie", clearState());
1388
1551
  return res;
1389
1552
  };
1390
- var github_default = { login: login2, callback: callback2 };
1553
+ var verify3 = async (ctx) => {
1554
+ const { code, redirect_uri, code_verifier } = ctx.body ?? {};
1555
+ if (!code) throw ServerError_default.AUTH_NO_CODE();
1556
+ const raw = await getUserProfile(code, { redirect_uri, code_verifier });
1557
+ return finish2(ctx, raw, { json: true });
1558
+ };
1559
+ var github_default = { login: login2, callback: callback2, verify: verify3 };
1391
1560
 
1392
1561
  // src/auth/providers/google.ts
1393
1562
  var google_default = oauthProvider({
@@ -1437,7 +1606,7 @@ function defaultOnUser(fullUser) {
1437
1606
  return user;
1438
1607
  }
1439
1608
  var available = Object.keys(providers_default);
1440
- function parseAuthOptions(auth2, all) {
1609
+ function parseAuthOptions(auth2) {
1441
1610
  if (!auth2) return null;
1442
1611
  if (typeof auth2 === "string") {
1443
1612
  const [strategy2, provider] = auth2.split(":");
@@ -1460,15 +1629,8 @@ function parseAuthOptions(auth2, all) {
1460
1629
  const redirect2 = auth2.redirect || defaultRedirect;
1461
1630
  const { onProfile, onLogin, onLogout } = auth2;
1462
1631
  const onUser = auth2.onUser || defaultOnUser;
1463
- if (!auth2.store && !all.store) {
1464
- throw new Error("Need a userStore store for Auth");
1465
- }
1466
- if (!auth2.session && !all.store) {
1467
- throw new Error("Need a sessionStore store for Auth");
1468
- }
1469
- const store = all.store ? toStore(all.store) : null;
1470
- const authStore = auth2.store ? toStore(auth2.store) : store.prefix("user:");
1471
- const sessionStore = auth2.session ? toStore(auth2.session) : store.prefix("auth:");
1632
+ const onToken = auth2.onToken || defaultOnUser;
1633
+ const users = auth2.users ? toStore(auth2.users) : null;
1472
1634
  return {
1473
1635
  strategy,
1474
1636
  providers: list,
@@ -1476,23 +1638,12 @@ function parseAuthOptions(auth2, all) {
1476
1638
  onProfile,
1477
1639
  onLogin,
1478
1640
  onUser,
1641
+ onToken,
1479
1642
  onLogout,
1480
- store: authStore,
1481
- session: sessionStore
1643
+ users
1482
1644
  };
1483
1645
  }
1484
1646
 
1485
- // src/helpers/bucket.ts
1486
- import FileSystem from "bucket/fs";
1487
- function bucket(root) {
1488
- if (!root) return null;
1489
- if (typeof root === "string") return FileSystem(root);
1490
- if (typeof root.file === "function") return root;
1491
- throw new Error(
1492
- "Invalid bucket: pass a directory path or a `bucket` instance (with .file())"
1493
- );
1494
- }
1495
-
1496
1647
  // src/helpers/color.ts
1497
1648
  var map = {
1498
1649
  reset: 0,
@@ -1687,7 +1838,11 @@ function config(options = {}) {
1687
1838
  parser: options.parser ?? "parse",
1688
1839
  // Secure-by-default response headers + trustProxy for ctx.ip. `false` turns
1689
1840
  // the added headers off; see resolveSecurity for the defaults.
1690
- security: resolveSecurity(options.security)
1841
+ security: resolveSecurity(options.security),
1842
+ // Sessions: one record per device, exposed as ctx.session. Anything
1843
+ // polystore accepts works; raw sources (a Map, a Redis client) get a 1w
1844
+ // expiry, a built store is honored as-is, prefix and expiry included.
1845
+ sessions: toStoreExpiring(options.sessions ?? /* @__PURE__ */ new Map(), "1w")
1691
1846
  };
1692
1847
  if (options.cache !== void 0) settings.cache = options.cache;
1693
1848
  options.cors = options.cors || env2.CORS || null;
@@ -1728,29 +1883,29 @@ function config(options = {}) {
1728
1883
  }
1729
1884
  const publicDir = options.public || env2.PUBLIC;
1730
1885
  settings.public = publicDir ? bucket(publicDir) : null;
1731
- const up = options.uploads;
1732
- if (!up) {
1733
- settings.uploads = null;
1734
- } else if (typeof up === "object" && "bucket" in up) {
1735
- const { bucket: bucket2, maxSize, minSize, fileType: fileType2 } = up;
1736
- if (maxSize != null) parseBytes(maxSize);
1737
- if (minSize != null) parseBytes(minSize);
1738
- settings.uploads = { bucket: bucket(bucket2), maxSize, minSize, fileType: fileType2 };
1739
- } else {
1740
- settings.uploads = { bucket: bucket(up) };
1741
- }
1886
+ settings.uploads = resolveUploads(options.uploads);
1742
1887
  const favicon2 = options.favicon || env2.FAVICON;
1743
1888
  if (favicon2) settings.favicon = favicon2;
1744
- settings.store = options.store ? toStore(options.store) : null;
1745
- if (options.session) {
1746
- const store = typeof options.session === "object" && "store" in options.session ? options.session.store : options.session;
1747
- settings.session = { store: toStore(store) };
1748
- }
1749
- if (settings.store && !options.session) {
1750
- settings.session = { store: settings.store.prefix("session:") };
1751
- }
1889
+ const production = env2.NODE_ENV === "production";
1890
+ const defaulted = options.sessions == null;
1891
+ settings.sessionsDefault = defaulted;
1752
1892
  if (options.auth || env2.AUTH) {
1753
- settings.auth = parseAuthOptions(options.auth || env2.AUTH || null, options);
1893
+ settings.auth = parseAuthOptions(options.auth || env2.AUTH || null);
1894
+ }
1895
+ if (settings.auth) {
1896
+ if (!settings.auth.users) {
1897
+ if (production) {
1898
+ throw new Error(
1899
+ "Auth in production needs a persistent `users` store, like auth: { ..., users: kv(redis).prefix('user:') }."
1900
+ );
1901
+ }
1902
+ settings.auth.users = toStore(/* @__PURE__ */ new Map());
1903
+ }
1904
+ if (production && defaulted && !settings.auth.strategy.includes("jwt")) {
1905
+ throw new Error(
1906
+ "Auth in production needs a persistent `sessions` store, like sessions: kv(redis).prefix('session:')."
1907
+ );
1908
+ }
1754
1909
  }
1755
1910
  if (settings.auth?.strategy.includes("jwt") && settings.secret.startsWith("unsafe-")) {
1756
1911
  console.warn(
@@ -1774,7 +1929,7 @@ function config(options = {}) {
1774
1929
  }
1775
1930
  if (settings.public) log.message("public", loc(options.public));
1776
1931
  if (settings.uploads) log.message("uploads", loc(options.uploads));
1777
- if (settings.session) log.message("session", "enabled");
1932
+ if (options.sessions) log.message("sessions", "enabled");
1778
1933
  if (settings.cors) {
1779
1934
  const origin = settings.cors.origin === true ? "*" : String(settings.cors.origin);
1780
1935
  log.message("cors", origin);
@@ -1885,6 +2040,14 @@ function getMachine() {
1885
2040
  }
1886
2041
 
1887
2042
  // src/parseResponse.ts
2043
+ var warned = false;
2044
+ var warnDefault = () => {
2045
+ if (warned) return;
2046
+ warned = true;
2047
+ console.warn(
2048
+ "[server:sessions] Using the default in-memory session store in production: sessions are lost on restart and not shared across instances. Configure one with sessions: kv(redis).prefix('session:')."
2049
+ );
2050
+ };
1888
2051
  async function parseResponse(out, ctx) {
1889
2052
  if (!out && typeof out !== "string") return null;
1890
2053
  if (typeof out === "function") {
@@ -1952,11 +2115,12 @@ async function parseResponse(out, ctx) {
1952
2115
  if (ctx.time?.times?.length > 1) {
1953
2116
  out.headers.set("Server-Timing", ctx.time.headers());
1954
2117
  }
1955
- if (Object.keys(ctx.session || {}).length) {
1956
- if (!ctx.options.session?.store) {
1957
- throw ServerError_default.NO_STORE();
2118
+ const prev = loaded.get(ctx);
2119
+ if (prev && JSON.stringify(ctx.session ?? {}) !== prev.data) {
2120
+ if (ctx.options.sessionsDefault && ctx.platform.production) {
2121
+ warnDefault();
1958
2122
  }
1959
- let id = ctx.cookies.session;
2123
+ let id = prev.id;
1960
2124
  if (!id) {
1961
2125
  id = createId();
1962
2126
  out.headers.append(
@@ -1970,7 +2134,7 @@ async function parseResponse(out, ctx) {
1970
2134
  })
1971
2135
  );
1972
2136
  }
1973
- ctx.options.session.store.set(id, ctx.session);
2137
+ ctx.options.sessions.set(id, ctx.session);
1974
2138
  }
1975
2139
  if (ctx?.res?.headers) {
1976
2140
  for (const key in ctx.res.headers) {
@@ -2252,7 +2416,7 @@ function timingSafeEqual(a, b) {
2252
2416
  }
2253
2417
  return mismatch === 0;
2254
2418
  }
2255
- async function verify(password, hash3) {
2419
+ async function verify2(password, hash3) {
2256
2420
  if ("Bun" in globalThis) {
2257
2421
  return Bun.password.verify(password, hash3, "argon2id");
2258
2422
  }
@@ -2286,94 +2450,72 @@ async function verify(password, hash3) {
2286
2450
  });
2287
2451
  }
2288
2452
 
2289
- // src/auth/findSessionId.ts
2290
- var validateToken = (authorization) => {
2291
- const [type2, id] = authorization.trim().split(" ");
2292
- if (type2?.toLowerCase() !== "bearer") {
2453
+ // src/auth/getUser.ts
2454
+ async function getJwtUser(ctx) {
2455
+ const header = ctx.headers.authorization;
2456
+ if (!header) return;
2457
+ const [type2, token] = header.trim().split(" ");
2458
+ if (type2?.toLowerCase() !== "bearer" || !token) {
2293
2459
  throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
2294
2460
  }
2295
- if (id?.length !== 16) {
2296
- throw ServerError_default.AUTH_INVALID_TOKEN();
2297
- }
2298
- return id;
2299
- };
2300
- var validateCookie = (authorization) => {
2301
- if (authorization.length !== 16) {
2302
- throw ServerError_default.AUTH_INVALID_COOKIE();
2303
- }
2304
- return authorization;
2305
- };
2306
- function findSessionId(ctx) {
2307
- const strategy = ctx.options.auth.strategy;
2308
- if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
2309
- if (strategy.includes("token")) {
2310
- if (!ctx.headers.authorization) return;
2311
- return validateToken(ctx.headers.authorization);
2312
- }
2313
- if (strategy.includes("cookie")) {
2314
- if (!ctx.cookies.authentication) return;
2315
- return validateCookie(ctx.cookies.authentication);
2461
+ const payload = await verifyJwt(token, ctx.options.secret);
2462
+ if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
2463
+ const { iat, exp, ...claims } = payload;
2464
+ if (!claims.id || !claims.email) throw ServerError_default.AUTH_INVALID_TOKEN();
2465
+ if (!ctx.options.auth.providers.includes(claims.provider)) {
2466
+ throw ServerError_default.AUTH_INVALID_PROVIDER({
2467
+ provider: claims.provider,
2468
+ valid: ctx.options.auth.providers
2469
+ });
2316
2470
  }
2317
- throw new Error(`Invalid auth type "${strategy}"`);
2471
+ const exposed = await ctx.options.auth.onUser(claims, ctx);
2472
+ assertUser(exposed, "onUser");
2473
+ return exposed;
2318
2474
  }
2319
-
2320
- // src/auth/getUser.ts
2321
2475
  async function getAuthSession(ctx) {
2322
- const strategy = ctx.options.auth.strategy;
2323
- if (strategy.includes("jwt")) {
2324
- const header = ctx.headers.authorization;
2325
- if (!header) return;
2326
- const [type2, token] = header.trim().split(" ");
2327
- if (type2?.toLowerCase() !== "bearer" || !token) {
2328
- throw ServerError_default.AUTH_INVALID_HEADER({ type: type2 });
2329
- }
2330
- const payload = await verifyJwt(token, ctx.options.secret);
2331
- if (!payload) throw ServerError_default.AUTH_INVALID_TOKEN();
2332
- return payload;
2476
+ if (loaded.has(ctx)) {
2477
+ const session3 = ctx.session;
2478
+ return session3?.user ? session3 : void 0;
2333
2479
  }
2334
2480
  const id = findSessionId(ctx);
2335
2481
  if (!id) return;
2336
- return ctx.options.auth.session.get(id);
2482
+ const session2 = await ctx.options.sessions.get(id);
2483
+ return session2?.user ? session2 : void 0;
2337
2484
  }
2338
2485
  async function getUser(ctx) {
2339
2486
  if (!ctx.options.auth) return;
2340
2487
  const options = ctx.options.auth;
2488
+ if (options.strategy.includes("jwt")) return getJwtUser(ctx);
2341
2489
  const auth2 = await getAuthSession(ctx);
2342
2490
  if (!auth2) return;
2343
- if (options.strategy !== auth2.strategy) {
2344
- throw ServerError_default.AUTH_INVALID_STRATEGY({
2345
- strategy: auth2.strategy || "undefined",
2346
- valid: options.strategy
2347
- });
2348
- }
2349
2491
  if (!options.providers.includes(auth2.provider)) {
2350
2492
  throw ServerError_default.AUTH_INVALID_PROVIDER({
2351
2493
  provider: auth2.provider,
2352
2494
  valid: options.providers
2353
2495
  });
2354
2496
  }
2355
- const user = await ctx.options.auth.store.get(auth2.user);
2497
+ const user = await options.users.get(auth2.user);
2356
2498
  if (!user) throw ServerError_default.AUTH_NO_USER();
2357
- user.strategy = auth2.strategy;
2358
- user.provider = auth2.provider;
2359
- const exposed = await ctx.options.auth.onUser(user, ctx);
2499
+ const exposed = await options.onUser(user, ctx);
2360
2500
  assertUser(exposed, "onUser");
2361
2501
  return exposed;
2362
2502
  }
2363
2503
 
2364
2504
  // src/auth/logout.ts
2365
2505
  async function logout(ctx) {
2366
- const { strategy } = ctx.user;
2367
- if (!strategy) throw new Error(`Invalid strategy "${strategy}"`);
2506
+ const { strategy } = ctx.options.auth;
2368
2507
  if (!strategy.includes("jwt")) {
2369
- await ctx.options.auth.session.del(findSessionId(ctx));
2508
+ const prev = loaded.get(ctx);
2509
+ if (prev?.id) await ctx.options.sessions.del(prev.id);
2510
+ ctx.session = {};
2511
+ loaded.set(ctx, { id: void 0, data: "{}" });
2370
2512
  }
2371
2513
  if (ctx.options.auth.onLogout) await ctx.options.auth.onLogout(ctx);
2372
2514
  if (strategy.includes("token") || strategy.includes("jwt")) {
2373
2515
  return { token: null };
2374
2516
  }
2375
2517
  if (strategy.includes("cookie")) {
2376
- return cookies({ authentication: null }).redirect("/");
2518
+ return cookies({ session: null }).redirect("/");
2377
2519
  }
2378
2520
  throw new Error("Unknown auth type");
2379
2521
  }
@@ -2399,6 +2541,7 @@ function auth(app) {
2399
2541
  if (!env[`${key}_SECRET`]) throw new Error(`${key}_SECRET not defined`);
2400
2542
  app.get(`/auth/login/${name}`, providers_default[name].login);
2401
2543
  app.get(`/auth/callback/${name}`, providers_default[name].callback);
2544
+ app.post(`/auth/verify/${name}`, providers_default[name].verify);
2402
2545
  }
2403
2546
  if (enabled.includes("apple")) {
2404
2547
  const keys = ["APPLE_ID", "APPLE_TEAM_ID", "APPLE_KEY_ID", "APPLE_PRIVATE_KEY"];
@@ -2407,6 +2550,7 @@ function auth(app) {
2407
2550
  }
2408
2551
  app.get("/auth/login/apple", providers_default.apple.login);
2409
2552
  app.post("/auth/callback/apple", providers_default.apple.callback);
2553
+ app.post("/auth/verify/apple", providers_default.apple.verify);
2410
2554
  }
2411
2555
  if (enabled.includes("email")) {
2412
2556
  app.post("/auth/register/email", providers_default.email.register);
@@ -2681,39 +2825,6 @@ function preflight(ctx) {
2681
2825
  return 204;
2682
2826
  }
2683
2827
 
2684
- // src/middle/NoSession.ts
2685
- var NoSession = class {
2686
- };
2687
- function createNoSession() {
2688
- return new Proxy(NoSession, {
2689
- get(target, key) {
2690
- if (target[key]) return target[key];
2691
- if (key === "then") return target[key];
2692
- if (typeof key === "symbol") return target[key];
2693
- throw ServerError_default.NO_STORE_READ({ key: String(key) });
2694
- },
2695
- set(target, key, value) {
2696
- if (target[key] || key === "then" || typeof key === "symbol") {
2697
- target[key] = value;
2698
- return true;
2699
- }
2700
- throw ServerError_default.NO_STORE_WRITE({ key: String(key) });
2701
- }
2702
- });
2703
- }
2704
-
2705
- // src/middle/session.ts
2706
- async function session(ctx) {
2707
- const store = ctx.options.session?.store;
2708
- if (!store) {
2709
- ctx.session = createNoSession();
2710
- return;
2711
- }
2712
- if (ctx.cookies.session) {
2713
- ctx.session = await store.get(ctx.cookies.session) || {};
2714
- }
2715
- }
2716
-
2717
2828
  // src/middle/timer.ts
2718
2829
  var createTime = () => {
2719
2830
  const times2 = [["init", performance.now()]];
@@ -3128,6 +3239,9 @@ var Router = class _Router {
3128
3239
  options = rest.shift();
3129
3240
  }
3130
3241
  checkParserConflict(options, this.settings?.parser);
3242
+ if (options.uploads !== void 0) {
3243
+ options.uploads = resolveUploads(options.uploads);
3244
+ }
3131
3245
  const base = method === "socket" ? [] : this.middleware;
3132
3246
  const fns = [...base, ...rest].filter((fn) => fn != null);
3133
3247
  this.handlers[method].push({ path, options, fns });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.41.0",
3
+ "version": "0.43.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",
@@ -47,25 +47,21 @@
47
47
  "Github": "https://github.com/franciscop/server-next"
48
48
  },
49
49
  "documentation": {
50
- "Documentation": [
51
- "docs/0. Documentation.md",
52
- "docs/1. Getting Started.md"
53
- ],
54
- "Guides": "docs/2. Guides.md",
55
- "Options": "docs/3. Options.md",
56
- "Router": "docs/4. Router.md",
57
- "Context": "docs/5. Context.md",
58
- "Reply": "docs/6. Reply.md",
59
- "Authentication": "docs/7. Authentication.md",
60
- "Testing": "docs/8. Testing.md",
61
- "Platforms": "docs/9. Platforms.md",
62
- "FAQ": "docs/A. FAQ.md"
50
+ "Documentation": "docs/0. Documentation.md",
51
+ "Options": "docs/1. Options.md",
52
+ "Router": "docs/2. Router.md",
53
+ "Context": "docs/3. Context.md",
54
+ "Reply": "docs/4. Reply.md",
55
+ "Authentication": "docs/5. Authentication.md",
56
+ "Testing": "docs/6. Testing.md",
57
+ "Platforms": "docs/7. Platforms.md",
58
+ "FAQ": "docs/8. FAQ.md"
63
59
  },
64
60
  "tutorials": "docs/tutorials"
65
61
  },
66
62
  "dependencies": {
67
63
  "bucket": "^0.7.1",
68
- "polystore": "^0.24.0"
64
+ "polystore": "^0.26.0"
69
65
  },
70
66
  "devDependencies": {
71
67
  "@types/bun": "^1.3.0",
package/readme.md CHANGED
@@ -9,21 +9,22 @@ npm install @server/next
9
9
  ```js
10
10
  import server from '@server/next';
11
11
 
12
- export default server({ store: new Map(), uploads: './uploads' })
12
+ export default server({ uploads: './uploads' })
13
13
  .get('/', () => 'Hello world')
14
14
  .get('/users/:id', (ctx) => db.users.find(ctx.url.params.id))
15
15
  .post('/avatar', (ctx) => ctx.body.avatar.path);
16
16
  ```
17
17
 
18
- Key-value stores and file storage come included, so `store` takes a plain `Map` and `uploads` takes a folder path. For Redis, S3 and the rest, `kv` and `bucket` are exported too:
18
+ Key-value stores and file storage come included, so `sessions` work out of the box and `uploads` takes a folder path. For Redis, S3 and the rest, pass the client straight in:
19
19
 
20
20
  ```js
21
- import server, { kv, bucket } from '@server/next';
21
+ import server, { bucket } from '@server/next';
22
+ import { createClient } from 'redis';
22
23
 
23
- const store = kv(createClient({ url }).connect());
24
+ const sessions = createClient({ url });
24
25
  const uploads = bucket.S3('my-bucket', { id, key });
25
26
 
26
- export default server({ store, uploads });
27
+ export default server({ sessions, uploads });
27
28
  ```
28
29
 
29
30
  See the [full documentation](https://serverjs.io/documentation).