@server/next 0.38.0 → 0.39.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 +9 -8
  2. package/index.js +95 -167
  3. package/package.json +5 -3
  4. package/readme.md +28 -0
package/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import * as http from 'http';
2
+ export { default as kv } from 'polystore';
3
+ export { default as bucket } from 'bucket';
2
4
 
3
5
  type LimitOptions = {
4
6
  maxSize?: number | string;
@@ -128,6 +130,7 @@ type BasicValue = string | number | boolean | null;
128
130
  type SerializableValue = BasicValue | {
129
131
  [key: string]: SerializableValue;
130
132
  } | Array<SerializableValue>;
133
+ type StoreSource = KVStore | Map<string, any> | string | Record<string, any>;
131
134
  type KVStore = {
132
135
  name?: string;
133
136
  prefix: (prefix?: string) => KVStore;
@@ -157,8 +160,8 @@ type AuthOption = `${Strategy}:${Provider}` | "key" | {
157
160
  strategy: Strategy;
158
161
  providers?: Provider | Provider[];
159
162
  key?: string;
160
- session?: KVStore;
161
- store?: KVStore;
163
+ session?: StoreSource;
164
+ store?: StoreSource;
162
165
  redirect?: string;
163
166
  cleanUser?: <T = AuthUser>(user: T) => T | Promise<T>;
164
167
  };
@@ -204,10 +207,9 @@ type Options = {
204
207
  secret?: string;
205
208
  public?: string | Bucket;
206
209
  uploads?: string | Bucket | UploadOptions;
207
- store?: KVStore;
208
- cookies?: KVStore;
209
- session?: KVStore | {
210
- store: KVStore;
210
+ store?: StoreSource;
211
+ session?: StoreSource | {
212
+ store: StoreSource;
211
213
  };
212
214
  cors?: CorsOptions;
213
215
  auth?: AuthOption;
@@ -228,7 +230,6 @@ type Settings = {
228
230
  bucket: Bucket;
229
231
  } & LimitOptions) | null;
230
232
  store?: KVStore;
231
- cookies?: KVStore;
232
233
  session?: {
233
234
  store: KVStore;
234
235
  };
@@ -500,4 +501,4 @@ declare class Server<O extends ServerConfig = {}> extends Router<O> {
500
501
  }
501
502
  declare function server<Session extends Record<string, any> = {}, User extends Record<string, any> = {}>(options?: Options): Server<ServerConfig<Session, User>>;
502
503
 
503
- export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type BodyOption, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type FileInfo, type InferParamType, type InlineReply, type KVStore, 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, type UploadOptions, type UploadedFile, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
504
+ export { type AuthOption, type AuthSession, type AuthSettings, type AuthUser, type BasicValue, type Body, type BodyMode, type BodyOption, type Bucket, type BucketFile, type BunEnv, type CacheOption, type Context, type Cookie, type CorsSettings, type ExtractPathParams, type FileInfo, type InferParamType, type InlineReply, type KVStore, 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 StoreSource, type Strategy, type Time, type UploadOptions, type UploadedFile, cache, cookies, server as default, download, file, headers, json, redirect, router, send, status, type };
package/index.js CHANGED
@@ -175,7 +175,7 @@ async function saveFileToBucket(originalName, data, bucket2, contentType) {
175
175
  };
176
176
  }
177
177
  function validateFile(originalName, data, contentType, limits) {
178
- const { maxSize, minSize, fileType } = limits;
178
+ const { maxSize, minSize, fileType: fileType2 } = limits;
179
179
  if (maxSize !== void 0 && data.length > parseBytes(maxSize)) {
180
180
  throw new Error(
181
181
  `File "${originalName}" is too large (${data.length} bytes, limit is ${maxSize})`
@@ -186,15 +186,15 @@ function validateFile(originalName, data, contentType, limits) {
186
186
  `File "${originalName}" is too small (${data.length} bytes, minimum is ${minSize})`
187
187
  );
188
188
  }
189
- if (fileType && fileType.length > 0) {
189
+ if (fileType2 && fileType2.length > 0) {
190
190
  const ext2 = getExt(originalName);
191
191
  const mime = contentType.toLowerCase();
192
- const allowed = fileType.some(
192
+ const allowed = fileType2.some(
193
193
  (t) => t.toLowerCase() === mime || t.toLowerCase() === ext2
194
194
  );
195
195
  if (!allowed) {
196
196
  throw new Error(
197
- `File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType.join(", ")})`
197
+ `File type not allowed for "${originalName}" (got "${contentType}", allowed: ${fileType2.join(", ")})`
198
198
  );
199
199
  }
200
200
  }
@@ -531,9 +531,9 @@ async function parseBody(input, contentType, dest, max = INF) {
531
531
  let limits;
532
532
  if (dest && "bucket" in dest) {
533
533
  bucket2 = dest.bucket;
534
- const { maxSize, minSize, fileType } = dest;
535
- if (maxSize != null || minSize != null || fileType != null) {
536
- limits = { maxSize, minSize, fileType };
534
+ const { maxSize, minSize, fileType: fileType2 } = dest;
535
+ if (maxSize != null || minSize != null || fileType2 != null) {
536
+ limits = { maxSize, minSize, fileType: fileType2 };
537
537
  }
538
538
  } else {
539
539
  bucket2 = dest;
@@ -661,8 +661,8 @@ function normalizeExpires(expires) {
661
661
  }
662
662
  function createCookies(key, val) {
663
663
  if (val.value === null) val.expires = EXPIRED;
664
- const { value, path: path2, expires, maxAge, httpOnly, secure, sameSite } = val;
665
- let str = `${key}=${encodeURIComponent(value ?? "")};Path=${path2 || "/"}`;
664
+ const { value, path, expires, maxAge, httpOnly, secure, sameSite } = val;
665
+ let str = `${key}=${encodeURIComponent(value ?? "")};Path=${path || "/"}`;
666
666
  if (typeof expires !== "undefined") str += `;Expires=${normalizeExpires(expires)}`;
667
667
  if (typeof maxAge === "number") str += `;Max-Age=${maxAge}`;
668
668
  if (httpOnly) str += ";HttpOnly";
@@ -725,6 +725,16 @@ function clientIp(headers2, opts = {}) {
725
725
  return normalize(remoteAddress);
726
726
  }
727
727
 
728
+ // src/helpers/store.ts
729
+ import kv from "polystore";
730
+ function toStore(source) {
731
+ const store = source;
732
+ if (store && typeof store.prefix === "function" && typeof store.get === "function" && typeof store.set === "function") {
733
+ return store;
734
+ }
735
+ return kv(source);
736
+ }
737
+
728
738
  // src/helpers/disposition.ts
729
739
  var encodeExt = (name) => encodeURIComponent(name).replace(
730
740
  /['()*]/g,
@@ -740,6 +750,14 @@ function disposition(name) {
740
750
  return `${value}; filename*=UTF-8''${encodeExt(clean)}`;
741
751
  }
742
752
 
753
+ // src/helpers/fileType.ts
754
+ function fileType(file2) {
755
+ if (file2.type) return file2.type;
756
+ const name = file2.path || file2.name || "";
757
+ const ext2 = name.split(".").pop()?.toLowerCase();
758
+ return ext2 ? mimes_default[ext2] : void 0;
759
+ }
760
+
743
761
  // src/helpers/isHtml.ts
744
762
  var TAG = /^\s*<[a-zA-Z!/]/;
745
763
  function isHtml(body) {
@@ -817,22 +835,22 @@ var Reply = class {
817
835
  }
818
836
  return this.send(JSON.stringify(body));
819
837
  }
820
- redirect(path2) {
821
- this.headers("location", path2);
838
+ redirect(path) {
839
+ this.headers("location", path);
822
840
  if (this.res.status == null) this.res.status = 302;
823
841
  return this.send();
824
842
  }
825
- async file(path2) {
826
- if (typeof path2 !== "string") {
827
- if (!await path2.exists()) return this.status(404).send();
828
- return this.type(path2.type).send(path2.stream());
843
+ async file(path) {
844
+ if (typeof path !== "string") {
845
+ if (!await path.exists()) return this.status(404).send();
846
+ return this.type(fileType(path)).send(path.stream());
829
847
  }
830
- if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(path2)) return this.status(404).send();
848
+ if (/(?:^|[\\/])\.\.(?:[\\/]|$)/.test(path)) return this.status(404).send();
831
849
  try {
832
- const fs2 = await import("fs");
833
- const ext2 = path2.split(".").pop();
834
- await fs2.promises.access(path2);
835
- const stream = fs2.createReadStream(path2);
850
+ const fs = await import("fs");
851
+ const ext2 = path.split(".").pop();
852
+ await fs.promises.access(path);
853
+ const stream = fs.createReadStream(path);
836
854
  return this.type(ext2).send(stream);
837
855
  } catch (error) {
838
856
  if (error.code === "ENOENT" || error.code === "EISDIR") {
@@ -1301,8 +1319,8 @@ var oauth = async (code) => {
1301
1319
  code
1302
1320
  })
1303
1321
  });
1304
- return (path2) => {
1305
- return fch(`https://api.github.com${path2}`, {
1322
+ return (path) => {
1323
+ return fch(`https://api.github.com${path}`, {
1306
1324
  headers: { Authorization: `Bearer ${res.access_token}` }
1307
1325
  });
1308
1326
  };
@@ -1437,112 +1455,24 @@ function parseAuthOptions(auth2, all) {
1437
1455
  if (!auth2.session && !all.store) {
1438
1456
  throw new Error("Need a sessionStore store for Auth");
1439
1457
  }
1440
- const store = auth2.store || all.store.prefix("user:");
1441
- const session2 = auth2.session || all.store.prefix("auth:");
1458
+ const store = all.store ? toStore(all.store) : null;
1459
+ const authStore = auth2.store ? toStore(auth2.store) : store.prefix("user:");
1460
+ const sessionStore = auth2.session ? toStore(auth2.session) : store.prefix("auth:");
1442
1461
  return {
1443
1462
  strategy,
1444
1463
  providers: list,
1445
1464
  redirect: redirect2,
1446
1465
  cleanUser,
1447
- store,
1448
- session: session2
1466
+ store: authStore,
1467
+ session: sessionStore
1449
1468
  };
1450
1469
  }
1451
1470
 
1452
1471
  // src/helpers/bucket.ts
1453
- import * as fs from "fs";
1454
- import * as fsp from "fs/promises";
1455
- import * as path from "path";
1456
- function localBucket(root, prefix = "") {
1457
- const base = path.resolve(root);
1458
- const resolveKey = (name) => {
1459
- if (!name) throw new Error("File name is required");
1460
- const full = path.resolve(base, name.replace(/^\/+/, ""));
1461
- if (full !== base && !full.startsWith(base + path.sep)) {
1462
- throw new Error(`Path "${name}" escapes the bucket root`);
1463
- }
1464
- return full;
1465
- };
1466
- const file2 = (name, win) => {
1467
- const full = resolveKey(name);
1468
- const key = prefix + name.replace(/^\/+/, "");
1469
- const type2 = mimes_default[path.extname(name).slice(1).toLowerCase()];
1470
- const read = () => {
1471
- let opts;
1472
- if (win) {
1473
- opts = { start: win.start };
1474
- if (Number.isFinite(win.end)) opts.end = Math.max(win.start, win.end - 1);
1475
- }
1476
- const nodeStream = fs.createReadStream(full, opts);
1477
- return new ReadableStream({
1478
- start(controller) {
1479
- nodeStream.on("data", (chunk) => controller.enqueue(chunk));
1480
- nodeStream.on("end", () => controller.close());
1481
- nodeStream.on("error", (err) => controller.error(err));
1482
- },
1483
- cancel() {
1484
- nodeStream.destroy();
1485
- }
1486
- });
1487
- };
1488
- return {
1489
- path: key,
1490
- name: path.basename(name),
1491
- type: type2,
1492
- async exists() {
1493
- const stats = await fsp.stat(full).catch(() => null);
1494
- return !!stats?.isFile();
1495
- },
1496
- async info() {
1497
- const stats = await fsp.stat(full).catch(() => null);
1498
- if (!stats?.isFile()) return null;
1499
- const size = win ? Math.max(0, Math.min(win.end, stats.size) - win.start) : stats.size;
1500
- return { size, type: type2 ?? null, modified: stats.mtime };
1501
- },
1502
- // Read-only view of [start, end), composed relative to the current window.
1503
- slice(start, end) {
1504
- const base2 = win?.start ?? 0;
1505
- const cap = win?.end ?? Number.POSITIVE_INFINITY;
1506
- const s = Math.min(cap, base2 + Math.max(0, start));
1507
- const e = end === void 0 ? cap : Math.min(cap, base2 + end);
1508
- return file2(name, { start: s, end: e });
1509
- },
1510
- async write(content) {
1511
- await fsp.mkdir(path.dirname(full), { recursive: true });
1512
- if (content instanceof ReadableStream) {
1513
- const writable = fs.createWriteStream(full);
1514
- for await (const chunk of content) {
1515
- writable.write(chunk);
1516
- }
1517
- await new Promise((resolve2, reject) => {
1518
- writable.on("error", reject);
1519
- writable.end(() => resolve2());
1520
- });
1521
- return;
1522
- }
1523
- await fsp.writeFile(full, content);
1524
- },
1525
- stream() {
1526
- return read();
1527
- },
1528
- async bytes() {
1529
- if (win) return new Uint8Array(await new Response(read()).arrayBuffer());
1530
- return new Uint8Array(await fsp.readFile(full));
1531
- },
1532
- async remove() {
1533
- await fsp.unlink(full).catch(() => {
1534
- });
1535
- }
1536
- };
1537
- };
1538
- return {
1539
- file: file2,
1540
- folder: (sub) => localBucket(path.join(base, sub), `${prefix}${sub.replace(/^\/+|\/+$/g, "")}/`)
1541
- };
1542
- }
1472
+ import FileSystem from "bucket/fs";
1543
1473
  function bucket(root) {
1544
1474
  if (!root) return null;
1545
- if (typeof root === "string") return localBucket(root);
1475
+ if (typeof root === "string") return FileSystem(root);
1546
1476
  if (typeof root.file === "function") return root;
1547
1477
  throw new Error(
1548
1478
  "Invalid bucket: pass a directory path or a `bucket` instance (with .file())"
@@ -1638,14 +1568,14 @@ function createLogger(level) {
1638
1568
  const request = (ctx, res) => {
1639
1569
  if (!enabled) return;
1640
1570
  const method = ctx.method.toUpperCase();
1641
- const path2 = ctx.url.pathname;
1571
+ const path = ctx.url.pathname;
1642
1572
  const reqLen = Number(ctx.headers["content-length"]) || 0;
1643
1573
  const resLen = Number(res.headers.get("content-length")) || 0;
1644
1574
  const status2 = res.status;
1645
1575
  const text = STATUS_TEXT[status2] || "";
1646
1576
  const reqSize = reqLen ? ` ${formatBytes(reqLen)}` : "";
1647
1577
  const resSize = resLen ? ` ${formatBytes(resLen)}` : "";
1648
- let line = `${method} ${path2}${reqSize} \u2192 ${status2}${text ? ` ${text}` : ""}${resSize}`;
1578
+ let line = `${method} ${path}${reqSize} \u2192 ${status2}${text ? ` ${text}` : ""}${resSize}`;
1649
1579
  const location = res.headers.get("location");
1650
1580
  if (location) line += ` \u2192 ${location}`;
1651
1581
  message("api", line);
@@ -1772,22 +1702,22 @@ function config(options = {}) {
1772
1702
  if (!up) {
1773
1703
  settings.uploads = null;
1774
1704
  } else if (typeof up === "object" && "bucket" in up) {
1775
- const { bucket: bucket2, maxSize, minSize, fileType } = up;
1705
+ const { bucket: bucket2, maxSize, minSize, fileType: fileType2 } = up;
1776
1706
  if (maxSize != null) parseBytes(maxSize);
1777
1707
  if (minSize != null) parseBytes(minSize);
1778
- settings.uploads = { bucket: bucket(bucket2), maxSize, minSize, fileType };
1708
+ settings.uploads = { bucket: bucket(bucket2), maxSize, minSize, fileType: fileType2 };
1779
1709
  } else {
1780
1710
  settings.uploads = { bucket: bucket(up) };
1781
1711
  }
1782
1712
  const favicon2 = options.favicon || env2.FAVICON;
1783
1713
  if (favicon2) settings.favicon = favicon2;
1784
- settings.store = options.store ?? null;
1785
- settings.cookies = options.cookies ?? null;
1714
+ settings.store = options.store ? toStore(options.store) : null;
1786
1715
  if (options.session) {
1787
- settings.session = "store" in options.session ? options.session : { store: options.session };
1716
+ const store = typeof options.session === "object" && "store" in options.session ? options.session.store : options.session;
1717
+ settings.session = { store: toStore(store) };
1788
1718
  }
1789
- if (options.store && !options.session) {
1790
- settings.session = { store: options.store.prefix("session:") };
1719
+ if (settings.store && !options.session) {
1720
+ settings.session = { store: settings.store.prefix("session:") };
1791
1721
  }
1792
1722
  if (options.auth || env2.AUTH) {
1793
1723
  settings.auth = parseAuthOptions(options.auth || env2.AUTH || null, options);
@@ -1940,9 +1870,10 @@ async function parseResponse(out, ctx) {
1940
1870
  if (!await out.exists()) {
1941
1871
  out = new Response(null, { status: 404 });
1942
1872
  } else {
1873
+ const type2 = fileType(out);
1943
1874
  out = new Response(
1944
1875
  out.stream(),
1945
- out.type ? { headers: { "content-type": out.type } } : void 0
1876
+ type2 ? { headers: { "content-type": type2 } } : void 0
1946
1877
  );
1947
1878
  }
1948
1879
  }
@@ -2011,13 +1942,6 @@ async function parseResponse(out, ctx) {
2011
1942
  }
2012
1943
  ctx.options.session.store.set(id, ctx.session);
2013
1944
  }
2014
- if (ctx.options.cookies) {
2015
- if (Object.keys(ctx.res?.cookies || {}).length) {
2016
- for (const cookie of Object.values(ctx.res.cookies)) {
2017
- ctx.res.headers.append("set-cookie", cookie);
2018
- }
2019
- }
2020
- }
2021
1945
  if (ctx?.res?.headers) {
2022
1946
  for (const key in ctx.res.headers) {
2023
1947
  out.headers[key] = ctx.res.headers[key];
@@ -2027,14 +1951,14 @@ async function parseResponse(out, ctx) {
2027
1951
  }
2028
1952
 
2029
1953
  // src/pathPattern.ts
2030
- function pathPattern(pattern, path2) {
2031
- if (pattern === "*" && path2 === "/") return {};
1954
+ function pathPattern(pattern, path) {
1955
+ if (pattern === "*" && path === "/") return {};
2032
1956
  pattern = `/${pattern.replace(/^\//, "")}`;
2033
1957
  pattern = pattern.replace(/\/$/, "") || "/";
2034
- path2 = path2.replace(/\/$/, "") || "/";
2035
- if (pattern === path2) return {};
1958
+ path = path.replace(/\/$/, "") || "/";
1959
+ if (pattern === path) return {};
2036
1960
  const params = {};
2037
- const pathParts = path2.split("/").slice(1).map((u) => decodeURIComponent(u));
1961
+ const pathParts = path.split("/").slice(1).map((u) => decodeURIComponent(u));
2038
1962
  const pattParts = pattern.split("/").slice(1);
2039
1963
  let allSame = true;
2040
1964
  for (let i = 0; i < Math.max(pathParts.length, pattParts.length); i++) {
@@ -2093,7 +2017,7 @@ function validate(ctx, schema) {
2093
2017
  } catch (error) {
2094
2018
  if (error.name === "ZodError" || error.constructor.name === "ZodError") {
2095
2019
  const message = error.issues.map(
2096
- ({ path: path2, message: message2 }) => `[${base}.${path2.join(".")}]: ${message2}`
2020
+ ({ path, message: message2 }) => `[${base}.${path.join(".")}]: ${message2}`
2097
2021
  ).sort().join("\n");
2098
2022
  throw new StatusError(message, 422);
2099
2023
  }
@@ -2289,7 +2213,7 @@ async function verify(password, hash3) {
2289
2213
  const [, variant, , memory, passes, parallelism, saltB64, hashB64] = match;
2290
2214
  const nonce = Buffer.from(saltB64, "base64");
2291
2215
  const expected = Buffer.from(hashB64, "base64");
2292
- return new Promise((resolve2, reject) => {
2216
+ return new Promise((resolve, reject) => {
2293
2217
  crypto3.argon2(
2294
2218
  `argon2${variant}`,
2295
2219
  {
@@ -2303,9 +2227,9 @@ async function verify(password, hash3) {
2303
2227
  (err, derivedKey) => {
2304
2228
  if (err) return reject(err);
2305
2229
  if (derivedKey.length === expected.length && timingSafeEqual(derivedKey, expected)) {
2306
- resolve2(true);
2230
+ resolve(true);
2307
2231
  } else {
2308
- resolve2(false);
2232
+ resolve(false);
2309
2233
  }
2310
2234
  }
2311
2235
  );
@@ -2500,8 +2424,8 @@ async function assets(ctx) {
2500
2424
  const info = file2.info?.bind(file2);
2501
2425
  const meta = info ? await info() : null;
2502
2426
  if (info ? !meta : !await file2.exists()) return;
2503
- const ext2 = ctx.url.pathname.split(".").pop();
2504
- const ctype = meta?.type || ext2;
2427
+ const ext2 = ctx.url.pathname.split(".").pop()?.toLowerCase();
2428
+ const ctype = ext2 && mimes_default[ext2] || meta?.type || ext2;
2505
2429
  const headers2 = { "cache-control": CACHE_CONTROL };
2506
2430
  let tag;
2507
2431
  if (meta) {
@@ -2564,7 +2488,7 @@ async function favicon(ctx) {
2564
2488
  }
2565
2489
 
2566
2490
  // src/middle/openapi.ts
2567
- import * as fsp2 from "fs/promises";
2491
+ import * as fsp from "fs/promises";
2568
2492
  var entities = {
2569
2493
  "&": "&amp;",
2570
2494
  "<": "&lt;",
@@ -2610,7 +2534,7 @@ function zodToSchema(schema) {
2610
2534
  }
2611
2535
  return { type: type2 };
2612
2536
  }
2613
- var pkgProm = fsp2.readFile("package.json", "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
2537
+ var pkgProm = fsp.readFile("package.json", "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
2614
2538
  var getTag = (name, fn) => {
2615
2539
  const found = fn.toString().split("\n").filter((l) => /\s+\/\/\s/.test(l)).map((l) => l.trim().replace("// ", "")).find((l) => l.startsWith(name));
2616
2540
  if (!found) return "";
@@ -2622,14 +2546,14 @@ var generateOpenApiPaths = (handlers) => {
2622
2546
  const paths = {};
2623
2547
  for (const [method, routes] of Object.entries(handlers)) {
2624
2548
  for (const route of routes) {
2625
- const path2 = route.path;
2549
+ const path = route.path;
2626
2550
  const fn = route.fns.find((p) => typeof p === "function");
2627
2551
  const meta = route.fns.find((p) => typeof p === "object");
2628
2552
  const config2 = getConfig(route.options);
2629
- if (typeof path2 !== "string" || path2 === "*" || path2 === "/docs" || !fn) {
2553
+ if (typeof path !== "string" || path === "*" || path === "/docs" || !fn) {
2630
2554
  continue;
2631
2555
  }
2632
- const normalizedPath = path2.replace(/\(\w+\)/gi, "").replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
2556
+ const normalizedPath = path.replace(/\(\w+\)/gi, "").replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
2633
2557
  if (!paths[normalizedPath]) {
2634
2558
  paths[normalizedPath] = {};
2635
2559
  }
@@ -2656,7 +2580,7 @@ var generateOpenApiPaths = (handlers) => {
2656
2580
  };
2657
2581
  }
2658
2582
  const parameters = [];
2659
- const matched = Array.from(path2.matchAll(/:[\w()]+/gi));
2583
+ const matched = Array.from(path.matchAll(/:[\w()]+/gi));
2660
2584
  matched.forEach((match) => {
2661
2585
  const [name, type2 = "string"] = match[0].slice(1).replace(/\)/, "").split("(");
2662
2586
  parameters.push({
@@ -2995,18 +2919,18 @@ async function createNode(req, app) {
2995
2919
  const cookies2 = parseCookies(headers2.cookie);
2996
2920
  const scheme = req.socket instanceof TLSSocket ? "https" : "http";
2997
2921
  const host = headers2.host || `localhost:${app.settings.port}`;
2998
- const path2 = (req.url || "/").replace(/\/$/, "") || "/";
2922
+ const path = (req.url || "/").replace(/\/$/, "") || "/";
2999
2923
  const baseUrl = `${scheme}://${host}`;
3000
- const url = new URL(path2, baseUrl);
2924
+ const url = new URL(path, baseUrl);
3001
2925
  define(
3002
2926
  url,
3003
2927
  "query",
3004
2928
  (url2) => Object.fromEntries(url2.searchParams.entries())
3005
2929
  );
3006
2930
  const source = {
3007
- getBuffer: () => new Promise((resolve2, reject) => {
2931
+ getBuffer: () => new Promise((resolve, reject) => {
3008
2932
  const chunks2 = [];
3009
- req.on("data", (chunk) => chunks2.push(chunk)).on("end", () => resolve2(Buffer.concat(chunks2))).on("error", reject);
2933
+ req.on("data", (chunk) => chunks2.push(chunk)).on("end", () => resolve(Buffer.concat(chunks2))).on("error", reject);
3010
2934
  }),
3011
2935
  getStream: () => toWeb(req)
3012
2936
  };
@@ -3158,9 +3082,9 @@ var Router = class _Router {
3158
3082
  // functions into a single flat `fns` list. A plain options object may sit
3159
3083
  // between the path and the handlers, and it's pulled out here.
3160
3084
  handle(method, pathOrFn, ...rest) {
3161
- let path2 = "*";
3085
+ let path = "*";
3162
3086
  if (typeof pathOrFn === "string") {
3163
- path2 = pathOrFn;
3087
+ path = pathOrFn;
3164
3088
  } else if (pathOrFn != null) {
3165
3089
  rest.unshift(pathOrFn);
3166
3090
  }
@@ -3170,7 +3094,7 @@ var Router = class _Router {
3170
3094
  }
3171
3095
  const base = method === "socket" ? [] : this.middleware;
3172
3096
  const fns = [...base, ...rest].filter((fn) => fn != null);
3173
- this.handlers[method].push({ path: path2, options, fns });
3097
+ this.handlers[method].push({ path, options, fns });
3174
3098
  return this.self();
3175
3099
  }
3176
3100
  socket(pathOrMid, optionsOrMid, ...middleware) {
@@ -3235,31 +3159,33 @@ function isSerializable(body) {
3235
3159
  }
3236
3160
  function ServerTest(app) {
3237
3161
  const port = app.settings.port;
3238
- const fetch2 = async (method, path2, options = {}) => {
3162
+ const fetch2 = async (method, path, options = {}) => {
3239
3163
  if (!options.headers) options.headers = {};
3240
3164
  if (isSerializable(options.body)) {
3241
3165
  options.headers["content-type"] = "application/json";
3242
3166
  options.body = JSON.stringify(options.body);
3243
3167
  }
3244
3168
  return await app.fetch(
3245
- new Request(`http://localhost:${port}${path2}`, {
3169
+ new Request(`http://localhost:${port}${path}`, {
3246
3170
  method,
3247
3171
  ...options
3248
3172
  })
3249
3173
  );
3250
3174
  };
3251
3175
  return {
3252
- get: (path2, options) => fetch2("get", path2, options),
3253
- head: (path2, options) => fetch2("head", path2, options),
3254
- post: (path2, body, options) => fetch2("post", path2, { body, ...options }),
3255
- put: (path2, body, options) => fetch2("put", path2, { body, ...options }),
3256
- patch: (path2, body, options) => fetch2("patch", path2, { body, ...options }),
3257
- delete: (path2, options) => fetch2("delete", path2, options),
3258
- options: (path2, options) => fetch2("options", path2, options)
3176
+ get: (path, options) => fetch2("get", path, options),
3177
+ head: (path, options) => fetch2("head", path, options),
3178
+ post: (path, body, options) => fetch2("post", path, { body, ...options }),
3179
+ put: (path, body, options) => fetch2("put", path, { body, ...options }),
3180
+ patch: (path, body, options) => fetch2("patch", path, { body, ...options }),
3181
+ delete: (path, options) => fetch2("delete", path, options),
3182
+ options: (path, options) => fetch2("options", path, options)
3259
3183
  };
3260
3184
  }
3261
3185
 
3262
3186
  // src/index.ts
3187
+ import { default as default2 } from "polystore";
3188
+ import { default as default3 } from "bucket";
3263
3189
  var Server = class extends Router {
3264
3190
  settings;
3265
3191
  platform;
@@ -3327,6 +3253,7 @@ function server(options) {
3327
3253
  export {
3328
3254
  Server,
3329
3255
  ServerError_default as ServerError,
3256
+ default3 as bucket,
3330
3257
  cache,
3331
3258
  cookies,
3332
3259
  server as default,
@@ -3334,6 +3261,7 @@ export {
3334
3261
  file,
3335
3262
  headers,
3336
3263
  json,
3264
+ default2 as kv,
3337
3265
  redirect,
3338
3266
  router,
3339
3267
  send,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.38.0",
3
+ "version": "0.39.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",
@@ -64,15 +64,17 @@
64
64
  },
65
65
  "tutorials": "docs/tutorials"
66
66
  },
67
+ "dependencies": {
68
+ "bucket": "^0.6.0",
69
+ "polystore": "^0.23.2"
70
+ },
67
71
  "devDependencies": {
68
72
  "@types/bun": "^1.3.0",
69
73
  "@types/jest": "^30.0.0",
70
74
  "@types/node": "^24.10.0",
71
- "bucket": "^0.5.0",
72
75
  "bun": "^1.3.13",
73
76
  "check-dts": "^0.8.2",
74
77
  "jest": "^29.7.0",
75
- "polystore": "^0.21.1",
76
78
  "tsup": "^8.5.1",
77
79
  "typescript": "^6.0.2"
78
80
  },
package/readme.md CHANGED
@@ -1 +1,29 @@
1
1
  # Server JS [![@server/next](https://img.shields.io/npm/v/@server/next?label=@server/next&color=greenlime)](https://www.npmjs.com/package/@server/next) [![tests](https://github.com/franciscop/server-next/workflows/tests/badge.svg)](https://github.com/franciscop/server-next/actions)
2
+
3
+ A modern web server for Bun and Node, with routing, authentication, uploads, WebSockets and testing built in.
4
+
5
+ ```bash
6
+ npm install @server/next
7
+ ```
8
+
9
+ ```js
10
+ import server from '@server/next';
11
+
12
+ export default server({ store: new Map(), uploads: './uploads' })
13
+ .get('/', () => 'Hello world')
14
+ .get('/users/:id', (ctx) => db.users.find(ctx.url.params.id))
15
+ .post('/avatar', (ctx) => ctx.body.avatar.path);
16
+ ```
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:
19
+
20
+ ```js
21
+ import server, { kv, bucket } from '@server/next';
22
+
23
+ const store = kv(createClient({ url }).connect());
24
+ const uploads = bucket.S3('my-bucket', { id, key });
25
+
26
+ export default server({ store, uploads });
27
+ ```
28
+
29
+ See the [full documentation](https://serverjs.io/documentation).