Skip to content

Commit de389ab

Browse files
Nick Ficanoclaude
andcommitted
docs(middleware-frameworks): document Effect-migration scope decision for node, bun, express, fastify, hono
All five framework adapters (@arcp/node, @arcp/bun, @arcp/express, @arcp/fastify, @arcp/hono) are thin wrappers over the legacy onTransport(legacyTransport, req) callback contract. Node, Express, Fastify, and Hono all delegate to attachArcpUpgrade from @arcp/node; Bun terminates its native Bun.serve socket inside BunWebSocketTransport before the callback fires. None hold scoped runtime state, none own a concurrent resource graph, and none have an error pipeline that benefits from Effect.Layer composition. The onTransport callback already gives Effect-graph consumers full dispatch control — they wire it into their own ManagedRuntime via runtime.runFork(...). Adding dedicated effect twins here would re-wrap the existing helpers and force a runtime effect dependency on every middleware package without changing any observable behavior or unlocking new composition. acceptSessionEffect remains the right call-site only for already-Effect-shape transports (stdio pipes, custom TransportEffect factories), not for these adapters' socket-owned transports. The 2 fastify + 2 bun safety-net tests and the 45 SDK integration tests remain green. Closes #48 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5c6cd1e commit de389ab

5 files changed

Lines changed: 110 additions & 0 deletions

File tree

packages/middleware/bun/src/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,45 @@
1+
// Implementation note (Effect migration, issue #48):
2+
// `@arcp/bun` is a single-file `Bun.serve({ websocket })` adapter that
3+
// terminates ARCP connections on Bun's native WS server (no `ws` dep) and
4+
// hands each accepted socket to `onTransport` as a `BunWebSocketTransport`.
5+
// All runtime state — handshake, dispatch loop, back-pressure, resume — is
6+
// owned by `ARCPServer.accept`; this package never holds it. The Bun socket
7+
// is only available inside the `websocket.open` callback and is consumed by
8+
// `BunWebSocketTransport` immediately, leaving no socket-level seam for an
9+
// Effect-shape `TransportEffect` factory to slot into without rewriting the
10+
// per-socket state machine. Adding a dedicated `effect` twin here would
11+
// duplicate the legacy adapter and force a runtime `effect` dependency on
12+
// the Bun package.
13+
//
14+
// Effect-graph consumers should keep using `serveArcp` and dispatch the
15+
// legacy transport into their `ManagedRuntime` from `onTransport`:
16+
//
17+
// import { Effect, ManagedRuntime } from "effect"
18+
// import {
19+
// ARCPRuntimeLayer,
20+
// ARCPServerService,
21+
// } from "@arcp/runtime"
22+
// import { serveArcp } from "@arcp/bun"
23+
//
24+
// const runtime = ManagedRuntime.make(ARCPRuntimeLayer({ ... }))
25+
// const handle = serveArcp({
26+
// port: 7777,
27+
// onTransport: (transport) =>
28+
// runtime.runFork(
29+
// Effect.gen(function* () {
30+
// const { server } = yield* ARCPServerService
31+
// server?.accept(transport)
32+
// }),
33+
// ),
34+
// })
35+
//
36+
// `acceptSessionEffect` (from `@arcp/runtime`) remains the right call-site
37+
// when the consumer is driving sessions from an already-Effect-shape
38+
// transport (e.g. their own `TransportEffect` over a stdio pipe); it is not
39+
// a better fit for the Bun adapter's `ServerWebSocket`-owned socket — that
40+
// socket is already inside `BunWebSocketTransport` by the time `onTransport`
41+
// fires.
42+
143
import { BunWebSocketTransport } from "./transport.js";
244
import type { ArcpServeHandle, BunServeArcpOptions } from "./types.js";
345

packages/middleware/express/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
// Implementation note (Effect migration, issue #48):
2+
// `@arcp/express` is a re-export of `attachArcpUpgrade` from `@arcp/node`
3+
// wrapped with a tiny Host-header guard middleware. The upgrade event is
4+
// emitted by Node's `http.Server` before Express's request pipeline runs, so
5+
// nothing here owns runtime state. Effect-graph consumers should keep using
6+
// `attachArcpToExpress`; see the Effect-integration note in
7+
// `@arcp/node/src/index.ts` for the recommended `ManagedRuntime` wiring.
8+
// Adding a dedicated `effect` twin here would only re-wrap the Node helper
9+
// it already delegates to.
10+
111
import type { Server as HttpServer } from "node:http";
212

