DreamDB

Browser usage

In the browser, you usually only need the read pathSpace plus a way to turn blob entries into <img src> URLs. @dreamlake/dreamdb works directly against any S3-compatible HTTP endpoint with no app server in between.

Open a Space

ts
import { Space } from "@dreamlake/dreamdb";

const space = await Space.fromUri(
  "https://your-bucket.s3.us-east-1.amazonaws.com/refs/my-dataset",
);

// Discover what's there.
console.log(space.tracks().map(t => t.modality));

// Materialize samples (anchors + scalar values + per-field blob accessors).
const { samples } = await space.samples();

Read URIs come in two flavors:

URI formSemantics
…/refs/<name>Mutable. Resolves the named ref to a Manifest at fetch time (no-cache).
…/manifests/<hash>Pinned. The Manifest is content-addressed — cached forever, safe to share.

Render a blob field as <img>

For an image / video / audio field on a Sample, fetch the bytes and wrap them in a same-origin object URL:

ts
import { blobUrlFor, revokeBlobUrl } from "@dreamlake/dreamdb/browser";

const url = await blobUrlFor(sample, "image");
imgEl.src = url;

// When the element is removed, free the bytes:
revokeBlobUrl(url);

blobUrlFor handles:

  • Direct fragments
  • FragmentPack-style packed blobs (one HTTP GET per pack, then byte-slicing)
  • HLS playlists for video.hls.* fields

It returns null if the field has no entry for that sample.

Why object URLs?

Browsers won't render <img src="https://cross-origin.s3..."> if the bucket lacks Access-Control-Allow-Origin. URL.createObjectURL sidesteps CORS by giving the <img> a same-origin blob: URL backed by bytes you already fetched.

The trade-off is lifecycle: object URLs leak until revoked. With >10K thumbnails on a page that adds up fast. Always revokeBlobUrl on element removal.

React idiom

tsx
function Thumb({ sample }: { sample: Sample }) {
  const [url, setUrl] = useState<string | null>(null);

  useEffect(() => {
    let revoked = false;
    let resolved: string | null = null;
    blobUrlFor(sample, "image").then((u) => {
      if (revoked || !u) return;
      resolved = u;
      setUrl(u);
    });
    return () => {
      revoked = true;
      if (resolved) revokeBlobUrl(resolved);
    };
  }, [sample]);

  return url ? <img src={url} className="h-24 w-24 object-cover" /> : null;
}

The revoked flag handles the late-arrival case (fetch resolves after unmount) — without it you'd revoke a URL that was never set.

See the React example for a full grid component.

Time-travel

ts
const history = await space.history(20);
// history[0].manifest = current, [1] = parent, ...

const past = await Space.fromUri(
  `${space.backendUrl}/manifests/${history[5].manifest}`,
);

Every commit is a free, immutable snapshot you can open forever.

Service workers + chunked items

Very large blobs are sometimes split across multiple object-store rows ("chunked Items" / "Item Manifests"). The current blobBytesAsync throws on these — full streaming reassembly is on the v0.2 roadmap. Until then, use service-worker-based stitching (browser-side reassembly via intercepted GETs) or stick to packed Fragments.