tada5hi/rapiq
tada5hi/rapiq has 35 GitHub stars and 7 forks. GitHot measured 0 stars in 24 hours and +1 in 7 days. Last checked 2026-11-11 17:09 UTC. Last push 2026-10-28.
Comment ces chiffres sont mesurés
Typed REST queries - build, transport, validate, execute. The packages implement a JSON-API-style query scheme (fields, filters, relations, pagination, sort) end-to-end: typed builders, parsers, URL codec, SQL/TypeORM/in-memory adapters.
Progression mesurée des étoiles
- in 24h
- 0
- in 7 days
- +1
- in 15 days
- Historique en cours
- in 1 month
- Historique en cours
Variations nettes sur des périodes qui se chevauchent, pas des totaux quotidiens. Les mesures absentes restent vides. Les étoiles indiquent l’intérêt, pas la qualité.
README.md
Original
rapiq
Typed REST queries: build, transport, validate, execute.
Rapiq (Rest Api Query) builds an efficient interface between applications:
browser ↔ API just as well as service ↔ service. It defines a scheme for the request, but not for the response.
Documentation · Getting Started · Guide · Migration from v1
[!NOTE] The documentation for v1 (and prior) lives on the v1 branch.
Why rapiq?
Every REST list endpoint answers the same five questions: which fields, which filters, which relations, which page, which order. rapiq turns them into one typed pipeline instead of ad-hoc string parsing.
- 🧭 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. - 🛡️ The receiving side has the last word: a
Schemadeclares what a caller may request per parameter (allow-lists, defaults, mappings). Invalid input is dropped or rejected according to the parser dialect and schema policy, and server conditions (query.filters.and(...)) remain conjuncts alongside caller input. - 🔁 Loss-free transport: within each codec dialect,
decode(encode(query))restores the same query; outside its subset, encoding fails loudly with a typed error instead of silently changing semantics. - 🔌 Any backend: the same AST executes everywhere: parameterized SQL fragments with presets for Postgres, MySQL, SQLite, MSSQL & Oracle, a TypeORM
SelectQueryBuilder, a PrismafindManyargs object, a drizzle relational-queries config, or compiled functions over in-memory data. - 📦 Composable packages: no monolith, install only what each side needs;
@rapiq/coreis the single shared foundation.
The two ends are just applications. A browser querying an API is the common case, but services compose the same way:
an API gateway, for instance, validates an incoming query against its own schema, scopes it
(query.filters.and(...)) and re-encodes it for the upstream service.
Usage
One query, from the caller all the way to the database. Both sides share the same record types:
type Realm = { id: string, name: string };
type User = { id: number, name: string, email: string, age: number, realm: Realm };
1. Build 🔧 calling application
defineQuery takes typed input and returns a
Query AST: no magic value strings, every path checked against User.
import { defineQuery } from '@rapiq/core';
import { createURLCodec } from '@rapiq/codec-url';
const query = defineQuery<User>({
fields: ['id', 'name'],
filters: { age: { $gte: 18 } },
relations: ['realm'],
sorts: '-id',
pagination: { limit: 20 },
});
await fetch(`/users?${createURLCodec().encode(query)}`);
⬇ over the wire self-described, so the receiver knows how to read it
codec=url-expression&fields=id,name&filter=gte(age,'18')&page[limit]=20&include=realm&sort=-id
2. Validate 🛡️ receiving application
A Schema is the allow-list: it decides what a caller may ask for, per parameter.
import { SchemaRegistry, defineSchema } from '@rapiq/core';
const registry = new SchemaRegistry();
registry.add(defineSchema<Realm>({
name: 'realm',
fields: { allowed: ['id', 'name'] },
}));
registry.add(defineSchema<User>({
name: 'user',
fields: { allowed: ['id', 'name', 'email'] },
filters: { allowed: ['id', 'name', 'age'] },
relations: { allowed: ['realm'] },
sorts: { allowed: ['id', 'name'] },
pagination: { maxLimit: 20 },
schemaMapping: { realm: 'realm' },
}));
// accepts a raw query string or a pre-parsed object (express req.query)
const query = createURLCodec(registry).decode(req.query, { schema: 'user' });
Anything the schema does not allow never reaches step 3: it is dropped or rejected, per the schema's failure policy.
⬇ applied by an adapter
3. Execute 🗄️ database
import { TypeormAdapter } from '@rapiq/adapter-typeorm';
const adapter = new TypeormAdapter({ queryBuilder, relations: { joinAndSelect: true } });
const { pagination } = adapter.execute(query);
const [entities, total] = await queryBuilder.getManyAndCount();
Values are always bound as parameters, never interpolated into the statement:
WHERE "user"."age" >= $1 -- params: [18]
ORDER BY "user"."id" DESC
LIMIT 20
The complete Express + TypeORM endpoint
Assumes express and
typeorm are installed, with User and Realm declared as
TypeORM entities and the registry from step 2 in scope.
import { Request, Response } from 'express';
import { createURLCodec } from '@rapiq/codec-url';
import { TypeormAdapter } from '@rapiq/adapter-typeorm';
// your app's TypeORM DataSource instance
import { dataSource } from './data-source';
const codec = createURLCodec(registry);
/**
* Get many users.
*
* Request example
* - url: /users?codec=url-expression&page[limit]=10&include=realm&filter=eq(id,'1')&fields=id,name
*/
export async function getUsers(req: Request, res: Response) {
// map the URL wire names (filter, page, include, ...) to their canonical
// parameters and validate against the schema allow-lists.
const query = codec.decode(req.query, { schema: 'user' });
if (!query) {
return res.status(400).end();
}
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();
return res.json({
data: entities,
meta: {
total,
limit: pagination.limit,
offset: pagination.offset,
},
});
}
No TypeORM? The same query runs through @rapiq/adapter-prisma, @rapiq/adapter-drizzle, @rapiq/adapter-sql (parameterized fragments for any driver) or @rapiq/adapter-memory (plain arrays). Full walkthrough in the docs.
Installation
Version 2 splits the former single rapiq package into focused @rapiq/* packages, so each side
installs only what it needs. There is no rapiq umbrella package for v2, and @rapiq/core is a
peer dependency of every other package.
# calling application: build queries, encode them as URL query strings
npm install @rapiq/core @rapiq/codec-url
# receiving application: decode, validate, execute
npm install @rapiq/core @rapiq/codec-url @rapiq/adapter-sql @rapiq/adapter-typeorm
Swap the adapters for @rapiq/adapter-prisma, @rapiq/adapter-drizzle or @rapiq/adapter-memory
to match your backend.
Packages
| Package | Purpose |
|---|---|
| @rapiq/core | Query AST, typed build layer (defineQuery, condition helpers, mergeQueries), schema system & registry |
| @rapiq/parser-simple | Parses plain object/array input (the "simple" dialect) into a Query |
| @rapiq/parser-expression | Parses filter expressions like and(eq(name, 'John'), gte(age, '18')) |
| @rapiq/parser-mongo | Parses MongoDB-style filter documents like { age: { $gte: 18 } } |
| @rapiq/codec-url | URL query-string codec; writes expression filters and reads expression plus legacy simple filters |
| @rapiq/adapter-sql | Dialect-agnostic SQL adapter (pg, mysql, sqlite, mssql & oracle presets) |
| @rapiq/adapter-typeorm | Applies a parsed Query to a TypeORM SelectQueryBuilder |
| @rapiq/adapter-prisma | Serializes a parsed Query into a Prisma argument object |
| @rapiq/adapter-drizzle | Serializes a parsed Query into a Drizzle relational query config |
| @rapiq/adapter-memory | Evaluates a parsed Query against in-memory objects & arrays |
| @rapiq/cache | Tag-invalidated result cache for query results, with a memory driver |
| @rapiq/cache-redis | Redis driver for @rapiq/cache |
Parameters
The query scheme is based on the JSON-API specification:
| Parameter | URL name | Description |
|---|---|---|
fields |
fields |
Return only specific resource fields or extend the default selection. |
filters |
filter |
Filter the resources, according to specific criteria. |
relations |
include |
Include related resources of the primary resource. |
pagination |
page |
Limit the number of resources returned from the entire collection. |
sorts |
sort |
Sort the resources according to one or more keys in asc/desc direction. |
groups |
group |
Group the resources by columns or UTC time buckets (opt-in, docs). |
aggregates |
aggregate |
Count or sum per group (opt-in). |
License
Made with 💚
Published under MIT License.
Explorer des projets similaires
Asabeneh/30-Days-Of-Python
Le défi de programmation 30 Days of Python est un guide pas à pas pour apprendre le langage de programmation Python en 30 jours. Ce défi peut prendre plus de 100 jours. Avancez à votre rythme. Ces vidéos peuvent aussi vous aider : https://www.youtube.com/channel/UC7PNRuno1rzYPb1xLa4yktw
78,560 étoilesgrafana/grafana
Plateforme ouverte et composable d’observabilité et de visualisation de données. Visualise les métriques, les journaux et les traces issus de plusieurs sources, comme Prometheus, Loki, Elasticsearch, InfluxDB, Postgres et bien d’autres.
77,830 étoilesredis/redis
Pour les développeurs qui créent des applications en temps réel pilotées par les données, Redis est le cache, le serveur de structures de données et le moteur de requêtes documentaires et vectorielles préféré, le plus rapide et le plus riche en fonctionnalités.
77,395 étoilesapache/superset
Apache Superset est une plateforme de visualisation et d'exploration de données.
75,473 étoiles