Skip to main content

zarrextra Overview

zarrextra is the layer between zarrita and @spatialdata/core. It holds the things a SpatialData reader needs that are not specific to SpatialData: opening a store and reading its whole hierarchy up front, navigating that hierarchy, decoding image codecs zarrita does not ship, and moving chunk decode off the main thread.

It is published outside the @spatialdata/* namespace because nothing in it knows what an image or a table is. If you are reading SpatialData stores, you want @spatialdata/core, which re-exports the parts of this package you are likely to need. If you are reading some other zarr hierarchy and want the same tree and codec machinery, you can depend on zarrextra alone.

:::info alpha prerelease

Versioned alongside the @spatialdata/* packages for now, but able to version independently in future releases. The API described here is not yet stable.

:::

npm install zarrextra
# or
pnpm add zarrextra

Opening a store

openExtraConsolidated takes a URL or any zarrita Readable, resolves consolidated metadata, and reads the entire hierarchy into a tree — every group, every array, and every node's attributes — before returning.

import { openExtraConsolidated, isErr } from 'zarrextra';

const result = await openExtraConsolidated('https://example.com/store.zarr');
if (isErr(result)) {
console.error(result.error);
} else {
const { zarritaStore, tree } = result.value;
}

It returns a Result, not a thrown error: failing to open a store is an ordinary outcome for a URL a user typed, and the message is worth handling rather than catching.

The ConsolidatedStore it resolves to has two halves, and which one you want depends on whether you need data or only structure:

FieldWhat it isUse it for
zarritaStorezarr.Listable<Readable> — the store, with metadata served from memoryOpening arrays, reading chunks
treeZarrTree — the hierarchy as a plain objectEnumerating, navigating, reading metadata without I/O

The tree is the reason this package exists. Because every node's attributes and array metadata are already in memory, a question like "what columns does this table have, and what type is each one?" is answerable synchronously, before deciding whether any of it is worth loading. Tree nodes covers the shape of a node, how to tell a group from an array, and how to read a data type out of one.

Store extras

Two smaller helpers that come up when a store is not being consumed whole:

  • createPrefixedStore(store, prefix) — a Readable view rooted at a subpath. Used to hand a table's own subtree to anndata.js without it knowing about SpatialData's layout above.
  • loadOmeZarrMultiscalesFromStore(store, path) — reads an OME-Zarr multiscales group into Viv-compatible pixel sources, reusing an already-open store rather than making the viewer open its own.

Result

Result<T, E> is a small Rust-style success-or-error union, used wherever a failure is expected rather than exceptional:

type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

with Ok, Err, isOk, isErr, unwrap and unwrapOr. It is re-exported from @spatialdata/core, so importing it from either package gives the same type. See error handling for how core uses it.

Codecs and workers

SpatialData stores in the wild use image codecs zarrita does not register by default, and decoding them on the main thread will stall a render. Those two concerns are documented where they are used rather than repeated here:

TopicWhere
Registering JP2K and experimental HTJ2K decode, encoding HTJ2Kpackage README
Which setup each context needs (Node, browser with vis, browser without)codec fixtures
Shipping a worker entry point consumers can use without private URLsworker bundling pattern

The short version: in Node, call registerJpeg2kCodec() / registerExperimentalHtj2kCodec() on the main thread. In a browser using @spatialdata/vis, do nothing — the renderer path enables the bundled codec worker for you. In a browser without vis, call enableWorkerChunkDecode() from zarrextra/workers before loading codec-backed data.

Weight

zarrita is deliberately minimal. zarrextra is not, and it is worth being explicit about where that lands before depending on it.

Measured from the current build:

EntryRawGzipped
zarrextra15.4 kB5.2 kB
zarrextra/codec-worker3.2 MB899 kB

The main entry is small, and the WASM codec packages are optionalDependencies whose decoders are injected by the caller — so importing zarrextra does not drag OpenJPEG or OpenJPH in. What is heavy is the codec worker, and it is one bundle carrying every codec: enabling worker decode for JP2K also ships OpenJPH, and vice versa. An application reading uncompressed or blosc-compressed stores — which is most of them — gets no benefit from either.

The install footprint is also larger than the import graph suggests. zod is a hard dependency reached by a single internal schema module that nothing currently calls, so every consumer installs it whether or not anything uses it. Which way that should resolve is open: array metadata is read from a store with a bare JSON.parse today and typed as an unvalidated record, so there is at least as good a case for validating more at that boundary — and earning the dependency — as for dropping it.

:::caution subject to redesign

None of this is settled. Splitting the worker so applications can opt into lighter bundles when they do not need JP2K or HTJ2K is the obvious first move, and is already noted as future work in the package README. Treat the packaging — not the APIs on this page — as the part most likely to change.

:::