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

@cytario/web

v2.3.1

Published

Cytario Web — scientific imaging data browser and viewer for OME-TIFF, OME-Zarr, Parquet and GeoTIFF on S3-compatible storage.

Readme

Cytario Web

CI Test Coverage Badge License: AGPL-3.0 GitHub release Node.js GitHub Stars

A web-based file browser and viewer for scientific imaging data. Cytario Web lets you explore, visualize, and manage large-scale datasets (OME-TIFF, GeoTIFF, Parquet) stored in S3-compatible object storage.

For the hosted product, see cytario.com.

Screenshot

Architecture

| Layer | Technology | | ------------- | -------------------------------------------------------------------------- | | Framework | React Router v7 (SSR), React 19, Vite 6 | | Language | TypeScript (strict mode) | | Visualization | deck.gl, Viv, DuckDB-WASM, Apache Arrow | | Styling | Tailwind CSS, @cytario/design | | Auth | OAuth 2.0 via Keycloak, STS for S3 credentials | | Database | PostgreSQL (Prisma ORM), Redis/Valkey (sessions) | | Cloud | AWS SDK v3 (S3, STS) | | CI/CD | GitHub Actions, semantic-release, GHCR |

Plugin model

Cytario Web supports third-party plugins that contribute additional image formats to the viewer. A plugin is a regular npm package whose default export satisfies the @cytario/plugin-api contract (see packages/plugin-api/src/):

import type { CytarioPlugin } from "@cytario/plugin-api";

const plugin: CytarioPlugin = {
  name: "@vendor/my-loader",
  apiVersion: "^1.0.0",
  register(ctx) {
    // `extension` accepts a string, a string[] of aliases, or a RegExp
    // tested against the URL. See `FormatExtension` in @cytario/plugin-api.
    ctx.formats.register(["myext", "myext.gz"], {
      load: async (url, opts) => {
        const res = await opts.signedFetch(url, { signal: opts.signal });
        // …parse and return { data: Loader, metadata: Image }
      },
      fileTypeMeta: { label: "My Format", icon: "Microscope" },
    });
  },
};

export default plugin;

Loading. The set of plugins is fixed at build time. The CYTARIO_PLUGINS env var (comma-separated npm package names) drives a Vite codegen step that writes app/plugins.generated.ts; the host imports that module at startup, calls plugin.register(ctx) for each entry, and hands the plugin a PluginContext containing a scoped FormatRegistry and a Logger. A plugin can only register handlers under its own name — the registry rejects cross-plugin extension collisions.

Compatibility gate. Each plugin declares an apiVersion semver range, checked against the host's bundled @cytario/plugin-api version on bootstrap. Mismatched plugins are logged and skipped — the host keeps running.

Security boundary. All S3 traffic flows through the host-supplied signedFetch. Plugin-supplied headers pass through sanitizeHeaders (allowlist: Range, If-None-Match, Accept, Cache-Control; always denied: Authorization, Host, Cookie, x-amz-*) before being merged behind the signed headers, so a plugin cannot override the SigV4 signature or smuggle credentials.

Reference. Built-in OME-TIFF and OME-Zarr handlers in app/components/.client/ImageViewer/state/formats/builtins.ts are implemented against the same contract. A minimal stub plugin lives in __tests__/fixtures/noop-plugin/ and exercises the registry, the apiVersion gate, and the FILE_TYPE_REGISTRY auto-derivation.

Using @cytario/web as a package

@cytario/web can be installed as an npm dependency and assembled into a deployable container together with one or more format-handler plugins. This is how Cytario Enterprise Edition is built: the AGPL-licensed open core (@cytario/web) is bundled with proprietary plugins (for example, vendor-specific microscopy format loaders) to produce a single deployable image. Anyone with a plugin that satisfies the @cytario/plugin-api contract can follow the same pattern.

