G
YouTube TikTok Games
Skip to content

Repository files navigation

interbox-workspace

npm version Docker version Helm chart CI Docs

Reference pipeline for Interbox, an extensible integration engine. Fork this repo, adjust the pipeline for your own data, and deploy.

Quick start

cp .env.example .env   # fill in the license — see that file for options
docker compose up

Open http://localhost:3001 for the dashboard. The image is licensed: the pipeline stays paused until you set INTERBOX_LICENSE in .env, or activate through the dashboard itself (portal OAuth) — until then you'll see an activation screen there.

Send HL7v2 over MLLP to localhost:2575 and watch messages flow through to the FHIR server.

Send test traffic

If you don't have a real upstream to point at it yet, the bundled HL7v2 simulator generates synthetic traffic (ADT / ORU / SIU / MDM / RDE / RAS) from several independent sources at once — each with its own pace, MSH identity and MRN pool, so routing and per-source error handling get exercised rather than a single uniform stream. With the stack up:

bun run hl7v2:simulator

That installs the simulator and opens its UI on http://localhost:4003, already pointed at the MLLP port compose publishes. Click Start all and watch messages land in the dashboard.

See utils/hl7v2-simulator/ for targets, source types, fault injection, and the generator CLI. It's a self-contained package with its own dependencies — deliberately outside the root install, so it costs you nothing if you never use it.

Edit pipelines from the dashboard

The dashboard's Repository tab browses this checkout. It can also edit it — saving straight into the directory compose mounts, which the engine picks up within two seconds, so the loop is edit → save → running.

INTERBOX_WORKSPACE_ACCESS=apply in .env is what turns that on, and .env.example already sets it — so the Quick start above is all it takes. Skip the .env and the engine falls back to read (browse only) and the editor does not appear. Worth knowing what apply grants before you run this somewhere shared: the dashboard has had a login of its own since 1.20, but compose turns it off and publishes 3001 on every interface, so anyone who can open it can change the code this engine runs. To turn the login on, set INTERBOX_AUTH_ENABLED=true and an INTERBOX_AUTH_ADMIN_PASSWORD in .env; .env.example shows how.

Edits live in your browser until you press Save, and committing what you saved stays yours to do, in your own git. A deployment works the other way round: the engine owns a clone, so the dashboard commits and pushes a branch instead — see The Workspace in the docs for both, and for what each access level allows.

Deploy on Kubernetes

The dashboard has had a login of its own since 1.20; Securing Dashboard Access in the docs covers it. For a cluster deployment with the dashboard properly secured — internal ingress, TLS, and an SSO login gate in front of it — see k8s/:

  • k8s/k3d/ — a runnable, one-command reference on k3d (local). Stands up the full secure stack (ingress-nginx + cert-manager + oauth2-proxy + Dex) so you can see it work before a real cluster.
  • k8s/aks/ — production templates for AKS (internal LB, cert-manager DNS-01 / internal CA, Entra ID SSO, managed Postgres).

The Interbox Helm chart itself lives in HealthSamurai/helm-charts.

Configure the dashboard assistant

The dashboard's assistant is a Claude Code agent running inside the container, which has no ~/.claude of its own — so you hand it credentials through .env. It's optional: clear all three and the dashboard still works, the assistant just shows a "no credentials" banner instead of answering.

Path Set in .env Billing
Reuse host login (default; Linux/Windows dev) CLAUDE_CONFIG_DIR=${HOME}/.claude — bind-mounts your existing login into the container Your existing session
Subscription token (any OS) CLAUDE_CODE_OAUTH_TOKEN — run claude setup-token on the host (macOS: ./scripts/setup-claude-mac.sh does it for you) Claude Pro/Max subscription
API key ANTHROPIC_API_KEY=sk-ant-… Pay-per-token

