Pyric
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 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:

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