|
1 | | -import { Fetch, RequestInfo, RequestInit } from 'openai/internal/builtin-types'; |
| 1 | +import { type Fetch, type RequestInfo, type RequestInit, type Response } from 'openai/internal/builtin-types'; |
2 | 2 | import { PassThrough } from 'stream'; |
3 | 3 |
|
| 4 | +/** |
| 5 | + * Creates a mock `fetch` function and a `handleRequest` function for intercepting `fetch` calls. |
| 6 | + * |
| 7 | + * You call `handleRequest` with a callback function that handles the next `fetch` call. |
| 8 | + * It returns a Promise that: |
| 9 | + * - waits for the next call to `fetch` |
| 10 | + * - calls the callback with the `fetch` arguments |
| 11 | + * - resolves `fetch` with the callback output |
| 12 | + */ |
4 | 13 | export function mockFetch(): { |
5 | 14 | fetch: Fetch; |
6 | 15 | handleRequest: (handle: Fetch) => void; |
7 | 16 | handleStreamEvents: (events: any[]) => void; |
8 | 17 | handleMessageStreamEvents: (iter: AsyncIterable<any>) => void; |
9 | 18 | } { |
10 | | - const queue: Promise<typeof fetch>[] = []; |
11 | | - const readResolvers: ((handler: typeof fetch) => void)[] = []; |
| 19 | + const fetchQueue: ((handler: typeof fetch) => void)[] = []; |
| 20 | + const handlerQueue: Promise<typeof fetch>[] = []; |
12 | 21 |
|
13 | | - let index = 0; |
| 22 | + const enqueueHandler = () => { |
| 23 | + handlerQueue.push( |
| 24 | + new Promise<typeof fetch>((resolve) => { |
| 25 | + fetchQueue.push((handle: typeof fetch) => { |
| 26 | + enqueueHandler(); |
| 27 | + resolve(handle); |
| 28 | + }); |
| 29 | + }), |
| 30 | + ); |
| 31 | + }; |
| 32 | + enqueueHandler(); |
14 | 33 |
|
15 | 34 | async function fetch(req: string | RequestInfo, init?: RequestInit): Promise<Response> { |
16 | | - const idx = index++; |
17 | | - if (!queue[idx]) { |
18 | | - queue.push(new Promise((resolve) => readResolvers.push(resolve))); |
19 | | - } |
20 | | - |
21 | | - const handler = await queue[idx]!; |
| 35 | + const handler = await handlerQueue.shift(); |
| 36 | + if (!handler) throw new Error('expected handler to be defined'); |
| 37 | + const signal = init?.signal; |
| 38 | + if (!signal) return await handler(req, init); |
22 | 39 | return await Promise.race([ |
23 | 40 | handler(req, init), |
24 | | - new Promise<Response>((_resolve, reject) => { |
25 | | - if (init?.signal?.aborted) { |
26 | | - // @ts-ignore |
| 41 | + new Promise<Response>((resolve, reject) => { |
| 42 | + if (signal.aborted) { |
| 43 | + // @ts-ignore does exist in Node |
27 | 44 | reject(new DOMException('The user aborted a request.', 'AbortError')); |
28 | 45 | return; |
29 | 46 | } |
30 | | - init?.signal?.addEventListener('abort', (_e) => { |
31 | | - // @ts-ignore |
| 47 | + signal.addEventListener('abort', (e) => { |
| 48 | + // @ts-ignore does exist in Node |
32 | 49 | reject(new DOMException('The user aborted a request.', 'AbortError')); |
33 | 50 | }); |
34 | 51 | }), |
35 | 52 | ]); |
36 | 53 | } |
37 | 54 |
|
38 | | - function handleRequest(handler: typeof fetch): void { |
39 | | - if (readResolvers.length) { |
40 | | - const resolver = readResolvers.shift()!; |
41 | | - resolver(handler); |
42 | | - return; |
43 | | - } |
44 | | - queue.push(Promise.resolve(handler)); |
| 55 | + function handleRequest(handle: typeof fetch): Promise<void> { |
| 56 | + return new Promise<void>((resolve, reject) => { |
| 57 | + fetchQueue.shift()?.(async (req, init) => { |
| 58 | + try { |
| 59 | + return await handle(req, init); |
| 60 | + } catch (err) { |
| 61 | + reject(err); |
| 62 | + return err as any; |
| 63 | + } finally { |
| 64 | + resolve(); |
| 65 | + } |
| 66 | + }); |
| 67 | + }); |
45 | 68 | } |
46 | 69 |
|
47 | 70 | function handleStreamEvents(events: any[]) { |
|
0 commit comments