From 882db3a9f4083bba8738efbe3d5ee4a6845f91b4 Mon Sep 17 00:00:00 2001 From: adelnizamutdinov Date: Sun, 16 Aug 2026 10:42:59 +0000 Subject: [PATCH] Allow injecting fetch into TypeScript clients --- internal/tsemit/templates/api.ts.gotmpl | 6 ++- oasmith_test.go | 57 +++++++++++++++++++++++ testdata/golden/config-typescript/api.ts | 10 +++- testdata/golden/private-typescript/api.ts | 10 +++- 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/internal/tsemit/templates/api.ts.gotmpl b/internal/tsemit/templates/api.ts.gotmpl index 589ac90..42a7fb2 100644 --- a/internal/tsemit/templates/api.ts.gotmpl +++ b/internal/tsemit/templates/api.ts.gotmpl @@ -11,6 +11,7 @@ export type FetchInterceptorChain = { export type ClientOptions = { baseURL?: string; + fetch?: typeof globalThis.fetch; interceptors?: FetchInterceptor[]; responseTimeoutMs?: number; sseIdleTimeoutMs?: number; @@ -489,6 +490,7 @@ async function* parseEventStream( async function runInterceptors( request: Request, interceptors: FetchInterceptor[], + fetch: typeof globalThis.fetch, ): Promise { async function dispatch(index: number, nextRequest: Request): Promise { const interceptor = interceptors[index]; @@ -511,6 +513,7 @@ async function runInterceptors( {{end}}export class DefaultApi { private baseURL: string; + private fetch: typeof globalThis.fetch; private interceptors: FetchInterceptor[]; private responseTimeoutMs: number | undefined; {{if .HasSSE}} @@ -522,6 +525,7 @@ async function runInterceptors( constructor(options: ClientOptions = {}) { this.baseURL = options.baseURL ?? ''; + this.fetch = options.fetch ?? globalThis.fetch; this.interceptors = options.interceptors ?? []; this.responseTimeoutMs = configuredTimeout( options, @@ -559,7 +563,7 @@ async function runInterceptors( }; try { return { - response: await runInterceptors(finalRequest, this.interceptors), + response: await runInterceptors(finalRequest, this.interceptors, this.fetch), timeout: timedRequest, request: finalRequest, }; diff --git a/oasmith_test.go b/oasmith_test.go index 19725b0..3a2b884 100644 --- a/oasmith_test.go +++ b/oasmith_test.go @@ -300,6 +300,63 @@ import assert from "node:assert/strict" import { DefaultApi, type FetchInterceptor } from "./api.ts" void describe("api interceptors", () => { + void test("passes the final intercepted request to a custom fetch", async () => { + let fetchedRequest: Request | undefined + const api = new DefaultApi({ + baseURL: "https://example.test", + fetch: async request => { + assert.ok(request instanceof Request) + fetchedRequest = request + return new Response("[]") + }, + interceptors: [ + async chain => { + const headers = new Headers(chain.request.headers) + headers.set("traceparent", "custom-trace") + return await chain.proceed(new Request(chain.request, { headers })) + }, + ], + }) + + await api.listTestEmails() + + assert.ok(fetchedRequest instanceof Request) + assert.equal(fetchedRequest.headers.get("traceparent"), "custom-trace") + }) + + void test("uses custom fetch for SSE reconnects", async () => { + let fetchCalls = 0 + const encoder = new TextEncoder() + const api = new DefaultApi({ + baseURL: "https://example.test", + fetch: async () => { + fetchCalls += 1 + return new Response(new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode("data: {}\n\n")) + controller.close() + }, + }), { headers: { "content-type": "text/event-stream" } }) + }, + responseTimeoutMs: 0, + sseMaxRetries: 1, + sseReconnectBaseDelayMs: 0, + }) + + const response = await api.episodeProcessingEventsResult({ + episodeId: "episode_1", + showId: "show_1", + teamId: "team_1", + }) + assert.equal(response.status, 200) + if (response.status !== 200) throw new Error("unexpected status") + const iterator = response.body[Symbol.asyncIterator]() + assert.equal((await iterator.next()).done, false) + assert.equal((await iterator.next()).done, false) + assert.equal(fetchCalls, 2) + await iterator.return?.() + }) + void test("run in order and allow repeated proceed", async () => { const events: Array = [] const interceptors: Array = [ diff --git a/testdata/golden/config-typescript/api.ts b/testdata/golden/config-typescript/api.ts index f30b2e6..9237d69 100644 --- a/testdata/golden/config-typescript/api.ts +++ b/testdata/golden/config-typescript/api.ts @@ -9,6 +9,7 @@ export type FetchInterceptorChain = { export type ClientOptions = { baseURL?: string + fetch?: typeof globalThis.fetch interceptors?: FetchInterceptor[] responseTimeoutMs?: number sseIdleTimeoutMs?: number @@ -99,6 +100,7 @@ function responseTimeout( async function runInterceptors( request: Request, interceptors: FetchInterceptor[], + fetch: typeof globalThis.fetch, ): Promise { async function dispatch( index: number, @@ -120,11 +122,13 @@ async function runInterceptors( export class DefaultApi { private baseURL: string + private fetch: typeof globalThis.fetch private interceptors: FetchInterceptor[] private responseTimeoutMs: number | undefined constructor(options: ClientOptions = {}) { this.baseURL = options.baseURL ?? "" + this.fetch = options.fetch ?? globalThis.fetch this.interceptors = options.interceptors ?? [] this.responseTimeoutMs = configuredTimeout( options, @@ -157,7 +161,11 @@ export class DefaultApi { } try { return { - response: await runInterceptors(finalRequest, this.interceptors), + response: await runInterceptors( + finalRequest, + this.interceptors, + this.fetch, + ), timeout: timedRequest, request: finalRequest, } diff --git a/testdata/golden/private-typescript/api.ts b/testdata/golden/private-typescript/api.ts index a776938..36e68fe 100644 --- a/testdata/golden/private-typescript/api.ts +++ b/testdata/golden/private-typescript/api.ts @@ -50,6 +50,7 @@ export type FetchInterceptorChain = { export type ClientOptions = { baseURL?: string + fetch?: typeof globalThis.fetch interceptors?: FetchInterceptor[] responseTimeoutMs?: number sseIdleTimeoutMs?: number @@ -555,6 +556,7 @@ async function* parseEventStream( async function runInterceptors( request: Request, interceptors: FetchInterceptor[], + fetch: typeof globalThis.fetch, ): Promise { async function dispatch( index: number, @@ -686,6 +688,7 @@ export interface RenderShowRSSFeedRequest { export class DefaultApi { private baseURL: string + private fetch: typeof globalThis.fetch private interceptors: FetchInterceptor[] private responseTimeoutMs: number | undefined @@ -696,6 +699,7 @@ export class DefaultApi { constructor(options: ClientOptions = {}) { this.baseURL = options.baseURL ?? "" + this.fetch = options.fetch ?? globalThis.fetch this.interceptors = options.interceptors ?? [] this.responseTimeoutMs = configuredTimeout( options, @@ -736,7 +740,11 @@ export class DefaultApi { } try { return { - response: await runInterceptors(finalRequest, this.interceptors), + response: await runInterceptors( + finalRequest, + this.interceptors, + this.fetch, + ), timeout: timedRequest, request: finalRequest, }