@expresswebjs/ew-query-repository
v1.0.4
Published
ExpressWeb query repository
Downloads
10
Readme
*** Important upgrade notice ***
Because TypeScript 4.1 supports template literal string types, the function syntax is no longer necessary. You can now use strings while maintaining type-safety. The function syntax is removed since version 4.
To help with the upgrade, you can run npx ew-query-repository -u string-parameters to automatically switch over to the string syntax.
ew-query-repository
Standing on the shoulders of Knex.js, but now everything is typed!
Goals:
- Be useful for 80% of the use cases, for the other 20% easily switch to lower-level Knex.js.
- Be as concise a possible.
- Mirror Knex.js as much a possible, with these exceptions:
- Don't use
this.- Be selective on what returns a
Promiseand what not.- Less overloading, which makes typings easier and code completion better.
- Get the most of the benefits TypeScript provides: type-checking of parameters, typed results, rename refactorings.
Install:
npm install @expresswebjs/ew-query-repositoryMake sure experimentalDecorators and emitDecoratorMetadata are turned on in your tsconfig.json:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
...
},
...
}Tested with Knex.js v0.95.0, TypeScript v4.2.3 and Node.js v14.11.0
Breaking changes in v4
- Because TypeScript 4.1 supports template literal string types, the function syntax is no longer necessary. You can now use strings while maintaining type-safety. The function syntax is removed.
Run
npx ew-query-repository -u string-parametersto automatically upgrade. .onColumn()is deprecated. Use.on(). Remember that the columns switched eg.onColumns(i=>i.prop1, '=' j=>j.prop2) should become .on("prop2", '=', "prop1"). Runnpx ew-query-repository -u join-on-columns-to-onto automatically upgrade.- The use of optional columns (
@Column() public nickName?: string;) is deprecated. This was used to signal a nullable column. The correct way to do this is@Column() public nickName: string | null;.
Documentation
Quick example
To reference a column, use the name. Like this .select("name") or this .where("name", "Hejlsberg")
import * as Knex from "knex";
import { ModelRepository } from "@expresswebjs/ew-query-repository";
const knex = Knex({
client: "pg",
connection: "postgres://user:pass@localhost:5432/dbname",
});
async function example() {
const model = new ModelRepository(knex);
const query = model
.query(User)
.innerJoin("category")
.where("name", "Hejlsberg")
.select("id", "category.name");
const oneUser = await query.getSingle();
console.log(oneUser.id); // Ok
console.log(oneUser.category.name); // Ok
console.log(oneUser.name); // Compilation error
}Define tables
Use the Table decorator to reference a table and use the Column decorator to reference a column.
Use @Column({ primary: true }) for primary key columns.
Use @Column({ name: '[column name]' }) on property with the type of another Table to reference another table.
import { Column, Table } from "@expresswebjs/ew-query-repository";
@Table("userCategories")
export class UserCategory {
@Column({ primary: true })
public id: string;
@Column()
public name: string;
@Column()
public year: number;
}
@Table("users")
export class User {
@Column({ primary: true })
public id: string;
@Column()
public name: string;
@Column()
public categoryId: string;
@Column({ name: "categoryId" })
public category: UserCategory;
@Column()
public someNullableValue: string | null;
}Create instance
import * as Knex from "knex";
import { ModelRepository } from "@expresswebjs/ew-query-repository";
const knex = Knex({
client: "pg",
connection: "postgres://user:pass@localhost:5432/dbname",
});
const model = new ModelRepository(knex);Helper
Querybuilder
General
Getting the results (Promises)
- findByPrimaryKey
- getFirstOrNull
- getFirst
- getSingleOrNull
- getSingle
- getMany
- getCount
- insertItem
- insertItems
- insertSelect
- del
- delByPrimaryKey
- updateItem
- updateItemByPrimaryKey
- updateItemsByPrimaryKey
- execute
Building the query
- select
- where
- andWhere
- orWhere
- whereNot
- whereColumn
- whereNull
- orWhereNull
- whereNotNull
- orWhereNotNull
- orderBy
- orderByRaw
- innerJoinColumn
- innerJoinTableOnFunction
- leftOuterJoinColumn
- leftOuterJoinTableOnFunction
- selectRaw
- selectQuery
- whereIn
- whereNotIn
- orWhereIn
- orWhereNotIn
- whereBetween
- whereNotBetween
- orWhereBetween
- orWhereNotBetween
- whereExists
- orWhereExists
- whereNotExists
- orWhereNotExists
- whereParentheses
- groupBy
- having
- havingNull
- havingNotNull
- havingIn
- havingNotIn
- havingExists
- havingNotExists
- havingBetween
- havingNotBetween
- union
- unionAll
- min
- count
- countDistinct
- max
- sum
- sumDistinct
- avg
- avgDistinct
- clearSelect
- clearWhere
- clearOrder
- limit
- offset
- whereRaw
- havingRaw
- truncate
- distinct
- clone
- groupByRaw
getTableName
const tableName = getTableName(User);
// tableName = 'users'getColumnName
const columnName = getColumnName(User, "id");
// columnName = 'id'registerBeforeInsertTransform
Hook that is run before doing an insert. Execute this function as soon as possible. For example at the top of index.ts or server.ts.
registerBeforeInsertTransform((item: any, typedQueryBuilder: ITypedQueryBuilder<{}, {}, {}>) => {
if (typedQueryBuilder.columns.find(column => column.name === 'created_at') && !item.hasOwnProperty('created_at')) {
item.created_at = new Date();
}
if (typedQueryBuilder.columns.find(column => column.name === 'updated_at') && !item.hasOwnProperty('updated_at')) {
item.updated_at = new Date();
}
if (typedQueryBuilder.columns.find(column => column.name === 'id') && !item.hasOwnProperty('id')) {
item.id = guid();
}
return item;
});registerBeforeUpdateTransform
Hook that is run before doing an update. Execute this function as soon as possible. For example at the top of index.ts or server.ts.
registerBeforeUpdateTransform((item: any, typedQueryBuilder: ITypedQueryBuilder<{}, {}, {}>) => {
if (typedQueryBuilder.columns.find("name" === 'updated_at') && !item.hasOwnProperty('updated_at')) {
item.updated_at = new Date();
}
return item;
});query
Use model.query(Type) to create a query for the table referenced by Type
const query = model.query(User);select
https://knexjs.org/#Builder-select
model.query(User).select("id");model.query(User).select("id","name");where
https://knexjs.org/#Builder-where
model.query(User).where("name", "name");Or with operator
model.query(User).where("name", "like", "%user%");
// select * from "users" where "users"."name" like '%user%'andWhere
model
.query(User)
.where("name", "name")
.andWhere("name", "name");model
.query(User)
.where("name", "name")
.andWhere("name", "like", "%na%");orWhere
model
.query(User)
.where("name", "name")
.orWhere("name", "name");model
.query(User)
.where("name", "name")
.orWhere("name", "like", "%na%");whereNot
https://knexjs.org/#Builder-whereNot
model.query(User).whereNot("name", "name");whereColumn
To use in subqueries. First parameter is for sub query columns and the third parameter is for columns from the parent query.
model.query(User).whereNotExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});whereNull
model.query(User).whereNull("name");orWhereNull
model
.query(User)
.whereNull("name")
.orWhereNull("name");whereNotNull
model.query(User).whereNotNull("name");orWhereNotNull
model
.query(User)
.whereNotNull("name")
.orWhereNotNull("name");orderBy
model.query(User).orderBy("id");orderByRaw
await model.query(User).orderByRaw("SUM(??) DESC", "users.year");
// select * from "users" order by SUM("users"."year") DESCinnerJoinColumn
model.query(User).innerJoinColumn("category");innerJoinTableOnFunction
model.query(User).innerJoinTableOnFunction("evilTwin", User, (join) => {
join.on(
"id",
"=",
"id"
)
.andOn(
"name",
"=",
"id"
)
.orOn(
"someValue",
"=",
"id"
)
.onVal("name", "=", "1")
.andOnVal("name", "=", "2")
.orOnVal("name", "=", "3")
.onNull("name");
});leftOuterJoinColumn
model.query(User).leftOuterJoinColumn("category");leftOuterJoinTableOnFunction
model.query(User).leftOuterJoinTableOnFunction("evilTwin", User, (join) => {
join.on(
"id",
"=",
"id"
)
.andOn(
"name",
"=",
"id"
)
.orOn(
"someValue",
"=",
"id"
)
.onVal("name", "=", "1")
.andOnVal("name", "=", "2")
.orOnVal("name", "=", "3")
.onNull("name");
});selectRaw
model.query(User).selectRaw("otherId", Number, "select other.id from other");selectQuery
model
.query(UserCategory)
.select("id")
.selectQuery("total", Number, User, (subQuery) => {
subQuery.count("id", "total").whereColumn("categoryId", "=", "id");
});select "userCategories"."id" as "id", (select count("users"."id") as "total" from "users" where "users"."categoryId" = "userCategories"."id") as "total" from "userCategories"findByPrimaryKey
const user = await model.query(User).findByPrimaryKey("id", "d","name");whereIn
model.query(User).whereIn("name", ["user1", "user2"]);whereNotIn
model.query(User).whereNotIn("name", ["user1", "user2"]);orWhereIn
model
.query(User)
.whereIn("name", ["user1", "user2"])
.orWhereIn("name", ["user3", "user4"]);orWhereNotIn
model
.query(User)
.whereIn("name", ["user1", "user2"])
.orWhereNotIn("name", ["user3", "user4"]);whereBetween
model.query(UserCategory).whereBetween("year", [1, 2037]);whereNotBetween
model.query(User).whereNotBetween("year", [1, 2037]);orWhereBetween
model
.query(User)
.whereBetween("year", [1, 10])
.orWhereBetween("year", [100, 1000]);orWhereNotBetween
model
.query(User)
.whereBetween("year", [1, 10])
.orWhereNotBetween("year", [100, 1000]);whereExists
model.query(User).whereExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});orWhereExists
model.query(User).orWhereExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});whereNotExists
model.query(User).whereNotExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});orWhereNotExists
model.query(User).orWhereNotExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});whereParentheses
model
.query(User)
.whereParentheses((sub) => sub.where("id", "1").orWhere("id", "2"))
.orWhere("name", "Tester");
const queryString = query.toQuery();
console.log(queryString);Outputs:
select * from "users" where ("users"."id" = '1' or "users"."id" = '2') or "users"."name" = 'Tester'groupBy
model
.query(User)
.select("someValue")
.selectRaw("total", Number, 'SUM("numericValue")')
.groupBy("someValue");having
model.query(User).having("numericValue", ">", 10);havingNull
model.query(User).havingNull("numericValue");havingNotNull
model.query(User).havingNotNull("numericValue");havingIn
model.query(User).havingIn("name", ["user1", "user2"]);havingNotIn
model.query(User).havingNotIn("name", ["user1", "user2"]);havingExists
model.query(User).havingExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});havingNotExists
model.query(User).havingNotExists(UserSetting, (subQuery) => {
subQuery.whereColumn("userId", "=", "id");
});havingBetween
model.query(User).havingBetween("numericValue", [1, 10]);havingNotBetween
model.query(User).havingNotBetween("numericValue", [1, 10]);union
model.query(User).union(User, (subQuery) => {
subQuery.select("id").where("numericValue", 12);
});unionAll
model
.query(User)
.select("id")
.unionAll(User, (subQuery) => {
subQuery.select("id").where("numericValue", 12);
});min
model.query(User).min("numericValue", "minNumericValue");count
model.query(User).count("numericValue", "countNumericValue");countDistinct
model.query(User).countDistinct("numericValue", "countDistinctNumericValue");max
model.query(User).max("numericValue", "maxNumericValue");sum
model.query(User).sum("numericValue", "sumNumericValue");sumDistinct
model.query(User).sumDistinct("numericValue", "sumDistinctNumericValue");avg
model.query(User).avg("numericValue", "avgNumericValue");avgDistinct
model.query(User).avgDistinct("numericValue", "avgDistinctNumericValue");clearSelect
model
.query(User)
.select("id")
.clearSelect()
.select("name");clearWhere
model
.query(User)
.where("id", "name")
.clearWhere()
.where(("name", "name");clearOrder
model
.query(User)
.orderBy("id")
.clearOrder()
.orderBy(("name");limit
model.query(User).limit(10);offset
model.query(User).offset(10);useKnexQueryBuilder
Use useKnexQueryBuilder to get to the underlying Knex.js query builder.
const query = model.query(User)
.useKnexQueryBuilder(queryBuilder => queryBuilder.where('somethingelse', 'value')
.select("name");
);keepFlat
Use keepFlat to prevent unflattening of the result.
const item = await model
.query(User)
.where("name", 'name')
.innerJoinColumn("category");
.select("name", "category.name")
.getFirst();
// returns { name: 'user name', category: { name: 'category name' }}
const item = await model
.query(User)
.where("name", 'name')
.innerJoinColumn("category");
.select("name", "category.name")
.keepFlat()
.getFirst();
// returns { name: 'user name', category.name: 'category name' }toQuery
const query = model.query(User);
console.log(query.toQuery()); // select * from "users"getFirstOrNull
| Result | No item | One item | Many items |
| ----------------- | ------- | -------- | ---------- |
| getFirst | Error | Item | First item |
| getSingle | Error | Item | Error |
| getFirstOrNull | null | Item | First item |
| getSingleOrNull | null | Item | Error |
const user = await model
.query(User)
.where("name", "name")
.getFirstOrNull();getFirst
| Result | No item | One item | Many items |
| ----------------- | ------- | -------- | ---------- |
| getFirst | Error | Item | First item |
| getSingle | Error | Item | Error |
| getFirstOrNull | null | Item | First item |
| getSingleOrNull | null | Item | Error |
const user = await model
.query(User)
.where("name", "name")
.getFirst();getSingleOrNull
| Result | No item | One item | Many items |
| ----------------- | ------- | -------- | ---------- |
| getFirst | Error | Item | First item |
| getSingle | Error | Item | Error |
| getFirstOrNull | null | Item | First item |
| getSingleOrNull | null | Item | Error |
const user = await model
.query(User)
.where("name", "name")
.getSingleOrNull();getSingle
| Result | No item | One item | Many items |
| ----------------- | ------- | -------- | ---------- |
| getFirst | Error | Item | First item |
| getSingle | Error | Item | Error |
| getFirstOrNull | null | Item | First item |
| getSingleOrNull | null | Item | Error |
const user = await model
.query(User)
.where("name", "name")
.getSingle();getMany
const users = await model
.query(User)
.whereNotNull("name")
.getMany();getCount
model.query(User);insertItem
model.query(User);insertItems
model.query(User);insertSelect
await model.query(User);
.selectRaw('f', String, '\'fixedValue\'')
.select("name")
.distinct()
.whereNotNull("name")
.insertSelect(UserSetting, "id", "initialValue");
// insert into "userSettings" ("userSettings"."id","userSettings"."initialValue") select distinct ('fixedValue') as "f", "users"."name" as "name" from "users" where "users"."name" is not nulldel
model.query(User);delByPrimaryKey
model.query(User);updateItem
model.query(User);updateItemByPrimaryKey
model.query(User);updateItemsByPrimaryKey
model.query(User);execute
model.query(User);whereRaw
model.query(User);havingRaw
model.query(User);transacting
const model = new ModelRepository(database);
const transaction = await model.beginTransaction();
try {
await model.query(User).transacting(transaction).insertItem(user1);
await model.query(User).transacting(transaction).insertItem(user2);
await transaction.commit();
} catch (error) {
await transaction.rollback();
// handle error
}truncate
model.query(User);distinct
model.query(User);clone
model.query(User);groupByRaw
model.query(User);Transactions
const model = new ModelRepository(database);
const transaction = await model.beginTransaction();
try {
await model.query(User).transacting(transaction).insertItem(user1);
await model.query(User).transacting(transaction).insertItem(user2);
await transaction.commit();
} catch (error) {
await transaction.rollback();
// handle error
}Validate tables
Use the validateTables function to make sure that the Table's and Column's in TypeScript exist in the database.
import * as Knex from "knex";
import { validateTables } from "@expresswebjs/ew-query-repository";
const knex = Knex({
client: "pg",
connection: "postgres://user:pass@localhost:5432/dbname",
});
await validateTables(knex);Test
npm testUpdate version
npm version major|minor|patch
update CHANGELOG.md
git commit --amend
npm publish --access=public --otp=CODE
git pushfor beta
update version to x.x.x-beta.x
npm publish --access public --tag beta --otp=CODE