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.
114 lines (112 loc) • 3.84 kB
JavaScript
import { iterSse, parseSseEvent } from "../../components/execution/streaming.js";
//#region src/experimental/durable-endpoints/client.ts
/**
* Entrypoint file for client-side Durable Endpoint utilities.
*/
/**
* Fetch a durable endpoint URL and consume its SSE stream, dispatching
* lifecycle callbacks (metadata, data, commit, rollback) as
* events arrive. Returns the final `Response` reconstructed from the
* terminal `inngest.response` SSE event.
*
* If the server does not respond with `text/event-stream`, the raw
* `Response` is returned as-is (non-streaming path).
*/
async function fetchWithStream(url, opts) {
const fetchFn = opts?.fetch ?? globalThis.fetch;
const baseHeaders = {};
if (opts?.fetchOpts?.headers) new Headers(opts.fetchOpts.headers).forEach((value, key) => {
baseHeaders[key] = value;
});
const initialRes = await fetchFn(url, {
...opts?.fetchOpts,
headers: {
...baseHeaders,
Accept: "text/event-stream"
}
});
if (!(initialRes.headers.get("content-type") ?? "").includes("text/event-stream")) return initialRes;
if (!initialRes.body) throw new Error("No response body");
let resp;
const source = iterSseFollowRedirects(initialRes.body, fetchFn, opts?.fetchOpts?.signal ?? void 0);
outer: for await (const sseEvent of source) switch (sseEvent.type) {
case "inngest.stream":
opts?.onData?.({
data: sseEvent.data,
hashedStepId: sseEvent.hashedStepId ?? null
});
break;
case "inngest.commit":
opts?.onCommit?.({ hashedStepId: sseEvent.hashedStepId });
break;
case "inngest.rollback":
opts?.onRollback?.({ hashedStepId: sseEvent.hashedStepId });
break;
case "inngest.response":
resp = new Response(sseEvent.response.body, {
status: sseEvent.response.statusCode,
headers: sseEvent.response.headers
});
break outer;
case "inngest.metadata":
opts?.onMetadata?.({ runId: sseEvent.runId });
break;
default: break;
}
if (!resp) throw new Error("No response");
return resp;
}
/**
* Async generator that yields parsed SSE events from an already-fetched
* response body, following `inngest.redirect_info` redirects.
*
* When a redirect event arrives, the redirect URL is fetched eagerly in the
* background so the connection is already established by the time the direct
* stream closes. This minimizes the window for late-joiner data loss.
*/
async function* iterSseFollowRedirects(body, fetchFn, signal) {
const fetchOpts = {
headers: { Accept: "text/event-stream" },
signal
};
let redirectUrl;
let eagerResponse;
try {
for await (const raw of iterSse(body)) {
const sseEvent = parseSseEvent(raw);
if (!sseEvent) continue;
if (sseEvent.type === "inngest.redirect_info") {
redirectUrl = sseEvent.url;
if (sseEvent.url && !eagerResponse) eagerResponse = fetchFn(sseEvent.url, fetchOpts).catch(() => void 0);
yield sseEvent;
continue;
}
yield sseEvent;
}
if (!redirectUrl) return;
let redirectRes;
if (eagerResponse) {
const eager = await eagerResponse;
if (eager?.ok && eager.body) redirectRes = eager;
else await eager?.body?.cancel();
eagerResponse = void 0;
}
if (!redirectRes) {
if (signal?.aborted) throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
const fallback = await fetchFn(redirectUrl, fetchOpts);
if (!fallback.ok) throw new Error(`Stream request failed: ${fallback.status} ${fallback.statusText}`);
if (!fallback.body) throw new Error("No response body");
redirectRes = fallback;
}
for await (const raw of iterSse(redirectRes.body)) {
const sseEvent = parseSseEvent(raw);
if (!sseEvent) continue;
yield sseEvent;
}
} finally {
if (eagerResponse) eagerResponse.then((r) => r?.body?.cancel()).catch(() => {});
}
}
//#endregion
export { fetchWithStream };
//# sourceMappingURL=client.js.map