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.
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.
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.
| Package | Description |
|---|---|
@relayerjs/drizzle | Drizzle ORM adapter, the main package |
@relayerjs/core | ORM-agnostic types and contracts |
@relayerjs/next | Next.js App Router integration |
import { relations } from 'drizzle-orm';import { integer, jsonb, pgTable, serial, text } from 'drizzle-orm/pg-core';import { createRelayerDrizzle, createRelayerEntity } from '@relayerjs/drizzle';
// Schemaconst 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 modelconst 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 clientconst r = createRelayerDrizzle({ db, schema, entities: { users: User },});
// Query: everything is fully type-safeconst 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,});
// Aggregationconst stats = await r.posts.aggregate({ groupBy: ['author.name'], _count: true, _max: { title: true },});Relayer is inspired by Prisma query API, Hasura GraphQL filters, nestjs-query, and many other tools that make database access feel effortless.