Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
# nodeFileFetch — Node `file://` adapter
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`nodeFileFetch` is a server-tier `FetchLike` that closes a real gap in
Node's global `fetch`: undici (the engine backing `globalThis.fetch` in
every Node release through 24) refuses `file:` URLs outright. Consumers
that point `bundleUriPlugin` at a local-disk `rootUrl` on Node therefore
cannot use the default plugin wiring — the first `get` rejects with a
transport error. This adapter is the supported fix.
The function is exposed at the dedicated `./node-fetch` subpath so the
package's default export stays universal-pure (no `node:*` imports
from `src/index.ts`). Node consumers opt into the adapter with one
explicit import, browser/Bun/edge consumers continue to use the default
`globalThis.fetch` wiring untouched.
## Features
- WHATWG `fetch` shape — assignable to `@suaveplan/types.FetchLike` and
to the plugin's `options.fetch` slot with no cast.
- `file:` URL → `node:fs/promises.readFile` round trip with a real
`Response` (status 200, `content-length`, `last-modified`).
- `HEAD` short-circuit returns headers only (no buffer read).
- `Range: bytes=<start>-<end>` honoured — returns `206 Partial Content`
with `content-range`. Empty end (`bytes=10-`) is interpreted as
"through end of file", matching RFC 7233. Out-of-range requests return
`416 Range Not Satisfiable` with `content-range: bytes */<size>`.
- `init.signal` threaded into `fs.readFile({ signal })`. A pre-aborted
signal short-circuits the read and rejects with an `AbortError` (or
the signal's `reason` if provided) so the plugin's abort pipeline can
rewrap it as `BUNDLE_URI.ABORTED`.
- Non-`file:` URLs delegate to `globalThis.fetch`, so a single adapter
instance handles both local-disk and CDN roots without branching at
the call site.
- Missing path or non-file inode (directory, symlink loop) returns
`404 Not Found`, matching the rest of the plugin's NOT_FOUND mapping.
## Usage: Basic
```ts
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
import { nodeFileFetch } from "@suaveplan/storage-uri-plugin-bundle/node-fetch";
const plugin = bundleUriPlugin({
packageSlug: "audio-ir-room-plugin-club",
rootUrl: new URL("./assets/", import.meta.url),
fetch: nodeFileFetch,
});
const bytes = await plugin.adapter.get("club.wav");
```
## Usage: Advanced
```ts
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
import { nodeFileFetch } from "@suaveplan/storage-uri-plugin-bundle/node-fetch";
// Range read — plugin issues "Range: bytes=10-19", adapter answers 206
// with the 10-byte slice, plugin returns it verbatim.
const plugin = bundleUriPlugin({
packageSlug: "x",
rootUrl: new URL("file:///pkgs/x/assets/"),
fetch: nodeFileFetch,
});
const controller = new AbortController();
const slice = await plugin.adapter.getPartial(
"club.wav",
10,
10,
controller.signal
);
// controller.abort() before resolution → BUNDLE_URI.ABORTED.
```
## API Reference
- `nodeFileFetch(input, init?): Promise<Response>` — `FetchLike`. For
`file:` URLs reads the path through `node:fs/promises`; for any other
scheme delegates to `globalThis.fetch`.
## Implementation Notes
The adapter never throws for HTTP-shaped problems — missing files become
`404`, range overflows become `416`, etc. — because the calling plugin
already owns the status → `BUNDLE_URI.*` mapping and expects a real
`Response` shape. The one exception is `AbortSignal`: a pre-aborted
signal throws synchronously (before the `stat` round trip) so the
plugin's `ensureNotAborted` upstream check is symmetric with the
adapter's downstream check. Mid-flight aborts propagate as the native
`AbortError` that `fs.readFile({ signal })` raises; the plugin's
`wrapAbortError` catches that and rewrites it to `BUNDLE_URI.ABORTED`.
Range parsing tolerates both `bytes=10-19` (closed interval) and
`bytes=10-` (open through EOF) per RFC 7233 §2.1. End values past EOF
are clamped to `size-1` so a caller asking for `bytes=10-` against a
20-byte file gets a 10-byte slice + `content-range: bytes 10-19/20`. A
single regex match runs per call — no allocation past the `Headers`
construction the calling code already paid for.
`node:fs/promises` is the only `node:*` import. The module sits at the
`./node-fetch` subpath so universal consumers never pull it through the
default barrel; tree-shaking and tier checks both stay green.