mirror of
https://github.com/nicotsx/zerobyte.git
synced 2026-01-19 10:48:07 -05:00
* chore(deps): bump the minor-patch group with 9 updates Bumps the minor-patch group with 9 updates: | Package | From | To | | --- | --- | --- | | [@inquirer/prompts](https://github.com/SBoudrias/Inquirer.js) | `8.1.0` | `8.2.0` | | [better-auth](https://github.com/better-auth/better-auth/tree/HEAD/packages/better-auth) | `1.4.10` | `1.4.12` | | [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.70.0` | `7.71.0` | | [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) | `0.90.2` | `0.90.3` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.0.3` | `25.0.7` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.7` | `19.2.8` | | [oxfmt](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxfmt) | `0.23.0` | `0.24.0` | | [oxlint](https://github.com/oxc-project/oxc/tree/HEAD/npm/oxlint) | `1.38.0` | `1.39.0` | | [vite-tsconfig-paths](https://github.com/aleclarson/vite-tsconfig-paths) | `6.0.3` | `6.0.4` | Updates `@inquirer/prompts` from 8.1.0 to 8.2.0 - [Release notes](https://github.com/SBoudrias/Inquirer.js/releases) - [Commits](https://github.com/SBoudrias/Inquirer.js/compare/@inquirer/prompts@8.1.0...@inquirer/prompts@8.2.0) Updates `better-auth` from 1.4.10 to 1.4.12 - [Release notes](https://github.com/better-auth/better-auth/releases) - [Commits](https://github.com/better-auth/better-auth/commits/v1.4.12/packages/better-auth) Updates `react-hook-form` from 7.70.0 to 7.71.0 - [Release notes](https://github.com/react-hook-form/react-hook-form/releases) - [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md) - [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.70.0...v7.71.0) Updates `@hey-api/openapi-ts` from 0.90.2 to 0.90.3 - [Release notes](https://github.com/hey-api/openapi-ts/releases) - [Changelog](https://github.com/hey-api/openapi-ts/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/hey-api/openapi-ts/compare/@hey-api/openapi-ts@0.90.2...@hey-api/openapi-ts@0.90.3) Updates `@types/node` from 25.0.3 to 25.0.7 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/react` from 19.2.7 to 19.2.8 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `oxfmt` from 0.23.0 to 0.24.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxfmt/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxfmt_v0.24.0/npm/oxfmt) Updates `oxlint` from 1.38.0 to 1.39.0 - [Release notes](https://github.com/oxc-project/oxc/releases) - [Changelog](https://github.com/oxc-project/oxc/blob/main/npm/oxlint/CHANGELOG.md) - [Commits](https://github.com/oxc-project/oxc/commits/oxlint_v1.39.0/npm/oxlint) Updates `vite-tsconfig-paths` from 6.0.3 to 6.0.4 - [Release notes](https://github.com/aleclarson/vite-tsconfig-paths/releases) - [Commits](https://github.com/aleclarson/vite-tsconfig-paths/compare/v6.0.3...v6.0.4) --- updated-dependencies: - dependency-name: "@inquirer/prompts" dependency-version: 8.2.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: better-auth dependency-version: 1.4.12 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: react-hook-form dependency-version: 7.71.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: "@hey-api/openapi-ts" dependency-version: 0.90.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/node" dependency-version: 25.0.7 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: "@types/react" dependency-version: 19.2.8 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch - dependency-name: oxfmt dependency-version: 0.24.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: oxlint dependency-version: 1.39.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: minor-patch - dependency-name: vite-tsconfig-paths dependency-version: 6.0.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-patch ... Signed-off-by: dependabot[bot] <support@github.com> * chore: gen api client --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Meienberger <github@thisprops.com>
240 lines
6.3 KiB
TypeScript
240 lines
6.3 KiB
TypeScript
// This file is auto-generated by @hey-api/openapi-ts
|
|
|
|
import type { Config } from "./types.gen";
|
|
|
|
export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> &
|
|
Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
|
|
/**
|
|
* Fetch API implementation. You can use this option to provide a custom
|
|
* fetch instance.
|
|
*
|
|
* @default globalThis.fetch
|
|
*/
|
|
fetch?: typeof fetch;
|
|
/**
|
|
* Implementing clients can call request interceptors inside this hook.
|
|
*/
|
|
onRequest?: (url: string, init: RequestInit) => Promise<Request>;
|
|
/**
|
|
* Callback invoked when a network or parsing error occurs during streaming.
|
|
*
|
|
* This option applies only if the endpoint returns a stream of events.
|
|
*
|
|
* @param error The error that occurred.
|
|
*/
|
|
onSseError?: (error: unknown) => void;
|
|
/**
|
|
* Callback invoked when an event is streamed from the server.
|
|
*
|
|
* This option applies only if the endpoint returns a stream of events.
|
|
*
|
|
* @param event Event streamed from the server.
|
|
* @returns Nothing (void).
|
|
*/
|
|
onSseEvent?: (event: StreamEvent<TData>) => void;
|
|
serializedBody?: RequestInit["body"];
|
|
/**
|
|
* Default retry delay in milliseconds.
|
|
*
|
|
* This option applies only if the endpoint returns a stream of events.
|
|
*
|
|
* @default 3000
|
|
*/
|
|
sseDefaultRetryDelay?: number;
|
|
/**
|
|
* Maximum number of retry attempts before giving up.
|
|
*/
|
|
sseMaxRetryAttempts?: number;
|
|
/**
|
|
* Maximum retry delay in milliseconds.
|
|
*
|
|
* Applies only when exponential backoff is used.
|
|
*
|
|
* This option applies only if the endpoint returns a stream of events.
|
|
*
|
|
* @default 30000
|
|
*/
|
|
sseMaxRetryDelay?: number;
|
|
/**
|
|
* Optional sleep function for retry backoff.
|
|
*
|
|
* Defaults to using `setTimeout`.
|
|
*/
|
|
sseSleepFn?: (ms: number) => Promise<void>;
|
|
url: string;
|
|
};
|
|
|
|
export interface StreamEvent<TData = unknown> {
|
|
data: TData;
|
|
event?: string;
|
|
id?: string;
|
|
retry?: number;
|
|
}
|
|
|
|
export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = {
|
|
stream: AsyncGenerator<TData extends Record<string, unknown> ? TData[keyof TData] : TData, TReturn, TNext>;
|
|
};
|
|
|
|
export const createSseClient = <TData = unknown>({
|
|
onRequest,
|
|
onSseError,
|
|
onSseEvent,
|
|
responseTransformer,
|
|
responseValidator,
|
|
sseDefaultRetryDelay,
|
|
sseMaxRetryAttempts,
|
|
sseMaxRetryDelay,
|
|
sseSleepFn,
|
|
url,
|
|
...options
|
|
}: ServerSentEventsOptions): ServerSentEventsResult<TData> => {
|
|
let lastEventId: string | undefined;
|
|
|
|
const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
|
|
const createStream = async function* () {
|
|
let retryDelay: number = sseDefaultRetryDelay ?? 3000;
|
|
let attempt = 0;
|
|
const signal = options.signal ?? new AbortController().signal;
|
|
|
|
while (true) {
|
|
if (signal.aborted) break;
|
|
|
|
attempt++;
|
|
|
|
const headers =
|
|
options.headers instanceof Headers
|
|
? options.headers
|
|
: new Headers(options.headers as Record<string, string> | undefined);
|
|
|
|
if (lastEventId !== undefined) {
|
|
headers.set("Last-Event-ID", lastEventId);
|
|
}
|
|
|
|
try {
|
|
const requestInit: RequestInit = {
|
|
redirect: "follow",
|
|
...options,
|
|
body: options.serializedBody,
|
|
headers,
|
|
signal,
|
|
};
|
|
let request = new Request(url, requestInit);
|
|
if (onRequest) {
|
|
request = await onRequest(url, requestInit);
|
|
}
|
|
// fetch must be assigned here, otherwise it would throw the error:
|
|
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
|
const _fetch = options.fetch ?? globalThis.fetch;
|
|
const response = await _fetch(request);
|
|
|
|
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`);
|
|
|
|
if (!response.body) throw new Error("No body in SSE response");
|
|
|
|
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
|
|
let buffer = "";
|
|
|
|
const abortHandler = () => {
|
|
try {
|
|
reader.cancel();
|
|
} catch {
|
|
// noop
|
|
}
|
|
};
|
|
|
|
signal.addEventListener("abort", abortHandler);
|
|
|
|
try {
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) break;
|
|
buffer += value;
|
|
// Normalize line endings: CRLF -> LF, then CR -> LF
|
|
buffer = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
|
|
const chunks = buffer.split("\n\n");
|
|
buffer = chunks.pop() ?? "";
|
|
|
|
for (const chunk of chunks) {
|
|
const lines = chunk.split("\n");
|
|
const dataLines: Array<string> = [];
|
|
let eventName: string | undefined;
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith("data:")) {
|
|
dataLines.push(line.replace(/^data:\s*/, ""));
|
|
} else if (line.startsWith("event:")) {
|
|
eventName = line.replace(/^event:\s*/, "");
|
|
} else if (line.startsWith("id:")) {
|
|
lastEventId = line.replace(/^id:\s*/, "");
|
|
} else if (line.startsWith("retry:")) {
|
|
const parsed = Number.parseInt(line.replace(/^retry:\s*/, ""), 10);
|
|
if (!Number.isNaN(parsed)) {
|
|
retryDelay = parsed;
|
|
}
|
|
}
|
|
}
|
|
|
|
let data: unknown;
|
|
let parsedJson = false;
|
|
|
|
if (dataLines.length) {
|
|
const rawData = dataLines.join("\n");
|
|
try {
|
|
data = JSON.parse(rawData);
|
|
parsedJson = true;
|
|
} catch {
|
|
data = rawData;
|
|
}
|
|
}
|
|
|
|
if (parsedJson) {
|
|
if (responseValidator) {
|
|
await responseValidator(data);
|
|
}
|
|
|
|
if (responseTransformer) {
|
|
data = await responseTransformer(data);
|
|
}
|
|
}
|
|
|
|
onSseEvent?.({
|
|
data,
|
|
event: eventName,
|
|
id: lastEventId,
|
|
retry: retryDelay,
|
|
});
|
|
|
|
if (dataLines.length) {
|
|
yield data as any;
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
signal.removeEventListener("abort", abortHandler);
|
|
reader.releaseLock();
|
|
}
|
|
|
|
break; // exit loop on normal completion
|
|
} catch (error) {
|
|
// connection failed or aborted; retry after delay
|
|
onSseError?.(error);
|
|
|
|
if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) {
|
|
break; // stop after firing error
|
|
}
|
|
|
|
// exponential backoff: double retry each attempt, cap at 30s
|
|
const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000);
|
|
await sleep(backoff);
|
|
}
|
|
}
|
|
};
|
|
|
|
const stream = createStream();
|
|
|
|
return { stream };
|
|
};
|