npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@freva-org/databrowser

v1.2.1

Published

Freva Data-Browser — framework-free, typed, zero runtime dependencies.

Downloads

146

Readme

@freva-org/databrowser

npm CI License: BSD 3-Clause

Framework-free, strictly-typed, zero runtime dependencies climate-data browser for the freva-nextgen REST API. Single mount point, self-contained styles, near-total teardown (one documented page-global exception for the opt-in Leaflet map — see below).

Install

npm install @freva-org/databrowser

Quick start

import { mountDataBrowser } from "@freva-org/databrowser";

const handle = mountDataBrowser(document.getElementById("app")!, {
  apiBase: "/api/freva-nextgen/databrowser", // default
  flavour: "freva", // default lens
  authEnabled: false, // heavy ops show disabled placeholders when off
  getAuthToken: () => null, // OIDC bearer supplier when authEnabled
  // carve-outs, off by default until verified against a live backend:
  enableStrictBBoxModes: false, // strict/file time+bbox semantics
  devNotes: false, // opt-in developer drawer
  isolate: false, // true → render inside a Shadow DOM so host global CSS can't bleed in
  // metadata.js (facet value descriptions — freva-web parity):
  metadata: undefined, // { facetKey: { value: 'human description' } } — config wins
  metadataScriptUrl: "/static/js/metadata.js", // deployment script; pass null to disable (config-only)
});

handle.destroy(); // removes the root, flushes every listener/timer/in-flight request
handle.getState(); // Readonly<AppState>
handle.setTheme("night"); // drive light/dark from a host control (fires theme.onModeChange)

The mount target must have a definite height (the component fills it); a full-viewport host uses 100vh.

Teardown exception (page globals)

destroy() returns the mount to a clean state — DOM removed, listeners/timers/requests flushed — with one deliberate exception: if the user opted into the Leaflet map (the on-demand "Zoom" upgrade), Leaflet's stylesheet, its window.L global, and the Leaflet custom-element registration are page-global and intentionally survive teardown, so a later re-mount reuses them instead of re-fetching the library. This is standard for a page-global script dependency and is why the map upgrade is gesture-gated (nothing loads until the user asks). If you never enable the map, nothing page-global is ever installed.

Configuration

| Option | Type | Default | Description | | ----------------------- | -------------------------------------------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | apiBase | string | /api/freva-nextgen/databrowser | Base URL of the freva-nextgen databrowser REST API | | flavour | FlavourName | freva | Default metadata lens | | authEnabled | boolean | false | When off, heavy/auth-gated operations render as disabled placeholders | | getAuthToken | () => string \| null | () => null | OIDC bearer-token supplier, used when authEnabled | | enableHeavyOps | boolean | false | Data-portal heavy ops (load / zarr convert / status / share / inspect) require this in addition to authEnabled | | getCsrfToken | () => string \| null | — | Optional; sends X-CSRFToken on mutating requests only when provided | | enableStrictBBoxModes | boolean | false | Config back-compat flag for strict/file bbox semantics | | syncUrl | boolean | true | Mirror the active query into the page URL and read it back on load (deep links) | | isolate | boolean | false | Render the whole widget inside a Shadow DOM so host global CSS can't bleed in (see Style isolation) | | baseFilters | Record<string, string \| string[]> | — | Always-applied client-side scope for a hosted filtered instance (not an auth boundary) | | inspectorUrl | string | CDN default | ESM URL for the lazy @freva-org/data-inspector component (self-host for air-gapped) | | brand | { title?, mark?, description? } | Freva / | Brand title, mark, and description shown in the shell | | devNotes | boolean | false | Opt-in developer drawer + window.__frevaPerf instrumentation | | metadata | { [facetKey]: { [value]: string } } | undefined | Facet-value descriptions; overrides the deployment script per (key, value) | | metadataScriptUrl | string \| null | /static/js/metadata.js | freva-web deployment asset for facet descriptions; null = config only | | theme | { both?, day?, night? } | — | Per-token colour overrides (see Theming) | | features | { themeToggle, terminal, overview, export, details, search, lensSwitcher, inspect, brand } | all true | Feature gating — every flag defaults to on |

Handle

mountDataBrowser returns a handle:

| Member | Signature | Description | | ---------- | ---------------------------------- | ----------------------------------------------------------------- | | destroy | () => void | Removes the root and flushes every listener, timer, and request | | getState | () => Readonly<AppState> | Current application state | | setTheme | (mode: "day" \| "night") => void | Drive light/dark from a host control (fires theme.onModeChange) |

Theming