313
import {

packages/middleware/fastify/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
// Implementation note (Effect migration, issue #48):
2+
// `@arcp/fastify` is a single-line delegation to `attachArcpUpgrade` from
3+
// `@arcp/node` against `app.server` — Fastify's own request pipeline is not
4+
// consulted for the upgrade event. There is no runtime state, no scoped
5+
// lifecycle, and no concurrent resource graph that an `Effect`/`Layer` twin
6+
// would help compose. Effect-graph consumers should keep using
7+
// `attachArcpToFastify`; see the Effect-integration note in
8+
// `@arcp/node/src/index.ts` for the recommended `ManagedRuntime` wiring.
9+
110
import {
211
type ArcpUpgradeHandle,
312
type AttachArcpUpgradeOptions,

packages/middleware/hono/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
// Implementation note (Effect migration, issue #48):
2+
// `@arcp/hono` re-exports `attachArcpUpgrade` from `@arcp/node` against the
3+
// `http.Server` returned by `@hono/node-server`'s `serve()` — Hono itself is
4+
// not consulted for the upgrade event. There is no runtime state, no scoped
5+
// lifecycle, and no concurrent resource graph that an `Effect`/`Layer` twin
6+
// would help compose. Effect-graph consumers should keep using
7+
// `attachArcpToHono`; see the Effect-integration note in
8+
// `@arcp/node/src/index.ts` for the recommended `ManagedRuntime` wiring.
9+
110
import type { Server as HttpServer } from "node:http";
211

312
import {

packages/middleware/node/src/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
// Implementation note (Effect migration, issue #48):
2+
// `@arcp/node` is a thin adapter (~30 lines of upgrade-handler glue) that
3+
// hands each accepted WebSocket to a user-supplied `onTransport` callback as
4+
// a legacy `WebSocketTransport`. The runtime contract — handshake, dispatch
5+
// loop, back-pressure — lives entirely inside `ARCPServer.accept` and the
6+
// transport class; this package never owns it. There is no concurrent
7+
// resource graph, no error pipeline, and no scoped lifecycle that benefits
8+
// from a dedicated `Effect`/`Layer` twin here. Adding one would force a
9+
// runtime `effect` dependency on a package whose entire job is a single
10+
// `server.on('upgrade', ...)` registration.
11+
//
12+
// Effect-graph consumers should keep using `attachArcpUpgrade` and dispatch
13+
// the legacy transport into their `ManagedRuntime` from `onTransport`:
14+
//
15+
// import { ManagedRuntime } from "effect"
16+
// import {
17+
// ARCPRuntimeLayer,
18+
// ARCPServerService,
19+
// } from "@arcp/runtime"
20+
// import { attachArcpUpgrade } from "@arcp/node"
21+
//
22+
// const runtime = ManagedRuntime.make(ARCPRuntimeLayer({ ... }))
23+
// attachArcpUpgrade(httpServer, {
24+
// path: "/arcp",
25+
// onTransport: (transport) =>
26+
// runtime.runFork(
27+
// Effect.gen(function* () {
28+
// const { server } = yield* ARCPServerService
29+
// server?.accept(transport)
30+
// }),
31+
// ),
32+
// })
33+
//
34+
// `acceptSessionEffect` (from `@arcp/runtime`) is the right call-site when
35+
// the consumer is driving sessions from an already-Effect-shape transport
36+
// (e.g. `websocketTransportEffect` over their own socket); it is NOT a
37+
// better fit for this adapter's `ws`-server-owned socket — that socket is
38+
// already inside the legacy `WebSocketTransport` by the time `onTransport`
39+
// fires.
40+
141
import type { Server as HttpServer, IncomingMessage } from "node:http";
242
import type { Duplex } from "node:stream";
343

0 commit comments

Comments
 (0)