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

@claxedo/workspace-relay

v0.5.1

Published

The Claxedo workspace relay — the single canonical tunnel process between cloud-hosted browsers and workspace-runtime hosts, whether the host is a cloud VM or a user laptop. One package, one process, one config surface — there is no separate `dev-relay` c

Readme

@claxedo/workspace-relay

The Claxedo workspace relay — the single canonical tunnel process between cloud-hosted browsers and workspace-runtime hosts, whether the host is a cloud VM or a user laptop. One package, one process, one config surface — there is no separate dev-relay codepath.

Production v1 is deliberately single-instance for user-hosted traffic. The relay keeps host presence and host-tunnel sockets in process-local maps, and a hostId has exactly one active tunnel at a time. Deploy one active relay process per user-hosted relay fleet; horizontal scaling needs a future routing owner for sticky host tunnels, split-brain prevention, and failover. A relay process, VM, or region failure drops existing user-hosted HTTP, WebSocket, SSE, and PTY sessions until workspace runtimes reconnect.

Deployment Security

The relay is a public edge service for workspace traffic, but it is not the identity provider or policy database. It verifies short-lived runtime access tokens, calls the configured target resolver/revocation callback, and forwards accepted traffic to the selected host with a freshly minted relay-host token.

| Token | Default TTL | Issuer | Audience | Purpose | | --- | ---: | --- | --- | --- | | Runtime Access Token (RAT) | 30 minutes | claxedo-control-plane | workspace-relay | User/browser authorization to reach one workspace and host through the relay. | | Host Tunnel Token (HTT) | 5 minutes | claxedo-control-plane | workspace-relay-host-tunnel | Workspace runtime authorization to register a user-hosted tunnel. | | Relay Host Token (RHT) | 60 seconds | workspace-relay | workspace-host-service | Per-request relay-to-host authorization minted after RAT validation. |

Runtime access tokens and host tunnel tokens bind issuer, audience, subject, workspace id, host id, expiry, issue time, and JTI. Runtime access tokens also bind role. Relay-host tokens additionally bind the deployment pair: cloud/cloud-vm or user-hosted/local-worktree.

Revocation And Active Checks

isRuntimeAccessTokenActive is the revocation/target freshness hook. Production deployments should implement it by calling the control plane or an equivalent authority on every new HTTP request and WebSocket upgrade. A false result rejects before forwarding to the host.

Long-lived relayed sockets are authorized at establishment time. Already-open SSE, PTY, and WebSocket streams may live until normal close, reconnect, relay drain, host disconnect, or process restart. If immediate stream revocation is a requirement, the control plane must also close the session/runtime channel.

Forwarding Boundary

The relay strips client-supplied x-forwarded-for, x-forwarded-host, x-forwarded-proto, x-real-ip, x-claxedo-internal-*, and x-supervisor-* headers. It replaces Authorization with an RHT, sets x-workspace-id, and adds x-forwarded-by: workspace-relay.

For user-hosted targets, Cookie is stripped before forwarding. User-hosted workspace processes may run near a developer's local browser cookie jar, so browser cookies must not be passed through to the local host service. Cloud VM targets may receive cookies when the caller intentionally sends them.

CORS

The relay owns CORS responses for browser-facing workspace requests. Do not forward upstream CORS headers as the source of truth. Add allowed product origins in the relay/server CORS configuration and keep wildcard origins out of credentialed deployments.

Metrics

GET /metrics is privileged operational data. In production, set CLAXEDO_RELAY_METRICS_TOKEN and scrape with Authorization: Bearer <token>. When no metrics token is configured, the endpoint only allows callers that the adapter identifies as loopback; if no remote-address resolver exists, it fails closed.

User-Hosted Topology

Production v1 supports one active relay instance for user-hosted traffic, or a load balancer with strict stickiness that keeps each hostId on the relay process that owns its tunnel socket. Non-sticky horizontal scaling needs an external directory and tunnel routing owner before it is safe to advertise as durable.

Why one package, no parallel impl

In the fa9cabf9 design pass the working assumption was clarified:

"I think this creates a parallel implementation problem — why is there no devmode in the prod relay?"

So this package is the relay. Local-dev configuration is selected by environment variables, not by a sibling dev-relay.ts module. If a future cycle re-introduces a separate dev-relay.* file in packages/claxedo-server/, that should be flagged as a regression.

Public surface

