UNPKG

inngest

Version:

Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.

1 lines 12.1 kB
{"version":3,"file":"streaming.cjs","names":["z","payload: Record<string, unknown>","dataLines: string[]","sseSchemasByEvent: Record<string, z.ZodType<SseEvent>>","parsed: unknown","UnreachableError","isRecord"],"sources":["../../../src/components/execution/streaming.ts"],"sourcesContent":["// No Node.js imports — this file is shared between server and client code.\n\nimport { z } from \"zod/v3\";\nimport { isRecord } from \"../../helpers/types.ts\";\nimport { UnreachableError } from \"../middleware/utils.ts\";\n\n// ---------------------------------------------------------------------------\n// Schemas — single source of truth for both runtime validation and types\n// ---------------------------------------------------------------------------\n\nconst sseMetadataSchema = z.object({\n type: z.literal(\"inngest.metadata\"),\n runId: z.string(),\n});\n\nconst sseStreamSchema = z.object({\n type: z.literal(\"inngest.stream\"),\n data: z.unknown(),\n hashedStepId: z.string().optional(),\n});\n\nconst sseCommitSchema = z.object({\n type: z.literal(\"inngest.commit\"),\n hashedStepId: z.string().nullable(),\n});\n\nconst sseRollbackSchema = z.object({\n type: z.literal(\"inngest.rollback\"),\n hashedStepId: z.string().nullable(),\n});\n\nconst sseResultSchema = z.object({\n type: z.literal(\"inngest.response\"),\n status: z.union([z.literal(\"succeeded\"), z.literal(\"failed\")]),\n response: z.object({\n body: z.string(),\n headers: z.record(z.string()),\n statusCode: z.number(),\n }),\n});\n\nconst sseRedirectSchema = z.object({\n type: z.literal(\"inngest.redirect_info\"),\n runId: z.string(),\n url: z.string(),\n});\n\n// ---------------------------------------------------------------------------\n// Types derived from schemas\n// ---------------------------------------------------------------------------\n\nexport type SseMetadataEvent = z.infer<typeof sseMetadataSchema>;\nexport type SseStreamEvent = z.infer<typeof sseStreamSchema>;\nexport type SseResultEvent = z.infer<typeof sseResultSchema>;\n\nexport type SseCommitEvent = z.infer<typeof sseCommitSchema>;\nexport type SseRollbackEvent = z.infer<typeof sseRollbackSchema>;\n\nexport type SseRedirectEvent = z.infer<typeof sseRedirectSchema>;\n\nexport type SseEvent =\n | SseMetadataEvent\n | SseStreamEvent\n | SseResultEvent\n | SseCommitEvent\n | SseRollbackEvent\n | SseRedirectEvent;\n\nexport interface RawSseEvent {\n event: string;\n data: string;\n}\n\n// ---------------------------------------------------------------------------\n// SSE event builders\n// ---------------------------------------------------------------------------\n\n/**\n * Builds a single SSE event with the given event name and JSON-serialized data.\n *\n * `undefined` is normalized to `null` so that the `data:` field is always valid\n * JSON (since `JSON.stringify(undefined)` returns the JS primitive `undefined`,\n * not the string `\"null\"`).\n */\nfunction buildSseEvent(event: string, data: unknown): string {\n return `event: ${event}\\ndata: ${JSON.stringify(data ?? null)}\\n\\n`;\n}\n\n/**\n * Builds an SSE metadata event string for a streaming response.\n *\n * The event follows the Server-Sent Events format and provides run context\n * (run ID) to consumers of the stream.\n */\nexport function buildSseMetadataEvent(runId: string): string {\n return buildSseEvent(\"inngest.metadata\", { runId });\n}\n\n/**\n * Builds an SSE stream event string for user-pushed data.\n *\n * Used by `stream.push()` and `stream.pipe()` to send arbitrary data to\n * clients as part of a streaming response.\n */\nexport function buildSseStreamEvent(\n data: unknown,\n hashedStepId?: string,\n): string {\n const payload: Record<string, unknown> = { data };\n if (hashedStepId) payload.hashedStepId = hashedStepId;\n return buildSseEvent(\"inngest.stream\", payload);\n}\n\nexport interface SseResponse {\n body: string;\n statusCode: number;\n headers: Record<string, string>;\n}\n\n/**\n * Builds an `inngest.response` SSE event with status `succeeded`.\n */\nexport function buildSseSucceededEvent(response: SseResponse): string {\n return buildSseEvent(\"inngest.response\", {\n status: \"succeeded\",\n response,\n });\n}\n\n/**\n * Builds an `inngest.response` SSE event with status `failed`.\n */\nexport function buildSseFailedEvent(error: string): string {\n return buildSseEvent(\"inngest.response\", {\n status: \"failed\",\n response: {\n body: JSON.stringify(error),\n statusCode: 500,\n headers: { \"content-type\": \"application/json\" },\n },\n });\n}\n\n/**\n * Builds an SSE redirect event telling the client that execution has switched\n * to async mode and it should reconnect elsewhere to get remaining output.\n *\n * The `url` already contains the realtime JWT as a query parameter, so no\n * separate token field is needed.\n */\nexport function buildSseRedirectEvent(data: {\n runId: string;\n url: string;\n}): string {\n return buildSseEvent(\"inngest.redirect_info\", data);\n}\n\n/**\n * Returns a new `ReadableStream` that emits `prefix` first, then pipes\n * through all chunks from the original `stream`.\n */\nexport function prependToStream(\n prefix: Uint8Array,\n stream: ReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n return new ReadableStream({\n async start(controller) {\n controller.enqueue(prefix);\n\n const reader = stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n controller.enqueue(value);\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n } finally {\n reader.releaseLock();\n }\n },\n });\n}\n\n// ---------------------------------------------------------------------------\n// Commit / Rollback event builders\n// ---------------------------------------------------------------------------\n\n/**\n * Builds an `inngest.commit` SSE event indicating a step's data is committed.\n */\nexport function buildSseCommitEvent(hashedStepId: string | null): string {\n return buildSseEvent(\"inngest.commit\", { hashedStepId });\n}\n\n/**\n * Builds an `inngest.rollback` SSE event indicating a step's data should be\n * rolled back (e.g. step errored and will retry, or disconnect mid-step).\n */\nexport function buildSseRollbackEvent(hashedStepId: string | null): string {\n return buildSseEvent(\"inngest.rollback\", { hashedStepId });\n}\n\n// ---------------------------------------------------------------------------\n// SSE line parser (async generator)\n// ---------------------------------------------------------------------------\n\n/**\n * Parses a `ReadableStream<Uint8Array>` as an SSE byte stream, yielding\n * `RawSseEvent` objects for each complete event.\n */\nexport async function* iterSse(\n body: ReadableStream<Uint8Array>,\n): AsyncGenerator<RawSseEvent> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n\n // SSE events are delimited by a blank line (double newline) per the\n // Server-Sent Events spec.\n const parts = buffer.split(\"\\n\\n\");\n buffer = parts.pop() ?? \"\";\n\n for (const part of parts) {\n if (!part.trim()) continue;\n\n let event = \"message\";\n const dataLines: string[] = [];\n\n for (const line of part.split(\"\\n\")) {\n if (line.startsWith(\"event: \")) {\n event = line.slice(7);\n } else if (line.startsWith(\"data: \")) {\n dataLines.push(line.slice(6));\n }\n }\n\n const data = dataLines.join(\"\\n\");\n\n yield { event, data };\n }\n }\n } finally {\n reader.releaseLock();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Raw SSE event -> typed SseEvent\n// ---------------------------------------------------------------------------\n\nconst sseSchemasByEvent: Record<string, z.ZodType<SseEvent>> = {\n \"inngest.metadata\": sseMetadataSchema,\n \"inngest.stream\": sseStreamSchema,\n \"inngest.response\": sseResultSchema,\n \"inngest.commit\": sseCommitSchema,\n \"inngest.rollback\": sseRollbackSchema,\n \"inngest.redirect_info\": sseRedirectSchema,\n};\n\n/**\n * Converts a `RawSseEvent` into a typed `SseEvent`, or returns `undefined`\n * if the event type is unrecognised or fails validation.\n */\nexport function parseSseEvent(raw: RawSseEvent): SseEvent | undefined {\n const schema = sseSchemasByEvent[raw.event];\n if (!schema) {\n return undefined;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw.data);\n } catch {\n throw new UnreachableError(\"SSE data is not a valid JSON string\");\n }\n if (!isRecord(parsed)) {\n return undefined;\n }\n\n const result = schema.safeParse({ ...parsed, type: raw.event });\n if (!result.success) {\n throw new Error(\"Unknown SSE event\", { cause: result.error });\n }\n\n return result.data;\n}\n"],"mappings":"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"}