Object storage (buckets)

Prerequisites

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).