Skip to content

oRPC

UQL queries are plain JSON, so they pass through oRPC procedures without any adapter. There is nothing to install beyond your existing oRPC setup: procedures call the querier pool directly, and oRPC’s type<T>() helper declares the pass-through input type.

import { os, type } from '@orpc/server';
import type { Query, Type } from 'uql-orm/type';
import { pool } from './uql.config.js';
import { User } from './shared/models/index.js';
function entityRouter<E extends object>(entity: Type<E>) {
return {
findMany: os
.input(type<Query<E>>())
.handler(({ input }) => pool.findMany(entity, input)),
insertOne: os
.input(type<E>((value) => value)) // optional identity mapper; type<E>() alone also works
.handler(({ input }) => pool.transaction((querier) => querier.insertOne(entity, input))),
};
}
export const router = { user: entityRouter(User) };

On the client, the whole query - filters, sorting, and nested relation loading - is fully typed end to end and reaches the server as plain JSON, with no per-procedure schema to keep in sync:

const users = await client.user.findMany({
$select: { id: true, name: true },
$where: { status: 'active', email: { $endsWith: '@domain.com' } },
// load each user's recent published posts in the same round-trip
$populate: { posts: { $select: { title: true }, $where: { published: true }, $sort: { createdAt: 'desc' }, $limit: 5 } },
$sort: { createdAt: 'desc' },
$limit: 10,
});
// `users` is fully typed: User[], each with a typed `posts: Post[]`