Skip to content
Fusion

Packages

The Fusion stack is a polyrepo of independently published @tikab-interactive/fusion-* packages. Each lives in its own repo, is versioned on its own, and can be installed without pulling in the rest. A product app composes whichever it needs.

This page covers the tier-1 leaf packages — the ones with no internal dependency on another fusion-* package, so they can be adopted in any order. Eight more leaf packages recently joined the set alongside fusion-gis, and every one of them is exercised by a sandbox in the example app (/sandbox/<name>) so you can see it run before wiring it into a real feature.

For where these sit in the dependency graph, see Architecture; for the conventions every package follows, Conventions.

Run-anywhere vs. needs-a-service

The split that matters in practice is whether a package needs an external service. A leaf with no infra runs the moment you install it; the rest degrade gracefully until you point them at a backend (the sandbox shows the "off" state too).

Loading diagram...

The leaf packages

PackageWhat it isRunsNeeds
fusion-metricsPrometheus metrics (prom-client)servernothing (METRICS_ENABLED)
fusion-cacheValkey cache + Better Auth secondaryStorageserverValkey/Redis (VALKEY_URL)
fusion-jobsHatchet background-job enqueue seamserverHatchet (HATCHET_CLIENT_TOKEN)
fusion-realtimeCentrifugo pub/sub — mint token + subscribeserver + /clientCentrifugo (CENTRIFUGO_*)
fusion-collabYjs CRDT + ProseKit collaborative editingbrowsery-websocket (optional)
fusion-edge-ssoTrusted reverse-proxy SSO header resolutionisomorphicnothing
fusion-ldapLDAP/AD directory logic + group→project synclogic / ./ldaptsLDAP/AD (live adapter only)
fusion-import-exportCSV import plan + export + preview tableiso + browsernothing
fusion-gisSWEREF 99 transforms + MapLibre maplogic / browsernothing

Each package ships raw TypeScript and follows the graceful-degradation contract: a feature no-ops (or returns a "not configured" result) until its env var is set, so the app always builds and runs.

  • fusion-metrics — process + HTTP metrics for a /api/metrics endpoint, via prom-client. metricsText() returns the exposition document; server-only so prom-client stays out of the browser bundle. Off unless METRICS_ENABLED=1.
  • fusion-cache — a small JSON cache (cacheGet/cacheSet/cacheTtl) over Valkey (a Redis fork; it wraps iovalkey), plus getSecondaryStorage() for Better Auth's cross-instance rate limits. Enabled by VALKEY_URL; otherwise reads miss and the app runs uncached.
  • fusion-jobs — splits the enqueue seam (isJobsEnabled(), the lightweight . entry) from the heavy Hatchet gRPC client (getHatchet() behind ./hatchet, dynamically imported) so the web bundle never pulls in the SDK. Needs a Hatchet engine + HATCHET_CLIENT_TOKEN.
  • fusion-realtime — the server mints a short-lived Centrifugo JWT (issueConnectionToken) and publishes (publish); the browser subscribes via the separate ./client entry (subscribe, wraps centrifuge). Needs a Centrifugo server + CENTRIFUGO_TOKEN_HMAC_SECRET and CENTRIFUGO_API_KEY.
  • fusion-collabcreateCollabSession() gives a Yjs document + awareness; defineCollab() binds it to a ProseKit editor (sync, collab-undo, remote cursors). A y-websocket URL is optional — with none it's a local-only doc, so the sandbox works offline and "turns on" sync when you pass a URL.
  • fusion-edge-sso — pure, zero-dependency logic that decides whether a request truly came through the trusted edge proxy (isTrustedRequest) and resolves the forwarded SSO identity (resolveEdgeIdentity) from Kerberos/SPNEGO or smartcard headers. The proxy does the real auth; this is the app-side trust check. Beyond the sandbox, the example wires it into a real trusted-header sign-in — see Enterprise SSO.
  • fusion-ldap — reads what an LDAP/AD directory knows about a user and computes AD-group→project membership diffs (planProjectMemberships). The . entry is pure logic with an InMemoryDirectory fake; the live network adapter (createLdaptsDirectory, wraps ldapts) is the separate ./ldapts entry. The example runs this diff on edge sign-in to reconcile project memberships (Enterprise SSO).
  • fusion-import-export — RFC-4180 parseCsv/toCsv, a typed two-phase import (planImport → classify each row new/update/skip/error → preview → execute), and the ImportPreview diff-table React component. The parse/plan halves are isomorphic; download + preview are browser.
  • fusion-gis — explicitly-named SWEREF 99 ⇄ WGS84 transforms (swerefToWgs84, wraps proj4) plus a MapLibre MapView React component (tile source is a prop, for air-gapped/self-hosted tiles). The transforms are server-safe; the map is browser-only.

Database-backed packages

Four more @tikab-interactive packages are not leaves — they import fusion-db's schema and take the app's injected createDb client (no DB env of their own), so they sit one tier up. They need only Postgres. The first three have a sandbox; fusion-search powers the live universal search:

PackageWhat it isTry it
fusion-audit"who did what, when" trail — recordAudit / listAudit/sandbox/audit
fusion-settingsruntime key/value app settings an admin can change without redeploy/sandbox/settings
fusion-maileroutbox-backed mailer — records every email, delivers via SMTP (SMTP_HOST) if set/sandbox/mailer
fusion-searchuniversal search — one owner-scoped index, FTS + trigram + vector retrieval fused with RRF/search

They're server-only (each stubs its browser entry) and best-effort where it makes sense (recordAudit and sendEmail never throw). fusion-search degrades to FTS + trigram with no embedder configured, so it works air-gapped too — see Universal search. See Architecture for how they fit the dependency tiers.

The apps package

The four project apps — ProcessN, ProtokollN, NyhetN, WikiN — don't live in the example app. Their logic ships as one published package, @tikab-interactive/pulsn-apps, with one subpath export per app (imported as @tikab-interactive/pulsn-apps/processn, and so on), plus …/errors.

SubpathAppWhat's in it
…/processnProcessNschema, permissions (typescript-rules), row-filters, server logic, cards
…/protokollnProtokollNschema, hierarchy-aware permissions, recursive-CTE filters, logic, cards
…/nyhetnNyhetNschema, feed read + Swedish-FTS search
…/wikinWikiNschema, handbook read + collaborative edit + Swedish-FTS search

There are no relations between the internal apps — each app's project is independent, scoped only by a projectKey. PulsN holds the canonical project (the "building": key, name, coordinates) and points to each app's project; that hub (pulsn_project) is a product concern owned by the consumer, not by any app. So a PulsN project maps to a ProcessN project, a ProtokollN project, and so on — but ProtokollN is never derived from ProcessN.

The package is logic only — no transport, no UI, no db singleton. Every function takes an explicit { db, reindex } context plus the caller's id; the consumer wraps it in a createServerFn (mapping the package's PermissionError → 403) and renders the result with fusion-ui (which owns every component). That keeps the package framework-agnostic and fully testable on its own: its suite spins up an ephemeral Postgres and exercises the real SQL — Swedish FTS, recursive-CTE visibility, the permission tree — with zero dependency on the app. Flatbox is a separate app; ChattN/Carola stays in the example for now.