Skip to content
This repository was archived by the owner on May 11, 2021. It is now read-only.

Commit a7f2200

Browse files
committed
feat: inital release
0 parents  commit a7f2200

19 files changed

+8022
-0
lines changed

.editorconfig

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
charset = utf-8
7+
trim_trailing_whitespace = true
8+
insert_final_newline = true
9+
10+
[*.md]
11+
trim_trailing_whitespace = false

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
.DS_Store

.npmignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
yarn.lock
2+
__snapshots__/

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package.json

.prettierrc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"tabWidth": 4
5+
}

.travis.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
language: node_js
2+
node_js:
3+
- lts/*
4+
cache: yarn
5+
notifications:
6+
email: false
7+
script: yarn verify
8+
jobs:
9+
include:
10+
- stage: release
11+
node_js: lts/*
12+
deploy:
13+
provider: script
14+
skip_cleanup: true
15+
script:
16+
- npx semantic-release
17+
env:
18+
global:
19+
- secure: HHvgRKLtWU+akAItiK4P4HU6O0AAzLSsxu2ccAXRLN+ZctziMvw/M5/K829bjj5CDVRPY6LK9IwFGcNFGsx8dqZq8pLjsJZT3gpXRkLb3qHkvfuLFeTTRXj24tgFg3kySbqu2CRAFM70zxnM0eaJuKeoOreS4Wzxgw5xAFYjzvkn5vhjl3h9Qe9MZcAGueeAi7Jgb2QIXtuSXV96Qvlv+a6u4J2cAj7x56O5nIYCT+GoxG2bYoDQwosV6KgzdTpGungY0/0saByiIih/NiKfzA+BjXjDoDb9NxrbRyqEYarFDZTXBgAoRaWbrK+sPmWGPfyMfDEjytK/5+PVKtWVSrkHyjHVBlG0JOqCzUL0ZS331/5EUHuW5uRJ/s/GlMfRaeUcmpGu2YELKiNu6WTOTA/IMiVYv1Lv0H7bDPhSTCxYazpbQfigNyO4KmlIgC7DpfE1RSB01ukRo6nfGU0s0Du0zUl+3iLacG2w2UGRLOA6D1r8Eul/TQnwFtwyT3AYhZeYe0JZDdgdSeJ934LwAnTFvLfM98Isaq6g7nIjzeUuS3Kw3T54dLq4dn+zb7mLb6mPx8ziTyvD+IsXz3JEMdhGkyl3oHg1iItkeB1l3eJua8hk2qulw64U7rq4w+vESas+N7mRjQQqoZjvhWLK3hWoRQJ5mgwqRkoJvhRS9eg=
20+
- secure: gayubakLx4qkVEvwyKl4l8/QOfinaoMath9XuE2Nyq0J/npVMYzho812O3tVHDaTzfmEyPgn1mzHIROVDVJDbA1X5I29/yWhQ0fZF75AC8cDsVDcSPHOUV6koz1dtsms5yDGtbnC+Gqkw8xANvLx+7MdQYRDGO4rZkvemdyPIaS9ZgeAEnYwu814HQpmoRtvUSIv9fmghV+WsxvykiOGQ9nEqETG/YAFU06e0PJ7/QBS46Dk5CsvyuNNl0CmvK88aP20CgKWNt/zcy7RMyolo80tkaK9N4csfMRmZTD/Q0M2rnPAVROCnsk9E0esqsuBrMwHRZ7e6t/3Xpb2jstX/zATAkkEWj71fgQCKzMyIWJ0BGECVsRvVft4UQjM45ddFSAeMPhb5RZs6uXajeaYeQH3UrQx6L+volPZF+30LfN3vx7QMsn3LVOSCoOIIp6hvXZ4eP4kbL8tzLxuu9efzX9yedOSy3g1q9RcsZd2VWUe5leJfMfRZZB+jHnfYoU/q+7iMFQUTcVkfJetYD5WMzg11K1BNCz7h0scPA4x/30Tb4cVTHTx9HnqUGpq5gEgln2GOwreE8YJ7KCLyEQYtp4keGa167PUOAsK9VVVvTq+16EjEqyM4N6cxfEQBk5Ca0bUvnqn/aPU9mut4dpjBljPbQxskVQ3T0ahWXe9+Jo=

.vscode/launch.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
// Use IntelliSense to learn about possible Node.js debug attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"type": "node",
9+
"request": "launch",
10+
"name": "Debug tests: current file",
11+
"program": "${workspaceRoot}/node_modules/.bin/jest",
12+
"args": ["${relativeFile}", "--runInBand"],
13+
"cwd": "${workspaceRoot}",
14+
"sourceMaps": true
15+
}
16+
]
17+
}

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"editor.formatOnSave": true
3+
}

.vscode/tasks.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
// See https://go.microsoft.com/fwlink/?LinkId=733558
3+
// for the documentation about the tasks.json format
4+
"version": "2.0.0",
5+
"tasks": [
6+
{
7+
"type": "typescript",
8+
"tsconfig": "tsconfig.json",
9+
"problemMatcher": ["$tsc"],
10+
"group": {
11+
"kind": "build",
12+
"isDefault": true
13+
}
14+
}
15+
]
16+
}

LICENCE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 Seven West Media
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# Node query executor
2+
3+
[![Build Status](https://travis-ci.com/sevenwestmedia-labs/node-knex-query-executor.svg?branch=master)](https://travis-ci.com/sevenwestmedia-labs/node-knex-query-executor) ![](https://img.shields.io/npm/v/node-knex-query-executor.svg)
4+
5+
A simple library which enables encapsulation of knex queries inside functions. It enables inversion of control for database queries, making it easy to mock the database layer entirely while making database queries themselves easy to test.
6+
7+
## Why
8+
9+
Using knex directly in code means often it is hard to create re-usable database queries, to avoid this the queries are put into functions and the `knex` instance passed into those function. This approach is hard to test and often results in many queries being written inline in places query code should not be written directly.
10+
11+
By forcing all queries to be encapsulated it encourages reuse of queries and building a collection of well tested queries.
12+
13+
## Usage
14+
15+
This library extends this concept to introduce a `QueryExecutor` which can be instructed to execute queries. There are 3 variations, the `ReadQueryExecutor` which is the entry point, when the `unitOfWork` function is called a `UnitOfWorkQueryExecutor` is passed in the callback, everything inside this callback will be executed inside a transaction. If the promise rejects the transaction will be rolled back.
16+
17+
### Constructor
18+
19+
The query executor is a class, to start using it you need to create an instance of the `ReadQueryExecutor`.
20+
21+
```ts
22+
const queryExecutor = new ReadQueryExecutor(
23+
// The knex instance
24+
knex,
25+
// The services object is available to all queries, ie a logger
26+
{
27+
logger
28+
},
29+
// Table names is an object with the tables you would like to access,
30+
// mapping from the JS name to the database table name
31+
{
32+
tableOne: 'table-one'
33+
},
34+
// Optional, you can wrap every query before execution, allowing you to hook in logs or
35+
// some other manipulation
36+
query => query
37+
)
38+
```
39+
40+
#### Query Executor types
41+
42+
There are 3 query executor classes, you should only need to construct the `ReadQueryExecutor` as above.
43+
44+
- `ReadQueryExecutor`: Entry point, represents a query executor not in a transaction
45+
- `UnitOfWorkQueryExecutor`: Type used when function wants to execute a query inside a transaction
46+
- `QueryExecutor`: Type when code does not care if the query is executed inside or outside a transaction
47+
48+
### Executing a query
49+
50+
```ts
51+
// If using TypeScript it is advised to use the create query helper
52+
// which can infer all the types from usage
53+
interface QueryArgs {
54+
someArg: string
55+
}
56+
57+
interface QueryResult {
58+
col1: string
59+
}
60+
61+
// NOTE: Name your functions here if possible, it makes the error messages when using
62+
// the mock query executor better
63+
const exampleQuery = queryExecutor.createQuery(async function exampleQuery<
64+
QueryArgs,
65+
QueryResult
66+
>({ args, tables, tableNames, query }) {
67+
// You can access the query arguments through `args`
68+
const { someArg } = args
69+
70+
// Use tables to get Knex.QueryBuilder's for each table
71+
const result = await tables.tableOne().where(...).select('col1')
72+
73+
// Use tableNames if you need to access a table name directly (for joins etc)
74+
// Use query() to access knex directly (it is a callback for wrapping purposes)
75+
const result = await query(knex => knex(tableNames.tableOne).select('col1'))
76+
77+
// It is the queries responsibility to ensure the type is correct
78+
return result
79+
})
80+
81+
// Then execute the query
82+
const queryResult = await queryExecutor.execute(exampleQuery).withArgs({})
83+
```
84+
85+
### Wrapping database queries
86+
87+
Sometimes you may want to instrument knex queries (for benchmarking, debugging etc), the query executor makes this really easy.
88+
89+
```ts
90+
const queryExecutor = new ReadQueryExecutor(knex, {}, tables, {
91+
queryBuilderWrapper: (query: Knex.QueryBuilder) => {
92+
// Do what you want here
93+
94+
return query
95+
},
96+
rawQueryWrapper: (query: Knex.Raw) => {
97+
// Do what you want here
98+
99+
return query
100+
}
101+
})
102+
```
103+
104+
### Testing
105+
106+
```ts
107+
import { NoMatch, MockQueryExecutor } from 'node-query-executor'
108+
109+
const queryExecutor = new MockQueryExecutor()
110+
111+
const exampleQuery = queryExecutor.createQuery<{}, number>(async ({}) => {
112+
// real query here
113+
})
114+
const exampleQuery2 = queryExecutor.createQuery<{ input: number }, number>(
115+
async ({}) => {
116+
// real query here
117+
}
118+
)
119+
120+
// Setup the mock in the query executor, returning the same value no matter the args
121+
queryExecutor.mock(exampleQuery).match(() => {
122+
return 1
123+
})
124+
125+
// You can also chain matches, inspecting the query arguments
126+
queryExecutor
127+
.mock(exampleQuery2)
128+
.match(({ input }) => {
129+
// Return 1 if even
130+
if (input % 2 === 0) {
131+
return 1
132+
}
133+
134+
// Use the NoMatch symbol otherwise
135+
return NoMatch
136+
})
137+
.match(({ input }) => {
138+
// Return 0 if odd
139+
if (input % 2 === 1) {
140+
return 0
141+
}
142+
143+
return NoMatch
144+
})
145+
```
146+
147+
## Simplifying types
148+
149+
Because the QueryExecutor types are generic, it often is verbose writing `QueryExecutor<typeof keyof tableNames, YourQueryServices>`, it is suggested you export your own closed generic types to make them easy to pass around.
150+
151+
```ts
152+
import * as KnexQueryExecutor from 'node-knex-query-executor'
153+
154+
interface YourQueryServices {
155+
log: Logger
156+
}
157+
158+
export type Query<QueryArguments, QueryResult> = KnexQueryExecutor.Query<
159+
QueryArguments,
160+
QueryResult,
161+
keyof typeof tableNames,
162+
YourQueryServices
163+
>
164+
export type QueryExecutor = KnexQueryExecutor.QueryExecutor<
165+
keyof typeof tableNames,
166+
YourQueryServices
167+
>
168+
export type ReadQueryExecutor = KnexQueryExecutor.ReadQueryExecutor<
169+
keyof typeof tableNames,
170+
YourQueryServices
171+
>
172+
export type UnitOfWorkQueryExecutor = KnexQueryExecutor.UnitOfWorkQueryExecutor<
173+
keyof typeof tableNames,
174+
YourQueryServices
175+
>
176+
export type TableNames = KnexQueryExecutor.TableNames<keyof typeof tableNames>
177+
export type Tables = KnexQueryExecutor.Tables<keyof typeof tableNames>
178+
```
179+
180+
## Further reading
181+
182+
This library is inspired by a few object oriented patterns, and a want to move away from repositories.
183+
184+
https://en.wikipedia.org/wiki/Specification_pattern
185+
https://martinfowler.com/eaaCatalog/queryObject.html
186+
https://lostechies.com/chadmyers/2008/08/02/query-objects-with-the-repository-pattern/
187+
https://codeopinion.com/query-objects-instead-of-repositories/

jest.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node'
4+
}

package.json

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "typescript-object-validator",
3+
"version": "0.0.0",
4+
"description": "TypeScript first object validator",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"scripts": {
8+
"prepack": "yarn build",
9+
"build": "tsc -p tsconfig.build.json",
10+
"lint": "yarn tslint --project .",
11+
"test": "jest",
12+
"verify": "yarn tsc -p tsconfig.json && yarn test && yarn lint",
13+
"cz": "git-cz"
14+
},
15+
"author": "Jake Ginnivan",
16+
"license": "MIT",
17+
"devDependencies": {
18+
"@types/jest": "^23.3.10",
19+
"commitizen": "^3.0.5",
20+
"cz-conventional-changelog": "^2.1.0",
21+
"jest": "^23.6.0",
22+
"semantic-release": "^15.13.2",
23+
"ts-jest": "^23.10.5",
24+
"tslint": "^5.12.0",
25+
"tslint-config-prettier": "^1.17.0",
26+
"tslint-eslint-rules": "^5.4.0",
27+
"typescript": "^3.2.2"
28+
}
29+
}

0 commit comments

Comments
 (0)