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 dev, 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 dev enforces storage.rules the same as firestore.rules and database.rules.json — but unlike those two, storage rules load at server boot and don’t hot-reload. Edit storage.rules and you need to restart the dev server to pick up the change.
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:
updateMetadatareplaces the settable fields rather than merging. Fetch first and spread, as above.customMetadatais 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.