Skip to content

Commit d16a1b6

Browse files
authored
refactor(client)!: new StandardLink (#193)
* LazyStandardRequest & LazyStandardResponse * toFetchRequest & toLazyStandardResponse * improve naming * sync tests * wip * improve * tests: standard link * StandardRPCLinkCodec * linkFetchClient * RPCLink * docs * sync * improve * improve * sync
1 parent 0165ee0 commit d16a1b6

36 files changed

+1199
-988
lines changed

apps/content/docs/client/rpc-link.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,38 +82,40 @@ const link = new RPCLink({
8282

8383
return 'POST'
8484
},
85-
fetch: (url, init, { context }) =>
86-
globalThis.fetch(url, { ...init, cache: context?.cache }),
85+
fetch: (request, init, { context }) => globalThis.fetch(request, {
86+
...init,
87+
cache: context?.cache,
88+
}),
8789
})
8890
```
8991

90-
## Event Source Configuration
92+
## Event Iterator Configuration
9193

92-
Customize the retry logic for event sources (the mechanism behind [Event Iterator](/docs/event-iterator)) using these options:
94+
Customize the retry logic for [Event Iterator](/docs/event-iterator) using these options:
9395

94-
- **eventSourceMaxNumberOfRetries:** Maximum retry attempts.
95-
- **eventSourceRetryDelay:** Delay between retries.
96-
- **eventSourceRetry:** Function to determine if a retry should occur.
96+
- **eventIteratorMaxRetries:** Maximum retry attempts.
97+
- **eventIteratorRetryDelay:** Delay between retries.
98+
- **eventIteratorShouldRetry:** Function to determine if a retry should occur.
9799

98100
```ts twoslash
99101
import { RPCLink } from '@orpc/client/fetch'
100102

101103
interface ClientContext {
102-
eventSourceRetry?: boolean
104+
eventIteratorShouldRetry?: boolean
103105
}
104106

105107
const link = new RPCLink<ClientContext>({
106108
url: 'http://localhost:3000/rpc',
107-
eventSourceRetry(reconnectOptions, options, path, input) {
109+
eventIteratorShouldRetry(reconnectOptions, options, path, input) {
108110
console.log(reconnectOptions.error)
109111

110-
return !options.context?.eventSourceRetry
112+
return !options.context?.eventIteratorShouldRetry
111113
}
112114
})
113115
```
114116

115117
:::tip
116-
You should disable event source retries when streaming results from a chatbot AI.
118+
You should disable event iterator retries when streaming results from a chatbot AI.
117119
:::
118120

119121
## Event-Source Ping Interval

apps/content/examples/openai-streaming.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,25 +33,25 @@ const complete = os
3333

3434
const router = { complete }
3535

36-
type ClientContext = { disableESRetry?: boolean }
36+
type ClientContext = { disableEIRetry?: boolean }
3737

3838
const link = new RPCLink<ClientContext>({
3939
url: 'https://example.com/rpc',
40-
eventSourceRetry: (_, { context }) => !context?.disableESRetry,
40+
eventIteratorShouldRetry: (_, { context }) => !context?.disableEIRetry,
4141
})
4242

4343
const client: RouterClient<typeof router, ClientContext> = createORPCClient(link)
4444

4545
const stream = await client.complete(
4646
{ content: 'Hello, world!' },
47-
{ context: { disableESRetry: true } }
47+
{ context: { disableEIRetry: true } }
4848
)
4949

5050
for await (const chunk of stream) {
5151
console.log(chunk.choices[0]?.delta?.content || '')
5252
}
5353
```
5454

55-
**Note:** Disable event source retries when streaming chatbot responses.
55+
**Note:** Disable event iterator retries when streaming chatbot responses.
5656

5757
Learn more about [RPCLink](/docs/client/rpc-link) and [Event Iterator](/docs/client/event-iterator).
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { StandardRequest } from '@orpc/standard-server'
2+
import * as StandardServerFetch from '@orpc/standard-server-fetch'
3+
import { LinkFetchClient } from './link-fetch-client'
4+
5+
const toFetchRequestSpy = vi.spyOn(StandardServerFetch, 'toFetchRequest')
6+
const toStandardLazyResponseSpy = vi.spyOn(StandardServerFetch, 'toStandardLazyResponse')
7+
8+
describe('linkFetchClient', () => {
9+
it('call', async () => {
10+
const fetch = vi.fn().mockResolvedValueOnce(new Response('body'))
11+
const client = new LinkFetchClient({
12+
fetch,
13+
})
14+
15+
const standardRequest: StandardRequest = {
16+
url: new URL('http://localhost:300/example'),
17+
body: { body: true },
18+
headers: {
19+
'x-custom': 'value',
20+
},
21+
method: 'POST',
22+
signal: AbortSignal.timeout(100),
23+
}
24+
25+
const options = {
26+
context: { context: true },
27+
lastEventId: 'last-event-id',
28+
signal: AbortSignal.timeout(100),
29+
}
30+
31+
const response = await client.call(standardRequest, options, ['example'], { body: true })
32+
33+
expect(response).toBe(toStandardLazyResponseSpy.mock.results[0]!.value)
34+
expect(toStandardLazyResponseSpy).toBeCalledTimes(1)
35+
expect(toStandardLazyResponseSpy).toBeCalledWith(await fetch.mock.results[0]!.value)
36+
37+
expect(toFetchRequestSpy).toBeCalledTimes(1)
38+
expect(toFetchRequestSpy).toBeCalledWith(standardRequest)
39+
40+
expect(fetch).toBeCalledTimes(1)
41+
expect(fetch).toBeCalledWith(
42+
toFetchRequestSpy.mock.results[0]!.value,
43+
{},
44+
options,
45+
['example'],
46+
{ body: true },
47+
)
48+
})
49+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { StandardLazyResponse, StandardRequest } from '@orpc/standard-server'
2+
import type { ClientContext, ClientOptionsOut } from '../../types'
3+
import type { StandardLinkClient } from '../standard'
4+
import { toFetchRequest, toStandardLazyResponse } from '@orpc/standard-server-fetch'
5+
6+
export interface LinkFetchClientOptions<T extends ClientContext> {
7+
fetch?: (
8+
request: Request,
9+
init: Record<never, never>,
10+
options: ClientOptionsOut<T>,
11+
path: readonly string[],
12+
input: unknown
13+
) => Promise<Response>
14+
}
15+
16+
export class LinkFetchClient<T extends ClientContext> implements StandardLinkClient<T> {
17+
private readonly fetch: Exclude<LinkFetchClientOptions<T>['fetch'], undefined>
18+
19+
constructor(options: LinkFetchClientOptions<T>) {
20+
this.fetch = options?.fetch ?? globalThis.fetch.bind(globalThis)
21+
}
22+
23+
async call(request: StandardRequest, options: ClientOptionsOut<T>, path: readonly string[], input: unknown): Promise<StandardLazyResponse> {
24+
const fetchRequest = toFetchRequest(request)
25+
26+
const fetchResponse = await this.fetch(fetchRequest, {}, options, path, input)
27+
28+
const lazyResponse = toStandardLazyResponse(fetchResponse)
29+
30+
return lazyResponse
31+
}
32+
}

0 commit comments

Comments
 (0)