Re-exported from src/index.ts:

| Concern | Module | Notable exports | | --- | --- | --- | | Token issuance / verification | src/auth.ts | mintRuntimeAccessToken, verifyRuntimeAccessToken, mintRelayHostToken, verifyRelayHostToken, mintHostTunnelToken, verifyHostTunnelToken, types RelayRole, RelayAccess, RelayBacking, RelayJwtAlgorithm, RuntimeAccessTokenClaims, RelayKey, RelayKeyResolver, error class WorkspaceRelayAuthError | | Hono HTTP surface | src/server.ts | createWorkspaceRelay, authorizeWorkspaceRelayRequest, types WorkspaceRelayOptions, WorkspaceRelayTarget, RuntimeAccessTokenActiveResult, WorkspaceRelayAuditEvent, WorkspaceRelayMetricsSources, RelayHostPublicKey | | Active-host directory | src/directory.ts | createWorkspaceRelayDirectory, disposeWorkspaceRelayDirectory, types WorkspaceRelayDirectory, HostTunnelPresence | | Bun-specific server bootstrap | src/bun.ts | createWorkspaceRelayBun, type WorkspaceRelayBunOptions, WorkspaceRelayBunDrainController, metrics getFragmentationStats, getSlowConsumerStats |

Wire types live in the sibling package @claxedo/workspace-relay-protocol (TUNNEL_PROTOCOL_VERSION, TunnelMessage, isTunnelMessage, makeTunnelPong). Keep that split — it lets non-Node consumers implement the tunnel protocol without pulling Hono and Jose.

Configuration

All knobs are environment variables. The relay refuses to boot in production if CLAXEDO_RELAY_RESOLVER_TOKEN is missing — the production fail-closed gate at src/main.ts.

| Env var | Purpose | | --- | --- | | CLAXEDO_RELAY_BIND_HOST, CLAXEDO_RELAY_BIND_PORT | Listening socket. Loopback by default. | | CLAXEDO_RELAY_PUBLIC_URL | Externally-resolvable URL the relay advertises in tokens. | | CLAXEDO_RELAY_RESOLVER_URL | Where to fetch RuntimeAccessTokenActiveResults (typically claxedo-server/internal/relay/target). | | CLAXEDO_RELAY_RESOLVER_TOKEN | Bearer token the relay sends to the resolver. Required in production. | | CLAXEDO_RELAY_JWKS_URL | Optional remote JWKS the relay uses to verify runtime-access tokens. | | CLAXEDO_RUNTIME_ACCESS_TOKEN_PUBLIC_KEY_PEM | Inline public key (alternative to JWKS). | | CLAXEDO_RELAY_HOST_VERIFY_PEM | Public PEM the relay uses to verify host-tunnel tokens. | | CLAXEDO_RELAY_HOST_SIGNING_KEY_PEM | Private PEM the relay uses to mint relay-host tokens. | | CLAXEDO_RELAY_METRICS_TOKEN | Optional bearer token for /metrics. Without it, /metrics requires a trusted loopback remote-address resolver. | | CLAXEDO_RELAY_DRAIN_TIMEOUT_MS | Graceful shutdown wait before force-closing sockets. Defaults to 30000. | | NODE_ENV | production / development / test. Switches the production fail-closed gate. |

Token verification is pluggable today via the RelayKey | RelayKeyResolver pair on verifyRuntimeAccessToken and friends, and via the unified TokenVerifier interface in @claxedo/workspace-relay-protocol. A custom verifier is only the crypto/introspection authority; the relay still validates its output into RuntimeAccessTokenClaims, binds the URL workspace id to the claims, applies revocation, and allowlists roles. Missing, unknown, or malformed roles deny.

To swap the relay's auth backend in a self-hosted deployment:

import { createStaticTokenVerifier } from "@claxedo/workspace-relay-protocol"

const verifier = createStaticTokenVerifier({
  tokens: {
    "tok-tenant-1": { subject: "u1", scopes: ["workspace:write"], claims: {} },
  },
})
// Pass `verifier` as `WorkspaceRelayOptions.tokenVerifier`.

The TokenVerifier interface intentionally contains only verify(token). JWKS fetching, audience binding, and replay caches belong to the implementation. Two reference implementations ship in the protocol package: createHttpTokenVerifier for remote verifiers (Clerk-style introspection, custom OIDC), and createStaticTokenVerifier for tests and self-hosted single-tenant setups.

