@jclanker/dbmanager 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -0
- package/index.d.ts +46 -0
- package/index.js +90 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# dbmanager
|
|
2
|
+
|
|
3
|
+
A small promise-based wrapper around [better-sqlite3](https://github.com/WiseLibs/better-sqlite3).
|
|
4
|
+
|
|
5
|
+
- Opens the database in WAL mode.
|
|
6
|
+
- Every method returns a Promise, so it drops into async code that expects an async DB API.
|
|
7
|
+
- Batch helpers run one prepared statement over many parameter sets, inside a single transaction.
|
|
8
|
+
- Optional per-query timing logs.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @jclanker/dbmanager
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
const DbManager = require('@jclanker/dbmanager')
|
|
20
|
+
|
|
21
|
+
const db = new DbManager('./data.db', true) // (path, showLogs = false)
|
|
22
|
+
|
|
23
|
+
await db.runQuery('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)')
|
|
24
|
+
|
|
25
|
+
const { lastID, changes } = await db.runQuery('INSERT INTO users (name) VALUES (?)', ['alice'])
|
|
26
|
+
const { result: user } = await db.getQuery('SELECT * FROM users WHERE id = ?', [lastID])
|
|
27
|
+
const { result: users } = await db.allQuery('SELECT * FROM users')
|
|
28
|
+
|
|
29
|
+
// Named parameters work too
|
|
30
|
+
await db.runQuery('UPDATE users SET name = @name WHERE id = @id', { id: 1, name: 'bob' })
|
|
31
|
+
|
|
32
|
+
db.close()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Batches
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
// One transaction; if any row fails, the whole batch is rolled back
|
|
39
|
+
const { changes } = await db.statementRun(
|
|
40
|
+
'INSERT INTO users (name) VALUES (?)',
|
|
41
|
+
[['carol'], ['dave']]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// Returns the rowid of each inserted row (no transaction; see note below)
|
|
45
|
+
const { lastIDs, changesList } = await db.statementRunReturningIds(
|
|
46
|
+
'INSERT INTO users (name) VALUES (?)',
|
|
47
|
+
[['erin'], ['frank']]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// Runs the query once per parameter set, in one transaction, and concatenates the rows
|
|
51
|
+
const { result } = await db.statementAll(
|
|
52
|
+
'SELECT * FROM users WHERE name = ?',
|
|
53
|
+
[['carol'], ['erin']]
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
| Method | Resolves to |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `new DbManager(path, showLogs = false)` | Opens (or creates) the database and enables WAL |
|
|
62
|
+
| `runQuery(sql, params = [])` | `{ changes, lastID, result: null }` |
|
|
63
|
+
| `getQuery(sql, params = [])` | `{ result }`: the first row, or `undefined` |
|
|
64
|
+
| `allQuery(sql, params = [])` | `{ result }`: an array of rows |
|
|
65
|
+
| `statementRun(sql, paramsList)` | `{ changes }`: total changes, in one transaction |
|
|
66
|
+
| `statementRunReturningIds(sql, paramsList)` | `{ lastIDs, changesList, changes }` |
|
|
67
|
+
| `statementAll(sql, paramsList)` | `{ result }`: concatenated rows, in one transaction |
|
|
68
|
+
| `close()` | Closes the database (synchronous) |
|
|
69
|
+
| `db` | The underlying better-sqlite3 `Database`, for anything else |
|
|
70
|
+
|
|
71
|
+
Errors (SQL errors, constraint violations) reject the returned Promise.
|
|
72
|
+
|
|
73
|
+
**Note:** better-sqlite3 is synchronous: each query runs and blocks when you call the method, and
|
|
74
|
+
the returned Promise is already settled. The Promise wrapper gives you an async-style interface;
|
|
75
|
+
it doesn't move work off the main thread.
|
|
76
|
+
|
|
77
|
+
`statementRunReturningIds` does not wrap its rows in a transaction, so rows inserted before a
|
|
78
|
+
failure stay inserted. Wrap the call in `db.db.transaction(...)` yourself if you need it atomic.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
ISC
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type BetterSqlite3 from 'better-sqlite3'
|
|
2
|
+
|
|
3
|
+
declare class DbManager {
|
|
4
|
+
constructor(path: string, showLogs?: boolean)
|
|
5
|
+
readonly db: BetterSqlite3.Database
|
|
6
|
+
showLogs: boolean
|
|
7
|
+
close(): void
|
|
8
|
+
runQuery(sql: string, params?: DbManager.Params): Promise<DbManager.RunResult>
|
|
9
|
+
getQuery<T = any>(sql: string, params?: DbManager.Params): Promise<DbManager.GetResult<T>>
|
|
10
|
+
allQuery<T = any>(sql: string, params?: DbManager.Params): Promise<DbManager.AllResult<T>>
|
|
11
|
+
statementRun(sql: string, params: DbManager.Params[]): Promise<DbManager.StatementRunResult>
|
|
12
|
+
statementRunReturningIds(sql: string, params: DbManager.Params[]): Promise<DbManager.StatementRunReturningIdsResult>
|
|
13
|
+
statementAll<T = any>(sql: string, params: DbManager.Params[]): Promise<DbManager.AllResult<T>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare namespace DbManager {
|
|
17
|
+
export { DbManager, DbManager as default }
|
|
18
|
+
|
|
19
|
+
export type Params = unknown[] | Record<string, unknown>
|
|
20
|
+
|
|
21
|
+
export interface RunResult {
|
|
22
|
+
changes: number
|
|
23
|
+
lastID: number | bigint
|
|
24
|
+
result: null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GetResult<T = any> {
|
|
28
|
+
result: T | undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AllResult<T = any> {
|
|
32
|
+
result: T[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StatementRunResult {
|
|
36
|
+
changes: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StatementRunReturningIdsResult {
|
|
40
|
+
lastIDs: Array<number | bigint>
|
|
41
|
+
changesList: number[]
|
|
42
|
+
changes: number
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export = DbManager
|
package/index.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const Database = require('better-sqlite3')
|
|
2
|
+
|
|
3
|
+
class DbManager {
|
|
4
|
+
constructor(path, showLogs = false) {
|
|
5
|
+
this.db = new Database(path)
|
|
6
|
+
this.showLogs = showLogs
|
|
7
|
+
this.db.pragma('journal_mode = WAL')
|
|
8
|
+
if (this.showLogs) {
|
|
9
|
+
console.log('better-sqlite3 db init', path)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
close() {
|
|
14
|
+
this.db.close()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_exec(label, sql, logParams, fn) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
try {
|
|
20
|
+
const t1 = this.showLogs ? performance.now() : 0
|
|
21
|
+
const result = fn(this.db.prepare(sql))
|
|
22
|
+
if (this.showLogs) {
|
|
23
|
+
const t2 = performance.now()
|
|
24
|
+
console.log(label, sql, logParams, `(${t2 - t1}ms)`)
|
|
25
|
+
}
|
|
26
|
+
resolve(result)
|
|
27
|
+
} catch (err) {
|
|
28
|
+
reject(err)
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
runQuery(sql, params = []) {
|
|
34
|
+
return this._exec('run', sql, params, (statement) => {
|
|
35
|
+
const { changes, lastInsertRowid } = statement.run(params)
|
|
36
|
+
return { changes, lastID: lastInsertRowid, result: null }
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getQuery(sql, params = []) {
|
|
41
|
+
return this._exec('get', sql, params, (statement) => ({ result: statement.get(params) }))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
allQuery(sql, params = []) {
|
|
45
|
+
return this._exec('all', sql, params, (statement) => ({ result: statement.all(params) }))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
statementRun(sql, params) {
|
|
49
|
+
return this._exec('statementRun', sql, params.length, (statement) => {
|
|
50
|
+
const runMany = this.db.transaction((items) => {
|
|
51
|
+
let totalChanges = 0
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
totalChanges += statement.run(item).changes
|
|
54
|
+
}
|
|
55
|
+
return totalChanges
|
|
56
|
+
})
|
|
57
|
+
return { changes: runMany(params) }
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
statementRunReturningIds(sql, params) {
|
|
62
|
+
return this._exec('statementRunReturningIds', sql, params.length, (statement) => {
|
|
63
|
+
const lastIDs = []
|
|
64
|
+
const changesList = []
|
|
65
|
+
for (const item of params) {
|
|
66
|
+
const { lastInsertRowid, changes } = statement.run(item)
|
|
67
|
+
lastIDs.push(lastInsertRowid)
|
|
68
|
+
changesList.push(changes)
|
|
69
|
+
}
|
|
70
|
+
return { lastIDs, changesList, changes: lastIDs.length }
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
statementAll(sql, params) {
|
|
75
|
+
return this._exec('statementAll', sql, params.length, (statement) => {
|
|
76
|
+
const getMany = this.db.transaction((items) => {
|
|
77
|
+
const allResults = []
|
|
78
|
+
for (const item of items) {
|
|
79
|
+
allResults.push(...statement.all(item))
|
|
80
|
+
}
|
|
81
|
+
return allResults
|
|
82
|
+
})
|
|
83
|
+
return { result: getMany(params) }
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = DbManager
|
|
89
|
+
module.exports.DbManager = DbManager
|
|
90
|
+
module.exports.default = DbManager
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jclanker/dbmanager",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Small promise-based wrapper around better-sqlite3 with WAL mode, batched transactional statements and optional query timing logs",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jclanker/dbmanager.git"
|
|
8
|
+
},
|
|
9
|
+
"author": "jclanker",
|
|
10
|
+
"main": "index.js",
|
|
11
|
+
"types": "index.d.ts",
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js",
|
|
14
|
+
"index.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"sqlite",
|
|
21
|
+
"sqlite3",
|
|
22
|
+
"better-sqlite3",
|
|
23
|
+
"database",
|
|
24
|
+
"promise",
|
|
25
|
+
"wal"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"better-sqlite3": "^12.10.0",
|
|
32
|
+
"@types/better-sqlite3": "^9.6.0"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"license": "ISC"
|
|
38
|
+
}
|