agnostic-query
Version:
Type-safe fluent builder for portable query schemas. Runtime-agnostic, database-agnostic — the same QuerySchema drives Drizzle, Kysely, db0, or raw SQL.
75 lines (64 loc) • 2.19 kB
text/typescript
import type { QuerySchema } from '../core';
import type { QueryOrderBy } from '../core/order-by.ts';
import type { QueryWhere } from '../core/where.ts';
import { isComparisonWhere } from '../core/where.ts';
import { fieldToStr, toSql } from '../sql/pg.ts';
import type { SqlResult } from '../sql/types.ts';
const build = (where: QueryWhere): SqlResult | undefined => {
if (!where) return;
if (where.op === 'not') {
const inner = build(where.condition);
if (!inner) return;
return { sql: `NOT (${inner.sql})`, params: inner.params };
}
if (where.op === 'and' || where.op === 'or') {
const parts = where.conditions
.map((c) => build(c))
.filter((c): c is NonNullable<typeof c> => c !== null);
if (parts.length === 0) return;
const joiner = ` ${where.op.toUpperCase()} `;
const sql = parts.map((p) => p.sql).join(joiner);
return {
sql: parts.length > 1 ? `(${sql})` : sql,
params: parts.flatMap((p) => p.params),
};
}
if (!isComparisonWhere(where)) return;
const fieldStr = fieldToStr(where.field);
if (where.op === 'in') {
const placeholders = where.values.map(() => '?').join(', ');
return { sql: `${fieldStr} IN (${placeholders})`, params: where.values };
}
if (where.op === 'is null') {
return { sql: `${fieldStr} IS NULL`, params: [] };
}
return { sql: `${fieldStr} ${where.op} ?`, params: [where.value] };
};
export const toDb0Where = (
where?: QueryWhere | undefined,
): SqlResult | undefined => {
if (!where) return;
return build(where);
};
export const toDb0OrderBy = <TShape extends Record<string, any>>(
orderBy?: QueryOrderBy<TShape>[] | null,
): SqlResult | undefined => {
if (!orderBy) return;
const clauses = Array.isArray(orderBy) ? orderBy : [orderBy];
return {
sql: clauses
.map((c) => `${fieldToStr(c.field)} ${c.direction.toUpperCase()}`)
.join(', '),
params: [],
};
};
export const toDb0 = async <T extends Record<string, any>>(
db: {
prepare: (sql: string) => { all: (...params: any[]) => Promise<unknown[]> };
},
json: QuerySchema<T>,
): Promise<T[]> => {
const result = toSql(json);
if (!result) return [];
return db.prepare(result.sql).all(...result.params) as any as Promise<T[]>;
};