trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
599 lines (595 loc) • 18.7 kB
JavaScript
import {
RelayPersistence,
init_relay_persistence
} from "./chunk-KXAISKZT.js";
import {
__esm
} from "./chunk-2ESYSVXG.js";
// src/realtime/blob-handler.ts
function createBlobRequestHandler(store, opts = {}) {
const maxBytes = opts.maxBlobBytes ?? DEFAULT_MAX_BLOB_BYTES;
const authorize = opts.authorizeBlobWrite ?? (() => true);
return (req, res) => {
const reqPath = (req.url ?? "/").split("?")[0];
const method = req.method ?? "GET";
if (reqPath === "/blob" || reqPath.startsWith("/blob/")) {
if (method === "OPTIONS") {
res.writeHead(204, BLOB_CORS);
res.end();
return true;
}
}
if (reqPath.startsWith("/blob/")) {
const rest = reqPath.slice("/blob/".length);
const metaMatch = /^([a-f0-9]{64})\/meta$/.exec(rest);
if (metaMatch) {
const hash2 = metaMatch[1];
if (method === "PUT") {
void handlePutMeta(req, res, store, hash2, authorize);
return true;
}
if (method === "GET") {
if (!store.has(hash2)) {
res.writeHead(404, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "not_found", hash: hash2 }));
return true;
}
const meta = store.getMeta(hash2) ?? {};
res.writeHead(200, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ hash: hash2, ...meta }));
return true;
}
res.writeHead(405, { allow: "GET, PUT, OPTIONS", ...BLOB_CORS });
res.end();
return true;
}
const hash = rest;
if (!HASH_RE.test(hash)) {
res.writeHead(400, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "invalid_hash", hash }));
return true;
}
if (method === "HEAD") {
const size = store.size(hash);
if (size == null) {
res.writeHead(404, BLOB_CORS);
res.end();
return true;
}
const meta = store.getMeta(hash);
res.writeHead(200, {
"content-type": meta?.contentType || "application/octet-stream",
"content-length": size,
etag: `"${hash}"`,
"accept-ranges": "bytes",
"cache-control": "public, max-age=31536000, immutable",
...BLOB_CORS
});
res.end();
return true;
}
if (method === "GET") {
const inm = req.headers["if-none-match"];
if (inm && etagMatches(inm, hash)) {
res.writeHead(304, {
etag: `"${hash}"`,
"cache-control": "public, max-age=31536000, immutable",
...BLOB_CORS
});
res.end();
return true;
}
const size = store.size(hash);
if (size == null) {
res.writeHead(404, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "not_found", hash }));
return true;
}
const meta = store.getMeta(hash);
const baseHeaders = {
"content-type": meta?.contentType || "application/octet-stream",
etag: `"${hash}"`,
"accept-ranges": "bytes",
"cache-control": "public, max-age=31536000, immutable",
...BLOB_CORS
};
const rangeHeader = req.headers["range"];
if (typeof rangeHeader === "string" && rangeHeader.length > 0) {
const range = parseByteRange(rangeHeader, size);
if (!range) {
res.writeHead(416, {
...baseHeaders,
"content-range": `bytes */${size}`
});
res.end();
return true;
}
const stream2 = store.createReadStream(hash, range);
if (!stream2) {
res.writeHead(404, { "content-type": "application/json", ...BLOB_CORS });
res.end(JSON.stringify({ error: "not_found", hash }));
return true;
}
res.writeHead(206, {
...baseHeaders,
"content-range": `bytes ${range.start}-${range.end}/${size}`,
"content-length": range.end - range.start + 1
});
pipeBlob(stream2, res);
return true;
}
const stream = store.createReadStream(hash);
if (!stream) {
res.writeHead(404, { "content-type": "application/json", ...BLOB_CORS });
res.end(JSON.stringify({ error: "not_found", hash }));
return true;
}
res.writeHead(200, {
...baseHeaders,
"content-length": size
});
pipeBlob(stream, res);
return true;
}
res.writeHead(405, {
allow: "GET, HEAD, OPTIONS",
...BLOB_CORS
});
res.end();
return true;
}
if (reqPath === "/blob" && method === "GET") {
const blobs = store.listHashes().map((hash) => {
const meta = store.getMeta(hash);
return {
hash,
size: store.size(hash) ?? 0,
...meta?.name ? { name: meta.name } : {},
...meta?.contentType ? { contentType: meta.contentType } : {},
...meta?.uploadedAt ? { uploadedAt: meta.uploadedAt } : {}
};
});
res.writeHead(200, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ blobs }));
return true;
}
if (reqPath === "/blob" && method === "PUT") {
void handlePut(req, res, store, maxBytes, authorize);
return true;
}
if (reqPath === "/blob") {
res.writeHead(405, {
allow: "GET, PUT, OPTIONS",
...BLOB_CORS
});
res.end();
return true;
}
return false;
};
}
function parseByteRange(header, size) {
const m = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
if (!m) return null;
const [, startStr, endStr] = m;
if (startStr === "" && endStr === "") return null;
let start;
let end;
if (startStr === "") {
const suffix = Number(endStr);
if (suffix <= 0) return null;
start = Math.max(0, size - suffix);
end = size - 1;
} else {
start = Number(startStr);
end = endStr === "" ? size - 1 : Math.min(Number(endStr), size - 1);
}
if (start < 0 || start > end || start >= size) return null;
return { start, end };
}
function pipeBlob(stream, res) {
stream.on("error", () => {
if (!res.headersSent) {
res.writeHead(500, { "content-type": "application/json", ...BLOB_CORS });
res.end(JSON.stringify({ error: "read_failed" }));
} else {
res.destroy();
}
});
stream.pipe(res);
}
function etagMatches(ifNoneMatch, hash) {
const expected = `"${hash}"`;
return ifNoneMatch.split(",").map((s) => s.trim()).some((tag) => tag === "*" || tag === expected || tag === `W/${expected}` || tag === hash);
}
async function handlePut(req, res, store, maxBytes, authorize) {
try {
const allowed = await authorize(req);
if (!allowed) {
res.writeHead(401, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "unauthorized" }));
return;
}
const declared = Number(req.headers["content-length"]);
if (Number.isFinite(declared) && declared > maxBytes) {
res.writeHead(413, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(
JSON.stringify({ error: "payload_too_large", maxBlobBytes: maxBytes })
);
return;
}
const body = await readBodyLimited(req, maxBytes);
if (body === "too_large") {
res.writeHead(413, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "payload_too_large", maxBlobBytes: maxBytes }));
return;
}
const hash = await store.put(body);
const rawName = req.headers["x-trellis-filename"];
const filename = typeof rawName === "string" ? rawName.trim().slice(0, 255) : "";
const declaredType = req.headers["content-type"];
const contentType = typeof declaredType === "string" && declaredType.length > 0 && !/^application\/octet-stream$/i.test(declaredType) ? declaredType.split(";")[0].trim().slice(0, 128) : void 0;
if (filename || contentType) {
store.setMeta(hash, {
name: filename || void 0,
contentType,
uploadedAt: Date.now()
});
}
res.writeHead(201, {
"content-type": "application/json",
etag: `"${hash}"`,
...BLOB_CORS
});
res.end(JSON.stringify({ hash }));
} catch {
if (!res.headersSent) {
res.writeHead(500, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "upload_failed" }));
}
}
}
async function handlePutMeta(req, res, store, hash, authorize) {
try {
const allowed = await authorize(req);
if (!allowed) {
res.writeHead(401, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "unauthorized" }));
return;
}
if (!store.has(hash)) {
res.writeHead(404, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "not_found", hash }));
return;
}
const body = await readBodyLimited(req, 64 * 1024);
if (body === "too_large") {
res.writeHead(413, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "payload_too_large" }));
return;
}
let parsed;
try {
parsed = JSON.parse(body.toString("utf8"));
} catch {
res.writeHead(400, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "invalid_json" }));
return;
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
res.writeHead(400, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "invalid_meta" }));
return;
}
const obj = parsed;
const name = typeof obj.name === "string" ? obj.name.trim().slice(0, 255) : void 0;
const contentType = typeof obj.contentType === "string" ? obj.contentType.trim().slice(0, 128) : void 0;
store.setMeta(hash, {
name,
contentType,
uploadedAt: typeof obj.uploadedAt === "number" ? obj.uploadedAt : Date.now()
});
res.writeHead(200, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ hash, ...store.getMeta(hash) ?? {} }));
} catch {
if (!res.headersSent) {
res.writeHead(500, {
"content-type": "application/json",
...BLOB_CORS
});
res.end(JSON.stringify({ error: "meta_failed" }));
}
}
}
function readBodyLimited(req, maxBytes) {
return new Promise((resolve, reject) => {
const chunks = [];
let total = 0;
let settled = false;
let tooLarge = false;
const finish = (value) => {
if (settled) return;
settled = true;
resolve(value);
};
req.on("data", (chunk) => {
if (tooLarge) return;
total += chunk.length;
if (total > maxBytes) {
tooLarge = true;
chunks.length = 0;
req.resume();
finish("too_large");
return;
}
chunks.push(chunk);
});
req.on("end", () => {
if (!settled) finish(Buffer.concat(chunks));
});
req.on("error", (err) => {
if (!settled) {
settled = true;
reject(err);
}
});
});
}
var HASH_RE, DEFAULT_MAX_BLOB_BYTES, BLOB_CORS;
var init_blob_handler = __esm({
"src/realtime/blob-handler.ts"() {
"use strict";
HASH_RE = /^[a-f0-9]{64}$/;
DEFAULT_MAX_BLOB_BYTES = 64 * 1024 * 1024;
BLOB_CORS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, HEAD, PUT, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, If-None-Match, X-Trellis-Filename"
};
}
});
// src/realtime/relay-server.ts
function isBlobRequestClaimed(req) {
return Boolean(req[TRELLIS_BLOB_CLAIMED]);
}
function resolveBlobStore(blobStore) {
if (blobStore === false || blobStore == null) return null;
return blobStore();
}
function blobHandlerOpts(opts) {
return {
maxBlobBytes: opts.maxBlobBytes,
authorizeBlobWrite: opts.authorizeBlobWrite
};
}
function roomFromPath(reqPath, basePath) {
const normalized = reqPath.replace(/\/+$/, "") || "/";
const base = basePath.replace(/\/+$/, "") || "/";
if (normalized === base) return DEFAULT_ROOM;
const prefix = base === "/" ? "/" : `${base}/`;
if (!reqPath.startsWith(prefix)) return null;
const rest = reqPath.slice(prefix.length).split("?")[0].replace(/\/+$/, "");
return rest ? decodeURIComponent(rest) : DEFAULT_ROOM;
}
async function attachRealtimeRelay(server, opts = {}) {
const path = opts.path ?? "/rt";
const graceMs = opts.replayGraceMs ?? REPLAY_GRACE_MS;
const makePersistence = opts.persistence === false ? null : opts.persistence ?? (() => new RelayPersistence());
const store = resolveBlobStore(opts.blobStore);
const handleBlob = store ? createBlobRequestHandler(store, blobHandlerOpts(opts)) : null;
const onRequest = handleBlob ? (req, res) => {
if (handleBlob(req, res)) {
req[TRELLIS_BLOB_CLAIMED] = true;
}
} : null;
if (onRequest) server.prependListener("request", onRequest);
const Wss = opts.WebSocketServerImpl ?? (await import("ws")).WebSocketServer;
const wss = new Wss({ noServer: true });
const rooms = /* @__PURE__ */ new Map();
const pendingRoom = /* @__PURE__ */ new WeakMap();
const roomState = (room) => {
let st = rooms.get(room);
if (!st) {
st = { clients: /* @__PURE__ */ new Set(), persistence: makePersistence?.() ?? null };
rooms.set(room, st);
}
return st;
};
const sendReplay = (ws, st) => {
if (!st.persistence) return;
const messages = st.persistence.buildReplay();
if (messages.length === 0) return;
const frame = {
v: 1,
t: "replay",
from: "relay",
messages
};
ws.send(JSON.stringify(frame));
};
wss.on("connection", (ws) => {
const room = pendingRoom.get(ws) ?? DEFAULT_ROOM;
pendingRoom.delete(ws);
const st = roomState(room);
st.clients.add(ws);
const replaySent = { value: false };
const deliverReplay = () => {
if (replaySent.value) return;
replaySent.value = true;
sendReplay(ws, st);
};
const graceTimer = graceMs > 0 ? setTimeout(deliverReplay, graceMs) : null;
graceTimer?.unref?.();
ws.on("message", (data, isBinary) => {
const raw = isBinary ? data : String(data);
let message;
try {
message = JSON.parse(String(raw));
} catch {
return;
}
if (message?.v === 1) {
if (message.t === "hello") deliverReplay();
st.persistence?.record(message);
}
for (const peer of st.clients) {
if (peer === ws || peer.readyState !== peer.OPEN) continue;
peer.send(String(raw));
}
});
ws.on("close", () => {
if (graceTimer) clearTimeout(graceTimer);
st.clients.delete(ws);
if (st.clients.size === 0) rooms.delete(room);
});
});
const onUpgrade = (req, socket, head) => {
const reqPath = (req.url ?? "").split("?")[0];
const room = roomFromPath(reqPath, path);
if (room === null) return;
wss.handleUpgrade(req, socket, head, (ws) => {
pendingRoom.set(ws, room);
wss.emit("connection", ws, req);
});
};
server.on("upgrade", onUpgrade);
return {
clientCount: (room) => {
if (room !== void 0) return rooms.get(room)?.clients.size ?? 0;
let total = 0;
for (const st of rooms.values()) total += st.clients.size;
return total;
},
rooms: () => [...rooms.keys()],
persistenceFor: (room) => rooms.get(room)?.persistence ?? null,
close: () => new Promise((resolve) => {
server.off("upgrade", onUpgrade);
if (onRequest) server.off("request", onRequest);
for (const st of rooms.values()) {
for (const ws of st.clients) {
try {
ws.close();
} catch {
}
}
st.clients.clear();
}
rooms.clear();
wss.close(() => resolve());
})
};
}
async function createRealtimeRelay(opts = {}) {
const { createServer } = await import("node:http");
const path = opts.path ?? "/rt";
const port = opts.port ?? 8231;
const hostname = opts.hostname ?? "0.0.0.0";
const store = resolveBlobStore(opts.blobStore);
const handleBlob = store ? createBlobRequestHandler(store, blobHandlerOpts(opts)) : null;
const server = createServer((req, res) => {
if (handleBlob?.(req, res)) return;
const reqPath = (req.url ?? "/").split("?")[0];
if (reqPath === "/" || reqPath === "/health") {
if (req.method === "OPTIONS") {
res.writeHead(204, RELAY_HEALTH_CORS);
res.end();
return;
}
if (req.method === "GET") {
res.writeHead(200, {
"content-type": "application/json",
...RELAY_HEALTH_CORS
});
res.end(JSON.stringify({ ok: true, relay: path }));
return;
}
}
res.writeHead(404).end("not found");
});
const { blobStore: _blob, ...relayOpts } = opts;
const relay = await attachRealtimeRelay(server, {
...relayOpts,
blobStore: false
});
await new Promise((resolve) => server.listen(port, hostname, resolve));
const addr = server.address();
const boundPort = typeof addr === "object" && addr ? addr.port : port;
return {
...relay,
port: boundPort,
server,
close: async () => {
await relay.close();
await new Promise((resolve) => server.close(() => resolve()));
}
};
}
var TRELLIS_BLOB_CLAIMED, REPLAY_GRACE_MS, DEFAULT_ROOM, RELAY_HEALTH_CORS;
var init_relay_server = __esm({
"src/realtime/relay-server.ts"() {
init_relay_persistence();
init_blob_handler();
init_blob_handler();
TRELLIS_BLOB_CLAIMED = Symbol.for("trellis.blobClaimed");
REPLAY_GRACE_MS = 250;
DEFAULT_ROOM = "default";
RELAY_HEALTH_CORS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS"
};
}
});
export {
BLOB_CORS,
createBlobRequestHandler,
TRELLIS_BLOB_CLAIMED,
isBlobRequestClaimed,
attachRealtimeRelay,
createRealtimeRelay,
init_relay_server
};