@stowage/adapter-fs
v0.2.0
Published
stowage adapter rooting a storage in one directory of the local file system.
Maintainers
Readme
@stowage/adapter-fs
A storage rooted in one directory of the local file system.
Install
npm install @stowage/adapter-fsExample
import { fsStorage } from "@stowage/adapter-fs";
const storage = fsStorage({ root: "/var/lib/my-app/storage" });
await storage.put("reports/2026/q3.csv", "region,revenue\n");
for await (const entry of storage.list({ prefix: "reports/" })) {
console.log(entry.key, entry.size, entry.lastModified);
}Runtimes
Node 24 and later, Bun and Deno, on Linux and macOS. workerd and Windows are not promised. CI last ran green on Bun 1.4.2 and Deno 2.9.6.
The bundle measures 5.7 kB minified and gzipped, @stowage/core included.
Limits
keyBytesPreservedis not declared: a key comes back Unicode-equivalent to what was written (spec 4.9).presignedUrlsis not declared:FsStoragehas neitherpresignGetnorpresignPut(spec 4.9).userMetadatais not declared:putwith a non-emptyuserMetadataisUnsupported, and reads return{}(spec 4.9).userMetadataTokenKeysis not declared: withoutuserMetadata, a key beyond identifiers isUnsupportednaminguserMetadatalike any other (spec 4.9).- A key segment longer than 255 bytes is
InvalidKey, and so is a key whose whole path is longer than the file system holds. macOS bounds one path at 1024 bytes with the root counted in (spec 6). - A name the file system refuses to create is
InvalidKeyonputand on the destination ofcopyandmove. APFS refuses every noncharacter, so a key holdingU+FFFEis written on Linux and refused on macOS (spec 6). - APFS keeps a name in the Unicode form it was written in, and folds the forms when it looks a name up, so a key in NFD reaches the object its NFC form wrote. A case-insensitive file system collides keys that differ in case alone. Nothing repairs either (spec 6).
- The content type is derived from the key's extension, and is
application/octet-streamwhere the extension is unknown or absent. ThecontentTypehanded toputis not stored, sostatmay report another one (spec 6). - No
etagis set (spec 4.4).
Notes
put takes no Blob. A caller holding one passes its stream
(spec 4.2):
import { fsStorage } from "@stowage/adapter-fs";
const storage = fsStorage({ root: "/var/lib/my-app/storage" });
const blob = new Blob(["region,revenue\n"], { type: "text/csv" });
await storage.put("reports/2026/q4.csv", blob.stream());stowage reports no progress
(spec 12).
A caller who wants it counts the bytes on their way into put:
import { fsStorage } from "@stowage/adapter-fs";
const storage = fsStorage({ root: "/var/lib/my-app/storage" });
function countBytes(report: (bytes: number) => void): TransformStream<Uint8Array, Uint8Array> {
let bytes = 0;
return new TransformStream({
transform(chunk, controller) {
bytes += chunk.byteLength;
report(bytes);
controller.enqueue(chunk);
},
});
}
const response = await fetch("https://example.com/video.mp4");
if (response.body === null) throw new Error("The response carries no body");
await storage.put("videos/intro.mp4", response.body.pipeThrough(countBytes(console.log)));Specification
docs/spec.md at @stowage/[email protected]
is the contract: a caller may rely on what it states and on nothing else this package happens to
export. The terms it uses
and the decisions behind it
are at the same tag.
License
MIT
