stable

relay

Transport-neutral frame dispatcher over a narrowed store profile — whole-vault replace and vault enumeration are absent from the type, not filtered at runtime.

@noy-db/in-relay · pnpm add @noy-db/hub @noy-db/in-relay@next Serve a vault to LAN or remote clients over any transport, with the dangerous store methods absent by construction.

What it does

createRelayHandler({ store }) returns (frame) => Promise<RelayResult> — it takes a decoded request frame and returns a decoded result, so you serve it over HTTP, a WebSocket, or QUIC without this package knowing which. The method vocabulary is fixed:

RELAY_METHODS // ['get', 'put', 'delete', 'list', 'loadAll', 'ping', 'listSince']

Its store is typed as NoydbRelayStore from @noy-db/hub/to — a narrowed store profile, not the full NoydbStore.

Two exclusions, two different reasons

absent methodwhy
saveAllwhole-vault replace — a relay that can be asked to overwrite a vault wholesale is a rollback superweapon aimed at the hosts trusting it
listVaultsenumeration is an existence leak — a relay serves vaults the caller already names, and never answers "what else is here"

The exclusions are structural. RELAY_METHODS is an allowlist by construction: those two names are absent because they are absent from NoydbRelayStore, not because they were filtered out. Adding either name to the list would not compile a call.

Why this is not in-rest with a narrower allow set.in-rest types its store as a full NoydbStore and gates dispatch with a runtime Set — correct for in-rest, whose job is to serve a whole store. Building a relay that way means handing the handler an object that carriessaveAll and trusting a Set not to call it: structural absence downgraded to a runtime allowlist, which is the one property a relay exists to have.

Unknown method, never forbidden

An excluded method is refused as 400 unknown, never 403 forbidden. A 403 would confirm the method exists and is merely disallowed here, which names the excluded surface to anyone probing.

Measured against the published 0.7.0-pre.16 — an excluded method and a name that never existed are indistinguishable:

get            → ok
saveAll        → status 400  UnknownRelayMethodError
listVaults     → status 400  UnknownRelayMethodError
totallyMadeUp  → status 400  UnknownRelayMethodError

That is the security posture, not a missing case. @doi-db/daemon reached the same conclusion independently in its own native implementation.

Usage

import { createRelayHandler } from '@noy-db/in-relay'
import { toFile } from '@noy-db/to-file'

const handle = createRelayHandler({ store: toFile({ dir: './vaults' }) })

// Over any transport you like — here, a bare fetch handler.
export default async function (req: Request): Promise<Response> {
  const frame = await req.json()
  const result = await handle(frame)
  return Response.json(result, { status: result.ok ? 200 : result.status })
}

RelayResult is a discriminated union: { ok: true, id, value }, or { ok: false, id, status: 400 | 500, error: { name, message } } — a transport maps status directly. Since 0.10.0-pre.2 a store failure reaches the client as the message 'store error': the original message is kept only for a 501 and for ConflictError, so an internal error detail no longer leaks to the caller.

Install note

Through 0.7.0-pre.16 this package's latest dist-tag pointed at a pre-release, so a plain npm i @noy-db/in-relay silently moved hub onto the pre line (noy-db#1293). That ended when stables shipped: latest is a stable release, and @noy-db/in-relay@next is the pre line — install the one that matches your hub.

See also

  • in-* family
  • rest — the full-store HTTP handler this deliberately is not
  • stores — the store contract NoydbRelayStore narrows