trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
137 lines (134 loc) • 4.4 kB
JavaScript
import {
__esm
} from "./chunk-2ESYSVXG.js";
// src/server/node-adapter.ts
async function startNodeServer(opts) {
const http = await import("http");
const httpServer = http.createServer(
async (req, res) => {
const claimed = Boolean(
req[Symbol.for("trellis.blobClaimed")]
);
if (claimed || res.headersSent || res.writableEnded) {
return;
}
try {
const fetchReq = await toFetchRequest(req);
const fetchRes = await opts.fetch(fetchReq);
await writeFetchResponse(res, fetchRes);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
res.statusCode = 500;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ error: "Internal Server Error", message: msg }));
}
}
);
let wss = null;
try {
const { WebSocketServer } = await import("ws");
wss = new WebSocketServer({ noServer: true });
httpServer.on("upgrade", (req, socket, head) => {
const reqPath = (req.url ?? "").split("?")[0];
if (reqPath !== "/realtime") return;
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit("connection", ws, req);
});
});
wss.on("connection", (ws) => {
Promise.resolve(opts.websocket.open(ws)).catch(() => {
});
ws.on("message", (raw) => {
const data = Array.isArray(raw) ? Buffer.concat(raw).toString() : raw instanceof ArrayBuffer ? Buffer.from(raw).toString() : raw.toString();
Promise.resolve(opts.websocket.message(ws, data)).catch(() => {
});
});
ws.on("close", () => opts.websocket.close(ws));
});
} catch {
httpServer.on("upgrade", (_req, socket) => {
socket.destroy();
});
}
await new Promise(
(resolve) => httpServer.listen(opts.port, opts.hostname, resolve)
);
if (opts.attachPresenceRelay) {
const { attachRealtimeRelay } = await import("./relay-server-JJPW5UT4.js");
const relayOpts = typeof opts.attachPresenceRelay === "object" ? opts.attachPresenceRelay : { path: "/rt" };
await attachRealtimeRelay(httpServer, relayOpts);
}
const addr = httpServer.address();
const boundPort = typeof addr === "object" && addr ? addr.port : opts.port;
const boundHost = typeof addr === "object" && addr ? addr.address : opts.hostname;
return wrapNodeServer(httpServer, wss, boundPort, boundHost);
}
async function toFetchRequest(req) {
const host = req.headers.host ?? "localhost";
const protocol = req.socket?.encrypted ? "https" : "http";
const url = `${protocol}://${host}${req.url ?? "/"}`;
const method = req.method ?? "GET";
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
if (Array.isArray(value)) {
for (const v of value) headers.append(key, v);
} else if (value != null) {
headers.set(key, value);
}
}
const hasBody = method !== "GET" && method !== "HEAD";
const body = hasBody ? new Uint8Array(await readBody(req)) : void 0;
return new Request(url, { method, headers, body });
}
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", () => resolve(Buffer.concat(chunks)));
req.on("error", reject);
});
}
async function writeFetchResponse(res, fetchRes) {
res.statusCode = fetchRes.status;
fetchRes.headers.forEach((value, key) => res.setHeader(key, value));
if (!fetchRes.body) {
res.end();
return;
}
const reader = fetchRes.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
res.write(value);
}
res.end();
}
function wrapNodeServer(httpServer, wss, port, hostname) {
return {
port,
hostname: hostname ?? "localhost",
stop(closeActiveConnections) {
return new Promise((resolve, reject) => {
const closeHttp = () => {
httpServer.close((err) => err ? reject(err) : resolve());
};
if (!wss) {
closeHttp();
return;
}
if (closeActiveConnections) {
for (const client of wss.clients) client.terminate();
}
wss.close(() => closeHttp());
});
}
};
}
var init_node_adapter = __esm({
"src/server/node-adapter.ts"() {
}
});
export {
startNodeServer,
init_node_adapter
};