A SQL query builder based on Knex with powerful TypeScript type support.
Intergrated Tracing of OpenTelemetry.
Declarative Transaction via decorator @Transactional
- Type-safe property of tables accessor
- Type-safe join table easily
- Type-safe auto-completion in IDE
- Auto Paging with one query
- Declarative transaction
- OpenTelemetry trace
npm i -g c8 lerna madge rollup tsx zx
npm i kmore && npm i -D kmore-cli
// for Midway.js
npm i @mwcp/kmore && npm i -D kmore-cli
# Then add one of the following:
npm install pg
npm install pg-native
npm install mssql
npm install oracle
npm install sqlite3
Edit the package.json
{
"script": {
"build": "tsc -b && npm run db:gen",
"db:gen": "kmore gen --path src/ test/",
"db:gen-cjs": "kmore gen --path src/ test/ --format cjs"
},
}
import { KnexConfig, kmoreFactory, genDbDict } from 'kmore'
// connection config
export const config: KnexConfig = {
client: 'pg',
connection: {
host: 'localhost',
user: 'postgres',
password: 'foo',
database: 'db_ci_test',
},
}
// Define database model
export interface Db {
tb_user: UserDo
tb_user_ext: UserExtDo
}
export interface UserDo {
uid: number
name: string
ctime: Date
}
export interface UserExtDo {
uid: number
age: number
address: string
}
const dict = genDbDict<Db>()
export const km = kmoreFactory({ config, dict })
await km.dbh.schema
.createTable('tb_user', (tb) => {
tb.increments('uid')
tb.string('name', 30)
tb.timestamp('ctime', { useTz: false })
})
.createTable('tb_user_ext', (tb) => {
tb.integer('uid')
tb.foreign('uid')
.references('tb_user.uid')
.onDelete('CASCADE')
.onUpdate('CASCADE')
tb.integer('age')
tb.string('address', 255)
})
.catch((err: Error) => {
assert(false, err.message)
})
// auto generated accessor tb_user() and tb_user_detail()
const { tb_user, tb_user_detail } = km.refTables
await tb_user()
.insert([
{ user_name: 'user1', ctime: new Date() }, // ms
{ user_name: 'user2', ctime: 'now()' }, // μs
])
.then()
const affectedRows = await tb_user_detail()
.insert([
{ uid: 1, age: 10, user_address: 'address1' },
{ uid: 2, age: 10, user_address: 'address1' },
])
.returning('*')
.then()
import { RecordCamelKeys } from '@waiting/shared-types'
// auto generated accessors tb_user() and tb_user_detail()
const { tb_user, tb_user_detail } = km.camelTables
interface UserDO {
user_name: string
ctime: date | string
}
type UserDTO = RecordCamelKeys<UserDO>
const users: UserDTO[] = await tb_user()
.insert([
{ userName: 'user1', ctime: new Date() }, // ms
{ userName: 'user2', ctime: 'now()' }, // μs
])
.returning('*')
.then()
const uid = 1
// tb_user JOIN tb_user_ext ON tb_user_ext.uid = tb_user.uid
const ret = await km.camelTables.tb_user()
.smartJoin(
'tb_user_ext.uid',
'tb_user.uid',
)
.select('*')
.where({ uid }) // <-- has auto-complete with 'uid'
// .where('uid', uid) <-- has auto-complete with 'uid'
// .where('tb_user_ext_uid', uid) <-- has auto-complete with 'tb_user_ext_uid'
// .where(km.dict.scoped.tb_user.uid, 1)
.then(rows => rows[0])
assert(ret)
ret.uid
ret.tb_user_ext_uid // <-- duplicate uid will be converted with table prefix like "<tb_name>_<column>"
More examples of join see joint-table
-
RawType:
const options: Partial<PagingOptions> = { page: 2, // default 1 pageSize: 20, // default 10 } const users = await tables.tb_user().autoPaging(options) assert(Array.isArray(users)) assert(users.length) // not enumerable properties of pager const { total, // total records page, // current page number, start from 1 pageSize, // size of items each page } = users const [ user ] = users
-
WrapType:
const options: Partial<PagingOptions> = { page: 2, // default 1 pageSize: 20, // default 10 } const users = await tables.tb_user().autoPaging(options, true) assert(! Array.isArray(users)) assert(Array.isArray(users.rows)) assert(users.rows.length) // enumerable properties of pager const { total, // total records page, // current page number, start from 1 pageSize, // size of items each page rows, // response records } = users const [ user ] = users.rows
More examples of auto paging see auto-paing
Limitation:
- not apply on base class
- apply
AsyncFunction
only, means every result of query builder must be "await"ed - transaction propagation current supports only:
PropagationType.REQUIRED
PropagationType.SUPPORTS
Usage:
-
Class decorator
import { Init, Inject, Singleton } from '@midwayjs/core' import { Transactional } from '@mwcp/kmore' @Transactional() // <-- @Singleton() export class UserRepo { @Inject() dbManager: DbManager<'master', Db> tb_user: Kmore<Db>['camelTables']['tb_user'] tb_user_ext: Kmore<Db>['camelTables']['tb_user_ext'] @Init() async init(): Promise<void> { const db = this.dbManager.getDataSource('master') assert(db) this.tb_user = db.camelTables.tb_user this.tb_user_ext = db.camelTables.tb_user_ext } async getUsers(): Promise<UserDTO[]> { const users = await this.tb_user() return users } // will throw error wrongUsage() { return this.tb_user() } }
-
Method decorator
@Singleton() export class UserRepo { @Inject() dbManager: DbManager<'master', Db> tb_user: Kmore<Db>['camelTables']['tb_user'] tb_user_ext: Kmore<Db>['camelTables']['tb_user_ext'] @Init() async init(): Promise<void> { const db = this.dbManager.getDataSource('master') assert(db) this.tb_user = db.camelTables.tb_user this.tb_user_ext = db.camelTables.tb_user_ext } @Transactional() // <-- async getUsers(): Promise<UserDTO[]> { const users = await this.tb_user() return users } }
// drop table
await km.dbh.raw(`DROP TABLE IF EXISTS "${tb}" CASCADE;`).then()
// disconnect
await km.dbh.destroy()
// file: src/config/config.{prod | local | unittest}.ts
import { genDbDict } from 'kmore-types'
import { KmoreSourceConfig } from '@mwcp/kmore'
import { TbAppDO, TbMemberDO } from '../do/database.do.js'
export interface Db {
tb_app: TbAppDO
tb_user: TbMemberDO
}
export const dbDict = genDbDict<Db>()
const master: DbConfig<Db> = {
config: {
client: 'pg',
connection: {
host: 'localhost',
port: 5432,
database: 'db_test',
user: 'postgres',
password: 'password',
},
},
dict: dbDict,
}
export const kmoreConfig: KmoreSourceConfig = {
dataSource: {
master,
// slave,
},
}
import { Init, Inject, Singleton } from '@midwayjs/core'
@Provide()
@Singleton()
export class UserRepo {
@Inject() dbManager: DbManager<'master' | 'slave', Db>
protected db: Kmore<Db>
@Init()
async init(): Promise<void> {
this.db = this.dbManager.getDataSource('master')
}
async getUser(uid: number): Promise<UserDTO | undefined> {
const { tb_user } = this.db.camelTables
const user = await tb_user()
.where({ uid })
.then(rows => rows[0])
return user
}
}
kmore is comprised of many specialized packages. This repository contains all these packages. Below you will find a summary of each package.
Package | Version |
---|---|
kmore |
|
kmore-types |
|
kmore-cli |
|
@mwcp/kmore |