Notes:

  • .env.example ships the reuse host login path enabled, so a fresh copy works on a host that already has Claude Code. It is .env-driven — no docker-compose.yaml edit.
  • That mount hands the container the whole config dir: your Claude settings, your session transcripts under projects/, and any MCP OAuth tokens the login file carries. Comment CLAUDE_CONFIG_DIR out to expose nothing — it then falls back to an empty managed volume — and use a token or API key instead.
  • A token or API key outranks the mounted login, so a leftover CLAUDE_CODE_OAUTH_TOKEN silently wins over your host session.
  • macOS keeps Claude Code creds in the Keychain, not in a file, so the reuse-host-login path can't work there — use the subscription token instead (./scripts/setup-claude-mac.sh mints one; needs the Claude Code CLI + a Pro/Max plan).

Author pipelines

Pipeline definitions for the Interbox engine. The engine has no built-in topology — it loads the pipelines declared here into its own runtime, and loads them again when they change, without a restart.

Pipelines are authored against @health-samurai/interbox. A pipeline is a chain: one source, any number of steps, one store, any number of steps, one sender. Every part is referenced by value, so each step's input is type-checked against the previous step's output. Secrets and deployment values are referenced with env() and resolved from the engine's environment, so definitions stay portable and secret-free.

The template's pipeline, in src/pipelines/hl7-to-aidbox/index.ts:

pipeline("hl7-to-aidbox")
  .source(mllpSource({ host: env("MLLP_HOST"), port: env("MLLP_PORT") }))
  .step((raws) => raws.withSearchText((raw) => raw))
  .store(postgresStore({ codec: "text" }), {
    index: { message_type: "btree", event_type: "btree", patient_id: "btree" },
    search: "trgm",
  })
  .step(hl7v2Parser())
  .step(v2ToFhir)
  .step(fhirItems())
  .step(skipUnchanged())
  .sender(
    aidbox({
      url: env("AIDBOX_URL"),
      auth: { kind: "basic", user: env("AIDBOX_CLIENT_ID", "root"), password: env("AIDBOX_CLIENT_SECRET") },
    }),
  );

The store keeps each MLLP message as raw text, and only then does the source answer AA, so a Retry after a parser fix parses the original again. After the store, hl7v2Parser() parses the message, v2ToFhir maps it to FHIR resources, fhirItems() keys each one for Aidbox, and skipUnchanged() drops a resource identical to the version already queued or delivered. v2ToFhir is the mapping step in src/mappers/v2-to-fhir/: it routes each message to its converter and adds one Provenance per message, targeting everything the message produced.

src/index.ts imports every pipeline module (for its registration side effect) and re-exports the registry the engine reads back:

import "./pipelines";
export { ChainRegistry } from "@health-samurai/interbox/chain";

See Pipelines and Steps, store and senders in the docs for the methods a step can use and what each part of the chain does.

How the engine loads it

