UNPKG

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.

288 lines (261 loc) 7.95 kB
import { describe, expect, it } from 'bun:test'; import { PGlite } from '@electric-sql/pglite'; import { Kysely, PGliteDialect, sql } from 'kysely'; import { fromKysely } from './fromKysely.ts'; import { toKyselyOrderBy } from './pg.ts'; interface DB { user: { id: string; name: string; age: number; tags: { id: number; name: string }[]; category: string[]; address: { city: { name: string; }; }; }; } const dialect = new PGliteDialect({ pglite: new PGlite() }); const db = new Kysely<DB>({ dialect }); describe('fromKysely: limit/offset/orderBy', () => { it('extracts limit', () => { const q = db.selectFrom('user').selectAll().limit(10); const schema = fromKysely(q); expect(schema.limit).toBe(10); }); it('extracts offset', () => { const q = db.selectFrom('user').selectAll().offset(5); const schema = fromKysely(q); expect(schema.offset).toBe(5); }); it('extracts limit and offset', () => { const q = db.selectFrom('user').selectAll().limit(20).offset(10); const schema = fromKysely(q); expect(schema.limit).toBe(20); expect(schema.offset).toBe(10); }); it('extracts orderBy from ColumnNode (.orderBy string)', () => { const q = db.selectFrom('user').selectAll().orderBy('name', 'asc'); const schema = fromKysely(q); expect(schema.orderBy).toHaveLength(1); expect(schema.orderBy![0]).toEqual({ field: ['name'], direction: 'asc' }); }); it('extracts multiple orderBy clauses', () => { const q = db .selectFrom('user') .selectAll() .orderBy('name', 'asc') .orderBy('age', 'desc'); const schema = fromKysely(q); expect(schema.orderBy).toHaveLength(2); expect(schema.orderBy![0]).toEqual({ field: ['name'], direction: 'asc' }); expect(schema.orderBy![1]).toEqual({ field: ['age'], direction: 'desc' }); }); it('handles RawNode orderBy from toKyselyOrderBy', () => { const q = db.selectFrom('user').selectAll(); const qWithOrder = toKyselyOrderBy(q, [ { field: ['name'], direction: 'asc' }, ]); const schema = fromKysely(qWithOrder); expect(schema.orderBy).toHaveLength(1); expect(schema.orderBy![0]).toEqual({ field: ['name'], direction: 'asc' }); }); it('round-trips JSON nested path orderBy via toKyselyOrderBy/fromKysely', () => { const q = db.selectFrom('user').selectAll(); const qWithOrder = toKyselyOrderBy(q, [ { field: ['address', 'city', 'name'], direction: 'desc' }, ]); const schema = fromKysely(qWithOrder); expect(schema.orderBy).toHaveLength(1); expect(schema.orderBy![0]).toEqual({ field: ['address', 'city', 'name'], direction: 'desc', }); }); it('round-trips PG array subscript orderBy via toKyselyOrderBy/fromKysely', () => { const q = db.selectFrom('user').selectAll(); const qWithOrder = toKyselyOrderBy(q, [ { field: ['category', 0], direction: 'asc' }, ]); const schema = fromKysely(qWithOrder); expect(schema.orderBy).toHaveLength(1); expect(schema.orderBy![0]).toEqual({ field: ['category', 0], direction: 'asc', }); }); it('round-trips nested array + object path orderBy via toKyselyOrderBy/fromKysely', () => { const q = db.selectFrom('user').selectAll(); const qWithOrder = toKyselyOrderBy(q, [ { field: ['tags', 0, 'name'], direction: 'desc' }, ]); const schema = fromKysely(qWithOrder); expect(schema.orderBy).toHaveLength(1); expect(schema.orderBy![0]).toEqual({ field: ['tags', 0, 'name'], direction: 'desc', }); }); it('returns empty schema for bare query', () => { const q = db.selectFrom('user').selectAll(); const schema = fromKysely(q); expect(schema.limit).toBeUndefined(); expect(schema.offset).toBeUndefined(); expect(schema.orderBy).toBeUndefined(); }); }); describe('fromKysely: where', () => { it('extracts eq', () => { const q = db.selectFrom('user').selectAll().where('name', '=', 'Alice'); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['name'], op: '=', value: 'Alice' }); }); it('extracts gt', () => { const q = db.selectFrom('user').selectAll().where('age', '>', 18); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['age'], op: '>', value: 18 }); }); it('extracts gte', () => { const q = db.selectFrom('user').selectAll().where('age', '>=', 18); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['age'], op: '>=', value: 18 }); }); it('extracts lt', () => { const q = db.selectFrom('user').selectAll().where('age', '<', 18); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['age'], op: '<', value: 18 }); }); it('extracts lte', () => { const q = db.selectFrom('user').selectAll().where('age', '<=', 18); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['age'], op: '<=', value: 18 }); }); it('extracts like', () => { const q = db.selectFrom('user').selectAll().where('name', 'like', '%test%'); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['name'], op: 'like', value: '%test%', }); }); it('extracts ilike', () => { const q = db .selectFrom('user') .selectAll() .where('name', 'ilike', '%Test%'); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['name'], op: 'ilike', value: '%Test%', }); }); it('extracts in', () => { const q = db.selectFrom('user').selectAll().where('id', 'in', ['1', '2']); const schema = fromKysely(q); expect(schema.where).toEqual({ field: ['id'], op: 'in', values: ['1', '2'], }); }); it('extracts and (chained .where)', () => { const q = db .selectFrom('user') .selectAll() .where('name', '=', 'Alice') .where('age', '>', 18); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'and', conditions: [ { field: ['name'], op: '=', value: 'Alice' }, { field: ['age'], op: '>', value: 18 }, ], }); }); it('extracts and with eb.and()', () => { const q = db .selectFrom('user') .selectAll() .where((eb) => eb.and([eb('name', '=', 'Alice'), eb('age', '>', 18)])); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'and', conditions: [ { field: ['name'], op: '=', value: 'Alice' }, { field: ['age'], op: '>', value: 18 }, ], }); }); it('extracts or', () => { const q = db .selectFrom('user') .selectAll() .where((eb) => eb.or([eb('name', '=', 'Alice'), eb('age', '>', 18)])); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'or', conditions: [ { field: ['name'], op: '=', value: 'Alice' }, { field: ['age'], op: '>', value: 18 }, ], }); }); it('extracts not', () => { const q = db .selectFrom('user') .selectAll() .where((eb) => eb.not(eb('age', '<', 18))); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'not', condition: { field: ['age'], op: '<', value: 18 }, }); }); it('extracts nested and/or/not', () => { const q = db .selectFrom('user') .selectAll() .where((eb) => eb.and([ eb.or([eb('name', 'like', '%test%'), eb.not(eb('age', '=', 0))]), eb('id', 'in', ['a', 'b']), ]), ); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'and', conditions: [ { op: 'or', conditions: [ { field: ['name'], op: 'like', value: '%test%' }, { op: 'not', condition: { field: ['age'], op: '=', value: 0 } }, ], }, { field: ['id'], op: 'in', values: ['a', 'b'] }, ], }); }); it('extracts 3+ chained ands (flattened)', () => { const q = db .selectFrom('user') .selectAll() .where('name', '=', 'Alice') .where('age', '>', 18) .where('id', 'in', ['1']); const schema = fromKysely(q); expect(schema.where).toEqual({ op: 'and', conditions: [ { field: ['name'], op: '=', value: 'Alice' }, { field: ['age'], op: '>', value: 18 }, { field: ['id'], op: 'in', values: ['1'] }, ], }); }); });