# Files A file's bytes never live in a pattern's cells. A pattern that takes files uploads them to the space's blob store through `cf-file-input`, keeps the small descriptor the upload returns, and serves a download as a link to the descriptor's URL. The store keeps each blob as a content-addressed document of its own in the space, so the pattern's cells stay small however large the file is, and the list syncs like any other list of records. `packages/patterns/file-share/main.tsx` is the whole shape in one file. ## Uploading `cf-file-input` opens the browser's file chooser, uploads each chosen file to the blob store of the space the pattern runs in, and fires `cf-change` with the files it just stored: ```tsx // Shown for illustration only. interface UploadedFile { id: string; // hash of the name, media type and bytes, prefixed `fid1:` name: string; mediaType: string; size: number; url: string; // absolute URL the bytes are served from } interface AddFilesEvent { detail?: { files?: UploadedFile[] }; } ; ``` The event carries more fields than these; keep the ones the pattern reads and push them into a shared list. Never keep the bytes or a data URL in a cell. `includeData` on the input puts a base64 copy of the file into `data` for a transient use such as sending an image to a model; a record that persists that copy makes the cell as large as the file, and a large cell syncs slowly and can fail to sync at all. With previews enabled the input renders its own remove buttons, and a click on one fires `cf-change` again with `files` holding every remaining file. A handler that treats `files` as new uploads then re-adds them. Either turn previews off, or handle `cf-remove` and read `allFiles` as the full list. ## Downloading A download is an anchor: ``. A blob stored with a name, as `cf-file-input` stores every file it uploads, is served with a `Content-Disposition` header naming the file, so the saved file has that name whichever host serves the space. A blob stored with no name is served with one naming no file. There the `download` attribute is what names the saved file, and browsers honor it only when the link is same-origin with the page, so a space served from another host saves the file under its hash instead. A URL with `?download` is served as an attachment whatever the blob's type, which a browser saves from any origin, under the blob's name. A pattern that holds a blob's reference, its space's DID and its id there, rather than its URL, offers it with `cf-blob-link`, as ``, which links to its `?download` URL, and shows an image with ``. Each asks the runtime for the URL the blob's space's host serves it at, which a pattern has no way to form itself. `cf-file-download` is not for stored files. It takes the content as a string and builds the download in the browser, so using it means holding the bytes in a cell. ## Renaming A handler renames a file by naming its blob again: `nameBlob(target, blobId, name)` returns the id of a blob holding the same bytes and media type under `name`, in the space `target`'s value lives in, and the handler stores that id where it kept the old one. It returns `undefined` when the space holds no blob `blobId`, which the handler checks before storing anything. A blob never changes, so the space then holds the bytes under both names. The new blob's record is stored when the handler's transaction commits, and its bytes travel with the commit: the handler loads them first, and is run again once they arrive. The commit carries the bytes base64-encoded, so under the routed transport, whose frames hold at most 8 MiB, a file well within the upload limit can still be too large to rename. ## What the store provides - One upload holds at most 10 MB; the server returns 413 above that. - A blob is addressed by the hash of its media type, its bytes, and the name it was stored with, if any, so the same file uploaded twice under the same name has the same id, and under two names has two. The URL is that id plus a short suffix: the last extension of the name the upload is posted under, which `cf-file-input` takes from the file's name, or, for an upload posted under none, of the blob's name, when that extension is one to eight letters or digits; otherwise one chosen from the media type, or `bin`. Two uploads with the same id share a URL when they get the same suffix and have two URLs when they do not. Each URL is immutable and cacheable forever. - A name is any non-empty string of well-formed Unicode of at most 1024 bytes as UTF-8. Every download's `Content-Disposition` is `inline` for a type served as itself, and `attachment` for every other, or for any type under `?download`, naming the file when the blob has a name. - The bytes stay in the space's own store beside its cells. There is no offload to an object store, and no signed or expiring URL. - GIF, JPEG, PNG, and WebP are served as their own media type, so an `` can show them. Every other type is served as `application/octet-stream`, so a browser downloads it rather than rendering it inline. Every blob is served with `X-Content-Type-Options: nosniff`, so a browser takes the type as given rather than guessing one from the bytes. - A record a handler stores with `nameBlob()` carries no label either, so the name it gives is readable by anyone who can fetch the blob. - Nothing deletes a blob. Removing a record drops the reference; the bytes stay. - The upload and download routes are not authenticated: anyone who holds a blob's URL can fetch it, and anyone can upload into a space. The store is `packages/toolshed/routes/blobs/blobs.index.ts`.