diff --git a/bun.lock b/bun.lock index 2cd807f3..62b2e88f 100644 --- a/bun.lock +++ b/bun.lock @@ -26,7 +26,7 @@ }, "cloud/core": { "name": "@opencode/cloud-core", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@aws-sdk/client-sts": "3.782.0", "drizzle-orm": "0.41.0", @@ -40,7 +40,7 @@ }, "cloud/function": { "name": "@opencode/cloud-function", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@ai-sdk/anthropic": "2.0.0", "@ai-sdk/openai": "2.0.2", @@ -60,7 +60,7 @@ }, "cloud/web": { "name": "@opencode/cloud-web", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@kobalte/core": "0.13.9", "@openauthjs/solid": "0.0.0-20250322224806", @@ -79,7 +79,7 @@ }, "packages/function": { "name": "@opencode/function", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@octokit/auth-app": "8.0.1", "@octokit/rest": "22.0.0", @@ -94,7 +94,7 @@ }, "packages/opencode": { "name": "opencode", - "version": "0.5.15", + "version": "0.5.28", "bin": { "opencode": "./bin/opencode", }, @@ -144,21 +144,20 @@ }, "packages/plugin": { "name": "@opencode-ai/plugin", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@opencode-ai/sdk": "workspace:*", }, "devDependencies": { - "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", "typescript": "catalog:", }, }, "packages/sdk/js": { "name": "@opencode-ai/sdk", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { - "@hey-api/openapi-ts": "0.80.1", + "@hey-api/openapi-ts": "0.81.0", }, "devDependencies": { "@hey-api/openapi-ts": "0.80.1", @@ -168,7 +167,7 @@ }, "packages/web": { "name": "@opencode/web", - "version": "0.5.15", + "version": "0.5.28", "dependencies": { "@astrojs/cloudflare": "12.6.3", "@astrojs/markdown-remark": "6.3.1", @@ -495,7 +494,7 @@ "@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.0.6", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-yktiFZoWPtEW8QKS65eqKwA5MTKp88CyiL8q72WynrBs/73SAaxlSWlA2zW/DZlywZ5hX1OYzrCC0wFdvO9c2w=="], - "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.81.0", "", { "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", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="], + "@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=="], "@hono/zod-validator": ["@hono/zod-validator@0.4.2", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-1rrlBg+EpDPhzOV4hT9pxr5+xDVmKuz6YJl+la7VCwK6ass5ldyKm5fD+umJdV2zhHD6jROoCCv8NbTwyfhT0g=="], @@ -3211,7 +3210,7 @@ "@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=="], + "@opencode-ai/sdk/@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.81.0", "", { "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", "js-yaml": "4.1.0", "open": "10.1.2", "semver": "7.7.2" }, "peerDependencies": { "typescript": "^5.5.3" }, "bin": { "openapi-ts": "bin/index.cjs" } }, "sha512-PoJukNBkUfHOoMDpN33bBETX49TUhy7Hu8Sa0jslOvFndvZ5VjQr4Nl/Dzjb9LG1Lp5HjybyTJMA6a1zYk/q6A=="], "@opencode/cloud-web/solid-js": ["solid-js@1.9.5", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "^1.1.0", "seroval-plugins": "^1.1.0" } }, "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw=="], diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 9ef1a2a8..887b0a73 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -19,7 +19,6 @@ "@opencode-ai/sdk": "workspace:*" }, "devDependencies": { - "@hey-api/openapi-ts": "0.81.0", "@tsconfig/node22": "catalog:", "typescript": "catalog:" } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index ad54199c..3f981501 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -29,6 +29,6 @@ "@tsconfig/node22": "catalog:" }, "dependencies": { - "@hey-api/openapi-ts": "0.80.1" + "@hey-api/openapi-ts": "0.81.0" } } diff --git a/packages/sdk/js/src/client.ts b/packages/sdk/js/src/client.ts index 8346fd8a..29b9de90 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 46a62694..34a8d0be 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 ce89a34c..06f21e3d 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 f3b116ba..db8e544c 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 84648c85..209bfbe8 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 e496d455..bc7b230f 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 8a4a1341..06606160 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 0a09619d..68ad1a77 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 1e27c8d1..96be3bc5 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 00000000..8f7fac54 --- /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 3a12e74c..16408b2d 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 00000000..be18c608 --- /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 b00216b8..f900c24f 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, })