TinyStore

Blobs / Blobs

Blobs

The Blobs engine keeps files by path in named sets, under blobs/ of the store's directory: avatars, attachments, exports. A file is written whole or not at all, replaced whole, checked against its SHA-256 when it is read whole, and served with an ETag. Folders group files by owner, so deleting a user's files is one call.

const avatars = store.files('avatars', { maxFileSize: '5MiB' })

await avatars.folder('users', 42).put('avatar.png', request.body!, { contentType: 'image/png' })
const file = await avatars.folder('users', 42).get('avatar.png') // StoredFile | undefined
await avatars.folder('users', 42).clear() // every file of user 42
avatars = store.files("avatars", max_file_size="5MiB")

await avatars.folder("users", 42).put("avatar.png", request.stream(), content_type="image/png")
file = await avatars.folder("users", 42).get("avatar.png")  # StoredFile | None
await avatars.folder("users", 42).clear()  # every file of user 42
avatars, err := blobs.OpenFiles(store, "avatars", blobs.MaxFileSize(5<<20))

_, err = avatars.Folder("users", 42).Put(ctx, "avatar.png", r.Body, blobs.ContentType("image/png"))
file, found, err := avatars.Folder("users", 42).Get(ctx, "avatar.png") // io.ReadSeekCloser
err = avatars.Folder("users", 42).Clear(ctx)                          // every file of user 42
let avatars = store.files("avatars").max_file_size(5 << 20).open()?;

avatars.folder("users").folder(42).key("avatar.png").content_type("image/png").put(&png)?;
let file = avatars.folder("users").folder(42).get("avatar.png")?; // Option<StoredFile>
avatars.folder("users").folder(42).clear()?; // every file of user 42

What it guarantees

  • A file appears whole at its commit, or not at all: an upload that is cut, cancelled or of the wrong size leaves nothing (contract BLOB-01).
  • A whole read is checked against the file's SHA-256 and fails corrupt before its end if a byte changed (contract BLOB-02).
  • A reader keeps the version it opened through a replace or a delete (contract BLOB-03).
  • Of two writes that read the same ETag, one conflicts (contract BLOB-04).
  • A copy shares the bytes and outlives its source (contract BLOB-05).
  • A path is its own bytes on every file system: Photo.jpg and photo.jpg are two files everywhere (contract BLOB-08).

In these guides

PageCovers
Uploadsstreams, upload and commit, size, maxFileSize
Serving filesETags, ranges, content-disposition, Go's ServeContent
Copies and expirycopy, rename, ttl, expire
Integritychecked reads, the scrub, ifMatch and create

New to Blobs? Attachments in the Handbook stores and serves a chat's uploads.

Reference

Bun and Node · Python · Go · Rust

Edit this page on GitHub Updated 19 October 2026

esc
↑↓ navigate ↵ open ⇥ filter 0 results