The engine takes a single INTERBOX_WORKSPACE_GIT_URL, builds src/index.ts into one self-contained bundle, and imports that:

  • remote (git@…, https://…, ssh://…) — deploy. Clones the repo and bun installs it. INTERBOX_WORKSPACE_GIT_KEY supplies a token for private https; INTERBOX_WORKSPACE_GIT_REF picks the branch.
  • local — a file:// URL (or bare path) to a checkout. Bundles the live working tree in place (uncommitted edits included), with no clone or install, for a fast edit → hot-reload loop. The checkout must already have its deps installed.

The URL scheme only picks a default. INTERBOX_WORKSPACE_MODE sets the lifecycle: clone means the engine owns a copy it may fetch, check out and reset, while worktree, which docker-compose.yaml sets, means it bundles the directory as it stands and never checks anything out or resets anything.

A change reloads into the engine's own runtime; the engine is not restarted. It looks for changes every INTERBOX_WORKSPACE_POLL_MS (2 seconds in docker-compose.yaml), and Sync now in the dashboard's Repository tab reloads at once instead of waiting for the poll. The new workspace is built before the running pipelines are swapped for it, so a change that fails to load leaves the old pipelines running, and the dashboard shows why.

Develop

Working on this workspace locally needs Bun 1.4 or newer. Older Bun cannot read bun.lock: bun install ignores it, resolves every dependency again and rewrites it, and bun install --frozen-lockfile, as CI runs it, fails.

bun install
bun run typecheck
bun test

Tests live in test/ — bun test is scoped to that directory (see bunfig.toml), so a test file placed next to the code it covers will not run. The bundled simulator under utils/ is a separate package with its own dependencies and its own suite; see its README.

bun run e2e checks the pipeline end to end against a running stack: it sends an ADT^A01 and an ORU^R01 over MLLP, expects AA for each, and waits for the Patient and the glucose Observation to appear in Aidbox. It needs the stack from docker compose up with both licenses activated, since the pipeline stays paused until then. It reaches MLLP at localhost:2575, Aidbox at http://localhost:8080 as AIDBOX_CLIENT_ID / AIDBOX_CLIENT_SECRET, and the dashboard API at INTERBOX_PUBLIC_URL. Only those Aidbox credentials and INTERBOX_PUBLIC_URL are read from .env, like the stack's own settings. E2E_MLLP_HOST, E2E_MLLP_PORT and E2E_AIDBOX_URL point it elsewhere, and E2E_TIMEOUT_MS (90 seconds by default) is how long each check waits for its result.

Track upstream releases

In a copy of this repo, a daily job keeps one PR open from the upstream-sync branch that brings main up to the latest upstream release. Set the variable INTERBOX_UPSTREAM_SYNC=off to stop it. When the PR includes a release with breaking changes, it is labelled breaking-change and lists the upgrade notes in its description.

A release that changes CI definitions for the job's own platform, such as .github/workflows/ on GitHub or azure-pipelines/ on Azure, is never pushed by the job. On GitHub the job opens an issue instead, and on Azure the run fails. Both give you the command to run locally once you have looked at those changes. The jobs run the CLI version pinned in bun.lock, so that version only changes when you merge a sync PR.

A copy made before these files existed needs one sync from a local checkout, with the latest @health-samurai/interbox release as <version>:

bunx @health-samurai/interbox@<version> workspace sync

It pushes upstream-sync with your own git credentials and prints the PR link and description. On Azure, delete that branch once its PR completes, because the pipeline can only rebuild a branch it created.

GitHub

.github/workflows/upstream-sync.yml needs:

  • Allow GitHub Actions to create and approve pull requests turned on under Settings → Actions → General. If it's greyed out, an organization owner has to allow it in the organization settings first.
  • A ruleset on the default branch and on the branch the engine deploys from (INTERBOX_WORKSPACE_GIT_REF). It should require a pull request with at least one approval and Require approval of the most recent reviewable push, require the test and simulator checks, block force pushes and deletion, and give GitHub Actions no bypass.

The PR opens as a draft, and CI starts when you mark it ready for review. GitHub doesn't count checks from runs that the workflow token starts, so this is the run your required checks wait for. When the job pushes a newer release to the branch, the PR goes back to draft. In a repo without draft PRs it opens ready instead; close and reopen it to start CI.

Optional repository variables: INTERBOX_UPSTREAM_SYNC=off turns the job off, and INTERBOX_UPSTREAM_URL points it at another upstream.

Azure DevOps

azure-pipelines/ needs:

  • On the repo, Create branch and Contribute to pull requests for <Project> Build Service (<org>), and nothing more. Whoever creates a branch gets Contribute and Force push on it, which covers the sync branch.

  • Limit job authorization scope to current project for non-release pipelines, Protect access to repositories in YAML pipelines and Limit variables that can be set at queue time turned on under Project settings → Pipelines → Settings.

  • Both pipelines, created from a checkout of main:

    az pipelines create --name ci --yml-path azure-pipelines/ci.yml
    az pipelines create --name upstream-sync --yml-path azure-pipelines/upstream-sync.yml
  • Branch policies on main: ci as build validation, at least one reviewer, requestors can't approve their own changes, the most recent pusher can't approve, and votes reset on new pushes.

Here a release with breaking changes opens as a draft PR.

The same two variables work as pipeline variables. Define them as not settable at queue time.

Releases

Used by

Contributors

Languages