Skip to content

awHamer/relayer

Relayer

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

npm version npm downloads License: MIT

Why Relayer?

Over the years, across many projects, we kept reimplementing the same pattern: 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 entity model with decorator-driven computed and derived fields
  • API-friendly query DSL: 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, no raw queries
  • Derived fields: automatic subquery JOINs with full filtering and sorting support
  • First-class JSON filtering: nested path queries with full comparison operators
  • Typed context: pass per-request data (current user, tenant, etc.) to field resolvers

Features

  • Class-based entities: define computed and derived fields with decorators on entity classes
  • Query DSL: select, where, orderBy, limit, offset
  • Computed fields: virtual columns defined as SQL expressions
  • Derived fields: automatic subquery JOINs (scalar and object types)
  • First-class JSON integration: transparent nested filtering with auto type casting
  • 20+ filter operators: eq, ne, gt, gte, lt, lte, in, contains, ilike, isNull, and more
  • Array operators: arrayContains, arrayContained, arrayOverlaps (PostgreSQL)
  • Relation filters: exists, some, every, none
  • Nested relation fields: computed and derived fields on relations with cross-entity type propagation
  • Aggregations: _count, _sum, _avg, _min, _max with groupBy and dot-notation
  • Typed context: pass per-query context to computed/derived resolvers
  • Transactions: $transaction with automatic client scoping
  • Multi-dialect: PostgreSQL, MySQL, SQLite
  • Full TypeScript inference: select, where, orderBy, and result types

Currently only Drizzle ORM (>=0.38.0) is supported. Future plans include adapters for TypeORM, Kysely, MikroORM, and others. The goal is a single unified query interface regardless of the underlying ORM. Contributions are always welcome.

Quick Start

npm install @relayerjs/drizzle drizzle-orm

Define your Drizzle schema

import { relations } from 'drizzle-orm';
import { integer, jsonb, pgTable, serial, text } from 'drizzle-orm/pg-core';

const users = pgTable('users', {
  id: serial('id').primaryKey(),
  firstName: text('first_name').notNull(),
  lastName: text('last_name').notNull(),
  email: text('email').notNull(),
  metadata: jsonb('metadata').$type<{ role: string; level: number }>(),
});

const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: text('title').notNull(),
  published: boolean('published').default(false).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 };

Define entity models

import { createRelayerDrizzle, createRelayerEntity } from '@relayerjs/drizzle';

const UserEntity = createRelayerEntity(schema, 'users');

class User extends UserEntity {
  @UserEntity.computed({
    resolve: ({ table, sql }) => sql`${table.firstName} || ' ' || ${table.lastName}`,
  })
  fullName!: string;

  @UserEntity.derived({
    query: ({ db, schema: s, sql, field }) =>
      db
        .select({ [field()]: sql`count(*)::int`, userId: s.posts.authorId })
        .from(s.posts)
        .groupBy(s.posts.authorId),
    on: ({ parent, derived, eq }) => eq(parent.id, derived.userId),
  })
  postsCount!: number;
}

Create the Relayer client

const r = createRelayerDrizzle({
  db, // your drizzle instance
  schema,
  entities: { users: User },
});

Query with Prisma-like DSL

// Select + filter + order
const results = await r.users.findMany({
  select: { id: true, firstName: true, fullName: true, postsCount: true },
  where: { email: { contains: '@example.com' } },
  orderBy: { field: 'firstName', order: 'asc' },
  limit: 10,
});

// JSON filtering: transparent nested queries
const admins = await r.users.findMany({
  where: { metadata: { role: 'admin', level: { gte: 5 } } },
});

// Load relations
const usersWithPosts = await r.users.findMany({
  select: { id: true, firstName: true, posts: { title: true } },
});

// Relation filters
const activeAuthors = await r.users.findMany({
  where: { posts: { some: { published: true } } },
});

// Aggregations: all functions + groupBy + dot-notation joins
const stats = await r.orders.aggregate({
  groupBy: ['status'],
  _count: true,
  _sum: { total: true },
  _avg: { total: true },
});

// Group by relation field (auto LEFT JOIN)
const ordersByUser = await r.orders.aggregate({
  groupBy: ['user.firstName'],
  _count: true,
  _sum: { total: true },
});

Packages

Package Description
@relayerjs/drizzle Drizzle ORM adapter, main package
@relayerjs/core ORM-agnostic types and contracts
@relayerjs/next Next.js App Router integration

Documentation

Full documentation is available at relayerjs.vercel.app

See also the Drizzle adapter README for a quick API reference.

Examples

See the examples/drizzle directory for runnable examples with PostgreSQL, MySQL, and SQLite.

Roadmap

Relayer is in early development. Planned packages:

  • @relayerjs/rest: auto-generate REST CRUD endpoints (Express, Fastify, Hono)
  • @relayerjs/nest: NestJS module with CRUD controllers and GraphQL resolvers
  • @relayerjs/graphql: standalone GraphQL schema generation
  • @relayerjs/react: React client with hooks for querying Relayer endpoints

Contributing

Prerequisites

  • Node.js >= 20
  • pnpm >= 10
  • Docker (for PostgreSQL and MySQL integration tests)

Setup

git clone https://github.com/awHamer/relayer.git
cd relayer
pnpm install
pnpm build

Run examples

cd examples
docker compose up -d        # start PostgreSQL + MySQL
cd drizzle
pnpm seed                   # create tables + seed data
pnpm start                  # run PG example
npx tsx src/test-mysql.ts   # run MySQL example
npx tsx src/test-sqlite.ts  # run SQLite example

Run tests

pnpm --filter @relayerjs/drizzle test        # all tests
pnpm --filter @relayerjs/drizzle test:unit   # unit tests only (no DB)
pnpm --filter @relayerjs/drizzle test:pg     # PostgreSQL integration
pnpm --filter @relayerjs/drizzle test:mysql  # MySQL integration
pnpm --filter @relayerjs/drizzle test:sqlite # SQLite integration (in-memory)

Run docs locally

pnpm docs:dev    # start dev server at localhost:4321
pnpm docs:build  # production build

Inspiration

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

License

MIT

About

Type-safe repository layer for ORMs with computed fields, derived fields, and query DSL

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors