Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client)!: client context mechanism and rewrite #62

Merged
merged 18 commits into from
Dec 29, 2024
33 changes: 21 additions & 12 deletions apps/content/content/docs/client/react-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ description: Simplify React Query usage with minimal integration using ORPC and

```ts twoslash
import { createORPCReactQueryUtils } from '@orpc/react-query';
import { createORPCFetchClient } from '@orpc/client';
import { createORPCClient } from '@orpc/client';
import { ORPCLink } from '@orpc/client/fetch';
import type { router } from 'examples/server';

// Create an ORPC client
export const client = createORPCFetchClient<typeof router>({
baseURL: 'http://localhost:3000/api',
});
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(orpcLink)

// Create React Query utilities for ORPC
export const orpc = createORPCReactQueryUtils(client);
Expand All @@ -35,13 +39,14 @@ orpc.getting.

```tsx twoslash
import { createORPCReactQueryUtils, RouterUtils } from '@orpc/react-query';
import { createORPCFetchClient } from '@orpc/client';
import { createORPCClient } from '@orpc/client';
import { ORPCLink } from '@orpc/client/fetch';
import { RouterClient } from '@orpc/server';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { router } from 'examples/server';
import * as React from 'react';

const ORPCContext = React.createContext<RouterUtils<RouterClient<typeof router>> | undefined>(undefined);
const ORPCContext = React.createContext<RouterUtils<RouterClient<typeof router, unknown>> | undefined>(undefined);

export function useORPC() {
const orpc = React.useContext(ORPCContext);
Expand All @@ -54,11 +59,15 @@ export function useORPC() {
}

export function ORPCProvider({ children }: { children: React.ReactNode }) {
const [client] = React.useState(() =>
createORPCFetchClient<typeof router>({
baseURL: 'http://localhost:3000/api',
})
);
const [client] = React.useState(() => {
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
});

return createORPCClient<typeof router>(orpcLink);
});
const [queryClient] = React.useState(() => new QueryClient());

const orpc = React.useMemo(() => createORPCReactQueryUtils(client), [client]);
Expand Down
17 changes: 12 additions & 5 deletions apps/content/content/docs/client/react.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@ npm i @orpc/client @orpc/react @tanstack/react-query

