> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/fdarian/ff/llms.txt
> Use this file to discover all available pages before exploring further.

# oRPC Integration

> Integrate oRPC routers with ff-serv handlers

oRPC is a modern RPC framework with first-class TypeScript support. ff-serv provides a handler that wraps [@orpc/server](https://orpc.dev) routers.

## Installation

```bash theme={null}
bun add @orpc/server @orpc/client
```

## Basic Usage

```typescript theme={null}
import { createFetchHandler } from 'ff-serv'
import { oRPCHandler } from 'ff-serv/orpc'
import { os } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { Effect } from 'effect'

const program = Effect.gen(function* () {
  // Define your oRPC router
  const router = {
    health: os.handler(() => ({ status: 'ok' })),
    greeting: os.handler((input: { name: string }) => `Hello, ${input.name}!`),
  }

  // Create the oRPC handler
  const handler = new RPCHandler(router)

  const fetch = yield* createFetchHandler([
    oRPCHandler(handler),
  ])

  Bun.serve({ port: 3000, fetch })
})

Effect.runPromise(program)
```

## With Effect Context

Pass Effect-based options to provide context to your oRPC handlers:

```typescript theme={null}
import { Context, Effect } from 'effect'

class Database extends Context.Tag('Database')<
  Database,
  { query: (sql: string) => Effect.Effect<unknown[]> }
>() {}

const router = {
  users: os.handler(async () => {
    // Access context in oRPC handler
    const db = await Effect.runPromise(Database)
    return db.query('SELECT * FROM users')
  }),
}

const program = Effect.gen(function* () {
  const db = yield* Database

  const fetch = yield* createFetchHandler([
    oRPCHandler(
      new RPCHandler(router),
      // Provide context to oRPC
      { context: { database: db } }
    ),
  ])

  Bun.serve({ port: 3000, fetch })
})
```

## Dynamic Options

Options can be computed per-request:

```typescript theme={null}
oRPCHandler(
  handler,
  (request) => {
    const token = request.headers.get('Authorization')
    return {
      context: {
        userId: parseToken(token),
      },
    }
  }
)
```

## Options as Effect

Options can also be an Effect:

```typescript theme={null}
import { HttpClient } from '@effect/platform'

oRPCHandler(
  handler,
  Effect.gen(function* () {
    const client = yield* HttpClient.HttpClient
    // Fetch config from external service
    const config = yield* client.get('https://api.example.com/config')
    
    return {
      context: { config: yield* config.json },
    }
  })
)
```

## Client Setup

Connect from the client using [@orpc/client](https://orpc.dev/client):

```typescript theme={null}
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'

type Router = typeof router

const client: RouterClient<Router> = createORPCClient(
  new RPCLink({ url: 'http://localhost:3000' })
)

// Fully typed calls
const result = await client.greeting({ name: 'Alice' })
console.log(result) // "Hello, Alice!"
```

## With ff-effect Client Wrapper

Use [ff-effect](https://github.com/fdarian/ff/tree/main/packages/effect)'s `wrapClient` to call oRPC from Effect code:

```typescript theme={null}
import { wrapClient } from 'ff-effect'
import { UnknownException } from 'effect/Cause'
import { Effect } from 'effect'

const program = Effect.gen(function* () {
  const call = wrapClient({
    client,
    error: ({ cause }) => new UnknownException(cause),
  })

  const result = yield* call((c) => c.greeting({ name: 'Bob' }))
  yield* Effect.log(result)
})

Effect.runPromise(program)
```

## Complete Example

```typescript src/server.ts theme={null}
import { createFetchHandler, basicHandler } from 'ff-serv'
import { oRPCHandler } from 'ff-serv/orpc'
import { os } from '@orpc/server'
import { RPCHandler } from '@orpc/server/fetch'
import { Effect, Context } from 'effect'

// Define a service
class Config extends Context.Tag('Config')<
  Config,
  { apiKey: string }
>() {}

const program = Effect.gen(function* () {
  const config = yield* Config

  // Define oRPC router with context
  const router = {
    health: os.handler(() => ({ status: 'ok' })),
    
    protected: os.handler((input: { data: string }) => {
      return {
        message: `Processed with key: ${config.apiKey}`,
        data: input.data,
      }
    }),
  }

  const fetch = yield* createFetchHandler([
    // oRPC endpoints at /rpc/*
    oRPCHandler(new RPCHandler(router), {
      context: { config },
    }),

    // Regular HTTP endpoint
    basicHandler('/version', () => 
      Response.json({ version: '1.0.0' })
    ),
  ])

  Bun.serve({ port: 3000, fetch })
  yield* Effect.log('Server ready')
})

Effect.runPromise(
  program.pipe(
    Effect.provideService(Config, { apiKey: 'secret' })
  )
)
```

```typescript src/client.ts theme={null}
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'
import type { router } from './server'

const client: RouterClient<typeof router> = createORPCClient(
  new RPCLink({ url: 'http://localhost:3000' })
)

const health = await client.health()
console.log(health) // { status: 'ok' }

const result = await client.protected({ data: 'test' })
console.log(result)
// { message: 'Processed with key: secret', data: 'test' }
```

## Type Signature

```typescript theme={null}
function oRPCHandler<T extends Context, E, R>(
  handler: FetchHandler<T>,
  opt?: 
    | FriendlyStandardHandleOptions<T>
    | Effect.Effect<FriendlyStandardHandleOptions<T>, E, R>
    | ((request: Request) => 
        | FriendlyStandardHandleOptions<T>
        | Effect.Effect<FriendlyStandardHandleOptions<T>, E, R>
      )
): Handler<'oRPCHandler', never>
```

* **handler**: An oRPC `FetchHandler` (from `@orpc/server/fetch`)
* **opt**: Context and options to pass to oRPC, as a value, Effect, or request function

<Note>
  The oRPC handler matches **all requests** by default. Place it after any custom `basicHandler` routes if you want to handle specific paths separately.
</Note>
