chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
import { type PrismaClient, prisma } from "~/db.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { singleton } from "~/utils/singleton";
|
||||
import type { SendFunction } from "~/utils/sse";
|
||||
import { ABORT_REASON_SEND_ERROR, createSSELoader } from "~/utils/sse";
|
||||
import { throttle } from "~/utils/throttle";
|
||||
import { getMollifierBuffer } from "~/v3/mollifier/mollifierBuffer.server";
|
||||
import { deserialiseMollifierSnapshot } from "~/v3/mollifier/mollifierSnapshot.server";
|
||||
import { runStore } from "~/v3/runStore.server";
|
||||
import { tracePubSub } from "~/v3/services/tracePubSub.server";
|
||||
|
||||
const PING_INTERVAL = 5_000;
|
||||
const STREAM_TIMEOUT = 30_000;
|
||||
|
||||
export class RunStreamPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public createLoader() {
|
||||
const prismaClient = this.#prismaClient;
|
||||
|
||||
return createSSELoader({
|
||||
timeout: STREAM_TIMEOUT,
|
||||
interval: PING_INTERVAL,
|
||||
handler: async (context) => {
|
||||
const runFriendlyId = context.params.runParam;
|
||||
|
||||
if (!runFriendlyId) {
|
||||
throw new Response("Missing runParam", { status: 400 });
|
||||
}
|
||||
|
||||
const userId = await requireUserId(context.request);
|
||||
|
||||
// Scope the lookup to organizations the requesting user is a member
|
||||
// of, matching RunPresenter's run lookup. Unauthorized and missing
|
||||
// runs are indistinguishable (both 404).
|
||||
// Run-ops read by friendlyId only (routes to the owning DB); the project/org
|
||||
// membership auth is a control-plane concern resolved separately below — joining
|
||||
// it here is a cross-DB join that returns nothing once the run lives in run-ops.
|
||||
const run = await runStore.findRun(
|
||||
{
|
||||
friendlyId: runFriendlyId,
|
||||
},
|
||||
{
|
||||
select: {
|
||||
traceId: true,
|
||||
projectId: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Authorize on the control-plane DB, keyed by the run's project. A non-member
|
||||
// (or unresolvable project) is treated as no-access: traceId stays null.
|
||||
let authorized = false;
|
||||
if (run) {
|
||||
const authorizedProject = await prismaClient.project.findFirst({
|
||||
where: {
|
||||
id: run.projectId,
|
||||
organization: { members: { some: { userId } } },
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
authorized = authorizedProject !== null;
|
||||
}
|
||||
|
||||
// Fall back to the mollifier buffer when the run isn't in PG yet.
|
||||
// The buffered run has no execution events to stream, but we still
|
||||
// attach a trace-pubsub subscription using the snapshot's traceId
|
||||
// so that the moment the drainer materialises the row and execution
|
||||
// begins, those events flow to this open SSE connection. Closing
|
||||
// with 404 would force the dashboard to keep retrying.
|
||||
let traceId: string | null = run && authorized ? run.traceId : null;
|
||||
if (!traceId) {
|
||||
const buffer = getMollifierBuffer();
|
||||
if (buffer) {
|
||||
try {
|
||||
const entry = await buffer.getEntry(runFriendlyId);
|
||||
// Same membership scoping as the PG lookup above — the buffer
|
||||
// entry carries the owning org's id.
|
||||
const isMember = entry
|
||||
? (await prismaClient.orgMember.findFirst({
|
||||
where: { organizationId: entry.orgId, userId },
|
||||
select: { id: true },
|
||||
})) !== null
|
||||
: false;
|
||||
if (entry && isMember) {
|
||||
// Go through the webapp wrapper so this read-side module
|
||||
// shares a single deserialisation path with readFallback —
|
||||
// see the contract comment in syntheticRedirectInfo.server.ts.
|
||||
const snapshot = deserialiseMollifierSnapshot(entry.payload);
|
||||
if (typeof snapshot.traceId === "string") {
|
||||
traceId = snapshot.traceId;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn("RunStreamPresenter buffer fallback failed", {
|
||||
runFriendlyId,
|
||||
err: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!traceId) {
|
||||
throw new Response("Not found", { status: 404 });
|
||||
}
|
||||
const resolvedRun = { traceId };
|
||||
|
||||
logger.info("RunStreamPresenter.start", {
|
||||
runFriendlyId,
|
||||
traceId: resolvedRun.traceId,
|
||||
});
|
||||
|
||||
const { unsubscribe, eventEmitter } = await tracePubSub.subscribeToTrace(
|
||||
resolvedRun.traceId
|
||||
);
|
||||
|
||||
// Only send max every 1 second
|
||||
const throttledSend = throttle(
|
||||
(args: { send: SendFunction; event?: string; data: string }) => {
|
||||
try {
|
||||
args.send({ event: args.event, data: args.data });
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
if (error.name !== "TypeError") {
|
||||
logger.debug("Error sending SSE in RunStreamPresenter", {
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// Abort the stream on send error. Uses a stackless string sentinel
|
||||
// from sse.ts — a no-arg abort() would create a DOMException with a
|
||||
// stack trace, which is unnecessary retention on the signal.reason.
|
||||
context.controller.abort(ABORT_REASON_SEND_ERROR);
|
||||
}
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
let messageListener: ((event: string) => void) | undefined;
|
||||
|
||||
return {
|
||||
initStream: ({ send }) => {
|
||||
throttledSend({ send, event: "message", data: new Date().toISOString() });
|
||||
|
||||
messageListener = (event: string) => {
|
||||
throttledSend({ send, event: "message", data: event });
|
||||
};
|
||||
eventEmitter.addListener("message", messageListener);
|
||||
|
||||
context.debug("Subscribed to trace pub/sub");
|
||||
},
|
||||
|
||||
iterator: ({ send }) => {
|
||||
try {
|
||||
// Send an actual message so the client refreshes
|
||||
throttledSend({ send, event: "message", data: new Date().toISOString() });
|
||||
} catch (_error) {
|
||||
// If we can't send a ping, the connection is likely dead
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
cleanup: () => {
|
||||
logger.info("RunStreamPresenter.cleanup", {
|
||||
runFriendlyId,
|
||||
traceId: resolvedRun.traceId,
|
||||
});
|
||||
|
||||
if (messageListener) {
|
||||
eventEmitter.removeListener("message", messageListener);
|
||||
}
|
||||
eventEmitter.removeAllListeners();
|
||||
|
||||
unsubscribe()
|
||||
.then(() => {
|
||||
logger.info("RunStreamPresenter.cleanup.unsubscribe succeeded", {
|
||||
runFriendlyId,
|
||||
traceId: resolvedRun.traceId,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error("RunStreamPresenter.cleanup.unsubscribe failed", {
|
||||
runFriendlyId,
|
||||
traceId: resolvedRun.traceId,
|
||||
error: {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const runStreamLoader = singleton("runStreamLoader", () => {
|
||||
const presenter = new RunStreamPresenter();
|
||||
return presenter.createLoader();
|
||||
});
|
||||
Reference in New Issue
Block a user