```tsx twoslash
import { createORPCReact } from '@orpc/react'
import { createORPCFetchClient } from '@orpc/client'
import { createORPCClient } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import { RouterClient } from '@orpc/server'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
import type { router } from 'examples/server'
import * as React from 'react'

export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */>>()
export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */, unknown>>()

export function ORPCProvider({ children }: { children: React.ReactNode }) {
const [client] = useState(() => createORPCFetchClient<typeof router /** must match with createORPCReact*/>({
baseURL: 'http://localhost:3000/api',
}))
const [client] = useState(() => {
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

return createORPCClient<typeof router>(orpcLink)
})
const [queryClient] = useState(() => new QueryClient())

return (
Expand Down
68 changes: 65 additions & 3 deletions apps/content/content/docs/client/vanilla.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ npm i @orpc/client
To create a fully typed client, you need either the type of the [router](/docs/server/router) you intend to use or the [contract](/docs/contract/builder).

```ts twoslash
import { createORPCFetchClient, ORPCError } from '@orpc/client'
import { createORPCClient, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'

const client = createORPCFetchClient<typeof router /* or contract router */>({
baseURL: 'http://localhost:3000/api',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(orpcLink)

// File upload out of the box
const output = await client.post.create({
Expand All @@ -34,3 +37,62 @@ const output = await client.post.create({
client.post.
// ^|
```

## Client Context

The `Client Context` feature allows you to pass additional contextual information (like caching policies) to your client calls.

```ts twoslash
import type { router } from 'examples/server'
import { createORPCClient, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'

type ClientContext = { cache?: RequestCache } | undefined
// if context is not undefinable, it will require you pass context in every call

const orpcLink = new ORPCLink<ClientContext>({
url: 'http://localhost:3000/api',
// headers: provide additional headers
fetch: (input, init, context) => globalThis.fetch(input, {
...init,
cache: context?.cache,
}),
})

const client = createORPCClient<typeof router, ClientContext>(orpcLink)

client.getting({ name: 'unnoq' }, { context: { cache: 'force-cache' } })
```

> **Note**: This works seamlessly with [Vue Query](/docs/client/vue-query) and [React Query](/docs/client/react-query).

## Dynamic Link

With the **Dynamic Link** mechanism, you can define custom logic to dynamically choose between different links based on the request's context, path, or input.

```ts twoslash
import type { router } from 'examples/server'
import { createORPCClient, DynamicLink, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'

const orpcLink1 = new ORPCLink({
url: 'http://localhost:3000/api',
// headers: provide additional headers
})

const orpcLink2 = new ORPCLink({
url: 'http://localhost:8000/api',
// headers: provide additional headers
})

const dynamicLink = new DynamicLink((path, input, options) => { // can be async
// const clientContext = options.context
if (path.includes('post')) {
return orpcLink1
}

return orpcLink2
})

const client = createORPCClient<typeof router>(dynamicLink)
```
13 changes: 9 additions & 4 deletions apps/content/content/docs/client/vue-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,19 @@ description: Simplify Vue Query usage with minimal integration using ORPC and Ta

```ts twoslash
import { createORPCVueQueryUtils } from '@orpc/vue-query';
import { createORPCFetchClient } from '@orpc/client';
import { createORPCClient } from '@orpc/client';
import { ORPCLink } from '@orpc/client/fetch';
import type { router } from 'examples/server';

// Create an ORPC client
export const client = createORPCFetchClient<typeof router>({
baseURL: 'http://localhost:3000/api',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
});

// Create an ORPC client
export const client = createORPCClient<typeof router>(orpcLink);

// Create Vue Query utilities for ORPC
export const orpc = createORPCVueQueryUtils(client);

Expand Down
9 changes: 6 additions & 3 deletions apps/content/content/docs/contract-first.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,17 @@ That's it! The contract definition and implementation are now completely separat
Create a fully typed client using just the contract definition:

```ts twoslash
import { createORPCFetchClient, ORPCError } from '@orpc/client'
import { createORPCClient, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import type { contract } from 'examples/contract'

const client = createORPCFetchClient<typeof contract /* or server router */>({
baseURL: 'http://localhost:3000/prefix',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/prefix',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof contract /* or server router */>(orpcLink)

// File upload out of the box
const output = await client.post.create({
Expand Down
9 changes: 6 additions & 3 deletions apps/content/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,17 @@ Start the server and visit http://localhost:3000/api/getting?name=yourname to se
Use the fully typed client in any environment:

```ts twoslash
import { createORPCFetchClient, ORPCError } from '@orpc/client'
import { createORPCClient, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'

const client = createORPCFetchClient<typeof router /* or contract router */>({
baseURL: 'http://localhost:3000/api',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(orpcLink)

// File upload out of the box
const output = await client.post.create({
Expand Down
11 changes: 8 additions & 3 deletions apps/content/content/docs/server/file-upload.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,17 @@ To upload files with oRPC from the client, set up an oRPC client
and pass a `File` object directly to the upload endpoint.

```typescript
import { createORPCFetchClient } from '@orpc/client'
import { createORPCClient } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'

const client = createORPCFetchClient<typeof appRouter>({
baseURL: 'http://localhost:3000',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof appRouter>(orpcLink)

// Example: Upload a file from an HTML file input
const fileInput = document.getElementById('file-input') as HTMLInputElement
fileInput.onchange = async () => {
Expand Down
26 changes: 18 additions & 8 deletions apps/content/content/home/client.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
<Tabs items={['Client', 'React', 'React Query', 'Vue Query', 'CURL']}>
<Tab value="Client">
```ts twoslash
import { createORPCFetchClient, ORPCError } from '@orpc/client'
import { createORPCClient, ORPCError } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'

const client = createORPCFetchClient<typeof router /* or contract router */>({
baseURL: 'http://localhost:3000/api',
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(orpcLink)

// File upload out of the box
const output = await client.post.create({
Expand Down Expand Up @@ -44,14 +47,15 @@ try {
<Tab value="React">
```tsx twoslash
import { createORPCReact } from '@orpc/react'
import { createORPCFetchClient } from '@orpc/client'
import { createORPCClient } from '@orpc/client'
import { ORPCLink } from '@orpc/client/fetch'
import { RouterClient } from '@orpc/server'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
import type { router } from 'examples/server'
import * as React from 'react'

export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */>>()
export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */, unknown>>()

// ------------------ Example ------------------

Expand Down Expand Up @@ -118,9 +122,15 @@ const queries = orpc.useQueries(o => [
// ------------------ Provider ------------------

export function ORPCProvider({ children }: { children: React.ReactNode }) {
const [client] = useState(() => createORPCFetchClient<typeof router /** must match with createORPCReact*/>({
baseURL: 'http://localhost:3000/api',
}))
const [client] = useState(() => {
const orpcLink = new ORPCLink({
url: 'http://localhost:3000/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

return createORPCClient<typeof router>(orpcLink)
})
const [queryClient] = useState(() => new QueryClient())

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/content/examples/react-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import type { RouterClient } from '@orpc/server'
import type { router } from 'examples/server'
import { createORPCReactQueryUtils } from '@orpc/react-query'

export const orpc = createORPCReactQueryUtils({} as RouterClient<typeof router /** or contract router */>)
export const orpc = createORPCReactQueryUtils({} as RouterClient<typeof router /** or contract router */, unknown>)
2 changes: 1 addition & 1 deletion apps/content/examples/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import type { RouterClient } from '@orpc/server'
import type { router } from 'examples/server'
import { createORPCReact } from '@orpc/react'

export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */>>()
export const { orpc, ORPCContext } = createORPCReact<RouterClient<typeof router /** or contract router */, unknown>>()
2 changes: 1 addition & 1 deletion apps/content/examples/vue-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import type { RouterClient } from '@orpc/server'
import type { router } from 'examples/server'
import { createORPCVueQueryUtils } from '@orpc/vue-query'

export const orpc = createORPCVueQueryUtils({} as RouterClient<typeof router /** or contract router */>)
export const orpc = createORPCVueQueryUtils({} as RouterClient<typeof router /** or contract router */, unknown>)
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default antfu({
'ts/consistent-type-definitions': 'off',
'react-refresh/only-export-components': 'off',
'react/prefer-destructuring-assignment': 'off',
'react/no-context-provider': 'off',
},
}, {
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.test-d.ts', '**/*.test-d.tsx', 'apps/content/examples/**', 'playgrounds/**'],
Expand Down
8 changes: 7 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./fetch": {
"types": "./dist/src/adapters/fetch/index.d.ts",
"import": "./dist/fetch.js",
"default": "./dist/fetch.js"
},
"./🔒/*": {
"types": "./dist/src/*.d.ts"
}
}
},
"exports": {
".": "./src/index.ts",
"./fetch": "./src/adapters/fetch/index.ts",
"./🔒/*": {
"types": "./src/*.ts"
}
Expand All @@ -37,7 +43,7 @@
"dist"
],
"scripts": {
"build": "tsup --clean --sourcemap --entry.index=src/index.ts --format=esm --onSuccess='tsc -b --noCheck'",
"build": "tsup --clean --sourcemap --entry.index=src/index.ts --entry.fetch=src/adapters/fetch/index.ts --format=esm --onSuccess='tsc -b --noCheck'",
"build:watch": "pnpm run build --watch",
"type:check": "tsc -b"
},
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/adapters/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './orpc-link'
export * from './types'
Loading
Loading