Skip to content

routier-collection


routier-collection / plugins/blob/src / createFiles

Function: createFiles() ​

createFiles(store): object

Defined in: plugins/blob/src/files.ts:22

The file half of an application: upload, read back, and clean up.

Bound to one store, and deliberately knows nothing about your database. That separation is the design — metadata is a row in whatever plugin you already use, bytes are an object in blob storage — and keeping the two halves unaware of each other is what lets any database pair with any store.

ts
const files = createFiles(fileSystemBlobStore('./uploads'));

const reference = await files.upload(fileFromInput);
await store.documents.addAsync({ ownerId, title, file: reference });
await store.saveChangesAsync();

Parameters ​

store ​

BlobStore

Returns ​

store ​

store: BlobStore

The store these files live in, for callers that need to reach it directly.

upload() ​

upload(content, options): Promise<FileReferenceValue>

Uploads content and returns the reference to store on a record.

Idempotent. The key is the SHA-256 of the bytes, so uploading the same content twice writes one object and the second call skips the transfer entirely. A retry after a failed save cannot produce a duplicate.

Upload before the save, not after. If the save then fails, the object is an orphan: it costs storage and nothing else, and sweepOrphans collects it. The other order leaves a row pointing at bytes that were never written, which is a broken download in front of a user.

Parameters ​

content ​

FileContent

options ​

UploadOptions = {}

Returns ​

Promise<FileReferenceValue>

createUploadUrl() ​

createUploadUrl(request, options): Promise<UploadGrant>

Signs an upload so a client can send bytes straight to storage.

The server half of the direct-upload flow; createDirectUploader is the browser half. Call it from an endpoint your users are authenticated against — a presigned URL is a bearer token for one object, so signing is the authorisation decision.

Returns no URL at all when the content is already stored. Keys are content-addressed, so "already stored" means the bytes are known to be identical, and the client uploads nothing: re-attaching a file someone else uploaded transfers zero bytes.

The digest the client claims is signed into the request, so the service verifies the body against it. A client cannot take a URL signed for one checksum and store different bytes under a key that promises to be their hash.

Enforce your own limits before calling this — request.size and request.contentType are the client's claims, and refusing to sign is how you reject an upload.

Parameters ​

request ​

UploadRequest

options ​
expiresIn? ​

number

Returns ​

Promise<UploadGrant>

bytes() ​

bytes(reference): Promise<Uint8Array<ArrayBufferLike>>

Reads the bytes for a reference.

Parameters ​

reference ​

FileReferenceValue

Returns ​

Promise<Uint8Array<ArrayBufferLike>>

text() ​

text(reference): Promise<string>

Reads the bytes and decodes them as UTF-8 text.

Parameters ​

reference ​

FileReferenceValue

Returns ​

Promise<string>

url() ​

url(reference, options?): Promise<string>

A URL a browser can fetch directly, when the store can issue one.

Throws for a store that cannot, rather than returning something that will not work.

Parameters ​

reference ​

FileReferenceValue

options? ​
expiresIn? ​

number

Returns ​

Promise<string>

sweepOrphans() ​

sweepOrphans(live, options): Promise<{ deleted: string[]; kept: number; }>

Deletes every object the given references do not cover.

This exists because keys are content-addressed, and that has a consequence worth being blunt about: two records can reference the same object, so removing a record must never delete its bytes. Nothing here deletes on remove. Storage is reclaimed only by this sweep, run when you choose, against the full set of references your database currently holds.

Get that set wrong and you delete live data. So it takes the references rather than discovering them, the caller assembles them from a query they can reason about, and a sweep with an empty set refuses to run — an empty set almost always means the query failed, not that every file is garbage.

Parameters ​

live ​

Iterable<Pick<FileReferenceValue, "key">>

options ​
allowEmpty? ​

boolean

dryRun? ​

boolean

Returns ​

Promise<{ deleted: string[]; kept: number; }>

Released under the MIT License.