Skip to main content

add-sse-endpoint

Add a Server-Sent Events (SSE) endpoint that stays inside the OpenAPI-driven contract. Use when the user wants streaming/long-lived push from Go to the renderer and WebSocket is not required. Covers registering `text/event-stream` in Huma, declaring a discriminated-union event schema, and writing the thin TS parser since `openapi-typescript` only types the stream body as `string`.

ソース情報

リポジトリ
zerx-lab/AiFox
ソースの最終更新活動
2026年6月24日 03:24
検出された SKILL.md の言語
英語
スター
1
フォーク
1

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
add-sse-endpoint
description
Add a Server-Sent Events (SSE) endpoint that stays inside the OpenAPI-driven contract. Use when the user wants streaming/long-lived push from Go to the renderer and WebSocket is not required. Covers registering `text/event-stream` in Huma, declaring a discriminated-union event schema, and writing the thin TS parser since `openapi-typescript` only types the stream body as `string`.
## When to use SSE vs WebSocket Default to SSE. Choose WS only if you need client-to-server frames or binary frames. SSE stays inside OpenAPI (so types flow through `schema.ts`); WS does not (see the `add-ws-frames` skill for the WS escape hatch). ## Workflow ### 1. Define the event payload as a named OneOf schema in Go Declare each event variant as its own struct, then a wrapper that Huma will register as a discriminated union. Use a `type` discriminator field so the TS parser can switch on it. ```go // internal/api/<feature>_events.go type ProgressEvent struct { Type string `json:"type" enum:"progress" doc:"Discriminator."` Percent float64 `json:"percent" minimum:"0" maximum:"100"` } type DoneEvent struct { Type string `json:"type" enum:"done" doc:"Discriminator."` Result string `json:"result"` } type ErrorEvent struct { Type string `json:"type" enum:"error" doc:"Discriminator."` Message string `json:"message"` } ``` ### 2. Register the operation with `text/event-stream` Huma supports streaming responses. The body type for SSE is conventionally written as a sealed interface or a `oneOf` schema; consult Huma's current docs for the exact registration helper before coding. Register the variant structs as named components so they end up in `openapi.yaml` and are emitted into `schema.ts`. The HTTP response content type must be `text/event-stream`. Key requirements: - Operation `Method: http.MethodGet`, `Path: "/feature/stream"`. - Response `Content-Type: text/event-stream`. - Each event payload **must** match one of the registered variant schemas, encoded as JSON in the `data:` field. - Flush after every event (`http.Flusher`) or buffering kills the stream. ### 3. Write the TS parser (this is the manual part) `openapi-typescript` emits the SSE body as `string`. We must split the stream into events ourselves and narrow to the discriminated union. Put the parser next to the call site under `electron/src/api/`. Import the variant types from the generated `schema.ts` — do **not** redeclare them. Sketch: ```ts import type { components } from "./schema"; type StreamEvent = | components["schemas"]["ProgressEvent"] | components["schemas"]["DoneEvent"] | components["schemas"]["ErrorEvent"]; export async function* readSseEvents( resp: Response, ): AsyncGenerator<StreamEvent> { const reader = resp.body!.getReader(); const decoder = new TextDecoder(); let buf = ""; while (true) { const { value, done } = await reader.read(); if (done) return; buf += decoder.decode(value, { stream: true }); let sep: number; // SSE event separator is a blank line. while ((sep = buf.indexOf("\n\n")) !== -1) { const raw = buf.slice(0, sep); buf = buf.slice(sep + 2); const dataLine = raw .split("\n") .find((l) => l.startsWith("data:")); if (!dataLine) continue; const json = dataLine.slice(5).trim(); if (!json) continue; yield JSON.parse(json) as StreamEvent; } } } ``` The caller switches on `event.type` and gets full type narrowing because the variant types each fix `type` to a string literal via the `enum:` tag. ### 4. Calling from the renderer `openapi-fetch` returns `Response` for non-JSON bodies. Pass that to the parser: ```ts const client = await getClient(); const { response } = await client.GET("/feature/stream", { parseAs: "stream" }); for await (const ev of readSseEvents(response)) { switch (ev.type) { case "progress": /* … */ break; case "done": /* … */ break; case "error": /* … */ break; } } ``` ### 5. Regenerate and verify ``` task openapi && task codegen task verify ``` Inspect `electron/src/api/schema.ts` to confirm each event variant appears under `components.schemas` and the operation's response uses the union. ## Hard rules - The variant schemas **must** be registered as named OpenAPI components. Inline anonymous structs produce ugly TS types and break narrowing. - Discriminator field is `type` with a string literal (`enum:"progress"` etc). Do not use a numeric tag. - TS parser is the **only** TS code allowed to interpret raw stream text. Everything downstream consumes the discriminated union. - If you find yourself wanting bidirectional messaging, stop and switch to WS (see `add-ws-frames`). ## Definition of done - `openapi.yaml` contains the operation with `text/event-stream` response and named OneOf payload. - `schema.ts` reflects the variant components and the operation. - Parser yields a discriminated union; renderer code typechecks under `task typecheck`. - `task verify` passes.
GitHubで見る