@server/next 0.49.1 → 0.49.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +12 -6
  2. package/index.js +50 -45
  3. package/package.json +2 -2
package/index.d.ts CHANGED
@@ -7,22 +7,28 @@ type FileInfo = {
7
7
  type: string | null;
8
8
  modified: Date;
9
9
  };
10
+ type ReadOptions = {
11
+ signal?: AbortSignal;
12
+ };
10
13
  type BucketFile = {
11
14
  readonly path: string;
12
15
  readonly name: string;
13
16
  readonly type?: string;
14
- exists(): Promise<boolean>;
15
- info?(): Promise<FileInfo | null>;
17
+ exists(opts?: ReadOptions): Promise<boolean>;
18
+ info?(opts?: ReadOptions): Promise<FileInfo | null>;
16
19
  write(content: string | Buffer | ReadableStream, options?: {
17
20
  type?: string;
18
- }): Promise<void>;
19
- stream(): ReadableStream;
21
+ } & ReadOptions): Promise<unknown>;
22
+ stream(opts?: ReadOptions): ReadableStream;
20
23
  slice?(start: number, end?: number): BucketFile;
21
- bytes(): Promise<Uint8Array>;
22
- remove(): Promise<void>;
24
+ bytes(opts?: ReadOptions): Promise<Uint8Array>;
25
+ remove(opts?: ReadOptions): Promise<unknown>;
23
26
  };
24
27
  type Bucket = {
25
28
  file(name: string): BucketFile;
29
+ create(content: string | Buffer | ReadableStream, options?: {
30
+ type?: string;
31
+ } & ReadOptions): Promise<BucketFile>;
26
32
  folder?(prefix: string): Bucket;
27
33
  };
28
34
 
package/index.js CHANGED
@@ -762,26 +762,25 @@ function resolveUploads(up) {
762
762
  maxFiles: DEFAULT_FILES
763
763
  };
764
764
  }
765
- function getExt(filename) {
766
- const i = filename.lastIndexOf(".");
767
- if (i <= 0) return ".bin";
768
- return filename.slice(i).toLowerCase();
769
- }
770
765
  function validateFile(originalName, contentType, limits, sniffed) {
771
766
  const { fileType: fileType2 } = limits;
772
- if (!fileType2 || fileType2.length === 0) return;
773
767
  if (sniffed === null && isSniffable(contentType)) {
774
768
  throw errors_default.UPLOAD_TYPE_NOT_ALLOWED({
775
769
  name: originalName,
776
770
  type: contentType,
777
- allowed: fileType2
771
+ allowed: fileType2 ?? [contentType]
778
772
  });
779
773
  }
780
- const ext = getExt(originalName);
781
- const mime = contentType.toLowerCase();
782
- const allowed = fileType2.some(
783
- (t) => t.toLowerCase() === mime || t.toLowerCase() === ext
784
- );
774
+ if (!fileType2 || fileType2.length === 0) return;
775
+ const base = (value) => value.split(";")[0].trim().toLowerCase();
776
+ const type2 = base(contentType);
777
+ const allowed = fileType2.some((one) => {
778
+ const entry = one.trim().toLowerCase();
779
+ if (entry.endsWith("/*")) return type2.startsWith(entry.slice(0, -1));
780
+ if (entry.includes("/")) return base(entry) === type2;
781
+ const mapped = mimes_default[entry.replace(/^\./, "")];
782
+ return Boolean(mapped) && base(mapped) === type2;
783
+ });
785
784
  if (!allowed) {
786
785
  throw errors_default.UPLOAD_TYPE_NOT_ALLOWED({
787
786
  name: originalName,
@@ -2147,11 +2146,11 @@ async function assets(ctx) {
2147
2146
  try {
2148
2147
  const key = ctx.url.pathname.replace(/^\/+/, "");
2149
2148
  const file2 = ctx.options.public.file(key);
2149
+ const read2 = { signal: ctx.signal };
2150
2150
  const info = file2.info?.bind(file2);
2151
- const meta2 = info ? await info() : null;
2152
- if (info ? !meta2 : !await file2.exists()) return;
2153
- const ext = ctx.url.pathname.split(".").pop()?.toLowerCase();
2154
- const ctype = mimeOf(ctx.url.pathname) || meta2?.type || ext;
2151
+ const meta2 = info ? await info(read2) : null;
2152
+ if (info ? !meta2 : !await file2.exists(read2)) return;
2153
+ const ctype = mimeOf(ctx.url.pathname) || meta2?.type || void 0;
2155
2154
  const headers2 = {
2156
2155
  "cache-control": resolveCache(ctx.options.cache) ?? DEFAULT_CACHE
2157
2156
  };
@@ -2180,10 +2179,10 @@ async function assets(ctx) {
2180
2179
  ...headers2,
2181
2180
  "content-range": `bytes ${start}-${end}/${meta2.size}`,
2182
2181
  "content-length": String(end - start + 1)
2183
- }).send(file2.slice(start, end + 1).stream());
2182
+ }).send(file2.slice(start, end + 1).stream(read2));
2184
2183
  }
2185
2184
  }
2186
- return type(ctype).headers(headers2).send(file2.stream());
2185
+ return type(ctype).headers(headers2).send(file2.stream(read2));
2187
2186
  } catch {
2188
2187
  }
