Skip to Content

HTTP bridge

registerHttpBridgeRoute connects a catalogued fnKey to a synthetic in-process HTTP request on your Express app. When the bridge is active, executeRPC does not call originalFn directly; it sends method + path with a JSON body built from RPC args, using invokeExpressSynthetic.

This does not replace normal HTTP. Browsers and clients still call your public URLs the same way. The bridge is an extra path used when QA (or tests) invoke that fnKey over RPC or the inspector WebSocket.

Why use it

Default RPC calls the registered function reference (class method or cat() wrapper) with positional args. That skips anything that only runs inside Express: global app.use middleware, express.json(), route-level middleware, auth that reads req, and the exact req.body shape your route expects.

Reasons to opt in:

  • You want invoke to prove the same code path as POST /api/... (middleware + handler), not a shortcut into the controller.
  • Your handler is (req, res)-shaped; driving it through HTTP matches production more closely than calling a separate service function.
  • You keep a single allowlisted fnKey in the catalog for QA, while still validating the HTTP response (status, headers, parsed body) returned from the route.

Benefits

  • Full Express pipeline for that route: whatever is mounted on app for method + path runs (including multi-handler stacks from registerCatPipeline).
  • In-process, no deploy: synthetic invoke uses a local ephemeral port; good for CI and local tooling.
  • Stable RPC surface: QA still sends fnKey + positional args; mapArgsToBody is your adapter to the JSON body your route reads.
  • Optional JSON Schema: registry paramsJsonSchema / returnJsonSchema still apply around the bridge path when configured.

How this fits with registerCatPipeline

PieceRole
registerCatPipelineMounts handlers on a router, sets pipelineId / pipelineIndex, and wires inspector behavior. Defines the HTTP stack for that route.
registerHttpBridgeRouteDoes not mount routes. It only says: for this fnKey, when executeRPC runs, issue one synthetic HTTP request to the given method + path on the given app.

Use both when you want the route defined with registerCatPipeline and RPC invokes to hit that same URL. The bridge never receives an array of middleware; it inherits whatever Express already dispatches for that path.

mapArgsToBody

executeRPC passes args as a positional array (same arity as the registry entry’s params).

  • args[0] — first RPC argument
  • args[1] — second argument
  • and so on

mapArgsToBody converts that tuple into the JSON object sent as the request body (after JSON.stringify inside synthetic invoke). Your Express route should read req.body in the same shape you return from mapArgsToBody.

Example: executeRPC({ fnKey: 'Orders.Create', args: ['user-42', 'SKU-9'] }) with
mapArgsToBody: (args) => ({ userId: args[0], sku: args[1] })
produces body { "userId": "user-42", "sku": "SKU-9" }.

Full example

Express route registered with registerCatPipeline; RPC invokes go through HTTP via the bridge.

import express from 'express' import { cat, registerCatPipeline, registerHttpBridgeRoute, executeRPC, resetInspectorState, } from '@gloocan/cat-inspector' resetInspectorState() const app = express() app.use(express.json()) // Handler reads req.body — same shape mapArgsToBody produces const createOrder = cat( 'Orders.Create', (req: express.Request, res: express.Response) => { const { userId, sku } = req.body as { userId?: string; sku?: string } res.status(201).json({ ok: true, userId, sku }) }, ) const router = express.Router() registerCatPipeline(router, 'post', '/api/orders', [createOrder]) app.use(router) // RPC uses positional args: [ userId, sku ] → body { userId, sku } registerHttpBridgeRoute(app, { fnKey: 'Orders.Create', method: 'post', path: '/api/orders', mapArgsToBody: (args) => ({ userId: args[0], // first RPC argument sku: args[1], // second RPC argument }), }) // Later, from QA / tests: const resp = await executeRPC({ requestId: 'r1', fnKey: 'Orders.Create', args: ['user-42', 'SKU-9'], }) // On success, result includes HTTP metadata from the bridge path, e.g. { http: { statusCode, headers, body } }

For a pipeline of middleware plus controller, list every handler in registerCatPipeline as usual; a single registerHttpBridgeRoute to that same path still runs all of them, because Express invokes the full stack for one request.

registerHttpBridgeRoute

function registerHttpBridgeRoute( app: Express, spec: { fnKey: string method: HttpMethod path: string mapArgsToBody: (args: unknown[]) => unknown }, ): void

Preconditions

  • spec.fnKey must already exist in the registry (registered via cat, catModule, or @Cat).
  • Calling registration sets the entry’s invokeKind to 'http_synthetic'.

Effect

executeRPC will no longer call originalFn directly for that key. Instead it serializes args through mapArgsToBody and runs an internal invokeExpressSynthetic against app.

clearHttpBridgeRegistry

Removes all bridge specs. Useful in tests (see testing-resets).

HttpBridgeSpec

type HttpBridgeSpec = { app: Express method: HttpMethod path: string mapArgsToBody: (args: unknown[]) => unknown }

This type describes the stored configuration; hosts normally use registerHttpBridgeRoute rather than constructing specs manually.

Errors

  • Unknown fnKey → throws at registration time.
  • Duplicate registration for the same fnKey → throws.
  • At invoke time, misconfigured bridges yield HTTP_BRIDGE_NOT_CONFIGURED-style RpcResponse errors when the spec is missing.

See also