From 762f3388578482ea31bc44edb23041e035ffcf83 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Sun, 24 Aug 2025 14:10:17 -0400 Subject: [PATCH] sdk: update generated client code and dependencies --- bun.lock | 7 +- .../src/cli/cmd/opentui/context/sync.tsx | 17 +- packages/sdk/js/package.json | 6 +- packages/sdk/js/script/generate.ts | 4 +- packages/sdk/js/src/client.ts | 4 +- .../gen/client/{client.ts => client.gen.ts} | 59 +++-- packages/sdk/js/src/gen/client/index.ts | 21 +- .../src/gen/client/{types.ts => types.gen.ts} | 67 ++++-- .../src/gen/client/{utils.ts => utils.gen.ts} | 142 +++--------- .../js/src/gen/core/{auth.ts => auth.gen.ts} | 2 + ...odySerializer.ts => bodySerializer.gen.ts} | 6 +- .../src/gen/core/{params.ts => params.gen.ts} | 2 + ...athSerializer.ts => pathSerializer.gen.ts} | 2 + .../js/src/gen/core/serverSentEvents.gen.ts | 210 ++++++++++++++++++ .../src/gen/core/{types.ts => types.gen.ts} | 6 +- packages/sdk/js/src/gen/core/utils.gen.ts | 109 +++++++++ packages/sdk/js/src/gen/sdk.gen.ts | 2 +- 17 files changed, 493 insertions(+), 173 deletions(-) rename packages/sdk/js/src/gen/client/{client.ts => client.gen.ts} (73%) rename packages/sdk/js/src/gen/client/{types.ts => types.gen.ts} (69%) rename packages/sdk/js/src/gen/client/{utils.ts => utils.gen.ts} (72%) rename packages/sdk/js/src/gen/core/{auth.ts => auth.gen.ts} (93%) rename packages/sdk/js/src/gen/core/{bodySerializer.ts => bodySerializer.gen.ts} (92%) rename packages/sdk/js/src/gen/core/{params.ts => params.gen.ts} (98%) rename packages/sdk/js/src/gen/core/{pathSerializer.ts => pathSerializer.gen.ts} (98%) create mode 100644 packages/sdk/js/src/gen/core/serverSentEvents.gen.ts rename packages/sdk/js/src/gen/core/{types.ts => types.gen.ts} (95%) create mode 100644 packages/sdk/js/src/gen/core/utils.gen.ts diff --git a/bun.lock b/bun.lock index c122cfe68..958785692 100644 --- a/bun.lock +++ b/bun.lock @@ -160,11 +160,8 @@ "packages/sdk/js": { "name": "@opencode-ai/sdk", "version": "0.5.18", - "dependencies": { - "@hey-api/openapi-ts": "0.80.1", - }, "devDependencies": { - "@hey-api/openapi-ts": "0.80.1", + "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", "typescript": "catalog:", }, @@ -3361,8 +3358,6 @@ "@openauthjs/solid/@openauthjs/openauth": ["@openauthjs/openauth@0.4.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-8+Bia559iffrZXfQ0LWXrVVVriochS88pDtB8indyQ1S+40MQgDBu8aBzKt+fgSrTmoQGCTT+wlOXgbjc9qIcw=="], - "@opencode-ai/sdk/@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.80.1", "", { "dependencies": { "@hey-api/json-schema-ref-parser": "1.0.6", "ansi-colors": "4.1.3", "c12": "2.0.1", "color-support": "1.1.3", "commander": "13.0.0", "handlebars": "4.7.8", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-AC478kg36vmmrseLZNFonZ/cmXXmDzW5yWz4PVg1S8ebJsRtVRJ/QU+mtnXfzf9avN2P0pz/AO4WAe4jyFY2gA=="], - "@opentelemetry/instrumentation-grpc/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.27.0", "", {}, "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg=="], "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], diff --git a/packages/opencode/src/cli/cmd/opentui/context/sync.tsx b/packages/opencode/src/cli/cmd/opentui/context/sync.tsx index 2ba1dc787..fef8b199b 100644 --- a/packages/opencode/src/cli/cmd/opentui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/opentui/context/sync.tsx @@ -1,20 +1,35 @@ -import type { Provider } from "@opencode-ai/sdk" +import type { Provider, Session } from "@opencode-ai/sdk" import { createStore } from "solid-js/store" import { useSDK } from "./sdk" import { createContext, onMount, useContext, type ParentProps } from "solid-js" +import type { Message } from "vscode-jsonrpc" function init() { const [store, setStore] = createStore<{ provider: Provider[] + session: Record + }> + }> }>({ provider: [], + session: {}, }) const sdk = useSDK() onMount(async () => { const events = await sdk.event.subscribe() + for await (const event of events.stream) { + switch (event.type) { + case "storage.write": + break + } + } }) sdk.config.providers().then((x) => setStore("provider", x.data!.providers)) diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 2437a1f98..5efbb33a9 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -25,10 +25,8 @@ ], "devDependencies": { "typescript": "catalog:", - "@hey-api/openapi-ts": "0.80.1", + "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:" }, - "dependencies": { - "@hey-api/openapi-ts": "0.80.1" - } + "dependencies": {} } diff --git a/packages/sdk/js/script/generate.ts b/packages/sdk/js/script/generate.ts index ffe0779c7..43a49dcd1 100755 --- a/packages/sdk/js/script/generate.ts +++ b/packages/sdk/js/script/generate.ts @@ -10,11 +10,13 @@ import { createClient } from "@hey-api/openapi-ts" await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode")) +await $`rm -rf src/gen` + await createClient({ input: "./openapi.json", output: { path: "./src/gen", - tsConfigPath: path.join(dir, 'tsconfig.json') + tsConfigPath: path.join(dir, "tsconfig.json"), }, plugins: [ { diff --git a/packages/sdk/js/src/client.ts b/packages/sdk/js/src/client.ts index 8346fd8a2..29b9de906 100644 --- a/packages/sdk/js/src/client.ts +++ b/packages/sdk/js/src/client.ts @@ -1,8 +1,8 @@ export * from "./gen/types.gen.js" export { type Config as OpencodeClientConfig, OpencodeClient } -import { createClient } from "./gen/client/client.js" -import { type Config } from "./gen/client/types.js" +import { createClient } from "./gen/client/client.gen.js" +import { type Config } from "./gen/client/types.gen.js" import { OpencodeClient } from "./gen/sdk.gen.js" export function createOpencodeClient(config?: Config) { diff --git a/packages/sdk/js/src/gen/client/client.ts b/packages/sdk/js/src/gen/client/client.gen.ts similarity index 73% rename from packages/sdk/js/src/gen/client/client.ts rename to packages/sdk/js/src/gen/client/client.gen.ts index 46a62694c..34a8d0bec 100644 --- a/packages/sdk/js/src/gen/client/client.ts +++ b/packages/sdk/js/src/gen/client/client.gen.ts @@ -1,4 +1,7 @@ -import type { Client, Config, RequestOptions } from "./types.js" +// This file is auto-generated by @hey-api/openapi-ts + +import { createSseClient } from "../core/serverSentEvents.gen.js" +import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen.js" import { buildUrl, createConfig, @@ -7,7 +10,7 @@ import { mergeConfigs, mergeHeaders, setAuthParams, -} from "./utils.js" +} from "./utils.gen.js" type ReqInit = Omit & { body?: any @@ -24,14 +27,15 @@ export const createClient = (config: Config = {}): Client => { return getConfig() } - const interceptors = createInterceptors() + const interceptors = createInterceptors() - const request: Client["request"] = async (options) => { + const beforeRequest = async (options: RequestOptions) => { const opts = { ..._config, ...options, fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, headers: mergeHeaders(_config.headers, options.headers), + serializedBody: undefined, } if (opts.security) { @@ -46,18 +50,26 @@ export const createClient = (config: Config = {}): Client => { } if (opts.body && opts.bodySerializer) { - opts.body = opts.bodySerializer(opts.body) + opts.serializedBody = opts.bodySerializer(opts.body) } // remove Content-Type header if body is empty to avoid sending invalid requests - if (opts.body === undefined || opts.body === "") { + if (opts.serializedBody === undefined || opts.serializedBody === "") { opts.headers.delete("Content-Type") } const url = buildUrl(opts) + + return { opts, url } + } + + const request: Client["request"] = async (options) => { + // @ts-expect-error + const { opts, url } = await beforeRequest(options) const requestInit: ReqInit = { redirect: "follow", ...opts, + body: opts.serializedBody, } let request = new Request(url, requestInit) @@ -166,20 +178,35 @@ export const createClient = (config: Config = {}): Client => { } } + const makeMethod = (method: Required["method"]) => { + const fn = (options: RequestOptions) => request({ ...options, method }) + fn.sse = async (options: RequestOptions) => { + const { opts, url } = await beforeRequest(options) + return createSseClient({ + ...opts, + body: opts.body as BodyInit | null | undefined, + headers: opts.headers as unknown as Record, + method, + url, + }) + } + return fn + } + return { buildUrl, - connect: (options) => request({ ...options, method: "CONNECT" }), - delete: (options) => request({ ...options, method: "DELETE" }), - get: (options) => request({ ...options, method: "GET" }), + connect: makeMethod("CONNECT"), + delete: makeMethod("DELETE"), + get: makeMethod("GET"), getConfig, - head: (options) => request({ ...options, method: "HEAD" }), + head: makeMethod("HEAD"), interceptors, - options: (options) => request({ ...options, method: "OPTIONS" }), - patch: (options) => request({ ...options, method: "PATCH" }), - post: (options) => request({ ...options, method: "POST" }), - put: (options) => request({ ...options, method: "PUT" }), + options: makeMethod("OPTIONS"), + patch: makeMethod("PATCH"), + post: makeMethod("POST"), + put: makeMethod("PUT"), request, setConfig, - trace: (options) => request({ ...options, method: "TRACE" }), - } + trace: makeMethod("TRACE"), + } as Client } diff --git a/packages/sdk/js/src/gen/client/index.ts b/packages/sdk/js/src/gen/client/index.ts index ce89a34cc..06f21e3d8 100644 --- a/packages/sdk/js/src/gen/client/index.ts +++ b/packages/sdk/js/src/gen/client/index.ts @@ -1,8 +1,14 @@ -export type { Auth } from "../core/auth.js" -export type { QuerySerializerOptions } from "../core/bodySerializer.js" -export { formDataBodySerializer, jsonBodySerializer, urlSearchParamsBodySerializer } from "../core/bodySerializer.js" -export { buildClientParams } from "../core/params.js" -export { createClient } from "./client.js" +// This file is auto-generated by @hey-api/openapi-ts + +export type { Auth } from "../core/auth.gen.js" +export type { QuerySerializerOptions } from "../core/bodySerializer.gen.js" +export { + formDataBodySerializer, + jsonBodySerializer, + urlSearchParamsBodySerializer, +} from "../core/bodySerializer.gen.js" +export { buildClientParams } from "../core/params.gen.js" +export { createClient } from "./client.gen.js" export type { Client, ClientOptions, @@ -12,7 +18,8 @@ export type { OptionsLegacyParser, RequestOptions, RequestResult, + ResolvedRequestOptions, ResponseStyle, TDataShape, -} from "./types.js" -export { createConfig, mergeHeaders } from "./utils.js" +} from "./types.gen.js" +export { createConfig, mergeHeaders } from "./utils.gen.js" diff --git a/packages/sdk/js/src/gen/client/types.ts b/packages/sdk/js/src/gen/client/types.gen.ts similarity index 69% rename from packages/sdk/js/src/gen/client/types.ts rename to packages/sdk/js/src/gen/client/types.gen.ts index f3b116bae..db8e544cf 100644 --- a/packages/sdk/js/src/gen/client/types.ts +++ b/packages/sdk/js/src/gen/client/types.gen.ts @@ -1,6 +1,9 @@ -import type { Auth } from "../core/auth.js" -import type { Client as CoreClient, Config as CoreConfig } from "../core/types.js" -import type { Middleware } from "./utils.js" +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth } from "../core/auth.gen.js" +import type { ServerSentEventsOptions, ServerSentEventsResult } from "../core/serverSentEvents.gen.js" +import type { Client as CoreClient, Config as CoreConfig } from "../core/types.gen.js" +import type { Middleware } from "./utils.gen.js" export type ResponseStyle = "data" | "fields" @@ -49,13 +52,18 @@ export interface Config } export interface RequestOptions< + TData = unknown, TResponseStyle extends ResponseStyle = "fields", ThrowOnError extends boolean = boolean, Url extends string = string, > extends Config<{ - responseStyle: TResponseStyle - throwOnError: ThrowOnError - }> { + responseStyle: TResponseStyle + throwOnError: ThrowOnError + }>, + Pick< + ServerSentEventsOptions, + "onSseError" | "onSseEvent" | "sseDefaultRetryDelay" | "sseMaxRetryAttempts" | "sseMaxRetryDelay" + > { /** * Any body that you want to add to your request. * @@ -71,6 +79,14 @@ export interface RequestOptions< url: Url } +export interface ResolvedRequestOptions< + TResponseStyle extends ResponseStyle = "fields", + ThrowOnError extends boolean = boolean, + Url extends string = string, +> extends RequestOptions { + serializedBody?: string +} + export type RequestResult< TData = unknown, TError = unknown, @@ -112,23 +128,36 @@ export interface ClientOptions { throwOnError?: boolean } -type MethodFn = < +type MethodFnBase = < TData = unknown, TError = unknown, ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = "fields", >( - options: Omit, "method">, + options: Omit, "method">, ) => RequestResult +type MethodFnServerSentEvents = < + TData = unknown, + TError = unknown, + ThrowOnError extends boolean = false, + TResponseStyle extends ResponseStyle = "fields", +>( + options: Omit, "method">, +) => Promise> + +type MethodFn = MethodFnBase & { + sse: MethodFnServerSentEvents +} + type RequestFn = < TData = unknown, TError = unknown, ThrowOnError extends boolean = false, TResponseStyle extends ResponseStyle = "fields", >( - options: Omit, "method"> & - Pick>, "method">, + options: Omit, "method"> & + Pick>, "method">, ) => RequestResult type BuildUrlFn = < @@ -143,7 +172,7 @@ type BuildUrlFn = < ) => string export type Client = CoreClient & { - interceptors: Middleware + interceptors: Middleware } /** @@ -171,8 +200,10 @@ type OmitKeys = Pick> export type Options< TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, + TResponse = unknown, TResponseStyle extends ResponseStyle = "fields", -> = OmitKeys, "body" | "path" | "query" | "url"> & Omit +> = OmitKeys, "body" | "path" | "query" | "url"> & + Omit export type OptionsLegacyParser< TData = unknown, @@ -180,12 +211,12 @@ export type OptionsLegacyParser< TResponseStyle extends ResponseStyle = "fields", > = TData extends { body?: any } ? TData extends { headers?: any } - ? OmitKeys, "body" | "headers" | "url"> & TData - : OmitKeys, "body" | "url"> & + ? OmitKeys, "body" | "headers" | "url"> & TData + : OmitKeys, "body" | "url"> & TData & - Pick, "headers"> + Pick, "headers"> : TData extends { headers?: any } - ? OmitKeys, "headers" | "url"> & + ? OmitKeys, "headers" | "url"> & TData & - Pick, "body"> - : OmitKeys, "url"> & TData + Pick, "body"> + : OmitKeys, "url"> & TData diff --git a/packages/sdk/js/src/gen/client/utils.ts b/packages/sdk/js/src/gen/client/utils.gen.ts similarity index 72% rename from packages/sdk/js/src/gen/client/utils.ts rename to packages/sdk/js/src/gen/client/utils.gen.ts index 84648c855..209bfbe8e 100644 --- a/packages/sdk/js/src/gen/client/utils.ts +++ b/packages/sdk/js/src/gen/client/utils.gen.ts @@ -1,84 +1,11 @@ -import { getAuthToken } from "../core/auth.js" -import type { QuerySerializer, QuerySerializerOptions } from "../core/bodySerializer.js" -import { jsonBodySerializer } from "../core/bodySerializer.js" -import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.js" -import type { Client, ClientOptions, Config, RequestOptions } from "./types.js" +// This file is auto-generated by @hey-api/openapi-ts -interface PathSerializer { - path: Record - url: string -} - -const PATH_PARAM_RE = /\{[^{}]+\}/g - -type ArrayStyle = "form" | "spaceDelimited" | "pipeDelimited" -type MatrixStyle = "label" | "matrix" | "simple" -type ArraySeparatorStyle = ArrayStyle | MatrixStyle - -const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { - let url = _url - const matches = _url.match(PATH_PARAM_RE) - if (matches) { - for (const match of matches) { - let explode = false - let name = match.substring(1, match.length - 1) - let style: ArraySeparatorStyle = "simple" - - if (name.endsWith("*")) { - explode = true - name = name.substring(0, name.length - 1) - } - - if (name.startsWith(".")) { - name = name.substring(1) - style = "label" - } else if (name.startsWith(";")) { - name = name.substring(1) - style = "matrix" - } - - const value = path[name] - - if (value === undefined || value === null) { - continue - } - - if (Array.isArray(value)) { - url = url.replace(match, serializeArrayParam({ explode, name, style, value })) - continue - } - - if (typeof value === "object") { - url = url.replace( - match, - serializeObjectParam({ - explode, - name, - style, - value: value as Record, - valueOnly: true, - }), - ) - continue - } - - if (style === "matrix") { - url = url.replace( - match, - `;${serializePrimitiveParam({ - name, - value: value as string, - })}`, - ) - continue - } - - const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string)) - url = url.replace(match, replaceValue) - } - } - return url -} +import { getAuthToken } from "../core/auth.gen.js" +import type { QuerySerializerOptions } from "../core/bodySerializer.gen.js" +import { jsonBodySerializer } from "../core/bodySerializer.gen.js" +import { serializeArrayParam, serializeObjectParam, serializePrimitiveParam } from "../core/pathSerializer.gen.js" +import { getUrl } from "../core/utils.gen.js" +import type { Client, ClientOptions, Config, RequestOptions } from "./types.gen.js" export const createQuerySerializer = ({ allowReserved, array, object }: QuerySerializerOptions = {}) => { const querySerializer = (queryParams: T) => { @@ -161,6 +88,21 @@ export const getParseAs = (contentType: string | null): Exclude & { + headers: Headers + }, + name?: string, +): boolean => { + if (!name) { + return false + } + if (options.headers.has(name) || options.query?.[name] || options.headers.get("Cookie")?.includes(`${name}=`)) { + return true + } + return false +} + export const setAuthParams = async ({ security, ...options @@ -169,6 +111,10 @@ export const setAuthParams = async ({ headers: Headers }) => { for (const auth of security) { + if (checkForExistence(options, auth.name)) { + continue + } + const token = await getAuthToken(auth, options.auth) if (!token) { @@ -192,13 +138,11 @@ export const setAuthParams = async ({ options.headers.set(name, token) break } - - return } } -export const buildUrl: Client["buildUrl"] = (options) => { - const url = getUrl({ +export const buildUrl: Client["buildUrl"] = (options) => + getUrl({ baseUrl: options.baseUrl as string, path: options.path, query: options.query, @@ -208,36 +152,6 @@ export const buildUrl: Client["buildUrl"] = (options) => { : createQuerySerializer(options.querySerializer), url: options.url, }) - return url -} - -export const getUrl = ({ - baseUrl, - path, - query, - querySerializer, - url: _url, -}: { - baseUrl?: string - path?: Record - query?: Record - querySerializer: QuerySerializer - url: string -}) => { - const pathUrl = _url.startsWith("/") ? _url : `/${_url}` - let url = (baseUrl ?? "") + pathUrl - if (path) { - url = defaultPathSerializer({ path, url }) - } - let search = query ? querySerializer(query) : "" - if (search.startsWith("?")) { - search = search.substring(1) - } - if (search) { - url += `?${search}` - } - return url -} export const mergeConfigs = (a: Config, b: Config): Config => { const config = { ...a, ...b } diff --git a/packages/sdk/js/src/gen/core/auth.ts b/packages/sdk/js/src/gen/core/auth.gen.ts similarity index 93% rename from packages/sdk/js/src/gen/core/auth.ts rename to packages/sdk/js/src/gen/core/auth.gen.ts index e496d4557..bc7b230f4 100644 --- a/packages/sdk/js/src/gen/core/auth.ts +++ b/packages/sdk/js/src/gen/core/auth.gen.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + export type AuthToken = string | undefined export interface Auth { diff --git a/packages/sdk/js/src/gen/core/bodySerializer.ts b/packages/sdk/js/src/gen/core/bodySerializer.gen.ts similarity index 92% rename from packages/sdk/js/src/gen/core/bodySerializer.ts rename to packages/sdk/js/src/gen/core/bodySerializer.gen.ts index 8a4a13410..066061605 100644 --- a/packages/sdk/js/src/gen/core/bodySerializer.ts +++ b/packages/sdk/js/src/gen/core/bodySerializer.gen.ts @@ -1,4 +1,6 @@ -import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer.js" +// This file is auto-generated by @hey-api/openapi-ts + +import type { ArrayStyle, ObjectStyle, SerializerOptions } from "./pathSerializer.gen.js" export type QuerySerializer = (query: Record) => string @@ -13,6 +15,8 @@ export interface QuerySerializerOptions { const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { if (typeof value === "string" || value instanceof Blob) { data.append(key, value) + } else if (value instanceof Date) { + data.append(key, value.toISOString()) } else { data.append(key, JSON.stringify(value)) } diff --git a/packages/sdk/js/src/gen/core/params.ts b/packages/sdk/js/src/gen/core/params.gen.ts similarity index 98% rename from packages/sdk/js/src/gen/core/params.ts rename to packages/sdk/js/src/gen/core/params.gen.ts index 0a09619d1..68ad1a778 100644 --- a/packages/sdk/js/src/gen/core/params.ts +++ b/packages/sdk/js/src/gen/core/params.gen.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + type Slot = "body" | "headers" | "path" | "query" export type Field = diff --git a/packages/sdk/js/src/gen/core/pathSerializer.ts b/packages/sdk/js/src/gen/core/pathSerializer.gen.ts similarity index 98% rename from packages/sdk/js/src/gen/core/pathSerializer.ts rename to packages/sdk/js/src/gen/core/pathSerializer.gen.ts index 1e27c8d18..96be3bc5a 100644 --- a/packages/sdk/js/src/gen/core/pathSerializer.ts +++ b/packages/sdk/js/src/gen/core/pathSerializer.gen.ts @@ -1,3 +1,5 @@ +// This file is auto-generated by @hey-api/openapi-ts + interface SerializeOptions extends SerializePrimitiveOptions, SerializerOptions {} interface SerializePrimitiveOptions { diff --git a/packages/sdk/js/src/gen/core/serverSentEvents.gen.ts b/packages/sdk/js/src/gen/core/serverSentEvents.gen.ts new file mode 100644 index 000000000..8f7fac549 --- /dev/null +++ b/packages/sdk/js/src/gen/core/serverSentEvents.gen.ts @@ -0,0 +1,210 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { Config } from "./types.gen.js" + +export type ServerSentEventsOptions = Omit & + Pick & { + /** + * 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) => void + /** + * 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 + url: string + } + +export interface StreamEvent { + data: TData + event?: string + id?: string + retry?: number +} + +export type ServerSentEventsResult = { + stream: AsyncGenerator ? TData[keyof TData] : TData, TReturn, TNext> +} + +export const createSseClient = ({ + onSseError, + onSseEvent, + responseTransformer, + responseValidator, + sseDefaultRetryDelay, + sseMaxRetryAttempts, + sseMaxRetryDelay, + sseSleepFn, + url, + ...options +}: ServerSentEventsOptions): ServerSentEventsResult => { + 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 | undefined) + + if (lastEventId !== undefined) { + headers.set("Last-Event-ID", lastEventId) + } + + try { + const response = await fetch(url, { ...options, headers, signal }) + + 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 + + const chunks = buffer.split("\n\n") + buffer = chunks.pop() ?? "" + + for (const chunk of chunks) { + const lines = chunk.split("\n") + const dataLines: Array = [] + 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 } +} diff --git a/packages/sdk/js/src/gen/core/types.ts b/packages/sdk/js/src/gen/core/types.gen.ts similarity index 95% rename from packages/sdk/js/src/gen/core/types.ts rename to packages/sdk/js/src/gen/core/types.gen.ts index 3a12e74c6..16408b2d0 100644 --- a/packages/sdk/js/src/gen/core/types.ts +++ b/packages/sdk/js/src/gen/core/types.gen.ts @@ -1,5 +1,7 @@ -import type { Auth, AuthToken } from "./auth.js" -import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.js" +// This file is auto-generated by @hey-api/openapi-ts + +import type { Auth, AuthToken } from "./auth.gen.js" +import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen.js" export interface Client { /** diff --git a/packages/sdk/js/src/gen/core/utils.gen.ts b/packages/sdk/js/src/gen/core/utils.gen.ts new file mode 100644 index 000000000..be18c608a --- /dev/null +++ b/packages/sdk/js/src/gen/core/utils.gen.ts @@ -0,0 +1,109 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { QuerySerializer } from "./bodySerializer.gen.js" +import { + type ArraySeparatorStyle, + serializeArrayParam, + serializeObjectParam, + serializePrimitiveParam, +} from "./pathSerializer.gen.js" + +export interface PathSerializer { + path: Record + url: string +} + +export const PATH_PARAM_RE = /\{[^{}]+\}/g + +export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { + let url = _url + const matches = _url.match(PATH_PARAM_RE) + if (matches) { + for (const match of matches) { + let explode = false + let name = match.substring(1, match.length - 1) + let style: ArraySeparatorStyle = "simple" + + if (name.endsWith("*")) { + explode = true + name = name.substring(0, name.length - 1) + } + + if (name.startsWith(".")) { + name = name.substring(1) + style = "label" + } else if (name.startsWith(";")) { + name = name.substring(1) + style = "matrix" + } + + const value = path[name] + + if (value === undefined || value === null) { + continue + } + + if (Array.isArray(value)) { + url = url.replace(match, serializeArrayParam({ explode, name, style, value })) + continue + } + + if (typeof value === "object") { + url = url.replace( + match, + serializeObjectParam({ + explode, + name, + style, + value: value as Record, + valueOnly: true, + }), + ) + continue + } + + if (style === "matrix") { + url = url.replace( + match, + `;${serializePrimitiveParam({ + name, + value: value as string, + })}`, + ) + continue + } + + const replaceValue = encodeURIComponent(style === "label" ? `.${value as string}` : (value as string)) + url = url.replace(match, replaceValue) + } + } + return url +} + +export const getUrl = ({ + baseUrl, + path, + query, + querySerializer, + url: _url, +}: { + baseUrl?: string + path?: Record + query?: Record + querySerializer: QuerySerializer + url: string +}) => { + const pathUrl = _url.startsWith("/") ? _url : `/${_url}` + let url = (baseUrl ?? "") + pathUrl + if (path) { + url = defaultPathSerializer({ path, url }) + } + let search = query ? querySerializer(query) : "" + if (search.startsWith("?")) { + search = search.substring(1) + } + if (search) { + url += `?${search}` + } + return url +} diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index b00216b83..f900c24f0 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -123,7 +123,7 @@ class Event extends _HeyApiClient { * Get events */ public subscribe(options?: Options) { - return (options?.client ?? this._client).get({ + return (options?.client ?? this._client).get.sse({ url: "/event", ...options, })