{"id":20896228,"url":"https://github.com/waitingsong/kmore","last_synced_at":"2025-08-07T02:14:03.866Z","repository":{"id":42501939,"uuid":"197718936","full_name":"waitingsong/kmore","owner":"waitingsong","description":"A SQL query builder based on Knex with powerful TypeScript type support. Intergrated Tracing of OpenTelemetry. Declarative Transaction via decorator @Transactional","archived":false,"fork":false,"pushed_at":"2024-11-22T04:28:48.000Z","size":5676,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-07-05T07:01:56.623Z","etag":null,"topics":["knex","otel","postgres","query-builder","sql","transactional","typeorm","types","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/waitingsong.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-19T06:55:08.000Z","updated_at":"2024-11-22T04:28:51.000Z","dependencies_parsed_at":"2024-01-30T11:46:39.706Z","dependency_job_id":"6cabfc04-515e-4a51-ab9c-8f85bba8e890","html_url":"https://github.com/waitingsong/kmore","commit_stats":{"total_commits":3381,"total_committers":3,"mean_commits":1127.0,"dds":0.09671694764862471,"last_synced_commit":"3e869290045ffa8bc70cb242f828978ab1a32677"},"previous_names":[],"tags_count":360,"template":false,"template_full_name":null,"purl":"pkg:github/waitingsong/kmore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waitingsong%2Fkmore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waitingsong%2Fkmore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waitingsong%2Fkmore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waitingsong%2Fkmore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/waitingsong","download_url":"https://codeload.github.com/waitingsong/kmore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waitingsong%2Fkmore/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269185726,"owners_count":24374634,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-07T02:00:09.698Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["knex","otel","postgres","query-builder","sql","transactional","typeorm","types","typescript"],"created_at":"2024-11-18T10:34:24.522Z","updated_at":"2025-08-07T02:14:03.843Z","avatar_url":"https://github.com/waitingsong.png","language":"TypeScript","readme":"# [kmore](https://waitingsong.github.io/kmore/)\n\nA SQL query builder based on [Knex](https://knexjs.org/) with powerful TypeScript type support.\nIntergrated Tracing of [OpenTelemetry].\nDeclarative Transaction via decorator `@Transactional`\n\n\n[![GitHub tag](https://img.shields.io/github/tag/waitingsong/kmore.svg)]()\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n[![](https://img.shields.io/badge/lang-TypeScript-blue.svg)]()\n[![ci](https://github.com/waitingsong/kmore/workflows/ci/badge.svg)](https://github.com/waitingsong/kmore/actions)\n[![codecov](https://codecov.io/gh/waitingsong/kmore/branch/main/graph/badge.svg?token=wNYqpmseCn)](https://codecov.io/gh/waitingsong/kmore)\n\n\n## Features\n- Type-safe property of tables accessor \n- Type-safe join table easily\n- Type-safe auto-completion in IDE\n- [Auto Paging with one query](#auto-paging)\n- [Declarative transaction](#declarative-transaction)\n- OpenTelemetry trace\n\n## Install global deps for development\n```sh\nnpm i -g c8 lerna madge rollup tsx zx\n```\n\n## Installation\n```sh\nnpm i kmore \u0026\u0026 npm i -D kmore-cli\n// for Midway.js\nnpm i @mwcp/kmore \u0026\u0026 npm i -D kmore-cli\n\n# Then add one of the following:\nnpm install pg\nnpm install pg-native\nnpm install mssql\nnpm install oracle\nnpm install sqlite3\n```\n\n[pg-native-installation][pg-native]\n\n## Basic usage\n\n### Build configuration:\n\nEdit the `package.json`\n```json\n{\n  \"script\": {\n    \"build\": \"tsc -b \u0026\u0026 npm run db:gen\",\n    \"db:gen\": \"kmore gen --path src/ test/\",\n    \"db:gen-cjs\": \"kmore gen --path src/ test/ --format cjs\"\n  },\n}\n```\n\n### Create connection\n```ts\nimport { KnexConfig, kmoreFactory, genDbDict } from 'kmore'\n\n// connection config\nexport const config: KnexConfig = {\n  client: 'pg',\n  connection: {\n    host: 'localhost',\n    user: 'postgres',\n    password: 'foo',\n    database: 'db_ci_test',\n  },\n}\n\n// Define database model\nexport interface Db {\n  tb_user: UserDo\n  tb_user_ext: UserExtDo\n}\n\nexport interface UserDo {\n  uid: number\n  name: string\n  ctime: Date\n}\nexport interface UserExtDo {\n  uid: number\n  age: number\n  address: string\n}  \n\nconst dict = genDbDict\u003cDb\u003e()\nexport const km = kmoreFactory({ config, dict })\n```\n\n### Create tables with instance of knex\n```ts\nawait km.dbh.schema\n  .createTable('tb_user', (tb) =\u003e {\n    tb.increments('uid')\n    tb.string('name', 30)\n    tb.timestamp('ctime', { useTz: false })\n  })\n  .createTable('tb_user_ext', (tb) =\u003e {\n    tb.integer('uid')\n    tb.foreign('uid')\n      .references('tb_user.uid')\n      .onDelete('CASCADE')\n      .onUpdate('CASCADE')\n    tb.integer('age')\n    tb.string('address', 255)\n  })\n  .catch((err: Error) =\u003e {\n    assert(false, err.message)\n  })\n```\n\n### Inert rows via auto generated table accessor\n\n#### Snake style\n```ts\n// auto generated accessor tb_user() and tb_user_detail()\nconst { tb_user, tb_user_detail } = km.refTables\n\nawait tb_user()\n  .insert([\n    { user_name: 'user1', ctime: new Date() }, // ms\n    { user_name: 'user2', ctime: 'now()' }, // μs\n  ])\n  .then()\n\nconst affectedRows = await tb_user_detail()\n  .insert([\n    { uid: 1, age: 10, user_address: 'address1' },\n    { uid: 2, age: 10, user_address: 'address1' },\n  ])\n  .returning('*')\n  .then()\n```\n\n#### Camel style\n```ts\nimport { RecordCamelKeys } from '@waiting/shared-types'\n\n// auto generated accessors tb_user() and tb_user_detail() \nconst { tb_user, tb_user_detail } = km.camelTables\n\ninterface UserDO {\n  user_name: string\n  ctime: date | string\n}\ntype UserDTO = RecordCamelKeys\u003cUserDO\u003e\n\nconst users: UserDTO[] = await tb_user()\n  .insert([\n    { userName: 'user1', ctime: new Date() }, // ms\n    { userName: 'user2', ctime: 'now()' }, // μs\n  ])\n  .returning('*')\n  .then()\n```\n\n### Smart Join tables with types hint and auto complete\n```ts\nconst uid = 1\n\n// tb_user JOIN tb_user_ext ON tb_user_ext.uid = tb_user.uid\nconst ret = await km.camelTables.tb_user()\n  .smartJoin(\n    'tb_user_ext.uid',\n    'tb_user.uid',\n  )\n  .select('*')\n  .where({ uid }) // \u003c-- has auto-complete with 'uid'\n  // .where('uid', uid)   \u003c-- has auto-complete with 'uid'\n  // .where('tb_user_ext_uid', uid) \u003c-- has auto-complete with 'tb_user_ext_uid'\n  // .where(km.dict.scoped.tb_user.uid, 1)\n  .then(rows =\u003e rows[0])\n\nassert(ret)\nret.uid\nret.tb_user_ext_uid   // \u003c-- duplicate uid will be converted with table prefix like \"\u003ctb_name\u003e_\u003ccolumn\u003e\"\n\n```\n\nMore examples of join see [joint-table](https://github.com/waitingsong/kmore/blob/main/packages/kmore/test/join-table/)\n\n\n### Auto Paging\n\n- RawType:\n\n  ```ts\n  const options: Partial\u003cPagingOptions\u003e = {\n    page: 2,      // default 1\n    pageSize: 20, // default 10\n  }\n  const users = await tables.tb_user().autoPaging(options)\n  assert(Array.isArray(users))\n  assert(users.length)\n\n  // not enumerable properties of pager\n  const { \n    total,    // total records\n    page,     // current page number, start from 1\n    pageSize, // size of items each page\n  } = users\n  const [ user ] = users\n  ```\n\n- WrapType:\n\n  ```ts\n  const options: Partial\u003cPagingOptions\u003e = {\n    page: 2,      // default 1\n    pageSize: 20, // default 10\n  }\n  const users = await tables.tb_user().autoPaging(options, true)\n  assert(! Array.isArray(users))\n  assert(Array.isArray(users.rows))\n  assert(users.rows.length)\n\n  // enumerable properties of pager\n  const { \n    total,    // total records\n    page,     // current page number, start from 1\n    pageSize, // size of items each page\n    rows,     // response records\n  } = users\n  const [ user ] = users.rows\n  ```\n\n\nMore examples of auto paging see [auto-paing](https://github.com/waitingsong/kmore/blob/main/packages/kmore/test/auto-paging/)\n\n\n### Declarative Transaction\n\nLimitation:\n- not apply on base class\n- apply `AsyncFunction` only, means every result of query builder must be \"await\"ed\n- transaction propagation current supports only:\n  - `PropagationType.REQUIRED`\n  - `PropagationType.SUPPORTS`\n\n\nUsage:\n- Class decorator\n\n  ```ts\n  import { Init, Inject, Singleton } from '@midwayjs/core'\n  import { Transactional } from '@mwcp/kmore'\n\n  @Transactional()  // \u003c-- \n  @Singleton()\n  export class UserRepo {\n    @Inject() dbManager: DbManager\u003c'master', Db\u003e\n\n    tb_user: Kmore\u003cDb\u003e['camelTables']['tb_user']\n    tb_user_ext: Kmore\u003cDb\u003e['camelTables']['tb_user_ext']\n\n    @Init()\n    async init(): Promise\u003cvoid\u003e {\n      const db = this.dbManager.getDataSource('master')\n      assert(db)\n      this.tb_user = db.camelTables.tb_user\n      this.tb_user_ext = db.camelTables.tb_user_ext\n    }\n\n    async getUsers(): Promise\u003cUserDTO[]\u003e {\n      const users = await this.tb_user()\n      return users\n    }\n\n    // will throw error\n    wrongUsage() {\n      return this.tb_user()\n    }\n  }\n  ```\n\n- Method decorator\n\n  ```ts\n  @Singleton()\n  export class UserRepo {\n    @Inject() dbManager: DbManager\u003c'master', Db\u003e\n\n    tb_user: Kmore\u003cDb\u003e['camelTables']['tb_user']\n    tb_user_ext: Kmore\u003cDb\u003e['camelTables']['tb_user_ext']\n\n    @Init()\n    async init(): Promise\u003cvoid\u003e {\n      const db = this.dbManager.getDataSource('master')\n      assert(db)\n      this.tb_user = db.camelTables.tb_user\n      this.tb_user_ext = db.camelTables.tb_user_ext\n    }\n\n    @Transactional()  // \u003c--\n    async getUsers(): Promise\u003cUserDTO[]\u003e {\n      const users = await this.tb_user()\n      return users\n    }\n  }\n  ```\n\n\n### Use instance of knex\n```ts\n// drop table\nawait km.dbh.raw(`DROP TABLE IF EXISTS \"${tb}\" CASCADE;`).then()\n\n// disconnect\nawait km.dbh.destroy()\n```\n\n## Midway.js component\n\n### Config\n```ts\n// file: src/config/config.{prod | local | unittest}.ts\n\nimport { genDbDict } from 'kmore-types'\nimport { KmoreSourceConfig } from '@mwcp/kmore'\nimport { TbAppDO, TbMemberDO } from '../do/database.do.js'\n\nexport interface Db {\n  tb_app: TbAppDO\n  tb_user: TbMemberDO\n}\n\nexport const dbDict = genDbDict\u003cDb\u003e()\n\nconst master: DbConfig\u003cDb\u003e = {\n  config: {\n    client: 'pg',\n    connection: {\n      host: 'localhost',\n      port: 5432,\n      database: 'db_test',\n      user: 'postgres',\n      password: 'password',\n    },\n  },\n  dict: dbDict,\n}\nexport const kmoreConfig: KmoreSourceConfig = {\n  dataSource: {\n    master,\n    // slave,\n  },\n}  \n```\n\n### Usage\n```ts\nimport { Init, Inject, Singleton } from '@midwayjs/core'\n\n@Provide()\n@Singleton()\nexport class UserRepo {\n\n  @Inject() dbManager: DbManager\u003c'master' | 'slave', Db\u003e\n\n  protected db: Kmore\u003cDb\u003e\n\n  @Init()\n  async init(): Promise\u003cvoid\u003e {\n    this.db = this.dbManager.getDataSource('master')\n  }\n\n  async getUser(uid: number): Promise\u003cUserDTO | undefined\u003e {\n    const { tb_user } = this.db.camelTables\n    const user = await tb_user()\n      .where({ uid })\n      .then(rows =\u003e rows[0])\n    return user\n  }\n}\n```\n\n\n## Demo\n- [see test](https://github.com/waitingsong/kmore/blob/main/test/)\n\n\n## Packages\n\nkmore is comprised of many specialized packages.\nThis repository contains all these packages. Below you will find a summary of each package.\n\n| Package         | Version                  |\n| --------------- | ------------------------ |\n| [`kmore`]       | [![kmore-svg]][kmore-ch] |\n| [`kmore-types`] | [![types-svg]][types-ch] |\n| [`kmore-cli`]   | [![cli-svg]][cli-ch]     |\n| [`@mwcp/kmore`] | [![mw-svg]][mw-ch]       |\n\n\n\n## License\n[MIT](LICENSE)\n\n\n### Languages\n- [English](README.md)\n- [中文](README.zh-CN.md)\n\n\n[`kmore`]: https://github.com/waitingsong/kmore/tree/main/packages/kmore\n[`kmore-types`]: https://github.com/waitingsong/kmore/tree/main/packages/kmore-types\n[`kmore-cli`]: https://github.com/waitingsong/kmore/tree/main/packages/kmore-cli\n[`@mwcp/kmore`]: https://github.com/waitingsong/kmore/tree/main/packages/midway-component-kmore\n\n[kmore-svg]: https://img.shields.io/npm/v/kmore.svg?maxAge=300\n[kmore-ch]: https://github.com/waitingsong/kmore/tree/main/packages/kmore/CHANGELOG.md\n\n[types-svg]: https://img.shields.io/npm/v/kmore-types.svg?maxAge=300\n[types-ch]: https://github.com/waitingsong/kmore/tree/main/packages/kmore-types/CHANGELOG.md\n\n[cli-svg]: https://img.shields.io/npm/v/kmore-cli.svg?maxAge=300\n[cli-ch]: https://github.com/waitingsong/kmore/tree/main/packages/kmore-clie/CHANGELOG.md\n\n\n[mw-svg]: https://img.shields.io/npm/v/@mwcp/kmore.svg?maxAge=300\n[mw-ch]: https://github.com/waitingsong/kmore/tree/main/packages/midway-component-kmore/CHANGELOG.md\n\n\n\u003cbr\u003e\n\n[pg-native]: https://github.com/brianc/node-pg-native\n[OpenTelemetry]: https://github.com/open-telemetry/opentelemetry-js-api\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaitingsong%2Fkmore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwaitingsong%2Fkmore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaitingsong%2Fkmore/lists"}