oor
v1.6.0
Published
Query core for suffix, condition, schema, and date helpers with ES, SQL builders, and a Drizzle table plugin.
Readme
OOR
oor is a small query core for TypeScript.
It focuses on:
- suffix parse
query -> condition- schema check with
zod - Elasticsearch body build
- PostgreSQL / SQLite / MySQL syntax-only builders
- UTC date helpers
It does not manage database links, CRUD runtime, ORM adapters, or provider state.
Install
pnpm add oorExports
Root oor only exports the abstract query core.
- suffix:
SUFFIX,parse - date:
toDate,hourStart,hourEnd,dayStart,dayEnd,monthStart,monthEnd,yearStart,yearEnd,dayRange,monthRange,yearRange - condition:
makeNode,withSoft,page,sort,sortDir - schema:
schema,read
Common types:
QueryInput,Node,Atom,QueryMeta,SoftRuleFieldKind,Suffix,Op,SortDir
Concrete builders live on subpaths:
oor/es:esQuery,esNode,esBody,esBodyNode,terms,dateGroup,range,avg,sum,min,max,uniqueoor/pg:whereSql,whereNode,querySql,queryNodeoor/sqlite:whereSql,whereNode,querySql,queryNodeoor/mysql:whereSql,whereNode,querySql,queryNodeoor/drizzle:withTable
Suffix
Use short, readable suffix names.
- no suffix:
status=active - text:
titleLike,titleStartWith,titleEndWith - list:
idIn,idNotIn - compare:
ageMin,ageMax,scoreMore,scoreLess - range:
createdAtBetween - date range:
createdAtDay,createdAtMonth,createdAtYear - date edge:
createdAtHourStart,createdAtHourEnd,createdAtDayStart,createdAtDayEnd,createdAtMonthStart,createdAtMonthEnd - null check:
deletedAtIsNull,deletedAtNotNull
When you build Node by hand, use the same plain ops:
EqualNotMoreMoreEqualLessLessEqualLikeStartWithEndWithInNotInBetweenIsNullNotNull
Parse
import { parse } from 'oor'
const rules = parse({
idIn: '1,2,3',
createdAtDay: '2026-04-07',
titleStartWith: 'oor'
})Condition
import { makeNode } from 'oor'
const node = makeNode(
{
ageMin: 18,
statusIn: ['active', 'pending']
},
{
soft: {
mode: 'flag',
field: 'isDeleted',
del: true,
keep: false
}
}
)Schema
import { z } from 'zod'
import { read, schema } from 'oor'
const query = schema({
id: z.number(),
title: z.string(),
createdAt: z.date()
})
const params = query.parse({
titleLike: 'oor',
createdAtDay: '2026-04-07',
page: '2',
size: '10',
sort: 'createdAt',
order: 'desc'
})
const same = read(
{
id: z.number(),
title: z.string()
},
{ titleLike: 'oor' }
)ES
import { avg, dateGroup, esBody, terms } from 'oor/es'
const body = esBody(
{
status: 'active',
page: 2,
size: 10,
sort: 'createdAt',
order: 'desc'
},
{
sort: { field: 'createdAt', by: 'desc' }
},
{
map: {
status: 'status.keyword',
createdAt: 'created_at',
price: 'price_cents'
},
sortMap: {
createdAt: 'created_at'
},
track: true,
groups: {
byStatus: terms('status', {
size: 10,
aggs: {
byMonth: dateGroup('createdAt', {
calendar: 'month'
})
}
}),
averagePrice: avg('price')
}
}
)SQL
import { querySql } from 'oor/mysql'
const query = querySql(
{
ageMin: 18,
nameLike: 'john',
page: 2,
size: 5,
sort: 'createdAt',
order: 'desc'
},
{
soft: {
mode: 'flag',
field: 'isDeleted',
del: true,
keep: false
}
},
{
map: {
age: { column: 'users.age', type: 'number' },
name: { column: 'users.name', type: 'string' },
createdAt: { column: 'users.created_at', type: 'date' },
isDeleted: { column: 'users.is_deleted', type: 'boolean' }
}
}
)oor/pg, oor/sqlite, and oor/mysql use the same API shape. They only build syntax and never open a database link.
Drizzle
If you use Drizzle and want a table-level api, use oor/drizzle instead of low-level SQL builders.
import { withTable } from 'oor/drizzle'
const userApi = withTable(db, users, {
page: { size: 20 },
sort: { field: 'createdAt', by: 'desc' }
})oor/drizzle keeps its source flat under drizzle/*.ts.
- table api entry:
drizzle/index.ts - shared drizzle types:
drizzle/type.ts - read/write helpers:
drizzle/read.ts,drizzle/write.ts - write methods must include a condition
- complex write filters use
updateNode,deleteNode, andhardDeleteNode
Date
import { dayRange, toDate } from 'oor'
const date = toDate('2026-04-07')
const [start, end] = dayRange(date)Design
- no
src - root exports abstract core only
- package layout is split by role:
core: condition, suffix, schema, sql, typeadapter: es, pg, mysql, sqliteutils: shared helpers such asdate.tsdrizzle: flat Drizzle plugin files, with shared types indrizzle/type.tstypeorm: reserved integration slot- dialect builders use subpaths
- Drizzle table api lives in
oor/drizzle - query core only
- SQL is syntax only
- runtime belongs in app code or integration packages
