Skip to content
WIP

This guide is a work in progress. Barry is also a work in progress.

@barry-rocks/sdk/services/http

Scaffolding for a bag’s own HTTP service: serve, applyBaseMiddleware, applyAuth for Barry’s local-secret check, request and error loggers, and camelResponse. It gives a service the same authentication and logging every built-in service uses.

import { … } from "@barry-rocks/sdk/services/http";
Property Type Description
rateLimitPerSecond? number Requests per second per IP. The SPA fires a burst on page load.

Property Type Description
transport? "stdout" | "stderr" | "silent" Where to write. The MCP server MUST pass “stderr”: stdout is reserved for the MCP protocol there, so request logs written to it are both invisible in the service’s error log and mixed into a stream a client is parsing.

child(context): Logger

Parameter Type
context LogContext

Logger

debug(msg, context?): void

Parameter Type
msg string
context? LogContext

void

error(msg, context?): void

Parameter Type
msg string
context? LogContext

void

flush(): Promise<void>

Promise<void>

info(msg, context?): void

Parameter Type
msg string
context? LogContext

void

warn(msg, context?): void

Parameter Type
msg string
context? LogContext

void


Property Type Description
policy (request) => RequestPolicy | null -
timeoutMs? number -
verifyCredential (credential, signal) => unknown The principal it vouches for, or a promise of it.

The HTTP scaffolding every Barry service puts in front of its routes.

Extracted when bags/sessions/api split into session and identity services. It is shared rather than copied because these are SECURITY defaults — auth, CORS, the CSRF content-type guard, rate limiting — and a copy that drifts is a service that is quietly less protected than its sibling while looking identical from outside.

Order is load-bearing and matches what bags/sessions/api had: security headers, body parsing, rate limit, CORS, then auth, then the CSRF guard. Anything a service wants to expose publicly (health, webhooks) must be registered on the app BEFORE calling applyAuth.

bags/vault is the deliberate exception and must NOT be converted onto this path. It runs under its own _barryvault UID specifically to defend against same-UID processes — the other Barry services included — and this path admits anyone holding the instance’s BARRY_SECRET, which every same-UID process on this machine can read. A vault that accepted the platform’s ambient BARRY_SECRET, or trusted loopback, would be readable by exactly the processes the UID boundary was created to exclude. Its own JWT issuance and per-account bearer tokens are the feature. If a sweep flags it as the last hand-rolled server, that sweep is wrong and this paragraph is why.

Property Type Description
context? Record<string, unknown> Additional context to include in all logs
skip? (req) => boolean Skip logging for certain paths (e.g., health checks)
transport? "stdout" | "stderr" | "silent" Where to write. Defaults to the logger’s own default (stdout). The MCP server MUST pass “stderr”: stdout is reserved for the MCP protocol there, and request logs written to it are both invisible in the service’s error log and mixed into a stream a client is parsing.

Property Type
authorize (principal, request, signal) => boolean | Promise<boolean>
scopes readonly ("sessions:read" | "sessions:write" | "sessions:turn" | "sessions:approve" | "events:read" | "events:write" | "events:subscribe" | "actions:read" | "actions:dispatch" | "tools:invoke" | "identities:read" | "artifacts:read" | "artifacts:write" | "artifacts:share")[]

Property Type
body? ZodType<unknown, unknown, $ZodTypeInternals<unknown, unknown>>
method "get" | "post" | "put" | "patch" | "delete"
operationId string
params? ZodObject<$ZodLooseShape, $strip>
path string
query? ZodObject<$ZodLooseShape, $strip>
responses Readonly<Record<number, z.ZodType | null>>
sensitive? boolean
stream? boolean

CreateLogger = (service, options?) => Logger

Parameter Type
service string
options? CreateLoggerOptions

Logger


LogContext = Record<string, unknown>

Arbitrary structured context attached to a log line.

applyAuth(app, authorization?): void

Authentication and the CSRF guard.

Call AFTER registering any public routes (health, webhooks): this rejects everything that has not already been handled.

Parameter Type
app Express
authorization? RequestAuthorizationOptions

void


applyBaseMiddleware(app, options?): void

Everything that runs BEFORE authentication.

Call this first, then register public routes, then applyAuth.

Parameter Type
app Express
options BaseMiddlewareOptions

void


camelResponse(body): unknown

Parameter Type
body unknown

unknown


createErrorLogger(service): ErrorRequestHandler

Error logging middleware Place after your routes to log errors

Parameter Type
service string

ErrorRequestHandler

app.use(routes);
app.use(createErrorLogger("web"));

createRequestLogger(service, options?): RequestHandler

Express middleware for request logging

Parameter Type Description
service string The service name
options RequestLoggerOptions Optional configuration

RequestHandler

import express from "express";
import { createRequestLogger } from "./index.js";
const app = express();
app.use(createRequestLogger("web", {
skip: (req) => req.path === "/health",
}));
app.get("/api/users", (req, res) => {
req.log.info("Fetching users", { userId: req.params.id });
// ...
});

serve(router, base, options?): object

Parameter Type
router Router
base string
options { casing?: "camel" | "verbatim"; }
options.casing? "camel" | "verbatim"

object

Name Type
delete() (declaration, …handlers) => void
get() (declaration, …handlers) => void
patch() (declaration, …handlers) => void
post() (declaration, …handlers) => void
put() (declaration, …handlers) => void
route() (declaration, …handlers) => void

servedRoutes(router): readonly Route[]

Parameter Type
router Router

readonly Route[]


setLoggerFactory(factory): void

Replace the logger factory, as a test does to record what is logged.

Parameter Type
factory CreateLogger

void


templatePath(path): string

Replace identifier-looking path segments with :id.

Without this every session id is its own route: the live logs show three ids producing three “endpoints” for the same handler, which makes a top-routes panel meaningless and would blow up any summarize by path aggregation.

The rules are deliberately narrow. Over-templating is its own failure — if a real route segment collapses to :id the panel silently groups unrelated endpoints and the numbers stop meaning anything. So a segment is only templated when it is shaped like an identifier and NOT like a word:

  • all digits (/identities/42)
  • a UUID
  • nanoid-shaped: 16+ chars from the nanoid alphabet, AND containing a digit, - or _, or mixed case. messages and available are long lowercase words and stay; F_1aL9OLQsKGNCZNxxyC9 goes.
Parameter Type
path string

string

Re-exports corsOrigins


Re-exports ENV_RULES


Re-exports EnvRule


Re-exports EnvValidationResult


Re-exports getHostingConfig


Re-exports getServicePort


Re-exports getServiceUrl


Re-exports HostingConfig


Re-exports originAllowed


Re-exports ServiceName


Re-exports validateEnv