License obligation. @cytario/web is licensed under AGPL-3.0. Distributing or operating an assembly that includes it — including over a network as a service — triggers the AGPL's source-disclosure requirement: the complete corresponding source of the assembly (including any proprietary plugins linked into it) must be made available to its users under AGPL-3.0. If that is incompatible with your distribution model, a commercial license is available — contact us at cytario.com.

The consumer's job is to install the packages, set CYTARIO_PLUGINS, and invoke the bundled CLI:

# .npmrc — only required if any of the plugins ship from a registry
# other than public npm. The example below routes a hypothetical
# closed plugin to GitHub Packages while keeping @cytario/web and
# @cytario/plugin-api on public npm.
@your-org/closed-plugin:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GH_TOKEN}
npm install @cytario/web @cytario/plugin-api @your-org/my-plugin

# Production build with the bundled plugin set.
CYTARIO_PLUGINS=@your-org/my-plugin npx cytario-web build

# Dev server with HMR.
CYTARIO_PLUGINS=@your-org/my-plugin npx cytario-web dev

# Production server against the existing build/ output.
npx cytario-web start

CYTARIO_PLUGINS is a comma-separated list of npm package names. Each package's default export must satisfy the @cytario/plugin-api contract.

CLI

| Command | Behaviour | | ------------------- | -------------------------------------------------------------------------------------------------------- | | cytario-web build | Codegen (Vite plugin reads CYTARIO_PLUGINS) + react-router build against the installed package root. | | cytario-web dev | Codegen + react-router dev. Extra args (--port, --host, …) are forwarded. | | cytario-web start | NODE_ENV=production node server.ts against the bundled build/server/index.js. |

All subcommands operate against @cytario/web's own install directory; the consumer never needs to know the on-disk layout.

Reference Dockerfile

# syntax=docker/dockerfile:1.7
FROM node:24-slim AS build
WORKDIR /app
COPY .npmrc package.json package-lock.json ./
# BuildKit secret: GH_TOKEN is exposed as an env var only for this
# RUN. It is never written to a layer and never appears in `docker
# history` or `docker inspect`. Do NOT add `ARG GH_TOKEN` — that
# would bake the value into image metadata.
RUN --mount=type=secret,id=gh_token,env=GH_TOKEN \
    npm ci
ENV CYTARIO_PLUGINS=@your-org/my-plugin
RUN npx cytario-web build

FROM node:24-slim
WORKDIR /app
COPY --from=build /app .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["npx", "cytario-web", "start"]

Build the image with the BuildKit secret bound from your environment (or a file):

# Docker — environment source (e.g. CI runner with a GH_TOKEN secret):
GH_TOKEN=… docker build --secret id=gh_token,env=GH_TOKEN -t my-cytario-image .

# Docker — file source:
docker build --secret id=gh_token,src=./gh_token.txt -t my-cytario-image .

# Podman — only file source is supported (no env= flavor); write the
# token to a temp file first:
printf '%s' "$GH_TOKEN" > /tmp/gh_token && \
    podman build --secret id=gh_token,src=/tmp/gh_token -t my-cytario-image . && \
    rm -f /tmp/gh_token

The # syntax=docker/dockerfile:1.7 parser directive at the top of the Dockerfile is honored by Docker's BuildKit and silently ignored by Podman's native parser — the rest of the file (multi-stage, --mount=type=cache, --mount=type=secret) works on both engines.

Mixed-registry note. @cytario/web and @cytario/plugin-api publish to public npm. Plugins are free to publish wherever they like — public npm, GitHub Packages, or a private registry. Because a single scope-wide @cytario:registry directive cannot route both public-npm and GitHub-Packages packages under the same scope, consumers pin individual plugin packages with a per-package <pkg>:registry=… override as shown above.

License

This project is licensed under AGPL-3.0. The source code is publicly available to provide full transparency and ensure long-term access for our users, independent of Cytario as a company.


Local Development

Prerequisites

The application requires several backend services. A local cluster is provided via Podman:

cd devenv
podman kube play local-deployment.yaml

