@rapiq/core
The foundation of rapiq: the query AST, the typed build layer & the schema system.
Every other @rapiq/* package builds on the interfaces defined here.
Documentation · Monorepo · npm
Part of rapiq. Typed REST queries: build, transport, validate, execute.
@rapiq/core owns the intermediate representation the whole pipeline revolves around: a typed Query AST (fields, filters, pagination, relations, sorts), the defineQuery build layer, the Schema allow-list system, and the visitor interfaces parsers, codecs and backend adapters implement.
- Typed end to end: every field path in
defineQuery<User>is checked against the record type; condition helpers (eq,gte,and,or, …) replace magic value strings. - One AST, many consumers: the same
Querynode graph is walked into SQL, a TypeORM builder or in-memory predicates through the visitor pattern; core never changes when a backend is added. - Schema = the receiving side's allow-list: declare per-parameter
allowed/default/mapping; parsers and codecs validate against it and drop or throw per policy. - Composable & immutable:
mergeQueriesuses keyed left priority for fields, relations and sorts, per-property left priority for pagination, and ordered logical AND for filters. TheFilterscombinators (merge,and,or) let a gateway add scope without losing predicates.
Installation
npm install @rapiq/core
Usage
Build a query
defineQuery builds the AST directly from typed input; every field path is checked against the record generic:
import { defineQuery, gte, or, eq } from '@rapiq/core';
const query = defineQuery<User>({
fields: ['id', 'name'],
filters: or(gte('age', 18), eq('deleted_at', null)),
relations: ['realm'],
sorts: '-created_at',
pagination: { limit: 10 },
});
Filters accept scalars ({ name: 'John' }), bare arrays (in, null is a legal element), $-operator objects ({ age: { $gte: 18 } }) and condition helpers (eq, gte, inArray, and, or, …). Queries compose immutably with mergeQueries: filters are ordered logical AND, while keyed parameters use left priority. Use or(...) inside a filter tree for alternatives.
Declare what a caller may request
A Schema is the receiving side's allow-list: parsers and decoders validate incoming input against it:
import { SchemaRegistry, defineSchema } from '@rapiq/core';
const registry = new SchemaRegistry();
registry.add(defineSchema<User>({
name: 'user',
fields: { allowed: ['id', 'name', 'age'] },
filters: { allowed: ['id', 'name', 'age'] },
relations: { allowed: ['realm'] },
sorts: { allowed: ['id', 'age'] },
pagination: { maxLimit: 50 },
schemaMapping: { realm: 'realm' },
}));
Consume the AST
Every node implements accept(visitor). Backends implement the visitor interfaces (IQueryVisitor, IFiltersVisitor, …) to walk a query into whatever they target. Ready-made adapters exist for SQL, TypeORM and in-memory data; parsers and URL codecs live in their own packages as well.
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: rapiq.tada5hi.net. See Building Queries, Schemas and Merging Queries.
License
Published under the MIT License.