@wess/atlas/storage#
S3-compatible object storage with zero external dependencies.
Exports#
createStore(opts)→Storeupload(store, { key, body, contentType?, acl?, maxBytes?, signal? })→Promise<{ key, url }>download(store, key)→Promise<Response>remove(store, key)→Promise<void>(no-ops on 404)stat(store, key)→Promise<{ size, contentType, etag } | null>(null on 404)list(store, prefix?)→Promise<ListResult>presign(store, key, { expires?, method?, query? })→string
Types#
Store = { endpoint, bucket, region, accessKey, secretKey }UploadOptions = { key; body: string | Uint8Array | Blob | ReadableStream; contentType?; acl?: "private" | "public-read"; maxBytes?: number; signal?: AbortSignal }ListResult = { keys: string[]; truncated: boolean }
Architecture#
store/— Store type and createStore factorysigning/— AWS Signature V4 implementation using node:cryptooperations/— upload, download, remove, list (signed S3 requests)presign/— presigned URL generation for direct client access
API#
createStore(opts) => Store#
Creates a store configuration object.
const store = createStore({
endpoint: "http://localhost:9000",
bucket: "mybucket",
accessKey: "minioadmin",
secretKey: "minioadmin",
region: "us-east-1", // optional, defaults to us-east-1
})
upload(store, opts) => Promise<{ key, url }>#
Upload a file. Body can be string, Uint8Array, Blob, or ReadableStream.
Uploads default to a 5 GiB limit; set maxBytes to an application's smaller bound.
signal cancels hashing, stream preparation, and the HTTP request. Blobs are hashed
incrementally and replayed without a whole-file buffer. One-shot byte streams use
a private temporary file, removed on success, failure, or cancellation. Allow enough
temporary disk space for concurrent uploads; use multipart transfers for larger files.
await upload(store, { key: "photos/cat.jpg", body: file, contentType: "image/jpeg" })
// public media served directly from a bucket or CDN
await upload(store, {
key: "media/photo.jpg",
body: file,
contentType: "image/jpeg",
acl: "public-read",
})
ACL is per upload. Omitting it sends no x-amz-acl header, preserving the default
private upload behavior. Set "private" explicitly when needed, or "public-read"
for public objects on providers that support object ACLs. The ACL header is included
in the request signature. No bucket policy or store-wide setting is changed.
Keys are encoded once, preserving spaces, Unicode, %, ?, and #. Dot path
segments (. or ..) are rejected because URL normalization would change the key.
@wess/atlas/storage/signing exports signRequest, sha256, and hmacSha256.
signRequest accepts payloadHash for an already-computed SHA-256 hex digest;
callers must ensure the digest matches the actual body.
download(store, key) => Promise#
Download a file. Returns the raw fetch Response.
remove(store, key) => Promise#
Delete a file. No-ops on 404.
list(store, prefix?) => Promise<{ keys, truncated }>#
List objects by prefix. Requests URL-encoded keys in the S3 XML response and decodes them once, preserving reserved characters, line breaks, and Unicode.
presign(store, key, opts?) => string#
Generate a presigned URL for direct client access.
const url = presign(store, "file.pdf", { expires: 900, method: "GET" })
// signed extra parameters, e.g. the filename a download saves as
presign(store, "files/a1b2", {
query: { "response-content-disposition": 'attachment; filename="brushes.zip"' },
})
A browser upload straight to the bucket is a presigned PUT. The bucket needs a CORS
rule allowing PUT from the app's origin.
stat(store, key) => Promise<{ size, contentType, etag } | null>#
Signed HEAD. Returns null when the object is missing, so an app can confirm a direct
browser upload actually landed before trusting it.
Testing#
bun test packages/storage/ # signing + presign (no server needed)
S3_TEST=1 bun test packages/storage/ # includes operations (needs S3 server)
Conventions#
- Functional style, no classes
- Zero external dependencies (node:crypto only)
- All files lowercase, no dashes or underscores
Dependencies#
- Sibling packages: none —
@wess/atlas/storageis standalone. - External: none. Uses
node:cryptofor AWS Signature V4 andfetchfor transport.