feat(server): stream events across locations

This commit is contained in:
Dax Raad 2026-06-22 08:12:38 -04:00
commit 595cc91ddc
11 changed files with 2172 additions and 310 deletions

View file

@ -152,7 +152,7 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
normalizeLegacyErrorResponses(operation)
}
normalizeLegacyOperation(operation, path, method)
if ((path === "/event" || path === "/global/event") && method === "get") {
if ((path === "/event" || path === "/global/event" || path === "/api/event") && method === "get") {
// HttpApi has no first-class SSE response schema, and these handlers are
// raw/streaming routes. Document the actual wire protocol explicitly.
operation.responses!["200"] = {
@ -162,7 +162,9 @@ function matchLegacyOpenApi(input: Record<string, unknown>) {
schema:
path === "/event"
? { $ref: "#/components/schemas/Event" }
: { $ref: "#/components/schemas/GlobalEvent" },
: path === "/global/event"
? { $ref: "#/components/schemas/GlobalEvent" }
: { $ref: "#/components/schemas/V2Event" },
},
},
}

View file

@ -734,11 +734,11 @@ const scenarios: Scenario[] = [
.stream()
.status(
200,
(ctx, result) =>
(_ctx, result) =>
Effect.sync(() => {
check(result.contentType.includes("text/event-stream"), "v2 event should be an SSE stream")
check(result.text.includes("server.connected"), "v2 event should emit initial connection event")
check(!!ctx.directory && result.text.includes(ctx.directory), "v2 event should include the resolved location")
check(!result.text.includes('"location"'), "v2 connection event should not be scoped to a location")
}),
"status",
),

View file

@ -21,10 +21,12 @@ function request(route: string, directory: string, init: RequestInit = {}) {
const Event = Schema.Struct({
id: Schema.String,
type: Schema.String,
location: Schema.Struct({
directory: Schema.String,
project: Schema.Struct({ id: Schema.String, directory: Schema.String }),
}),
location: Schema.optional(
Schema.Struct({
directory: Schema.String,
project: Schema.Struct({ id: Schema.String, directory: Schema.String }),
}),
),
data: Schema.Unknown,
})
@ -64,17 +66,20 @@ describe("v2 location HttpApi", () => {
}
})
test("streams native EventV2 payloads with resolved locations", async () => {
await using tmp = await tmpdir({ git: true })
const response = await request("/api/event", tmp.path)
test("streams native EventV2 payloads across locations", async () => {
await using subscriber = await tmpdir({ git: true })
await using publisher = await tmpdir({ git: true })
const response = await request("/api/event", subscriber.path)
const reader = response.body!.getReader()
expect((await readEvent(reader)).type).toBe("server.connected")
const connected = await readEvent(reader)
expect(connected.type).toBe("server.connected")
expect(connected.location).toBeUndefined()
const created = await request("/session", tmp.path, { method: "POST" })
const created = await request("/session", publisher.path, { method: "POST" })
expect(created.status).toBe(200)
expect(await readEventType(reader, "session.created")).toMatchObject({
type: "session.created",
location: { directory: tmp.path, project: { directory: tmp.path } },
location: { directory: publisher.path, project: { directory: publisher.path } },
data: { sessionID: expect.any(String) },
})
await reader.cancel()