| Service | Port | Description | | ---------- | ---------- | -------------------------------- | | Keycloak | 8080 | Identity provider (admin/admin) | | MinIO | 9000, 9001 | S3-compatible object storage | | PostgreSQL | 5433 | Application database | | Valkey | 6379 | Session cache (Redis-compatible) |

To stop: podman kube down devenv/local-deployment.yaml

Getting Started

npm install
cp .env.template .env    # Pre-configured for the Podman cluster
npm run dev

Session Cache (Redis/Valkey)

Sessions hold OAuth access/refresh/ID tokens and short-lived STS credentials. TLS is required in production. The app refuses to boot when NODE_ENV !== "development" unless one of the following is true:

| Env var | Value | Meaning | | -------------------------------- | -------- | ---------------------------------------------------------------------------------------------------- | | REDIS_TLS | "true" | Wrap the ioredis connection in TLS (recommended). | | REDIS_CA_CERT | PEM | Optional CA bundle for self-signed deployments. Multi-line PEM string. | | REDIS_TLS_SERVER_NAME | hostname | Optional SNI / certificate hostname override. | | REDIS_INSECURE_ALLOW_PLAINTEXT | "true" | Explicit opt-out for trusted private networks. Logs a warning. Not for use on shared infrastructure. |

The local Podman cluster runs Valkey without TLS, which is allowed because NODE_ENV=development. Managed Valkey deployments (helm chart, AWS ElastiCache, etc.) should set REDIS_TLS=true. Valkey reuses the standard 6379 port for TLS when tls.enabled is set — it does not move the listener to 6380 and refuses plaintext on the same port — so leave REDIS_PORT at 6379 unless your provider explicitly publishes a separate TLS endpoint.

In the production cluster (see cytario-infrastructure, C-212) the Valkey leaf cert is signed by a cluster-internal CA managed by cert-manager. The CA's public cert is distributed to every namespace as a cytario-internal-ca ConfigMap by trust-manager, and the cytario-web helm chart's redis.caCertConfigMap.{name,key} wires it into the pod as REDIS_CA_CERT via valueFrom.configMapKeyRef. The app sees the PEM through the normal env var path — no code-side knowledge of the trust source is required.

Database

PostgreSQL with Prisma ORM. Connection configured via DATABASE_URL in .env.

npx prisma migrate dev --name <migration-name>   # Create + apply migration
npx prisma migrate deploy                         # Apply pending migrations
npx prisma studio                                 # Database GUI
npx prisma generate                               # Regenerate client

Testing

npm test              # Unit & component tests (vitest, watch mode)
npm run coverage      # Unit tests with coverage report

E2E tests (Playwright) live in a sibling repository and are triggered automatically on every PR via cross-repo dispatch.

Design System

To develop @cytario/design components locally and see changes reflected in cytario-web, run a single command. Assumes both repos are cloned as siblings (../cytario-design):

npm run dev:design

This links @cytario/design via npm link, starts tsup --watch in the design repo, and runs the cytario-web dev server — all in one process. Changes to design system source are rebuilt by tsup and picked up by Vite's HMR automatically.

The vite.config.ts is configured to handle the symlink: optimizeDeps.exclude skips pre-bundling, ssr.noExternal processes it through Vite's pipeline, and server.watch picks up changes in node_modules.

Note: Switching back to npm run dev automatically unlinks @cytario/design and restores the published version (via the predev script). No manual npm install needed.

Debugging

The app uses Zustand for state management with the devtools middleware. Install the Redux DevTools browser extension to inspect store state and actions.

Commits

Conventional Commits enforced via commitlint (feat, fix, docs, refactor, test, build, ci, chore, etc.).

Deployment

npm run build    # Outputs build/server and build/client
npm start        # Production server

For containerized deployments, see the Dockerfile. Database migrations run automatically on startup via docker-entrypoint.sh.

Acknowledgements

Built on Viv, a library for multiscale visualization of high-resolution, highly multiplexed bioimaging data on the web, developed by the HIDIVE Lab at Harvard Medical School.