> Every UQL docs page, as Markdown: https://uql-orm.dev/llms.txt
> The same docs over MCP: https://uql-orm.dev/mcp
> Before writing UQL code, read the skill: https://uql-orm.dev/.well-known/agent-skills/uql-orm/SKILL.md

# oRPC

> Expose UQL entities as oRPC procedures that take the JSON query as input.

Source: https://uql-orm.dev/orpc

A UQL query is plain JSON, so it can be an [oRPC](https://orpc.dev) procedure’s input with no adapter. Use oRPC for per-procedure contracts, and the [HTTP handler](https://uql-orm.dev/http.md) for CRUD across many entities; both are fetch-native and mount side by side.

## Router

oRPC’s `type<T>()` declares the input type:

```ts title="src/router.ts"
import { os, type } from '@orpc/server';
import type { EntityData, Type, WireQuery } from 'uql-orm';
import { User } from './shared/models/index.js';
import { pool } from './uql.config.js';

function entityRouter<E extends object>(entity: Type<E>) {
  return {
    findMany: os
      .input(type<WireQuery<E>>())
      .handler(({ input }) => pool.findMany(entity, input)),
    insertOne: os
      // type<T>() alone cannot resolve a mapped type over a generic, so pass the mapper
      .input(type<EntityData<E>>((value) => value))
      .handler(({ input }) => pool.insertOne(entity, input)),
  };
}

export const router = { user: entityRouter(User) };
```

The client gets the whole query typed, nested relations included:

```ts title="src/client.ts"
import { createORPCClient } from '@orpc/client';
import { RPCLink } from '@orpc/client/fetch';
import type { RouterClient } from '@orpc/server';
import type { router } from './router.js';

const client: RouterClient<typeof router> = createORPCClient(
  new RPCLink({ url: '/rpc' }),
);

const users = await client.user.findMany({
  $select: { id: true, name: true },
  $populate: { posts: { $select: { title: true }, $limit: 5 } },
  $limit: 10,
});
// typed User[], each with a typed posts: Post[]
```

## Validating inputs

`type<T>()` declares a type and checks nothing at runtime. For procedures an untrusted client can call, validate a few arguments with a schema such as zod and build the query on the server. oRPC’s OpenAPI generator also skips type-only inputs.

For tenant isolation, run the handler inside `withContext({ tenantId: ctx.session.tenantId }, () => ...)`, with the id from a verified session, and declare a `security` [filter](https://uql-orm.dev/querying/filters.md). Unlike a `$where` added by hand, the filter reaches every relation and fails closed. See [Multi-tenancy](https://uql-orm.dev/multi-tenancy.md).