createHttpTokenVerifier is a reference implementation. Its HTTPS endpoint is the crypto authority and must enforce issuer, audience, expiry, key selection, and replay policy before returning claims. Treat the endpoint as trusted operator configuration, not as tenant/user input.

Long-lived relayed sockets are authorized at establishment. Revocation is checked for new HTTP requests and WebSocket upgrades; already-established WebSocket/SSE/PTY streams may live until their normal close, reconnect, relay drain, or process restart.

Tunnel Lifecycle

Host-tunnel reconnects replace the old socket deterministically. Replacement cleans the old socket's pending HTTP responses, child WebSocket channels, heartbeat timer, and buffered work before installing the new socket. Stale close events identity-check the current owner before deleting presence, so an old socket cannot mark a replacement offline.

Relay drain sets /health unhealthy, rejects new workspace requests and tunnel registrations with 503 relay_draining, closes active host tunnels so runtimes reconnect promptly, waits for pending work up to the configured timeout, then stops the server. Truly uncaught exceptions and unhandled rejections are fatal: the relay marks itself draining, stops accepting work, disposes timers, and exits for supervisor restart.

External Directory Design

The current WorkspaceRelayDirectory is an in-memory implementation of the directory contract:

type WorkspaceRelayDirectory = {
  registerHostTunnel(input: { hostId: string; workspaceIds: string[] }): HostTunnelPresence
  recordPong(hostId: string): HostTunnelPresence | undefined
  disconnectHost(hostId: string): void
  activeHost(input: { hostId: string; workspaceId: string }): HostTunnelPresence | undefined
  sweep(): void
  dispose(): void
  size(): number
}

A Redis, Durable Object, or equivalent implementation should keep the same semantics:

  • one active owner for a hostId;
  • TTL extension on heartbeat pong;
  • immediate removal on disconnect;
  • workspace membership checks before user-hosted forwarding;
  • split-brain prevention when a replacement tunnel connects;
  • observability for active host count and stale owner cleanup.

The missing piece for true multi-instance user-hosted relay is not just durable presence storage. HTTP/WebSocket/SSE/PTY traffic must also route to the process or durable object that owns the live tunnel socket for that hostId.

Routing

The relay has no /w/{workspaceId}/* URL prefix of its own. The gateway pattern lives in claxedo-server:

  • The user's browser calls /api/claxedo/....
  • claxedo-server mounts workspaceRuntimeProxy middleware (packages/claxedo-server/src/proxy.ts:205) on its top-level Hono app at packages/claxedo-server/src/server.ts:93.
  • The proxy resolves the active workspace target via internal-relay.ts (control-plane auth — see "Seam: internal-relay vs workspace-relay" below), strips its own prefix, and forwards to the relay's tunnel endpoint.

If you are adding a new HTTP-level workspace surface, attach it to claxedo-server's gateway, not to workspace-relay. The relay is intentionally narrow — it only handles tunnel traffic and the auth checks that gate it.

Seam: internal-relay vs workspace-relay

| | claxedo-server/src/routes/internal-relay.ts | @claxedo/workspace-relay | | --- | --- | --- | | Layer | Control-plane auth | Tunnel transport | | Trust | Authenticates the relay process itself (loopback or bearer) | Authenticates the user's runtime-access token | | Routes | GET /internal/relay/target, GET /internal/relay/revocation | WS upgrade + tunnel framing | | Owners | claxedo-server (depends on Convex authority, Clerk, audit log) | workspace-relay (no Convex/Clerk dependency) | | Lives in | packages/claxedo-server/ (server-side only) | packages/workspace-relay/ (own process) |

This split is deliberate: the relay never reads Convex or Clerk directly. It calls back to claxedo-server over HTTP for resolver decisions. The decision data crosses the seam as RuntimeAccessTokenActiveResult (defined in server.ts).

Development

bun --cwd packages/workspace-relay dev   # hot-reload main.ts
bun --cwd packages/workspace-relay test  # run all *.test.ts
bun --cwd packages/workspace-relay typecheck

The TS error baseline for this package is 0 — keep it that way.

Boundary rule

Consumers outside this package import @claxedo/workspace-relay only — not @claxedo/workspace-relay/src/.... Direct deep-source imports break the package boundary. The check is

grep -rn "workspace-relay/src/" packages/claxedo-{server,app}/src

— required to return zero hits.