@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.
- package/index.d.ts +12 -6
- package/index.js +50 -45
- 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<
|
|
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<
|
|
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
|
-
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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(
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
69
|
+
"bucket": "^0.10.1"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/bun": "^1.3.0",
|