Object storage (buckets)
Prerequisites
@org/fluffy-chainsaw-storage(the client library).- Locally,
fluffy-chainsaw localspins up a local GCS emulator — no cloud credentials or configuration needed.
Declare a bucket
Use the storage namespace from the wrapper SDK to declare a bucket connection:
import { storage } from "@org/fluffy-chainsaw-storage";
const media = storage.bucket("media"); // declares waitlist bucket connection
buckets:
media:
location: US # all fields optional
# public: true
# versioning: true
# lifecycle: { abort_incomplete_upload_days: 7 }
The real bucket name is {project}-{logical-name} — deterministic and globally
unique; your code only ever uses the logical name.
Grant it to a runner
Bucket grants are objects, not bare names — each names the key prefix the runner
is scoped to (required) and the access level (viewer and/or writer, at least
one):
runners:
orders-api:
uses:
buckets:
- name: media
prefix: uploads/ # the runner sees only keys under this prefix
writer: true # writer and/or viewer
Use it
import { storage } from "@org/fluffy-chainsaw-storage";
const media = storage.bucket("media"); // prefix-scoped client
await media.put("avatar.png", buf, { contentType: "image/png" });
const bytes = await media.get("avatar.png");
await media.delete("avatar.png");
const ok = await media.exists("avatar.png");
const keys = await media.list(); // or media.list("2026/")
const url = await media.getSignedUrl("avatar.png", {
action: "read", // "read" | "write"
expiresInSeconds: 3600,
});
All keys are relative to the granted prefix (available as media.prefix) — the
client prepends it, so code never sees or escapes its slice of the bucket.
media.unwrap() returns the underlying GCS bucket handle when you need something
the client doesn't wrap.
Local vs deployed
Same code everywhere. Locally the provider is a GCS emulator; deployed it is GCS.
Stick to the gcs provider. S3 provider configuration is accepted in the yaml for
routing but is not yet implemented in the wrapper client.
Using a bucket another app owns
buckets:
media: { existing: true } # looked up at deploy, granted onto, never created/destroyed
Owner-only knobs (public, versioning, lifecycle, location) are rejected on
an existing bucket — you don't own its config (E_BUCKET_EXISTING_CONFIG).