@server/next 0.49.1 → 0.49.3
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 +13 -6
- package/index.js +74 -46
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -7,22 +7,29 @@ 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>;
|
|
26
|
+
delete?(opts?: ReadOptions): Promise<unknown>;
|
|
23
27
|
};
|
|
24
28
|
type Bucket = {
|
|
25
29
|
file(name: string): BucketFile;
|
|
30
|
+
create?(content: string | Buffer | ReadableStream, options?: {
|
|
31
|
+
type?: string;
|
|
32
|
+
} & ReadOptions): Promise<BucketFile>;
|
|
26
33
|
folder?(prefix: string): Bucket;
|
|
27
34
|
};
|
|
28
35
|
|
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
|
}
|
|
@@ -2396,6 +2395,18 @@ async function socketUser(app, headers2, cookies2) {
|
|
|
2396
2395
|
}
|
|
2397
2396
|
|
|
2398
2397
|
// src/body/bodyParts.ts
|
|
2398
|
+
var extByMime = {};
|
|
2399
|
+
for (const ext in mimes_default) extByMime[mimes_default[ext].split(";")[0].trim()] = ext;
|
|
2400
|
+
function keyFor(type2) {
|
|
2401
|
+
const ext = extByMime[type2.split(";")[0].trim()];
|
|
2402
|
+
return `${createId()}${ext ? `.${ext}` : ""}`;
|
|
2403
|
+
}
|
|
2404
|
+
async function discard(file2) {
|
|
2405
|
+
try {
|
|
2406
|
+
await (file2.remove ? file2.remove() : file2.delete?.());
|
|
2407
|
+
} catch {
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2399
2410
|
var asIterable = (s) => s;
|
|
2400
2411
|
function getMatching(string, regex) {
|
|
2401
2412
|
const matches2 = string.match(regex);
|
|
@@ -2409,8 +2420,6 @@ function isProbablyText(buffer) {
|
|
|
2409
2420
|
}
|
|
2410
2421
|
return true;
|
|
2411
2422
|
}
|
|
2412
|
-
var extByMime = {};
|
|
2413
|
-
for (const ext in mimes_default) extByMime[mimes_default[ext]] = ext;
|
|
2414
2423
|
function addField(body, name, value) {
|
|
2415
2424
|
if (body[name] === void 0) {
|
|
2416
2425
|
body[name] = value;
|
|
@@ -2419,7 +2428,7 @@ function addField(body, name, value) {
|
|
|
2419
2428
|
if (!Array.isArray(body[name])) body[name] = [body[name]];
|
|
2420
2429
|
body[name].push(value);
|
|
2421
2430
|
}
|
|
2422
|
-
function makeFilePart(name, filename, declared, bucket2, limits, budget) {
|
|
2431
|
+
function makeFilePart(name, filename, declared, bucket2, limits, budget, signal) {
|
|
2423
2432
|
return {
|
|
2424
2433
|
kind: "file",
|
|
2425
2434
|
name,
|
|
@@ -2428,13 +2437,14 @@ function makeFilePart(name, filename, declared, bucket2, limits, budget) {
|
|
|
2428
2437
|
bucket: bucket2,
|
|
2429
2438
|
limits,
|
|
2430
2439
|
budget,
|
|
2440
|
+
signal,
|
|
2431
2441
|
head: [],
|
|
2432
2442
|
headSize: 0,
|
|
2433
2443
|
opened: null,
|
|
2434
2444
|
size: 0
|
|
2435
2445
|
};
|
|
2436
2446
|
}
|
|
2437
|
-
function startPart(headerStr, bucket2, limits, budget) {
|
|
2447
|
+
function startPart(headerStr, bucket2, limits, budget, signal) {
|
|
2438
2448
|
const name = getMatching(headerStr, /name="(.+?)"/).trim().replace(/\[\]$/, "");
|
|
2439
2449
|
if (!name) return { kind: "skip" };
|
|
2440
2450
|
const filename = getMatching(headerStr, /filename="(.+?)"/).trim();
|
|
@@ -2447,7 +2457,7 @@ function startPart(headerStr, bucket2, limits, budget) {
|
|
|
2447
2457
|
if (maxFiles != null && budget.files > maxFiles) {
|
|
2448
2458
|
throw errors_default.UPLOAD_TOO_MANY_FILES({ limit: String(maxFiles) });
|
|
2449
2459
|
}
|
|
2450
|
-
return makeFilePart(name, filename, type2, bucket2, limits, budget);
|
|
2460
|
+
return makeFilePart(name, filename, type2, bucket2, limits, budget, signal);
|
|
2451
2461
|
}
|
|
2452
2462
|
async function abortFile(part, error) {
|
|
2453
2463
|
if (part.opened) {
|
|
@@ -2457,8 +2467,7 @@ async function abortFile(part, error) {
|
|
|
2457
2467
|
});
|
|
2458
2468
|
} catch {
|
|
2459
2469
|
}
|
|
2460
|
-
|
|
2461
|
-
});
|
|
2470
|
+
if (part.opened.handle) await discard(part.opened.handle);
|
|
2462
2471
|
}
|
|
2463
2472
|
throw error;
|
|
2464
2473
|
}
|
|
@@ -2491,20 +2500,27 @@ function openFile(part) {
|
|
|
2491
2500
|
const sniffed = sniff(head);
|
|
2492
2501
|
const type2 = resolveType(sniffed, part.declared);
|
|
2493
2502
|
validateFile(part.filename, type2, part.limits, sniffed);
|
|
2494
|
-
const ext = sniffed ? extByMime[type2] : void 0;
|
|
2495
|
-
const id = `${createId()}${ext ? `.${ext}` : ""}`;
|
|
2496
2503
|
let controller;
|
|
2497
2504
|
const readable = new ReadableStream({
|
|
2498
2505
|
start(c) {
|
|
2499
2506
|
controller = c;
|
|
2500
2507
|
}
|
|
2501
2508
|
});
|
|
2502
|
-
const
|
|
2509
|
+
const write = { type: type2, signal: part.signal };
|
|
2510
|
+
if (part.bucket.create) {
|
|
2511
|
+
part.opened = {
|
|
2512
|
+
type: type2,
|
|
2513
|
+
controller,
|
|
2514
|
+
write: part.bucket.create(readable, write)
|
|
2515
|
+
};
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
const handle = part.bucket.file(keyFor(type2));
|
|
2503
2519
|
part.opened = {
|
|
2504
2520
|
type: type2,
|
|
2505
|
-
file: file2,
|
|
2506
2521
|
controller,
|
|
2507
|
-
|
|
2522
|
+
handle,
|
|
2523
|
+
write: handle.write(readable, write).then(() => handle)
|
|
2508
2524
|
};
|
|
2509
2525
|
}
|
|
2510
2526
|
async function feedPart(part, data) {
|
|
@@ -2548,11 +2564,10 @@ async function endPart(part, body) {
|
|
|
2548
2564
|
}
|
|
2549
2565
|
const opened = part.opened;
|
|
2550
2566
|
opened.controller.close();
|
|
2551
|
-
await opened.write;
|
|
2567
|
+
const file2 = await opened.write;
|
|
2552
2568
|
const { minSize } = part.limits;
|
|
2553
2569
|
if (minSize != null && part.size < parseBytes(minSize)) {
|
|
2554
|
-
await
|
|
2555
|
-
});
|
|
2570
|
+
await discard(file2);
|
|
2556
2571
|
throw errors_default.UPLOAD_TOO_SMALL({
|
|
2557
2572
|
name: part.filename,
|
|
2558
2573
|
size: String(part.size),
|
|
@@ -2561,7 +2576,7 @@ async function endPart(part, body) {
|
|
|
2561
2576
|
}
|
|
2562
2577
|
addField(body, part.name, {
|
|
2563
2578
|
name: part.filename,
|
|
2564
|
-
path:
|
|
2579
|
+
path: file2.path,
|
|
2565
2580
|
type: opened.type,
|
|
2566
2581
|
size: part.size
|
|
2567
2582
|
});
|
|
@@ -2581,7 +2596,7 @@ function getBoundary(header) {
|
|
|
2581
2596
|
return null;
|
|
2582
2597
|
}
|
|
2583
2598
|
var BREAK = Buffer.from("\r\n\r\n");
|
|
2584
|
-
async function parseMultipart(stream, boundary, bucket2, limits, max = INF) {
|
|
2599
|
+
async function parseMultipart(stream, boundary, bucket2, limits, max = INF, signal) {
|
|
2585
2600
|
const budget = { used: 0, max: INF, files: 0 };
|
|
2586
2601
|
const delim = Buffer.from(`\r
|
|
2587
2602
|
--${boundary}`);
|
|
@@ -2623,7 +2638,8 @@ async function parseMultipart(stream, boundary, bucket2, limits, max = INF) {
|
|
|
2623
2638
|
buf.subarray(0, i).toString("utf-8"),
|
|
2624
2639
|
bucket2,
|
|
2625
2640
|
limits,
|
|
2626
|
-
budget
|
|
2641
|
+
budget,
|
|
2642
|
+
signal
|
|
2627
2643
|
);
|
|
2628
2644
|
buf = buf.subarray(i + BREAK.length);
|
|
2629
2645
|
state = "body";
|
|
@@ -2687,12 +2703,16 @@ function parseUrlEncoded(text) {
|
|
|
2687
2703
|
}
|
|
2688
2704
|
return out;
|
|
2689
2705
|
}
|
|
2690
|
-
async function streamRawToBucket(stream, type2, bucket2, limits) {
|
|
2691
|
-
const part = makeFilePart(
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2706
|
+
async function streamRawToBucket(stream, type2, bucket2, limits, signal) {
|
|
2707
|
+
const part = makeFilePart(
|
|
2708
|
+
"body",
|
|
2709
|
+
"upload",
|
|
2710
|
+
type2,
|
|
2711
|
+
bucket2,
|
|
2712
|
+
limits,
|
|
2713
|
+
{ used: 0, max: INF, files: 0 },
|
|
2714
|
+
signal
|
|
2715
|
+
);
|
|
2696
2716
|
for await (const chunk of asIterable(stream)) {
|
|
2697
2717
|
await feedPart(part, Buffer.from(chunk));
|
|
2698
2718
|
}
|
|
@@ -2700,7 +2720,7 @@ async function streamRawToBucket(stream, type2, bucket2, limits) {
|
|
|
2700
2720
|
await endPart(part, body);
|
|
2701
2721
|
return part.size ? body.body : void 0;
|
|
2702
2722
|
}
|
|
2703
|
-
async function parseBody(input, contentType, dest, max = INF, length) {
|
|
2723
|
+
async function parseBody(input, contentType, dest, max = INF, length, signal) {
|
|
2704
2724
|
const type2 = Array.isArray(contentType) ? contentType[0] : contentType;
|
|
2705
2725
|
let bucket2;
|
|
2706
2726
|
let limits = {};
|
|
@@ -2714,7 +2734,14 @@ async function parseBody(input, contentType, dest, max = INF, length) {
|
|
|
2714
2734
|
if (type2 && /multipart\/form-data/i.test(type2)) {
|
|
2715
2735
|
const boundary = getBoundary(type2);
|
|
2716
2736
|
if (!boundary) throw errors_default.BODY_INVALID_MULTIPART();
|
|
2717
|
-
return parseMultipart(
|
|
2737
|
+
return parseMultipart(
|
|
2738
|
+
toStream(input),
|
|
2739
|
+
boundary,
|
|
2740
|
+
bucket2,
|
|
2741
|
+
limits,
|
|
2742
|
+
max,
|
|
2743
|
+
signal
|
|
2744
|
+
);
|
|
2718
2745
|
}
|
|
2719
2746
|
if (!type2 || /^text\//i.test(type2)) {
|
|
2720
2747
|
const buf = await toBuffer(input, max);
|
|
@@ -2742,7 +2769,7 @@ async function parseBody(input, contentType, dest, max = INF, length) {
|
|
|
2742
2769
|
limit: String(maxFileSize)
|
|
2743
2770
|
});
|
|
2744
2771
|
}
|
|
2745
|
-
return streamRawToBucket(toStream(input), type2, bucket2, limits);
|
|
2772
|
+
return streamRawToBucket(toStream(input), type2, bucket2, limits, signal);
|
|
2746
2773
|
}
|
|
2747
2774
|
|
|
2748
2775
|
// src/body/body.ts
|
|
@@ -2784,7 +2811,8 @@ async function resolveBody(ctx, mode = "parse", max = resolveMax(void 0)) {
|
|
|
2784
2811
|
ctx.headers["content-type"],
|
|
2785
2812
|
ctx.options.uploads,
|
|
2786
2813
|
max,
|
|
2787
|
-
Number.isFinite(declared) ? declared : void 0
|
|
2814
|
+
Number.isFinite(declared) ? declared : void 0,
|
|
2815
|
+
ctx.signal
|
|
2788
2816
|
);
|
|
2789
2817
|
if (size && !ctx.headers["content-length"]) {
|
|
2790
2818
|
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.3",
|
|
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",
|