2189
2188
  }
@@ -2409,8 +2408,6 @@ function isProbablyText(buffer) {
2409
2408
  }
2410
2409
  return true;
2411
2410
  }
2412
- var extByMime = {};
2413
- for (const ext in mimes_default) extByMime[mimes_default[ext]] = ext;
2414
2411
  function addField(body, name, value) {
2415
2412
  if (body[name] === void 0) {
2416
2413
  body[name] = value;
@@ -2419,7 +2416,7 @@ function addField(body, name, value) {
2419
2416
  if (!Array.isArray(body[name])) body[name] = [body[name]];
2420
2417
  body[name].push(value);
2421
2418
  }
2422
- function makeFilePart(name, filename, declared, bucket2, limits, budget) {
2419
+ function makeFilePart(name, filename, declared, bucket2, limits, budget, signal) {
2423
2420
  return {
2424
2421
  kind: "file",
2425
2422
  name,
@@ -2428,13 +2425,14 @@ function makeFilePart(name, filename, declared, bucket2, limits, budget) {
2428
2425
  bucket: bucket2,
2429
2426
  limits,
2430
2427
  budget,
2428
+ signal,
2431
2429
  head: [],
2432
2430
  headSize: 0,
2433
2431
  opened: null,
2434
2432
  size: 0
2435
2433
  };
2436
2434
  }
2437
- function startPart(headerStr, bucket2, limits, budget) {
2435
+ function startPart(headerStr, bucket2, limits, budget, signal) {
2438
2436
  const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
2439
2437
  if (!name) return { kind: "skip" };
2440
2438
  const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
@@ -2447,7 +2445,7 @@ function startPart(headerStr, bucket2, limits, budget) {
2447
2445
  if (maxFiles != null && budget.files > maxFiles) {
2448
2446
  throw errors_default.UPLOAD_TOO_MANY_FILES({ limit: String(maxFiles) });
2449
2447
  }
2450
- return makeFilePart(name, filename, type2, bucket2, limits, budget);
2448
+ return makeFilePart(name, filename, type2, bucket2, limits, budget, signal);
2451
2449
  }
2452
2450
  async function abortFile(part, error) {
2453
2451
  if (part.opened) {
@@ -2457,8 +2455,6 @@ async function abortFile(part, error) {
2457
2455
  });
2458
2456
  } catch {
2459
2457
  }
2460
- await part.opened.file.remove().catch(() => {
2461
- });
2462
2458
  }
2463
2459
  throw error;
2464
2460
  }
@@ -2491,20 +2487,16 @@ function openFile(part) {
2491
2487
  const sniffed = sniff(head);
2492
2488
  const type2 = resolveType(sniffed, part.declared);
2493
2489
  validateFile(part.filename, type2, part.limits, sniffed);
2494
- const ext = sniffed ? extByMime[type2] : void 0;
2495
- const id = `${createId()}${ext ? `.${ext}` : ""}`;
2496
2490
  let controller;
2497
2491
  const readable = new ReadableStream({
2498
2492
  start(c) {
2499
2493
  controller = c;
2500
2494
  }
2501
2495
  });
2502
- const file2 = part.bucket.file(id);
2503
2496
  part.opened = {
2504
2497
  type: type2,
2505
- file: file2,
2506
2498
  controller,
2507
- write: file2.write(readable, { type: type2 })
2499
+ write: part.bucket.create(readable, { type: type2, signal: part.signal })
2508
2500
  };
2509
2501
  }
2510
2502
  async function feedPart(part, data) {
@@ -2548,10 +2540,10 @@ async function endPart(part, body) {
2548
2540
  }
2549
2541
  const opened = part.opened;
2550
2542
  opened.controller.close();
2551
- await opened.write;
2543
+ const file2 = await opened.write;
2552
2544
  const { minSize } = part.limits;
2553
2545
  if (minSize != null && part.size < parseBytes(minSize)) {
2554
- await opened.file.remove().catch(() => {
2546
+ await file2.remove().catch(() => {
2555
2547
  });
2556
2548
  throw errors_default.UPLOAD_TOO_SMALL({
2557
2549
  name: part.filename,
@@ -2561,7 +2553,7 @@ async function endPart(part, body) {
2561
2553
  }
2562
2554
  addField(body, part.name, {
2563
2555
  name: part.filename,
2564
- path: opened.file.path,
2556
+ path: file2.path,
2565
2557
  type: opened.type,
2566
2558
  size: part.size
2567
2559
  });
@@ -2581,7 +2573,7 @@ function getBoundary(header) {
2581
2573
  return null;
2582
2574
  }
2583
2575
  var BREAK = Buffer.from("\r\n\r\n");
2584
- async function parseMultipart(stream, boundary, bucket2, limits, max = INF) {
2576
+ async function parseMultipart(stream, boundary, bucket2, limits, max = INF, signal) {
2585
2577
  const budget = { used: 0, max: INF, files: 0 };
2586
2578
  const delim = Buffer.from(`\r
2587
2579
  --${boundary}`);
@@ -2623,7 +2615,8 @@ async function parseMultipart(stream, boundary, bucket2, limits, max = INF) {
2623
2615
  buf.subarray(0, i).toString("utf-8"),
2624
2616
  bucket2,
2625
2617
  limits,
2626
- budget
2618
+ budget,
2619
+ signal
2627
2620
  );
2628
2621
  buf = buf.subarray(i + BREAK.length);
2629
2622
  state = "body";
@@ -2687,12 +2680,16 @@ function parseUrlEncoded(text) {
2687
2680
  }
2688
2681
  return out;
2689
2682
  }
2690
- async function streamRawToBucket(stream, type2, bucket2, limits) {
2691
- const part = makeFilePart("body", "upload", type2, bucket2, limits, {
2692
- used: 0,
2693
- max: INF,
2694
- files: 0
2695
- });
2683
+ async function streamRawToBucket(stream, type2, bucket2, limits, signal) {
2684
+ const part = makeFilePart(
2685
+ "body",
2686
+ "upload",
2687
+ type2,
2688
+ bucket2,
2689
+ limits,
2690
+ { used: 0, max: INF, files: 0 },
2691
+ signal
2692
+ );
2696
2693
  for await (const chunk of asIterable(stream)) {
2697
2694
  await feedPart(part, Buffer.from(chunk));
2698
2695
  }
@@ -2700,7 +2697,7 @@ async function streamRawToBucket(stream, type2, bucket2, limits) {
2700
2697
  await endPart(part, body);
2701
2698
  return part.size ? body.body : void 0;
2702
2699
  }
2703
- async function parseBody(input, contentType, dest, max = INF, length) {
2700
+ async function parseBody(input, contentType, dest, max = INF, length, signal) {
2704
2701
  const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
2705
2702
  let bucket2;
2706
2703
  let limits = {};
@@ -2714,7 +2711,14 @@ async function parseBody(input, contentType, dest, max = INF, length) {
2714
2711
  if (type2 && /multipart\/form-data/i.test(type2)) {
2715
2712
  const boundary = getBoundary(type2);
2716
2713
  if (!boundary) throw errors_default.BODY_INVALID_MULTIPART();
2717
- return parseMultipart(toStream(input), boundary, bucket2, limits, max);
2714
+ return parseMultipart(
2715
+ toStream(input),
2716
+ boundary,
2717
+ bucket2,
2718
+ limits,
2719
+ max,
2720
+ signal
2721
+ );
2718
2722
  }
2719
2723
  if (!type2 || /^text\//i.test(type2)) {
2720
2724
  const buf = await toBuffer(input, max);
@@ -2742,7 +2746,7 @@ async function parseBody(input, contentType, dest, max = INF, length) {
2742
2746
  limit: String(maxFileSize)
2743
2747
  });
2744
2748
  }
2745
- return streamRawToBucket(toStream(input), type2, bucket2, limits);
2749
+ return streamRawToBucket(toStream(input), type2, bucket2, limits, signal);
2746
2750
  }
2747
2751
 
2748
2752
  // src/body/body.ts
@@ -2784,7 +2788,8 @@ async function resolveBody(ctx, mode = "parse", max = resolveMax(void 0)) {
2784
2788
  ctx.headers["content-type"],
2785
2789
  ctx.options.uploads,
2786
2790
  max,
2787
- Number.isFinite(declared) ? declared : void 0
2791
+ Number.isFinite(declared) ? declared : void 0,
2792
+ ctx.signal
2788
2793
  );
2789
2794
  if (size && !ctx.headers["content-length"]) {
2790
2795
  ctx.headers["content-length"] = String(size);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.49.1",
3
+ "version": "0.49.2",
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",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "antarctic": "^0.6.0",
69
- "bucket": "^0.7.1"
69
+ "bucket": "^0.10.1"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/bun": "^1.3.0",