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.
162 lines (156 loc) • 4.39 kB
text/typescript
import type { QueryOrderBy } from './order-by.ts';
import type {
FieldPath,
FieldPathByShape,
GetPathType,
SchemaShape,
} from './schema.ts';
import {
type ComparisonWhere,
type ComparisonWhereValue,
createExpr,
newComparisonWhere,
type PredicateOp,
type QueryWhere,
type UnaryComparisonOp,
type WhereComparisonOp,
type WhereExpr,
type WhereOp,
} from './where.ts';
export interface QuerySchema<TShape extends SchemaShape = SchemaShape> {
where?: QueryWhere<TShape> | null;
orderBy?: QueryOrderBy<TShape>[];
limit?: number;
offset?: number;
mate?: Record<string, any>;
table?: string;
}
interface AgnosticQuery<TShape extends SchemaShape = SchemaShape> {
toJSON(): QuerySchema<TShape>;
where(
cb: (eb: WhereExpr<TShape>) => WhereExpr<TShape>,
): AgnosticQuery<TShape>;
where<
Col extends FieldPathByShape<TShape> | (keyof TShape & string),
Op extends WhereComparisonOp,
>(
col: Col,
op: Op,
value: ComparisonWhereValue<TShape, Col, Op>,
): AgnosticQuery<TShape>;
where<
Col extends FieldPathByShape<TShape> | (keyof TShape & string),
Op extends PredicateOp,
>(
col: Col,
op: Op,
): AgnosticQuery<TShape>;
where(where?: QueryWhere<TShape> | null): AgnosticQuery<TShape>;
orderBy<Col extends FieldPathByShape<TShape> | (keyof TShape & string)>(
col: Col,
direction?: 'asc' | 'desc',
): AgnosticQuery<TShape>;
limit(value?: number): AgnosticQuery<TShape>;
offset(value?: number): AgnosticQuery<TShape>;
}
export const aq = <TShape extends SchemaShape = SchemaShape>(
state?: QuerySchema<TShape>,
): AgnosticQuery<TShape> => {
// const state: QuerySchema<TShape> = initState || {};
const where = <
Col extends FieldPathByShape<TShape> | (keyof TShape & string),
Op extends WhereComparisonOp,
>(
col: Col,
op: Op,
value: ComparisonWhereValue<TShape, Col, Op>,
) => {
const field = Array.isArray(col) ? col : [col];
const inputWhere =
op === 'in' ? { field, op, values: value } :
op === 'is null' ? { field, op } :
{ field, op, value };
const oldWheres =
state?.where?.op === 'and'
? state.where.conditions || []
: state?.where
? [state.where]
: [];
const newWhere = state?.where
? {
op: 'and',
conditions: [...oldWheres, inputWhere],
}
: inputWhere;
return aq<TShape>({
...state,
where: newWhere as QueryWhere<TShape>,
});
};
return {
toJSON: () => state || {},
where: (col: any, op?: any, value?: any) => {
if (col === null || col === undefined) {
return aq<TShape>(state);
}
if (typeof col === 'function') {
const cbWhere = (col as (eb: WhereExpr<TShape>) => WhereExpr<TShape>)(
createExpr(),
)._q;
const changedWhere = state?.where
? { op: 'and', conditions: [state.where, cbWhere] }
: cbWhere;
return aq<TShape>({
...state,
where: changedWhere as QueryWhere<TShape>,
});
}
// 新增:col 是 QueryWhere 对象
if (col && typeof col === 'object' && 'op' in col) {
const changedWhere: QueryWhere<TShape> = state?.where
? { op: 'and', conditions: [state.where, col] }
: col;
return aq<TShape>({ ...state, where: changedWhere });
}
return where(col, op, value);
},
orderBy: <Col extends FieldPathByShape<TShape> | (keyof TShape & string)>(
col: Col,
direction: 'asc' | 'desc' = 'asc',
): AgnosticQuery<TShape> => {
const field = (
Array.isArray(col) ? col : [col]
) as FieldPathByShape<TShape>;
const newOrderBy = state?.orderBy
? [...state.orderBy, { field, direction }]
: [{ field, direction }];
return aq<TShape>({ ...state, orderBy: newOrderBy });
},
limit: (value?: number) => aq<TShape>({ ...state, limit: value }),
offset: (value?: number) => aq<TShape>({ ...state, offset: value }),
};
};
// type DemoShape = {
// id: number;
// name: string;
// tags: { id: number; name: string }[];
// category: string[];
// address: {
// city: {
// name: string;
// };
// };
// };
// const where5 = newComparisonWhere<DemoShape>()('name', '=', '5');
// aq<DemoShape>()
// .where(['address', 'city', 'name'], '=', '1')
// .where(['tags', 0, 'name'], '=', '2')
// .where('id', 'in', [1])
// .where(({ and, where, or, not }) =>
// or([where('name', '=', '3'), where('name', '=', '4'), where(where5)]),
// )
// .where()
// .orderBy('name')
// .orderBy('id', 'desc')
// .limit(31)
// .offset(0);