Skip to content

Relayer

Type-safe repository layer for ORMs with class-based entity models, computed fields, derived fields, and a powerful query DSL. Currently supports Drizzle ORM.

Why Relayer?

Over the years, across many projects, the same pattern kept appearing: a repository layer that brings database queries closer to API filters and makes dynamic fields (computed, derived) a first-class part of the data model, with full support for filtering, sorting, and aggregation. Not as an afterthought, not through raw SQL escape hatches, but as a core design principle.

Relayer is that pattern extracted into a library. Built with API integration in mind from day one.

Key Features

Class-Based Entities

Define computed and derived fields with decorators on entity classes. The class is the single source of truth for your data model.

Query DSL

Prisma-like findMany, where, select, orderBy with 20+ operators. The DSL is a plain JSON-serializable object, making it trivial to wire up as REST/GraphQL filters.

Computed Fields

Virtual SQL expressions evaluated at SELECT time. No raw queries, no post-processing: just declare and use.

Derived Fields

Automatic subquery JOINs with full filtering and sorting support. Scalar and object-type values with type-safe dot notation.

JSON Filtering

Transparent nested path queries on JSON columns with auto type casting for numeric and boolean comparisons.

Relation Filters

exists, some, every, none: filter parent records based on related data via EXISTS subqueries.

Aggregations

_count, _sum, _avg, _min, _max with groupBy and dot-notation for cross-table grouping via automatic LEFT JOIN.

Multi-Dialect

PostgreSQL, MySQL, and SQLite. Dialect-specific SQL is handled automatically: ILIKE, array operators, JSON paths, RETURNING.

Packages

PackageDescription
@relayerjs/drizzleDrizzle ORM adapter, the main package
@relayerjs/coreORM-agnostic types and contracts
@relayerjs/nextNext.js App Router integration

Quick Example

import { relations } from 'drizzle-orm';
import { integer, jsonb, pgTable, serial, text } from 'drizzle-orm/pg-core';
import { createRelayerDrizzle, createRelayerEntity } from '@relayerjs/drizzle';
// Schema
const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
metadata: jsonb('metadata').$type<{ role: string; level: number }>(),
});
const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
});
const usersRelations = relations(users, ({ many }) => ({ posts: many(posts) }));
const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
const schema = { users, posts, usersRelations, postsRelations };
// Entity model
const UserEntity = createRelayerEntity(schema, 'users');
class User extends UserEntity {
@UserEntity.computed({
resolve: ({ table, sql }) => sql`upper(${table.name})`,
})
displayName!: string;
@UserEntity.derived({
shape: { postsCount: 'number', latestTitle: 'string' },
query: ({ db, schema: s, sql, field }) =>
db
.select({
[field('postsCount')]: sql<number>`count(*)::int`,
[field('latestTitle')]: sql<string>`max(${s.posts.title})`,
authorId: s.posts.authorId,
})
.from(s.posts)
.groupBy(s.posts.authorId),
on: ({ parent, derived, eq }) => eq(parent.id, derived.authorId),
})
stats!: { postsCount: number; latestTitle: string };
}
// Relayer client
const r = createRelayerDrizzle({
db,
schema,
entities: { users: User },
});
// Query: everything is fully type-safe
const result = await r.users.findMany({
select: {
id: true,
displayName: true,
stats: { postsCount: true },
posts: { id: true, title: true },
},
where: {
metadata: { role: 'admin' },
posts: { some: { title: { contains: 'Relayer' } } },
},
orderBy: [
{ field: 'metadata.level', order: 'desc' },
{ field: 'stats.postsCount', order: 'desc' },
],
limit: 10,
});
// Aggregation
const stats = await r.posts.aggregate({
groupBy: ['author.name'],
_count: true,
_max: { title: true },
});

Inspiration

Relayer is inspired by Prisma query API, Hasura GraphQL filters, nestjs-query, and many other tools that make database access feel effortless.