All palette colours are CSS custom properties on the root and are embedder-adjustable two ways: (a) the theme mount-config section, or (b) setting the CSS variables directly on the .freva-db root element itself (e.g. inline on the mount root). Setting a token on an ancestor has no effect — the root declares its own default for each token, which overrides any inherited value. Under isolate: true the root lives inside a shadow root an outside stylesheet can't target, so use the theme.* config there (it lands as inline declarations on the root). Documented tokens (each --<token>): bg, surface, surface-2, surface-3, text, dim, faint, border, border-2, accent, accent-2, accent-soft, good, warn, danger, ocean, land.

theme: {
  both: { accent: "#4f8df7" },
  night: { bg: "#0a1120" },
  day: { bg: "#ffffff" },
}

both applies to both themes; day / night win per token. Any omitted token keeps its default.

Style isolation (isolate)

By default the widget mounts in the light DOM, so it inherits the page's CSS environment. In a host with aggressive global styles — a CSS reset, element-level rules (h1, kbd, pre), a * { line-height } rule, a docs framework or CMS theme — those rules can bleed into the widget (inflating the inspector title, shifting the terminal baseline, etc.).

Set isolate: true to render the entire widget inside a Shadow DOM attached to the mount element:

mountDataBrowser(document.getElementById("app")!, { isolate: true });

This blocks host CSS from bleeding in (and the widget's styles from leaking out), in both directions, without any host-specific workarounds. Overlays, popovers, the lazy data-inspector, and the on-demand Leaflet map all render inside the shadow root. Default is false, so existing light-DOM embeds are unchanged.

Theming in isolate mode. Use the theme.* mount config (see Theming). Those overrides are applied as inline custom-property declarations on the widget root (.freva-db), so they resolve inside the shadow root and win over the root's built-in defaults — e.g. theme: { both: { accent: "#4f8df7" } } themes the widget identically in isolate mode.

Note that setting a token such as --accent on the host or an ancestor element does not theme the widget: the root declares its own default for every documented token, and a declaration on the element wins over an inherited value — so the inherited host value is overridden. (This is true in the light DOM too; it is not specific to the shadow boundary.) Drive theming through theme.* config, which is the supported path in both modes.

metadata.js — facet value descriptions (freva-web parity)

Facet values can carry human descriptions from two optional sources, both degrading silently when absent:

  • Mount config — a metadata object shaped facetKey → (value → description) (e.g. { project: { cmip6: "Coupled Model Intercomparison Project 6" } }).
  • Deployment scriptmetadataScriptUrl (default /static/js/metadata.js), the freva-web asset that assigns per-facet window globals (window.project = { cmip6: "…" }, …). Only an allow-list of known facet keys is read off the global, and only when the value is a plain string → string block; arbitrary globals are ignored. Pass metadataScriptUrl: null to load from config only.

Config wins per (key, value): the deployment script fills gaps, the config object overrides on conflicts. Descriptions surface as hover tooltips on facet value rows (sidebar and overview); every description reaches the DOM via textContent. Resolution is async and non-blocking — the config object is applied synchronously at mount, the script merges under it when it lands.

Contract notes

load/{flavour} is a GET; time is sent unbracketed (time=<from> TO <to> + time_select); catalogue export requests max-results=100000 and handles the exact 413 detail "Result stream too big.", disabling export past 100,000 files. Browsing never fetches per-file metadata — the Details panel fetches it lazily via a ?file= call. All API/user strings reach the DOM via textContent; the terminal overlay escapes every token.

Source layout

  • src/types.ts — wire shapes + view model + public surface
  • src/api.ts — typed fetch wrappers (abort-per-channel, monotonic request ids)
  • src/state.ts — AppState, selectors, wire→view, query/CLI/Python builders
  • src/dom.ts — escaping, el() (no innerHTML hatch), Disposables registry
  • src/index.tsmountDataBrowser controller (shell DOM, search loop, wiring)
  • src/components/* — sidebar, chips, results, pickbar, overview, details, terminal, autocomplete, timeEditor, bboxEditor, notes
  • src/styles.css — readable styling source; src/styles.ts — generated injectable string

Development

npm install
npm run build       # emits dist/ (JS + .d.ts + maps)
npm test            # type-checks tests then runs node --test (pure-fn + DOM/integration via jsdom)
npm run typecheck   # tsc --strict --noEmit
npm run gen:styles  # regenerate src/styles.ts from src/styles.css

src/styles.ts is generated — never edit it by hand; edit styles.css and re-run npm run gen:styles. jsdom is a devDependency only (tests); the shipped component imports nothing external.