Skip to content

Commit

Permalink
feat(client)!: client context mechanism and rewrite (#62)
Browse files Browse the repository at this point in the history
* improve & tests

* fixed

* sync @orpc/react

* sync @orpc/react-query

* sync @orpc/vue-query

* fix global fetch problem

* type fixed

* lint fixed

* fixes

* rename createClient -> createORPCClient

* docs

* dynamic link

* improve cast type

* client context + dynamic link docs

* fix docs

* improve docs

* improve docs

* improve docs
  • Loading branch information
unnoq authored Dec 29, 2024
1 parent 2a66984 commit d7b5662
Show file tree
Hide file tree
Showing 76 changed files with 1,367 additions and 545 deletions.
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

0 comments on commit d7b5662

Please sign in to comment.