@nifrajs/core 5.3.0 → 5.5.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.
- package/CHANGELOG.md +65 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/route-lanes.d.ts.map +1 -1
- package/dist/internal/route-lanes.js +2 -1
- package/dist/internal/route-lanes.js.map +1 -1
- package/dist/internal/route-program.d.ts +5 -0
- package/dist/internal/route-program.d.ts.map +1 -1
- package/dist/internal/route-program.js +36 -15
- package/dist/internal/route-program.js.map +1 -1
- package/dist/range.d.ts.map +1 -1
- package/dist/range.js +62 -2
- package/dist/range.js.map +1 -1
- package/dist/router/router.d.ts +3 -2
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +22 -6
- package/dist/router/router.js.map +1 -1
- package/dist/security/baseline.d.ts.map +1 -1
- package/dist/security/baseline.js +4 -3
- package/dist/security/baseline.js.map +1 -1
- package/dist/server/body-lane.d.ts.map +1 -1
- package/dist/server/body-lane.js +4 -3
- package/dist/server/body-lane.js.map +1 -1
- package/dist/server/body.d.ts +2 -0
- package/dist/server/body.d.ts.map +1 -1
- package/dist/server/body.js +5 -0
- package/dist/server/body.js.map +1 -1
- package/dist/server/cookies.d.ts.map +1 -1
- package/dist/server/cookies.js +9 -2
- package/dist/server/cookies.js.map +1 -1
- package/dist/server/core-copies.js +1 -1
- package/dist/server/idempotency-lane.d.ts +11 -12
- package/dist/server/idempotency-lane.d.ts.map +1 -1
- package/dist/server/idempotency-lane.js +301 -151
- package/dist/server/idempotency-lane.js.map +1 -1
- package/dist/server/methods.d.ts +3 -2
- package/dist/server/methods.d.ts.map +1 -1
- package/dist/server/methods.js.map +1 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/multipart.js +42 -13
- package/dist/server/multipart.js.map +1 -1
- package/dist/server/node-outcome.d.ts.map +1 -1
- package/dist/server/node-outcome.js +2 -2
- package/dist/server/node-outcome.js.map +1 -1
- package/dist/server/query.d.ts +1 -1
- package/dist/server/query.d.ts.map +1 -1
- package/dist/server/query.js +13 -5
- package/dist/server/query.js.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -3
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/respond.d.ts +17 -0
- package/dist/server/respond.d.ts.map +1 -1
- package/dist/server/respond.js +81 -3
- package/dist/server/respond.js.map +1 -1
- package/dist/server/runtime-core.d.ts +15 -0
- package/dist/server/runtime-core.d.ts.map +1 -1
- package/dist/server/runtime-core.js +41 -1
- package/dist/server/runtime-core.js.map +1 -1
- package/dist/server/server.d.ts +14 -5
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +187 -62
- package/dist/server/server.js.map +1 -1
- package/dist/server/transport-lane.d.ts +2 -2
- package/dist/server/transport-lane.d.ts.map +1 -1
- package/dist/server/transport-lane.js +20 -20
- package/dist/server/transport-lane.js.map +1 -1
- package/dist/static-file.d.ts +127 -0
- package/dist/static-file.d.ts.map +1 -0
- package/dist/static-file.js +269 -0
- package/dist/static-file.js.map +1 -0
- package/package.json +6 -1
- package/src/index.ts +1 -1
- package/src/internal/route-lanes.ts +18 -16
- package/src/internal/route-program.ts +76 -12
- package/src/range.ts +61 -2
- package/src/router/router.ts +26 -5
- package/src/security/baseline.ts +4 -3
- package/src/server/body-lane.ts +4 -4
- package/src/server/body.ts +6 -0
- package/src/server/cookies.ts +7 -2
- package/src/server/core-copies.ts +1 -1
- package/src/server/idempotency-lane.ts +343 -209
- package/src/server/methods.ts +3 -2
- package/src/server/multipart.ts +62 -14
- package/src/server/node-outcome.ts +2 -1
- package/src/server/query.ts +12 -5
- package/src/server/request-context.ts +6 -3
- package/src/server/respond.ts +108 -6
- package/src/server/runtime-core.ts +54 -1
- package/src/server/server.ts +257 -102
- package/src/server/transport-lane.ts +28 -22
- package/src/static-file.ts +362 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Opt-in versioned transport integration. Kept outside the server kernel's bare dependency graph. */
|
|
2
|
-
import type
|
|
2
|
+
import { type TransportCodec, type TransportCodecRegistry } from "../transport-codec.js";
|
|
3
3
|
import { type ProtoPoisoning } from "./proto-guard.js";
|
|
4
4
|
import type { IdentityPlugin } from "./server.js";
|
|
5
5
|
interface TransportBodySource {
|
|
@@ -12,7 +12,7 @@ export type TransportDecodeResult = {
|
|
|
12
12
|
} | {
|
|
13
13
|
readonly matched: true;
|
|
14
14
|
readonly value: unknown;
|
|
15
|
-
readonly
|
|
15
|
+
readonly bytes: Uint8Array;
|
|
16
16
|
} | {
|
|
17
17
|
readonly matched: true;
|
|
18
18
|
readonly response: Response;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport-lane.d.ts","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"transport-lane.d.ts","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEzE,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,aAAa,CAAA;AAE5D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAA;IAChD,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;CACpC;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAA;CAAE,GAC3B;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAC/E;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3D,MAAM,WAAW,gBAAgB;IAC/B,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,CAAA;IACpD,aAAa,CACX,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAChC;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,sBAAsB,EAChC,OAAO,GAAE,sBAA2B,GACnC,cAAc,CA+GhB"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Opt-in versioned transport integration. Kept outside the server kernel's bare dependency graph. */
|
|
2
|
+
import { encodeTransportResponse, } from "../transport-codec.js";
|
|
1
3
|
import { readBoundedBytes } from "./body.js";
|
|
2
4
|
import { jsonError } from "./http.js";
|
|
3
5
|
import { guardDecodedValue } from "./proto-guard.js";
|
|
@@ -42,7 +44,7 @@ export function transportCodecs(registry, options = {}) {
|
|
|
42
44
|
return {
|
|
43
45
|
matched: true,
|
|
44
46
|
value: guardDecodedValue(codec.decode(text), protoPoisoning, acceptRegExp),
|
|
45
|
-
|
|
47
|
+
bytes: read.bytes,
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
catch {
|
|
@@ -73,36 +75,34 @@ export function transportCodecs(registry, options = {}) {
|
|
|
73
75
|
return undefined;
|
|
74
76
|
if ("response" in decoded)
|
|
75
77
|
return decoded.response;
|
|
76
|
-
//
|
|
77
|
-
// the
|
|
78
|
-
// the
|
|
79
|
-
|
|
80
|
-
request.headers
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
headers["content-type"] = "application/json";
|
|
84
|
-
headers["content-length"] = "2";
|
|
85
|
-
delete headers["transfer-encoding"];
|
|
78
|
+
// The decoded value rides the pre-decoded stash, which the body lane takes verbatim (this lane
|
|
79
|
+
// owns the cap and poisoning policy), so codec machinery stays out of the kernel. The request
|
|
80
|
+
// keeps the bytes and media type it was sent with: a route without a body schema, or a
|
|
81
|
+
// signature check, reads the payload itself, not a stand-in.
|
|
82
|
+
const headers = new Headers(request.headers);
|
|
83
|
+
headers.set("content-length", String(decoded.bytes.byteLength));
|
|
84
|
+
headers.delete("transfer-encoding");
|
|
86
85
|
const normalized = new Request(request.url, {
|
|
87
86
|
method: request.method,
|
|
88
87
|
headers,
|
|
89
|
-
|
|
88
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: every reader behind readBoundedBytes yields an unshared buffer, which a Request body accepts.
|
|
89
|
+
body: decoded.bytes,
|
|
90
90
|
signal: request.signal,
|
|
91
91
|
});
|
|
92
|
-
const stash = {
|
|
92
|
+
const stash = {
|
|
93
|
+
value: decoded.value,
|
|
94
|
+
byteLength: decoded.bytes.byteLength,
|
|
95
|
+
encoded: decoded.bytes,
|
|
96
|
+
contentType,
|
|
97
|
+
};
|
|
93
98
|
Object.defineProperty(normalized, PRE_DECODED_BODY, { value: stash });
|
|
94
99
|
return normalized;
|
|
95
100
|
});
|
|
96
101
|
app.afterHandle((result, context) => {
|
|
97
102
|
if (result === undefined || result instanceof Response || isResponseResult(result))
|
|
98
103
|
return result;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
headers.set("content-type", codec.mediaType);
|
|
102
|
-
return new Response(codec.encode(result), {
|
|
103
|
-
status: context.set.status ?? 200,
|
|
104
|
-
headers,
|
|
105
|
-
});
|
|
104
|
+
// The representation depends on `Accept`, so the response says so to every shared cache.
|
|
105
|
+
return encodeTransportResponse(result, runtime.responseCodec(context.req.headers.get("accept")), { status: context.set.status ?? 200, headers: context.set.headers });
|
|
106
106
|
});
|
|
107
107
|
return app;
|
|
108
108
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport-lane.js","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport-lane.js","sourceRoot":"","sources":["../../src/server/transport-lane.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a file served from disk is typed as, which files are never served, and when a client's copy of
|
|
3
|
+
* one is still current - the shared owner for every static-file surface.
|
|
4
|
+
*
|
|
5
|
+
* `@nifrajs/web`'s `servePublicDir` (dev, the Bun entry), the Deno entry's handler and
|
|
6
|
+
* `@nifrajs/node`'s `static` reader (the Node entry) answer the same deployed files. Copies that drift
|
|
7
|
+
* serve a `.pdf` as a download on one runtime and inline on the other, a `.well-known` file on one and
|
|
8
|
+
* a 404 on the other, or a resumed download spliced from two versions of a file on one, so all three
|
|
9
|
+
* read this module. The Node adapter runs a generated copy (scripts/gen-node-outcome.ts), so this
|
|
10
|
+
* module imports nothing.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Media types by extension. A table rather than the runtime's own file typing, which Node and Deno
|
|
14
|
+
* lack. Unknown extensions, and extensionless files such as ACME tokens, are
|
|
15
|
+
* `application/octet-stream`, which together with `nosniff` keeps a browser from guessing a more
|
|
16
|
+
* dangerous type.
|
|
17
|
+
*/
|
|
18
|
+
export declare const CONTENT_TYPES: Readonly<Record<string, string>>;
|
|
19
|
+
/** The media type for a file path or name by its extension, case-insensitively. */
|
|
20
|
+
export declare function contentTypeOf(path: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Whether a relative path crosses a dot-segment other than `.well-known` (RFC 8615, public by
|
|
23
|
+
* definition). Serving, the build's `public/` copy and the parity listing all apply it, so an editor
|
|
24
|
+
* or VCS artifact (`.env`, `.git/`, `.DS_Store`) is neither served nor shipped. Both separators split:
|
|
25
|
+
* a request path uses `/`, a relative file path the OS separator.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isHiddenPath(rel: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The stat fields that tell one version of a file from another on one host: node:fs `Stats` as it is,
|
|
30
|
+
* or Deno's `FileInfo` with its dates as epoch milliseconds and a field it lacks as `NaN`.
|
|
31
|
+
*/
|
|
32
|
+
export interface FileVersion {
|
|
33
|
+
readonly dev: number;
|
|
34
|
+
readonly ino: number;
|
|
35
|
+
readonly size: number;
|
|
36
|
+
readonly mtimeMs: number;
|
|
37
|
+
readonly ctimeMs: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The time a file can be dated by in Last-Modified, or `undefined`: its mtime, only while that is
|
|
41
|
+
* the time of its own last change. A copy or build that keeps or normalizes timestamps - `cp -p`,
|
|
42
|
+
* `rsync -a`, tar, a container image layer, SOURCE_DATE_EPOCH, the Nix store's 1 s - gives a
|
|
43
|
+
* same-size replacement the old file's date, so its files are left undated and their content tag
|
|
44
|
+
* validates them alone.
|
|
45
|
+
*/
|
|
46
|
+
export declare function lastModifiedOf(version: FileVersion): number | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* {@link lastModifiedOf} for a body streamed after its stat, where a write can still reach the bytes
|
|
49
|
+
* sent. Dated only once its change is older than the racy window, so such a write falls in a later
|
|
50
|
+
* HTTP-date second and the next `If-Modified-Since` fetches it. A weak validator: never pass it to
|
|
51
|
+
* {@link ifRangeAllowsPartial}, which would resume a range across two versions.
|
|
52
|
+
*/
|
|
53
|
+
export declare function streamedLastModifiedOf(version: FileVersion, now?: number): number | undefined;
|
|
54
|
+
/**
|
|
55
|
+
* Whether the client's copy is current, so the answer is a bodyless 304. `If-None-Match` (the header,
|
|
56
|
+
* or `null`) decides whenever it is sent (RFC 9110 section 13.2.2), by weak comparison with `etag`, and
|
|
57
|
+
* `*` matches any file. Otherwise an `If-Modified-Since` at or after `lastModified` (epoch
|
|
58
|
+
* milliseconds), compared at the whole seconds an HTTP date carries; an unparseable date, or a file
|
|
59
|
+
* with no date, is never current.
|
|
60
|
+
*/
|
|
61
|
+
export declare function isNotModified(ifNoneMatch: string | null, ifModifiedSince: string | null, etag: string | undefined, lastModified: number | undefined): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether `If-Range` (the header, or `null`) lets a range request be answered partially; otherwise the
|
|
64
|
+
* whole file is the conformant answer, not a fallback. An entity tag must equal the file's strong
|
|
65
|
+
* `etag` (RFC 9110 section 13.1.5). A date must equal `lastModified` to the second, and counts only
|
|
66
|
+
* for a file with no strong tag: a client holding the tag sends it, and a date cannot tell apart two
|
|
67
|
+
* versions dated in the same second - here or on another instance - so resuming by one could splice
|
|
68
|
+
* them.
|
|
69
|
+
*/
|
|
70
|
+
export declare function ifRangeAllowsPartial(ifRange: string | null, etag: string | undefined, lastModified: number | undefined): boolean;
|
|
71
|
+
/** What node:crypto's `createHash` returns; Bun, Node and Deno all provide it. */
|
|
72
|
+
export interface ContentHash {
|
|
73
|
+
update(bytes: Uint8Array): unknown;
|
|
74
|
+
digest(encoding: "base64url"): string;
|
|
75
|
+
}
|
|
76
|
+
/** Reads into `bytes` from byte `position` of a file, resolving to the count read (0 at its end). */
|
|
77
|
+
export type ReadAt = (bytes: Uint8Array, position: number) => Promise<number>;
|
|
78
|
+
/**
|
|
79
|
+
* Entity tags from file bytes, consistent across instances and runtimes. Files beyond the content
|
|
80
|
+
* hash limit instead use a per-instance identity tag. Callers must serve the same immutable bytes
|
|
81
|
+
* they hash; static HTTP readers use bounded snapshots for that guarantee. A tag is remembered per
|
|
82
|
+
* real path for the version it was computed from.
|
|
83
|
+
*/
|
|
84
|
+
export interface ContentTags {
|
|
85
|
+
/** The tag remembered for this version of the file at `path`, if any. */
|
|
86
|
+
cached(path: string, version: FileVersion): string | undefined;
|
|
87
|
+
/** The tag of `bytes`, read whole from this version of the file at `path`. */
|
|
88
|
+
ofBytes(path: string, version: FileVersion, bytes: Uint8Array): string;
|
|
89
|
+
/**
|
|
90
|
+
* The tag of `bytes`, the whole file at `path` read after a stat that gave `version`. A small
|
|
91
|
+
* snapshot is hashed; a larger one takes the tag remembered for `version` when `restat`, called
|
|
92
|
+
* after the read, still reports that version, since no write reached the file in between.
|
|
93
|
+
*/
|
|
94
|
+
ofSnapshot(path: string, version: FileVersion, bytes: Uint8Array, restat: () => Promise<FileVersion>): Promise<string>;
|
|
95
|
+
/**
|
|
96
|
+
* The tag of this version of the file at `path`, read whole through `read`; requests for a version
|
|
97
|
+
* already being read wait for that read. A file over {@link CONTENT_TAG_MAX} is not read: its tag
|
|
98
|
+
* names its identity within this tagger instance instead, or is `undefined` without a stable identity.
|
|
99
|
+
* Reading a mutable inode and later streaming it does not preserve this tag's byte identity.
|
|
100
|
+
*/
|
|
101
|
+
ofFile(path: string, version: FileVersion, read: ReadAt): Promise<string | undefined>;
|
|
102
|
+
}
|
|
103
|
+
export interface ContentTagOptions {
|
|
104
|
+
/** Most tags remembered at once (default 8192); past it the earliest is forgotten. */
|
|
105
|
+
readonly limit?: number;
|
|
106
|
+
/** The clock compared with a file's ctime (default `Date.now`). */
|
|
107
|
+
readonly now?: () => number;
|
|
108
|
+
/**
|
|
109
|
+
* Largest snapshot {@link ContentTags.ofSnapshot} hashes on every request (default 4 KiB). A larger
|
|
110
|
+
* one takes the tag remembered for its version once a restat shows that version. Raise it where a
|
|
111
|
+
* stat costs more than hashing, as on Deno.
|
|
112
|
+
*/
|
|
113
|
+
readonly rehashMax?: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Largest file tagged by its content. Its first request waits for one read of the whole file, ~0.1 s
|
|
117
|
+
* per 256 MiB with hardware SHA-256 and several times that on a slow disk or CPU, so a larger file -
|
|
118
|
+
* a long video that is only ever fetched by ranges, say - is tagged by its device, inode, size and
|
|
119
|
+
* times plus a random instance salt. Identity tags require immutable file versions and intentionally
|
|
120
|
+
* differ between tagger instances so local stat identities cannot collide across hosts.
|
|
121
|
+
*/
|
|
122
|
+
export declare const CONTENT_TAG_MAX: number;
|
|
123
|
+
/** Static HTTP readers tag only a whole, bounded snapshot; streamed inodes can change after hashing. */
|
|
124
|
+
export declare const CONTENT_SNAPSHOT_MAX: number;
|
|
125
|
+
/** Tags from `createHash` - node:crypto's, which every runtime nifra serves files on provides. */
|
|
126
|
+
export declare function contentTags(createHash: (algorithm: "sha256") => ContentHash, options?: ContentTagOptions): ContentTags;
|
|
127
|
+
//# sourceMappingURL=static-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-file.d.ts","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a file served from disk is typed as, which files are never served, and when a client's copy of
|
|
3
|
+
* one is still current - the shared owner for every static-file surface.
|
|
4
|
+
*
|
|
5
|
+
* `@nifrajs/web`'s `servePublicDir` (dev, the Bun entry), the Deno entry's handler and
|
|
6
|
+
* `@nifrajs/node`'s `static` reader (the Node entry) answer the same deployed files. Copies that drift
|
|
7
|
+
* serve a `.pdf` as a download on one runtime and inline on the other, a `.well-known` file on one and
|
|
8
|
+
* a 404 on the other, or a resumed download spliced from two versions of a file on one, so all three
|
|
9
|
+
* read this module. The Node adapter runs a generated copy (scripts/gen-node-outcome.ts), so this
|
|
10
|
+
* module imports nothing.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Media types by extension. A table rather than the runtime's own file typing, which Node and Deno
|
|
14
|
+
* lack. Unknown extensions, and extensionless files such as ACME tokens, are
|
|
15
|
+
* `application/octet-stream`, which together with `nosniff` keeps a browser from guessing a more
|
|
16
|
+
* dangerous type.
|
|
17
|
+
*/
|
|
18
|
+
export const CONTENT_TYPES = Object.freeze({
|
|
19
|
+
".html": "text/html; charset=utf-8",
|
|
20
|
+
".htm": "text/html; charset=utf-8",
|
|
21
|
+
".css": "text/css; charset=utf-8",
|
|
22
|
+
".js": "text/javascript; charset=utf-8",
|
|
23
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
24
|
+
".cjs": "text/javascript; charset=utf-8",
|
|
25
|
+
".json": "application/json; charset=utf-8",
|
|
26
|
+
".map": "application/json; charset=utf-8",
|
|
27
|
+
".webmanifest": "application/manifest+json",
|
|
28
|
+
".txt": "text/plain; charset=utf-8",
|
|
29
|
+
".md": "text/markdown; charset=utf-8",
|
|
30
|
+
".csv": "text/csv; charset=utf-8",
|
|
31
|
+
".xml": "application/xml",
|
|
32
|
+
".rss": "application/rss+xml",
|
|
33
|
+
".atom": "application/atom+xml",
|
|
34
|
+
".svg": "image/svg+xml",
|
|
35
|
+
".png": "image/png",
|
|
36
|
+
".jpg": "image/jpeg",
|
|
37
|
+
".jpeg": "image/jpeg",
|
|
38
|
+
".gif": "image/gif",
|
|
39
|
+
".webp": "image/webp",
|
|
40
|
+
".avif": "image/avif",
|
|
41
|
+
".ico": "image/x-icon",
|
|
42
|
+
".bmp": "image/bmp",
|
|
43
|
+
".woff": "font/woff",
|
|
44
|
+
".woff2": "font/woff2",
|
|
45
|
+
".ttf": "font/ttf",
|
|
46
|
+
".otf": "font/otf",
|
|
47
|
+
".wasm": "application/wasm",
|
|
48
|
+
".pdf": "application/pdf",
|
|
49
|
+
".zip": "application/zip",
|
|
50
|
+
".mp4": "video/mp4",
|
|
51
|
+
".webm": "video/webm",
|
|
52
|
+
".ogg": "audio/ogg",
|
|
53
|
+
".mp3": "audio/mpeg",
|
|
54
|
+
".wav": "audio/wav",
|
|
55
|
+
".m4a": "audio/mp4",
|
|
56
|
+
".vtt": "text/vtt; charset=utf-8",
|
|
57
|
+
});
|
|
58
|
+
/** The media type for a file path or name by its extension, case-insensitively. */
|
|
59
|
+
export function contentTypeOf(path) {
|
|
60
|
+
const dot = path.lastIndexOf(".");
|
|
61
|
+
// A dot before the last separator belongs to a directory; a leading dot names a dotfile, not a type.
|
|
62
|
+
const name = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1;
|
|
63
|
+
if (dot <= name)
|
|
64
|
+
return "application/octet-stream";
|
|
65
|
+
return CONTENT_TYPES[path.slice(dot).toLowerCase()] ?? "application/octet-stream";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Whether a relative path crosses a dot-segment other than `.well-known` (RFC 8615, public by
|
|
69
|
+
* definition). Serving, the build's `public/` copy and the parity listing all apply it, so an editor
|
|
70
|
+
* or VCS artifact (`.env`, `.git/`, `.DS_Store`) is neither served nor shipped. Both separators split:
|
|
71
|
+
* a request path uses `/`, a relative file path the OS separator.
|
|
72
|
+
*/
|
|
73
|
+
export function isHiddenPath(rel) {
|
|
74
|
+
for (const segment of rel.split(/[\\/]/)) {
|
|
75
|
+
if (segment.charCodeAt(0) === 46 /* '.' */ && segment !== ".well-known")
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// A write sets a file's mtime and ctime together (a chmod or rename right after adds well under a
|
|
81
|
+
// millisecond), and Deno truncates the ctime it reports to the second. No tool can set a ctime, so a
|
|
82
|
+
// larger gap means the mtime came from somewhere else.
|
|
83
|
+
const MODIFIED_LAG_MS = 1000;
|
|
84
|
+
/**
|
|
85
|
+
* The time a file can be dated by in Last-Modified, or `undefined`: its mtime, only while that is
|
|
86
|
+
* the time of its own last change. A copy or build that keeps or normalizes timestamps - `cp -p`,
|
|
87
|
+
* `rsync -a`, tar, a container image layer, SOURCE_DATE_EPOCH, the Nix store's 1 s - gives a
|
|
88
|
+
* same-size replacement the old file's date, so its files are left undated and their content tag
|
|
89
|
+
* validates them alone.
|
|
90
|
+
*/
|
|
91
|
+
export function lastModifiedOf(version) {
|
|
92
|
+
const lag = Math.abs(version.ctimeMs - version.mtimeMs);
|
|
93
|
+
return version.mtimeMs > 0 && lag < MODIFIED_LAG_MS ? version.mtimeMs : undefined;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* {@link lastModifiedOf} for a body streamed after its stat, where a write can still reach the bytes
|
|
97
|
+
* sent. Dated only once its change is older than the racy window, so such a write falls in a later
|
|
98
|
+
* HTTP-date second and the next `If-Modified-Since` fetches it. A weak validator: never pass it to
|
|
99
|
+
* {@link ifRangeAllowsPartial}, which would resume a range across two versions.
|
|
100
|
+
*/
|
|
101
|
+
export function streamedLastModifiedOf(version, now = Date.now()) {
|
|
102
|
+
return now - version.ctimeMs >= RACY_MS ? lastModifiedOf(version) : undefined;
|
|
103
|
+
}
|
|
104
|
+
/** HTTP dates carry whole seconds; comparing raw milliseconds makes every file look stale. */
|
|
105
|
+
function seconds(time) {
|
|
106
|
+
return Math.floor(time / 1000);
|
|
107
|
+
}
|
|
108
|
+
function opaqueTag(tag) {
|
|
109
|
+
return tag.startsWith("W/") ? tag.slice(2) : tag;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether the client's copy is current, so the answer is a bodyless 304. `If-None-Match` (the header,
|
|
113
|
+
* or `null`) decides whenever it is sent (RFC 9110 section 13.2.2), by weak comparison with `etag`, and
|
|
114
|
+
* `*` matches any file. Otherwise an `If-Modified-Since` at or after `lastModified` (epoch
|
|
115
|
+
* milliseconds), compared at the whole seconds an HTTP date carries; an unparseable date, or a file
|
|
116
|
+
* with no date, is never current.
|
|
117
|
+
*/
|
|
118
|
+
export function isNotModified(ifNoneMatch, ifModifiedSince, etag, lastModified) {
|
|
119
|
+
if (ifNoneMatch !== null) {
|
|
120
|
+
const current = etag === undefined ? undefined : opaqueTag(etag);
|
|
121
|
+
for (const raw of ifNoneMatch.split(",")) {
|
|
122
|
+
const candidate = raw.trim();
|
|
123
|
+
if (candidate === "*" || (current !== undefined && opaqueTag(candidate) === current)) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
if (ifModifiedSince === null || lastModified === undefined)
|
|
130
|
+
return false;
|
|
131
|
+
const since = Date.parse(ifModifiedSince);
|
|
132
|
+
return Number.isFinite(since) && seconds(since) >= seconds(lastModified);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Whether `If-Range` (the header, or `null`) lets a range request be answered partially; otherwise the
|
|
136
|
+
* whole file is the conformant answer, not a fallback. An entity tag must equal the file's strong
|
|
137
|
+
* `etag` (RFC 9110 section 13.1.5). A date must equal `lastModified` to the second, and counts only
|
|
138
|
+
* for a file with no strong tag: a client holding the tag sends it, and a date cannot tell apart two
|
|
139
|
+
* versions dated in the same second - here or on another instance - so resuming by one could splice
|
|
140
|
+
* them.
|
|
141
|
+
*/
|
|
142
|
+
export function ifRangeAllowsPartial(ifRange, etag, lastModified) {
|
|
143
|
+
if (ifRange === null)
|
|
144
|
+
return true;
|
|
145
|
+
const item = ifRange.trim();
|
|
146
|
+
const strong = etag !== undefined && !etag.startsWith("W/");
|
|
147
|
+
if (item.startsWith('"') || item.startsWith("W/"))
|
|
148
|
+
return strong && item === etag;
|
|
149
|
+
if (strong || lastModified === undefined)
|
|
150
|
+
return false;
|
|
151
|
+
const date = Date.parse(item);
|
|
152
|
+
return Number.isFinite(date) && seconds(date) === seconds(lastModified);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Largest file tagged by its content. Its first request waits for one read of the whole file, ~0.1 s
|
|
156
|
+
* per 256 MiB with hardware SHA-256 and several times that on a slow disk or CPU, so a larger file -
|
|
157
|
+
* a long video that is only ever fetched by ranges, say - is tagged by its device, inode, size and
|
|
158
|
+
* times plus a random instance salt. Identity tags require immutable file versions and intentionally
|
|
159
|
+
* differ between tagger instances so local stat identities cannot collide across hosts.
|
|
160
|
+
*/
|
|
161
|
+
export const CONTENT_TAG_MAX = 256 * 1024 * 1024;
|
|
162
|
+
/** Static HTTP readers tag only a whole, bounded snapshot; streamed inodes can change after hashing. */
|
|
163
|
+
export const CONTENT_SNAPSHOT_MAX = 256 * 1024;
|
|
164
|
+
// Past this, a restat proves a remembered version unchanged faster than hashing it again: served over
|
|
165
|
+
// HTTP on Node and Bun, an 8 KiB file is 1.02-1.05x faster that way, a 4 KiB one no faster.
|
|
166
|
+
const SNAPSHOT_REHASH_MAX = 4 * 1024;
|
|
167
|
+
const READ_CHUNK = 1024 * 1024;
|
|
168
|
+
// A file changed this recently can change again within its ctime's granularity (a jiffy on ext4, a
|
|
169
|
+
// second on HFS+ and as Deno reports it) without its stat showing it, so it is not tagged by version.
|
|
170
|
+
const RACY_MS = 2000;
|
|
171
|
+
function sameVersion(a, b) {
|
|
172
|
+
return (a.ctimeMs === b.ctimeMs &&
|
|
173
|
+
a.mtimeMs === b.mtimeMs &&
|
|
174
|
+
a.size === b.size &&
|
|
175
|
+
a.ino === b.ino &&
|
|
176
|
+
a.dev === b.dev);
|
|
177
|
+
}
|
|
178
|
+
function quoted(hash) {
|
|
179
|
+
return `"${hash.digest("base64url").slice(0, 22)}"`;
|
|
180
|
+
}
|
|
181
|
+
/** Tags from `createHash` - node:crypto's, which every runtime nifra serves files on provides. */
|
|
182
|
+
export function contentTags(createHash, options = {}) {
|
|
183
|
+
const limit = options.limit ?? 8192;
|
|
184
|
+
const now = options.now ?? Date.now;
|
|
185
|
+
const rehashMax = options.rehashMax ?? SNAPSHOT_REHASH_MAX;
|
|
186
|
+
let instance;
|
|
187
|
+
const known = new Map();
|
|
188
|
+
const reading = new Map();
|
|
189
|
+
const remember = (path, version, tag) => {
|
|
190
|
+
if (!(now() - version.ctimeMs >= RACY_MS))
|
|
191
|
+
return;
|
|
192
|
+
const { dev, ino, size, mtimeMs, ctimeMs } = version;
|
|
193
|
+
known.delete(path);
|
|
194
|
+
known.set(path, { dev, ino, size, mtimeMs, ctimeMs, tag });
|
|
195
|
+
for (const earliest of known.keys()) {
|
|
196
|
+
if (known.size <= limit)
|
|
197
|
+
break;
|
|
198
|
+
known.delete(earliest);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
const readWhole = async (path, version, read) => {
|
|
202
|
+
const hash = createHash("sha256");
|
|
203
|
+
const chunk = new Uint8Array(Math.min(version.size, READ_CHUNK));
|
|
204
|
+
let position = 0;
|
|
205
|
+
while (position < version.size) {
|
|
206
|
+
const count = await read(chunk.subarray(0, Math.min(chunk.length, version.size - position)), position);
|
|
207
|
+
if (count === 0)
|
|
208
|
+
break;
|
|
209
|
+
hash.update(chunk.subarray(0, count));
|
|
210
|
+
position += count;
|
|
211
|
+
}
|
|
212
|
+
const tag = quoted(hash);
|
|
213
|
+
if (position === version.size)
|
|
214
|
+
remember(path, version, tag);
|
|
215
|
+
return tag;
|
|
216
|
+
};
|
|
217
|
+
const cached = (path, version) => {
|
|
218
|
+
const entry = known.get(path);
|
|
219
|
+
return entry !== undefined && sameVersion(entry, version) ? entry.tag : undefined;
|
|
220
|
+
};
|
|
221
|
+
const ofBytes = (path, version, bytes) => {
|
|
222
|
+
const hash = createHash("sha256");
|
|
223
|
+
hash.update(bytes);
|
|
224
|
+
const tag = quoted(hash);
|
|
225
|
+
if (bytes.byteLength === version.size)
|
|
226
|
+
remember(path, version, tag);
|
|
227
|
+
return tag;
|
|
228
|
+
};
|
|
229
|
+
return {
|
|
230
|
+
cached,
|
|
231
|
+
ofBytes,
|
|
232
|
+
async ofSnapshot(path, version, bytes, restat) {
|
|
233
|
+
if (bytes.byteLength > rehashMax) {
|
|
234
|
+
const tag = cached(path, version);
|
|
235
|
+
// A remembered version is settled, so any write since its stat would show in the restat.
|
|
236
|
+
if (tag !== undefined && cached(path, await restat()) === tag)
|
|
237
|
+
return tag;
|
|
238
|
+
}
|
|
239
|
+
return ofBytes(path, version, bytes);
|
|
240
|
+
},
|
|
241
|
+
async ofFile(path, version, read) {
|
|
242
|
+
if (version.size > CONTENT_TAG_MAX) {
|
|
243
|
+
const { dev, ino, size, mtimeMs, ctimeMs } = version;
|
|
244
|
+
if (!Number.isFinite(ino) || !(now() - ctimeMs >= RACY_MS))
|
|
245
|
+
return undefined;
|
|
246
|
+
const hash = createHash("sha256");
|
|
247
|
+
// Filesystem identities are local: another host may reuse every one of these stat fields.
|
|
248
|
+
instance ??= crypto.getRandomValues(new Uint8Array(16));
|
|
249
|
+
hash.update(instance);
|
|
250
|
+
hash.update(new TextEncoder().encode(`${dev}:${ino}:${size}:${mtimeMs}:${ctimeMs}`));
|
|
251
|
+
return quoted(hash);
|
|
252
|
+
}
|
|
253
|
+
const pending = reading.get(path);
|
|
254
|
+
if (pending !== undefined && sameVersion(pending.version, version))
|
|
255
|
+
return pending.tag;
|
|
256
|
+
const entry = { version, tag: readWhole(path, version, read) };
|
|
257
|
+
reading.set(path, entry);
|
|
258
|
+
try {
|
|
259
|
+
return await entry.tag;
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
// A read of a newer version may have replaced this entry; leave that one in place.
|
|
263
|
+
if (reading.get(path) === entry)
|
|
264
|
+
reading.delete(path);
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=static-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static-file.js","sourceRoot":"","sources":["../src/static-file.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/core",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.5.0",
|
|
4
4
|
"description": "Bun-native, contract-first HTTP framework - the router, server, and route descriptor.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -200,6 +200,11 @@
|
|
|
200
200
|
"types": "./dist/range.d.ts",
|
|
201
201
|
"default": "./dist/range.js"
|
|
202
202
|
},
|
|
203
|
+
"./static-file": {
|
|
204
|
+
"bun": "./src/static-file.ts",
|
|
205
|
+
"types": "./dist/static-file.d.ts",
|
|
206
|
+
"default": "./dist/static-file.js"
|
|
207
|
+
},
|
|
203
208
|
"./contract": {
|
|
204
209
|
"bun": "./src/server/contract.ts",
|
|
205
210
|
"types": "./dist/server/contract.d.ts",
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* read its own package.json at runtime. `scripts/version.ts` rewrites it on every release bump and
|
|
17
17
|
* `check:publish` asserts it equals `@nifrajs/core`'s package version.
|
|
18
18
|
*/
|
|
19
|
-
export const VERSION = "5.
|
|
19
|
+
export const VERSION = "5.5.0" as const
|
|
20
20
|
|
|
21
21
|
export type Version = typeof VERSION
|
|
22
22
|
|
|
@@ -119,22 +119,24 @@ export function selectRouteLanes(options: {
|
|
|
119
119
|
!hasLedger &&
|
|
120
120
|
around === 0
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
? "
|
|
126
|
-
:
|
|
127
|
-
? "
|
|
128
|
-
:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
122
|
+
// The idempotency gate lives in the general program, so an idempotent route always runs it.
|
|
123
|
+
const lane: RouteExecutionLane =
|
|
124
|
+
authBeforeValidation || hasIdempotency
|
|
125
|
+
? "lifecycle"
|
|
126
|
+
: bare
|
|
127
|
+
? "bare"
|
|
128
|
+
: bodyOnly
|
|
129
|
+
? "body"
|
|
130
|
+
: !hasResponseContract &&
|
|
131
|
+
schema?.body === undefined &&
|
|
132
|
+
schema?.query !== undefined &&
|
|
133
|
+
plain &&
|
|
134
|
+
derives === 0 &&
|
|
135
|
+
beforeHandle === 0 &&
|
|
136
|
+
afterHandle === 0 &&
|
|
137
|
+
onError === 0
|
|
138
|
+
? "query"
|
|
139
|
+
: "lifecycle"
|
|
138
140
|
|
|
139
141
|
// Lifecycle routes with no params schema are the common middleware shape: derive/before plus an
|
|
140
142
|
// optional query or body schema. Select their complete validation stage at registration so the
|