npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rapiq/adapter-typeorm

v2.2.0

Published

Apply a rapiq query directly to a TypeORM SelectQueryBuilder (filters, relations, fields, sort & pagination).

Downloads

2,391

Readme


Part of rapiq. Typed REST queries: build, transport, validate, execute. This is the execute end for TypeORM: hand it a validated Query and a builder, get back a fully-shaped query.

Why

You decoded and validated a request query; now it has to become a real database query without losing your own tenant/auth scoping and without hand-writing andWhere glue for every parameter. That is all this package does, in one execute(query) call.

  • 🧩 Drop-in: bind your SelectQueryBuilder, call execute(query), run it. Nothing else to wire.
  • 🔒 Injection-safe: every filter value is bound as a parameter, never string-interpolated.
  • 🤝 Keeps your predicates: filters are applied with andWhere, so tenant/auth WHEREs already on the builder survive untouched.
  • 🔗 Relations → joins: leftJoinAndSelect (or inner), applied idempotently, validated against the entity metadata, with deterministic aliases shared with @rapiq/adapter-sql.
  • 🎛️ Dialect-aware: the SQL dialect is inferred from the builder's connection; case-folding is applied only to string columns (no lower(int) errors on Postgres).
  • ↩️ Familiar defaults: mirrors typeorm-extension's applyQuery contract (leftJoinAndSelect, returned pagination) for a painless migration.

What a query becomes

A single adapter.execute(query) walks the parsed AST and applies it to your builder:

| Query parameter | Applied to the SelectQueryBuilder | |---|---| | filters: { age: gte(18) } | .andWhere('"user"."age" >= :p0', { p0: 18 }) | | relations: ['realm'] | .leftJoinAndSelect('user.realm', 'realm') | | fields: ['id', 'name'] | .select(['user.id', 'user.name']) | | sorts: '-createdAt' | .orderBy('user.createdAt', 'DESC') | | pagination: { limit, offset } | .take(limit).skip(offset) |

Installation

npm install @rapiq/core @rapiq/adapter-sql @rapiq/adapter-typeorm

Usage

Quick start

import { TypeormAdapter } from '@rapiq/adapter-typeorm';

const queryBuilder = dataSource.getRepository(User).createQueryBuilder('user');

const adapter = new TypeormAdapter({
    queryBuilder,
    relations: { joinAndSelect: true },
});

const { pagination } = adapter.execute(query);

const [entities, total] = await queryBuilder.getManyAndCount();

The queryBuilder is bound at construction; adapter.execute(query) walks the parsed Query, collects the state into its sub-adapters, applies it to that builder, and returns the pagination it applied (handy for the response meta block).

Construct the adapter per request, like the SelectQueryBuilder you hand it: it holds per-call state. The shareable, long-lived part is your config (relations, …), spread into the per-request options with the request's builder as queryBuilder.

In a request handler

The query usually arrives from a URL decoder that validated req.query against a schema. Filters are applied with andWhere, so any application-owned scoping already on the builder (tenant, auth, soft-delete) survives untouched:

import { createURLCodec } from '@rapiq/codec-url';
import { TypeormAdapter } from '@rapiq/adapter-typeorm';

const codec = createURLCodec(registry); // registry: your SchemaRegistry

app.get('/users', async (req, res) => {
    const query = codec.decode(req.query, { schema: 'user' });
    if (!query) {
        return res.status(400).end();
    }

    const queryBuilder = dataSource
        .getRepository(User)
        .createQueryBuilder('user')
        // application-owned scoping: preserved, never overwritten
        .where('user.realm_id = :realmId', { realmId: req.realmId });

    const { pagination } = new TypeormAdapter({
        queryBuilder,
        relations: { joinAndSelect: true },
    }).execute(query);

    const [data, total] = await queryBuilder.getManyAndCount();

    res.json({ data, meta: { total, ...pagination } });
});

Relation join strategies

A relation referenced only by a filter or sort is joined without being selected; an included relation is hydrated according to your options:

// hydrate included relations: leftJoinAndSelect (records with no relation are kept)
new TypeormAdapter({
    queryBuilder,
    relations: { joinAndSelect: true },
}).execute(query);

// inner join + id-only hydration that survives GROUP BY user.id on strict dialects
new TypeormAdapter({
    queryBuilder,
    relations: {
        joinAndSelect: true,
        joinType: 'inner',
        hydrationMode: 'key',
        onJoin: (path, alias, qb) => qb.addGroupBy(`${alias}.id`),
    },
}).execute(query);

| relations option | Effect | |---|---| | joinAndSelect | Hydrate related entities (leftJoinAndSelect) instead of joining for filtering only. | | joinType | 'left' (default, keeps records with an absent relation) or 'inner'. | | hydrationMode | 'full' (default) selects the whole related subtree; 'key' selects only the related primary key, so a hydrated relation survives GROUP BY <root>.id. | | onJoin(path, alias, queryBuilder) | Invoked per applied join (pre-existing/skipped joins don't trigger it): extend the query, e.g. add a group-by or an extra condition. |

The SQL dialect is resolved from the attached builder's connection type; joins are applied idempotently and validated against the entity metadata.

Applying part of a query

adapter.execute() runs everything, but the per-parameter sub-adapters (adapter.filters, adapter.sorts, adapter.fields, …) are public: pair one with its matching @rapiq/adapter-sql visitor to apply a single parameter:

import { FiltersVisitor } from '@rapiq/adapter-sql';

const adapter = new TypeormAdapter({ queryBuilder });

query.filters.accept(new FiltersVisitor(adapter.filters)); // collect
adapter.filters.execute();                                 // flush to the builder

execute() also takes per-call options: { clear: false } accumulates several queries onto the same builder, and { visitor: { caseSensitive: ['token'] } } opts specific fields out of the case-insensitive equality default.

Migrating from typeorm-extension's applyQuery? The defaults mirror its contract (leftJoinAndSelect, returned pagination). See the migration guide. For the complete walkthrough, follow the end-to-end Express + TypeORM recipe.

The rapiq family

| Package | Purpose | |---|---| | @rapiq/core | Query AST, typed build layer & schema system (the shared foundation) | | @rapiq/parser-simple | Parse plain object/array input (the "simple" dialect) | | @rapiq/parser-expression | Parse filter expressions like and(eq(name,'John'), gte(age,'18')) | | @rapiq/parser-mongo | Parse MongoDB-style filter documents like { age: { $gte: 18 } } | | @rapiq/codec-url | URL query-string transport codec | | @rapiq/adapter-sql | Dialect-agnostic SQL fragment adapter (pg, mysql, sqlite, mssql, oracle) | | @rapiq/adapter-typeorm | Apply a query to a TypeORM SelectQueryBuilder | | @rapiq/adapter-prisma | Serialize a query into a Prisma argument object | | @rapiq/adapter-drizzle | Serialize a query into a Drizzle relational query config | | @rapiq/adapter-memory | Evaluate a query against in-memory objects & arrays |

Documentation

Full guide (options, dialect detection, alias convention): rapiq.tada5hi.net/packages/adapter-typeorm

License

Published under the MIT License.