Navigate

Run Cloud Storage locally

Storage support is incomplete. Check its generated conformance page for the exact public API coverage before depending on a feature.

Files land in your sandbox the way documents do: locally, with rules deciding what gets in.

Upload and download

import { initializeSandbox } from 'pyric/sandbox';
import { getStorageSandbox, ref, uploadBytes, getBlob } from 'pyric/storage';

const sandbox = initializeSandbox();
const storage = getStorageSandbox(sandbox.withAuth({ uid: 'alice' }));

const bytes = new TextEncoder().encode(JSON.stringify({ task: 'build a notes app' }));
await uploadBytes(ref(storage, 'sessions/s1'), bytes, { contentType: 'application/json' });

const blob = await getBlob(ref(storage, 'sessions/s1'));
console.log(JSON.parse(await blob.text()));

uploadString covers text without the encoder, and getBytes returns an ArrayBuffer when you want raw bytes instead of a Blob. Under pyric sandbox, a served page’s firebase/storage imports resolve to the sandbox’s shared object store, so uploads show up across tabs like every other write. pyric sandbox enforces storage.rules the same as firestore.rules and database.rules.json. Storage rules load at server boot and do not hot-reload. Restart the sandbox server after editing storage.rules.

List and delete

import { listAll, deleteObject } from 'pyric/storage';

const listing = await listAll(ref(storage, 'sessions'));
console.log(listing.items.map((item) => item.name)); // ['s1']

await deleteObject(ref(storage, 'sessions/s1'));

Metadata rides along

Set it at upload, read it back, patch it later:

import { getMetadata, updateMetadata } from 'pyric/storage';

await uploadBytes(ref(storage, 'sessions/s1'), bytes, {
  contentType: 'application/json',
  customMetadata: { sessionId: 's1', version: '1.0' },
});

const meta = await getMetadata(ref(storage, 'sessions/s1'));
console.log(meta.size, meta.customMetadata?.sessionId);

await updateMetadata(ref(storage, 'sessions/s1'), {
  customMetadata: { ...meta.customMetadata, version: '1.1' },
});

Two contracts worth knowing, both matching the upstream SDK:

  • updateMetadata replaces the settable fields rather than merging. Fetch first and spread, as above.
  • customMetadata is string-to-string, so numbers and objects need serializing.

Enforce storage rules in-process

Pass rules when you configure the handle, and every operation evaluates against them, no deploy anywhere:

const RULES = `service firebase.storage {
  match /b/{bucket}/o {
    match /sessions/{id} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && (request.method == 'delete'
                       || (request.resource.size < 10 * 1024 * 1024
                           && request.resource.contentType == 'application/json'));
    }
  }
}`;

const storage = getStorageSandbox(sandbox.withAuth({ uid: 'alice' }), { rules: RULES });

An anonymous upload now throws FirebaseError with storage/unauthenticated. An 11 MiB payload throws storage/unauthorized, the signed-in-but-not-allowed code.

Notice the request.method == 'delete' carve-out. deleteObject carries no incoming payload, and production treats reading or null-checking the absent request.resource as an error. Testing the method lets deletes through while keeping payload checks on creates and updates.

One rule-shape gotcha carried over faithfully from production: listAll requires read on the listed folder itself. A rule scoped to match /sessions/{id} grants nothing on /sessions, so give the folder its own read rule.

Check support before choosing an operation

Per-feature support is tracked on the Cloud Storage conformance page.

Where to go next

For structured documents and queries, use Store and query data.