Skip to content

Commit

Permalink
feat(client)!: make @orpc/client independent (#151)
Browse files Browse the repository at this point in the history
* init

* error

* client

* remove @orpc/contract from query

* serializer

* fix recursive dependencies problem

* client

* sync pnpm-lock

* fix tsconfig.json
  • Loading branch information
unnoq authored Feb 17, 2025
1 parent 3826d73 commit 59108a3
Show file tree
Hide file tree
Showing 138 changed files with 743 additions and 841 deletions.
10 changes: 6 additions & 4 deletions apps/content/content/docs/client/react-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import { createORPCReactQueryUtils } from '@orpc/react-query';
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import type { router } from 'examples/server';
import { RouterClient } from '@orpc/server'

const rpcLink = new RPCLink({
url: 'http://localhost:3000/rpc',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(rpcLink)
const client: RouterClient<typeof router> = createORPCClient(rpcLink)
// const client: ContractRouterClient<typeof contract> = createORPCClient(rpcLink)

// Create React Query utilities for ORPC
export const orpc = createORPCReactQueryUtils(client);
Expand All @@ -46,7 +48,7 @@ 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, unknown>> | undefined>(undefined);
const ORPCContext = React.createContext<RouterUtils<RouterClient<typeof router>> | undefined>(undefined);

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

export function ORPCProvider({ children }: { children: React.ReactNode }) {
const [client] = React.useState(() => {
const [client] = React.useState<RouterClient<typeof router>>(() => {
const rpcLink = new RPCLink({
url: 'https://exanple.com/api',
// fetch: optional override for the default fetch function
// headers: provide additional headers
});

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

Expand Down
10 changes: 7 additions & 3 deletions apps/content/content/docs/client/vanilla.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ To create a fully typed client, you need either the type of the [router](/docs/s
import { createORPCClient, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'
import type { RouterClient } from '@orpc/server'

const rpcLink = new RPCLink({
url: 'http://localhost:3000/rpc',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(rpcLink)
const client: RouterClient<typeof router> = createORPCClient(rpcLink)
// const client: ContractRouterClient<typeof contract> = createORPCClient(rpcLink)

// File upload out of the box
const output = await client.posts.createPost({
Expand All @@ -46,6 +48,7 @@ The `Client Context` feature allows you to pass additional contextual informatio
import type { router } from 'examples/server'
import { createORPCClient, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'

type ClientContext = { cache?: RequestCache }

Expand All @@ -72,7 +75,7 @@ const rpcLink = new RPCLink<ClientContext>({
},
})

const client = createORPCClient<typeof router, ClientContext>(rpcLink)
const client: RouterClient<typeof router, ClientContext> = createORPCClient(rpcLink)

client.getUser({ id: '123' }, { context: { cache: 'force-cache' } })
```
Expand All @@ -87,6 +90,7 @@ With the **Dynamic Link** mechanism, you can define custom logic to dynamically
import type { router } from 'examples/server'
import { createORPCClient, DynamicLink, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'

const rpcLink1 = new RPCLink({
url: 'http://localhost:3000/rpc',
Expand All @@ -106,5 +110,5 @@ const dynamicLink = new DynamicLink(({ context }, path, input) => { // can be as
return rpcLink2
})

const client = createORPCClient<typeof router>(dynamicLink)
const client: RouterClient<typeof router> = createORPCClient(dynamicLink)
```
3 changes: 2 additions & 1 deletion apps/content/content/docs/client/vue-colada.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { createORPCVueColadaUtils } from '@orpc/vue-colada';
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import type { router } from 'examples/server';
import type { RouterClient } from '@orpc/server';

const rpcLink = new RPCLink({
url: 'http://localhost:3000/rpc',
Expand All @@ -24,7 +25,7 @@ const rpcLink = new RPCLink({
});

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

// Create Vue Query utilities for ORPC
export const orpc = createORPCVueColadaUtils(client);
Expand Down
3 changes: 2 additions & 1 deletion apps/content/content/docs/client/vue-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { createORPCVueQueryUtils } from '@orpc/vue-query';
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import type { router } from 'examples/server';
import type { RouterClient } from '@orpc/server';

const rpcLink = new RPCLink({
url: 'http://localhost:3000/rpc',
Expand All @@ -24,7 +25,7 @@ const rpcLink = new RPCLink({
});

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

// Create Vue Query utilities for ORPC
export const orpc = createORPCVueQueryUtils(client);
Expand Down
6 changes: 4 additions & 2 deletions apps/content/content/docs/contract-first.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ 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 { ContractRouterClient } from '@orpc/contract'
import { createORPCClient, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { contract } from 'examples/contract'
Expand All @@ -146,8 +147,9 @@ const rpcLink = new RPCLink({
// headers: provide additional headers
})

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

const client: ContractRouterClient<typeof contract> = createORPCClient(rpcLink)
// const client: RouterClient<typeof router> = createORPCClient(rpcLink)

// File upload out of the box
const output = await client.posts.createPost({
title: 'My Amazing Title',
Expand Down
4 changes: 3 additions & 1 deletion apps/content/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ 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 { RouterClient } from '@orpc/server'
import { createORPCClient, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'
Expand All @@ -212,7 +213,8 @@ const rpcLink = new RPCLink({
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(rpcLink)
const client: RouterClient<typeof router> = createORPCClient(rpcLink)
// const client: ContractRouterClient<typeof contract> = createORPCClient(rpcLink)

// File upload out of the box
const output = await client.posts.createPost({
Expand Down
3 changes: 2 additions & 1 deletion apps/content/content/docs/server/event-iterator.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import { eventIterator, os, withEventMeta } from '@orpc/server'
import { z } from 'zod'
import type { RouterClient } from '@orpc/server'

// Define a streaming endpoint using the event iterator
const streaming = os
Expand All @@ -38,7 +39,7 @@ const streaming = os
const router = { streaming }

// Create an ORPC client and configure the SSE behavior
const client = createORPCClient<typeof router>(
const client: RouterClient<typeof router> = createORPCClient(
new RPCLink({
url: 'http://localhost:3000/rpc',
eventSourceMaxNumberOfRetries: 0, // Set to 0 to disable automatic retries on connection failure,
Expand Down
3 changes: 2 additions & 1 deletion apps/content/content/docs/server/file-upload.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ and pass a `File` object directly to the upload endpoint.
```typescript
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'

const rpcLink = new RPCLink({
url: 'http://localhost:3000/rpc',
// fetch: optional override for the default fetch function
// headers: provide additional headers
})

const client = createORPCClient<typeof appRouter>(rpcLink)
const client: RouterClient<typeof appRouter> = createORPCClient(rpcLink)

// Example: Upload a file from an HTML file input
const fileInput = document.getElementById('file-input') as HTMLInputElement
Expand Down
4 changes: 3 additions & 1 deletion apps/content/content/home/client.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Tabs items={['Client', 'React', 'React Query', 'Vue Query', 'CURL']}>
<Tab value="Client">
```ts twoslash
import { RouterClient } from '@orpc/server'
import { createORPCClient, ORPCError } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { router } from 'examples/server'
Expand All @@ -11,7 +12,8 @@ const rpcLink = new RPCLink({
// headers: provide additional headers
})

const client = createORPCClient<typeof router /* or contract router */>(rpcLink)
const client: RouterClient<typeof router> = createORPCClient(rpcLink)
// const client: ContractRouterClient<typeof router> = createORPCClient(rpcLink)

// File upload out of the box
const output = await client.posts.createPost({
Expand Down
15 changes: 12 additions & 3 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./openapi": {
"types": "./dist/src/openapi/index.d.ts",
"import": "./dist/openapi.js",
"default": "./dist/openapi.js"
},
"./rpc": {
"types": "./dist/src/rpc/index.d.ts",
"import": "./dist/rpc.js",
"default": "./dist/rpc.js"
},
"./fetch": {
"types": "./dist/src/adapters/fetch/index.d.ts",
"import": "./dist/fetch.js",
Expand All @@ -32,6 +42,8 @@
},
"exports": {
".": "./src/index.ts",
"./openapi": "./src/openapi/index.ts",
"./rpc": "./src/rpc/index.ts",
"./fetch": "./src/adapters/fetch/index.ts",
"./🔒/*": {
"types": "./src/*.ts"
Expand All @@ -48,14 +60,11 @@
"type:check": "tsc -b"
},
"dependencies": {
"@orpc/contract": "workspace:*",
"@orpc/server": "workspace:*",
"@orpc/server-standard": "^0.4.0",
"@orpc/server-standard-fetch": "^0.4.0",
"@orpc/shared": "workspace:*"
},
"devDependencies": {
"@orpc/openapi": "workspace:*",
"zod": "^3.24.1"
}
}
2 changes: 1 addition & 1 deletion packages/client/src/adapters/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './orpc-link'
export * from './rpc-link'
export * from './types'
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { os } from '@orpc/server'
import { getEventMeta, isAsyncIteratorObject, withEventMeta } from '@orpc/server-standard'
import { RPCHandler } from '@orpc/server/fetch'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { supportedDataTypes } from '../../../../server/tests/shared'
import { RPCLink } from './orpc-link'
import { supportedDataTypes } from '../../../tests/shared'
import { RPCLink } from './rpc-link'

beforeEach(() => {
vi.clearAllMocks()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { ClientContext, HTTPMethod } from '@orpc/contract'
import type { Value } from '@orpc/shared'
import type { ClientLink, ClientOptionsOut } from '../../types'
import type { ClientContext, ClientLink, ClientOptionsOut } from '../../types'
import type { FetchWithContext } from './types'
import { ORPCError } from '@orpc/contract'
import { isAsyncIteratorObject, type StandardBody } from '@orpc/server-standard'
import { toFetchBody, toStandardBody } from '@orpc/server-standard-fetch'
import { RPCSerializer } from '@orpc/server/standard'
import { trim, value } from '@orpc/shared'
import { ORPCError } from '../../error'
import { createAutoRetryEventIterator, type EventIteratorReconnectOptions } from '../../event-iterator'
import { RPCSerializer } from '../../rpc'

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'

export class InvalidEventSourceRetryResponse extends Error { }

Expand Down
3 changes: 1 addition & 2 deletions packages/client/src/adapters/fetch/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ClientContext } from '@orpc/contract'
import type { ClientOptionsOut } from '../../types'
import type { ClientContext, ClientOptionsOut } from '../../types'

export interface FetchWithContext<TClientContext extends ClientContext> {
(
Expand Down
69 changes: 21 additions & 48 deletions packages/client/src/client.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,24 @@
import type { Client, ClientContext } from '@orpc/contract'
import { oc } from '@orpc/contract'
import { implement, os } from '@orpc/server'
import { z } from 'zod'
import type { ContractRouterClient } from '@orpc/contract'
import type { RouterClient } from '@orpc/server'
import type { router as contract } from '../../contract/tests/shared'
import type { router } from '../../server/tests/shared'
import type { ClientLink } from './types'
import { createORPCClient } from './client'

describe('createORPCClient', () => {
const pingContract = oc
.input(z.object({ in: z.string() }).transform(i => i.in))
.output(z.string().transform(out => ({ out })))

const pongContract = oc.input(z.number())
const contractRouter = oc.router({
ping: pingContract,
nested: {
pong: pongContract,
},
})

const ping = implement(pingContract).handler(name => `ping ${name}`)
const pong = implement(pongContract).handler(num => `pong ${num}`)

const router = implement(contractRouter).router({
ping,
nested: os.lazy(() => Promise.resolve({ default: {
pong: os.lazy(() => Promise.resolve({ default: pong })),
} })),
})

it('build correct types with contract router', () => {
const client = createORPCClient<typeof contractRouter>({} as any)

expectTypeOf(client.ping).toEqualTypeOf<Client<ClientContext, { in: string }, { out: string }, Error>>()
expectTypeOf(client.nested.pong).toEqualTypeOf<Client<ClientContext, number, unknown, Error>>()
})

it('build correct types with router', () => {
const client = createORPCClient<typeof router>({} as any)

expectTypeOf(client.ping).toEqualTypeOf<Client<ClientContext, { in: string }, { out: string }, Error>>()
expectTypeOf(client.nested.pong).toEqualTypeOf<Client<ClientContext, number, string, Error>>()
})

it('pass correct context', () => {
type Context = { a: number }
const client = createORPCClient<typeof router, Context>({} as any)

expectTypeOf(client.ping).toEqualTypeOf<Client<{ a: number }, { in: string }, { out: string }, Error>>()
expectTypeOf(client.nested.pong).toEqualTypeOf < Client < { a: number }, number, string, Error>>()
})
it('createORPCClient require match context between client and link', () => {
const _1: RouterClient<typeof router, { cache: string }> = createORPCClient({} as ClientLink<{ cache: string }>)
const _11: RouterClient<typeof router, { cache?: string }> = createORPCClient({} as ClientLink<{ cache?: string }>)
const _111: RouterClient<typeof router, { cache?: string }> = createORPCClient({} as ClientLink<{ cache?: string, tags?: string[] }>)
const _1111: RouterClient<typeof router> = createORPCClient({} as ClientLink<{ cache?: string }>)
// @ts-expect-error -- cache is required
const _11111: RouterClient<typeof router> = createORPCClient({} as ClientLink<{ cache: string }>)
// @ts-expect-error -- expect cache is optional
const _2: RouterClient<typeof router, { cache?: string }> = createORPCClient({} as ClientLink<{ cache: string }>)
// @ts-expect-error -- expect cache is number
const _3: RouterClient<typeof router, { cache?: number }> = createORPCClient({} as ClientLink<{ cache?: string }>)

const _4: ContractRouterClient<typeof contract> = createORPCClient({} as ClientLink<{ cache?: string }>)
// @ts-expect-error -- cache is required
const _44: ContractRouterClient<typeof contract> = createORPCClient({} as ClientLink<{ cache: string }>)
const _444: ContractRouterClient<typeof contract, { cache: string }> = createORPCClient({} as ClientLink<{ cache: string }>)
})
3 changes: 1 addition & 2 deletions packages/client/src/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { ClientContext } from '@orpc/contract'
import type { ClientLink } from './types'
import type { ClientContext, ClientLink } from './types'
import { createORPCClient } from './client'

beforeEach(() => {
Expand Down
Loading

0 comments on commit 59108a3

Please sign in to comment.