Write your own adapter
An adapter is a pure function from a Query to whatever your backend expects.
type Compiler<TOutput> = (query: Query) => TOutputBecause it is a plain function, it works with query().to(compiler).
- Call
assertQuery(query)(orprepareQuery, same thing) first and use the returned copy. It validates a hand-madeQueryand guarantees the shape and the limits. - Throw on what your backend cannot express (aggregates, search, sub-queries). Never drop it silently.
- Read your own extensions with
getExtension(query, 'your-target'). The target is your adapter name, extensions for other targets are ignored. - Field names are only checked for length. Validate them for your syntax if they end up in a string or a path.
- Package it as
dialectr-adapter-*withdialectras a peer dependency.
Helpers
Section titled “Helpers”| Helper | Use |
|---|---|
assertQuery(query) |
Validate, return a deep copy |
groupByOr(nodes) |
Split nodes into AND clusters, joined by OR |
getExtension(query, target) |
Your extension payload or undefined |
parseLike(pattern) |
{ literal, leading, trailing, inner } |
containsPattern(pattern) |
Pattern wrapped in %...% when it has no wildcard |
groupByOr implements AND-before-OR: every node with boolean: 'or' starts a new cluster. Join nodes inside a cluster with AND and clusters with OR.
Example
Section titled “Example”A compiler that returns a SQL-like WHERE string and a limit:
import { assertQuery, groupByOr } from 'dialectr'import type { Compiler, FilterNode } from 'dialectr'
interface WhereClause { where: string limit?: number}
const expression = (nodes: FilterNode[]): string => groupByOr(nodes) .map(cluster => cluster .map(node => node.kind === 'group' ? `(${expression(node.nodes)})` : `${node.field} ${node.operator} ${JSON.stringify(node.value)}`, ) .join(' AND '), ) .join(' OR ')
export const toWhere: Compiler<WhereClause> = source => { const query = assertQuery(source) if (query.aggregates.length > 0) throw new Error('toWhere does not support aggregates') return { where: expression(query.filters), ...(query.limit === undefined ? {} : { limit: query.limit }), }}import { query } from 'dialectr'
query().where('a', 1).where('b', 2).orWhere('c', 3).limit(10).to(toWhere)// { where: 'a = 1 AND b = 2 OR c = 3', limit: 10 }Query shape
Section titled “Query shape”Query is { filters, sorts, selects, includes, aggregates, search?, extensions?, limit?, page? }. A filter node is a Condition (field, operator, value) or a Group (nodes), both with boolean: 'and' | 'or'. An Include holds a relation and its own Query. Look at the Lomkit, PocketBase and Strapi adapters in packages/adapters for complete examples.