@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.
Files changed (4) hide show
  1. package/README.md +82 -0
  2. package/index.d.ts +46 -0
  3. package/index.js +90 -0
  4. 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
+ }