Loaders
The loader layer turns bytes into typed data. Every loader is a pure
async function with the same shape — load<X>(url, opts) — and only
fetches what the format actually needs to render a preview. Where the
format allows it (CSV, JSONL, NumPy headers, MCAP metadata), loaders
issue Range requests so a multi-gigabyte file never gets pulled in
full.
Loaders are also re-exported from @dreamlake/viz, so a host that
just wants the data (without the matching view) can pull
loadParquet or loadMcap directly and feed the result into its own
UI.
Overview
Shared shape
Every loader is async (url: string, opts?: LoaderOpts) => Promise<TData>.
TData is per-loader (CsvData, ParquetData, …), but the input shape
is the same across the board.
signal is the load-bearing one — pair it with useLoader and the
hook automatically aborts the in-flight fetches when the dependent
URL changes or the component unmounts.
HTTP helpers
All loaders go through the same four primitives, which you can also import directly:
| Function | Returns | Used for |
|---|---|---|
fetchHead(url, signal?) | { size, contentType?, lastModified? } | Probe file size via a 1-byte Range GET (HEAD fails on SigV4 GET-signed URLs). |
fetchRange(url, start, end, signal?) | Uint8Array | Read bytes=start-end. |
fetchText(url, signal?) | string | Full-body fetch as text. |
fetchBytes(url, signal?) | Uint8Array | Full-body fetch as bytes. |
The Range-via-GET trick in fetchHead is the only reason these
helpers exist — a vanilla HEAD against an S3 SigV4 GET-signed URL
returns 403 SignatureDoesNotMatch. The 1-byte Range GET works
because Range is unsigned, and S3 replies with
Content-Range: bytes 0-0/<total>.
loadText
The text loader is trivial — it's just fetchText(url, opts.signal).
Exported for symmetry with the others and so useLoader consumers
have a unified call site.
useLoader
The hook every composed Preview uses. Takes a function that receives
an AbortSignal and returns { data, error, loading }. The deps
array drives the React-19 prop-derivation reset — when deps change,
the previous in-flight request is aborted and the state snaps back
to loading: true during render, so there's no flash of stale data.
Tabular formats
loadCsv
Range-fetches the first ~5 MB of a CSV, auto-detects the delimiter
(, / ; / \t / |), and parses header + up to 1000 rows. Handles
the RFC-4180 quote-escape rule and BOM-prefixed Excel exports. Trims
the last partial line if the slice cap was hit.
Defaults: maxBytes = 5 MB, maxRows = 1000. Pass either through
opts to widen or narrow the slice — the loader will respect both
caps and trim cleanly.
loadParquet
Reads Parquet via hyparquet.
Parquet metadata lives at the end of the file, so unlike CSV
there's no meaningful "first 5 MB" — it's whole-or-nothing. The
loader probes size first; if the file exceeds MAX_PARQUET_BYTES
(5 MB by default) it throws ParquetTooLargeError so the host can
render a too-large status without parsing.
Defaults: maxRows = 1000. Override maxBytes to raise or lower the
cap if you know the consumer can handle it. Columns are top-level
only — nested schemas appear as JSON-stringified values inside the
row tuples.
Structured formats
loadJson
A JSON document is a single value, so range-fetching would yield
unparseable fragments — the loader HEADs first and refuses anything
over MAX_JSON_BYTES (10 MB default) by throwing JsonTooLargeError.
The success result carries both the raw text (for a "raw" view) and
the parsed value (for a tree view); parse failures are captured into
parseError rather than thrown, so the host can still surface the
raw bytes alongside the syntax error.
loadJsonl
Fetches the first ~1 MB of a JSONL file via a single Range GET and
parses line-by-line. Lines that fail to parse are silently skipped —
typical for log files where not every line is strict JSON. If the
slice was truncated mid-line, the trailing partial line is dropped
before parsing.
Defaults: maxBytes = 1 MB, maxRows = 1000.
Binary formats
These loaders read only the metadata — never any element / message data — so previewing is fast and the host doesn't have to gate on file size.
loadNpy
Parses the header of a NumPy .npy file from a 256-byte Range
fetch. If the dict header claims to be longer, the loader widens
with one extra range request. We never touch the element data.
Header dicts are parsed with focused regexes — no eval. Supports
NPY format 1/2/3 and the standard scalar dtypes (f, i, u, b,
?, e, d, g, complex F / D / G).
loadMcap
Parses an MCAP file's metadata: the Header record near the start,
the Footer at the end, and the summary section pointed to by the
footer. No data records, no decompression — just three range
fetches, regardless of file size.
The summary section is what makes "metadata-only" practical — it holds per-channel counts and statistics, so we never need to walk the data records to compute message rates.