├── .npmrc ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── tsconfig.json ├── package.json ├── src ├── test │ ├── stream.test.ts │ ├── limited.test.ts │ └── index.test.ts └── index.ts ├── LICENSE ├── .gitignore └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: standard 10 | versions: 11 | - 16.0.3 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "moduleResolution": "node", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "allowSyntheticDefaultImports": true, 8 | "target": "es2019", 9 | "rootDir": "src", 10 | "outDir": "build", 11 | "sourceMap": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - 'docs/**' 7 | - '*.md' 8 | pull_request: 9 | paths-ignore: 10 | - 'docs/**' 11 | - '*.md' 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [20.x, 22.x] 20 | steps: 21 | - uses: actions/checkout@v3 22 | 23 | - name: Use Node.js 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | 28 | - name: Install 29 | run: | 30 | npm install 31 | 32 | - name: Lint 33 | run: | 34 | npm run lint 35 | 36 | - name: Run tests 37 | run: | 38 | npm run test 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@matteo.collina/sqlite-pool", 3 | "version": "0.6.0", 4 | "description": "A connection pool for better-sqlite3 compatible with atdatabases suite", 5 | "main": "build/index.js", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc --watch", 10 | "test": "npm run build && node --test build/test/*test.js", 11 | "lint": "prettier --check src", 12 | "lint:fix": "prettier --write src" 13 | }, 14 | "author": "Matteo Collina ", 15 | "license": "MIT", 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/mcollina/sqlite-pool.git" 19 | }, 20 | "dependencies": { 21 | "@databases/connection-pool": "^1.1.0", 22 | "@databases/escape-identifier": "^1.0.3", 23 | "@databases/sql": "^3.3.0", 24 | "@databases/sqlite-sync": "^3.0.0" 25 | }, 26 | "devDependencies": { 27 | "@types/node": "^24.0.4", 28 | "prettier": "^3.0.0", 29 | "typescript": "^5.1.3" 30 | }, 31 | "files": [ 32 | "build/index.js", 33 | "build/index.d.ts", 34 | "build/index.js.map" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/test/stream.test.ts: -------------------------------------------------------------------------------- 1 | import createConnectionPool, { sql } from "../"; 2 | import { test } from "node:test"; 3 | import assert from "node:assert"; 4 | 5 | async function testPool(t) { 6 | const db = createConnectionPool(); 7 | t.after(db.dispose.bind(db)); 8 | return db; 9 | } 10 | 11 | test("streaming", async (t) => { 12 | const db = await testPool(t); 13 | await db.query( 14 | sql`CREATE TABLE stream_values (id BIGINT NOT NULL PRIMARY KEY);`, 15 | ); 16 | const allValues = []; 17 | for (let batch = 0; batch < 10; batch++) { 18 | const batchValues = []; 19 | for (let i = 0; i < 10; i++) { 20 | const value = batch * 10 + i; 21 | batchValues.push(value); 22 | allValues.push(value); 23 | } 24 | await db.query(sql` 25 | INSERT INTO stream_values (id) 26 | VALUES ${sql.join( 27 | batchValues.map((v) => sql`(${v})`), 28 | sql`,`, 29 | )}; 30 | `); 31 | } 32 | const results = []; 33 | for await (const row of db.queryStream(sql`SELECT * FROM stream_values`)) { 34 | results.push(row.id); 35 | } 36 | assert.deepStrictEqual(results, allValues); 37 | }); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matteo Collina 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. 22 | -------------------------------------------------------------------------------- /src/test/limited.test.ts: -------------------------------------------------------------------------------- 1 | import createConnectionPool, { sql } from "../"; 2 | import { test } from "node:test"; 3 | import assert from "node:assert"; 4 | 5 | async function testPool(t) { 6 | const db = createConnectionPool( 7 | ":memory:", 8 | {}, 9 | { maxSize: 2, releaseTimeoutMilliseconds: 100 }, 10 | ); 11 | t.after(db.dispose.bind(db)); 12 | return db; 13 | } 14 | 15 | test("two parallel queries", async (t) => { 16 | const db = await testPool(t); 17 | 18 | let concurrent = 0; 19 | async function query() { 20 | const result = await db.tx(async (tx) => { 21 | if (++concurrent > 2) { 22 | throw new Error("Too many concurrent queries"); 23 | } 24 | const a = await tx.query(sql`SELECT 1 + ${41} as ${sql.ident("foo")}`); 25 | const b = await tx.query(sql`SELECT 1 + 2 as bar;`); 26 | return { a, b }; 27 | }); 28 | concurrent--; 29 | assert.deepStrictEqual(result, { a: [{ foo: 42 }], b: [{ bar: 3 }] }); 30 | } 31 | 32 | await Promise.all([query(), query(), query(), query()]); 33 | }); 34 | 35 | test("never releasing", async (t) => { 36 | const db = await testPool(t); 37 | 38 | await assert.rejects( 39 | db.tx(async () => { 40 | return new Promise(() => { 41 | // not calling resolve 42 | }); 43 | }), 44 | new Error("Transaction aborted"), 45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | build 132 | -------------------------------------------------------------------------------- /src/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { sql, createConnectionPool } from "../"; 2 | import { test } from "node:test"; 3 | import assert from "node:assert"; 4 | 5 | test("error messages", async (t) => { 6 | const db = createConnectionPool(); 7 | t.after(db.dispose.bind(db)); 8 | await assert.rejects(db.query(sql`SELECT * FRM 'baz;`), "SQLITE_ERROR"); 9 | }); 10 | 11 | test("query", async (t) => { 12 | const db = createConnectionPool(); 13 | t.after(db.dispose.bind(db)); 14 | const [{ foo }] = await db.query(sql`SELECT 1 + 1 as foo`); 15 | assert.strictEqual(foo, 2); 16 | }); 17 | 18 | test("query with params", async (t) => { 19 | const db = createConnectionPool(); 20 | t.after(db.dispose.bind(db)); 21 | const [{ foo }] = await db.query( 22 | sql`SELECT 1 + ${41} as ${sql.ident("foo")}`, 23 | ); 24 | assert.strictEqual(foo, 42); 25 | }); 26 | 27 | test("bigint", async (t) => { 28 | const db = createConnectionPool(); 29 | t.after(db.dispose.bind(db)); 30 | await db.query( 31 | sql`CREATE TABLE bigint_test_bigints (id BIGINT NOT NULL PRIMARY KEY);`, 32 | ); 33 | await db.query(sql` 34 | INSERT INTO bigint_test_bigints (id) 35 | VALUES (1), 36 | (2), 37 | (42); 38 | `); 39 | const result = await db.query(sql`SELECT id from bigint_test_bigints;`); 40 | assert.deepStrictEqual(result, [{ id: 1 }, { id: 2 }, { id: 42 }]); 41 | }); 42 | 43 | test("transaction", async (t) => { 44 | const db = createConnectionPool(); 45 | t.after(db.dispose.bind(db)); 46 | const result = await db.tx(async (tx) => { 47 | const a = await tx.query(sql`SELECT 1 + ${41} as ${sql.ident("foo")}`); 48 | const b = await tx.query(sql`SELECT 1 + 2 as bar;`); 49 | return { a, b }; 50 | }); 51 | assert.deepStrictEqual(result, { 52 | a: [{ foo: 42 }], 53 | b: [{ bar: 3 }], 54 | }); 55 | }); 56 | 57 | test("two parallel queries", async (t) => { 58 | const db = createConnectionPool(); 59 | t.after(db.dispose.bind(db)); 60 | 61 | async function query() { 62 | const [{ foo }] = await db.query(sql`SELECT 1 + 1 as foo`); 63 | assert.strictEqual(foo, 2); 64 | } 65 | 66 | await Promise.all([query(), query()]); 67 | }); 68 | 69 | test("log all queries", async (t) => { 70 | let called = false; 71 | const db = createConnectionPool( 72 | undefined, 73 | {}, 74 | { 75 | onQuery(query) { 76 | called = true; 77 | assert.strictEqual(query.text, "SELECT 1 + 1 as foo"); 78 | assert.deepStrictEqual(query.values, []); 79 | }, 80 | }, 81 | ); 82 | t.after(db.dispose.bind(db)); 83 | const [{ foo }] = await db.query(sql`SELECT 1 + 1 as foo`); 84 | assert.strictEqual(foo, 2); 85 | assert.strictEqual(called, true); 86 | }); 87 | 88 | test("transaction logs", async (t) => { 89 | let called = false; 90 | const db = createConnectionPool( 91 | undefined, 92 | {}, 93 | { 94 | onQuery(query) { 95 | called = true; 96 | assert.strictEqual(query.text, "SELECT 1 + 1 as foo;"); 97 | assert.deepStrictEqual(query.values, []); 98 | }, 99 | }, 100 | ); 101 | t.after(db.dispose.bind(db)); 102 | const result = await db.tx(async (tx) => { 103 | const b = await tx.query(sql`SELECT 1 + 1 as foo;`); 104 | return { b }; 105 | }); 106 | assert.deepStrictEqual(result, { 107 | b: [{ foo: 2 }], 108 | }); 109 | assert.strictEqual(called, true); 110 | }); 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlite-pool 2 | 3 | The `@matteo.collina/sqlite-pool` library provides an asynchronous, safe and convenient 4 | API for querying SQLite databases in node.js. Built on top of 5 | [better-sqlite3](https://www.npmjs.com/package/better-sqlite3). 6 | 7 | When using this module, consider that: 8 | 9 | > SQLite supports multiple simultaneous read transactions coming from separate database 10 | connections, possibly in separate threads or processes, but only one simultaneous 11 | write transaction - [source](https://www.sqlite.org/lang_transaction.html). 12 | 13 | ## Usage 14 | 15 | ```typescript 16 | import {sql, createConnectionPool} from '@matteo.collina/sqlite-pool'; 17 | // or in CommonJS: 18 | // const { createConnectionPool, sql } = require('@matteo.collina/sqlite-pool'); 19 | 20 | const db = createConnectionPool(); 21 | 22 | db.query(sql`SELECT * FROM users;`).then( 23 | (results) => console.log(results), 24 | (err) => console.error(err), 25 | ); 26 | ``` 27 | 28 | ```javascript 29 | const createConnectionPool = require('@databases/sqlite-pool'); 30 | const {sql} = require('@databases/sqlite-pool'); 31 | 32 | const db = createConnectionPool(); 33 | 34 | db.query(sql`SELECT * FROM users;`).then( 35 | (results) => console.log(results), 36 | (err) => console.error(err), 37 | ); 38 | ``` 39 | 40 | > For details on how to build queries, see [Building SQL Queries](sql.md) 41 | 42 | ## API 43 | 44 | ### `createConnectionPool(fileName)` 45 | 46 | Create a database createConnectionPoolion for a given database. You should only create one createConnectionPoolion per database for your entire applicaiton. Normally this means having one module that creates and exports the createConnectionPoolion pool. 47 | 48 | In memory: 49 | 50 | ```ts 51 | import createConnectionPool from '@databases/sqlite-pool'; 52 | 53 | const db = createConnectionPool(); 54 | ``` 55 | 56 | File system: 57 | 58 | ```ts 59 | import createConnectionPool from '@databases/sqlite-pool'; 60 | 61 | const db = createConnectionPool(FILE_NAME); 62 | ``` 63 | 64 | The `DatabaseConnection` inherits from `DatabaseTransaction`, so you call `DatabaseConnection.query` directly instead of having to create a transaction for every query. Since SQLite has very limited support for actual transactions, we only support running one transaction at a time, but multiple queries can be run in parallel. You should therefore only use transactions when you actually need them. 65 | 66 | ### `DatabaseConnection.query(SQLQuery): Promise` 67 | 68 | Run an SQL Query and get a promise for an array of results. 69 | 70 | ### `DatabaseConnection.queryStream(SQLQuery): AsyncIterable` 71 | 72 | Run an SQL Query and get an async iterable of the results. e.g. 73 | 74 | ```js 75 | for await (const record of db.queryStream(sql`SELECT * FROM massive_table`)) { 76 | console.log(result); 77 | } 78 | ``` 79 | 80 | ### `DatabaseConnection.tx(fn): Promise` 81 | 82 | Executes a callback function as a transaction, with automatically managed createConnectionPoolion. 83 | 84 | A transaction wraps a regular task with additional queries: 85 | 86 | 1. it executes `BEGIN` just before invoking the callback function 87 | 2. it executes `COMMIT`, if the callback didn't throw any error or return a rejected promise 88 | 3. it executes `ROLLBACK`, if the callback did throw an error or return a rejected promise 89 | 90 | ```ts 91 | const result = await db.tx(async (transaction) => { 92 | const resultA = await transaction.query(sql`SELECT 1 + 1 AS a`); 93 | const resultB = await transaction.query(sql`SELECT 1 + 1 AS b`); 94 | return resultA[0].a + resultB[0].b; 95 | }); 96 | // => 4 97 | ``` 98 | 99 | ### `DatabaseConnection.dispose(): Promise` 100 | 101 | Dispose the DatabaseConnection. Once this is called, any subsequent queries will fail. 102 | 103 | ## License 104 | 105 | MIT 106 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import sql, { SQLQuery, isSqlQuery } from "@databases/sql"; 2 | import connect, { 3 | DatabaseConnection as SyncDatabaseConnection, 4 | } from "@databases/sqlite-sync"; 5 | import createBaseConnectionPool, { 6 | ConnectionPool, 7 | PoolConnection, 8 | PoolOptions, 9 | } from "@databases/connection-pool"; 10 | import { escapeSQLiteIdentifier } from "@databases/escape-identifier"; 11 | import { once } from "events"; 12 | 13 | export type { SQLQuery }; 14 | export { sql, isSqlQuery }; 15 | 16 | type connectParameters = Parameters; 17 | 18 | type DatabaseOptions = connectParameters[1]; 19 | 20 | export interface DatabaseTransaction { 21 | query(query: SQLQuery): Promise; 22 | 23 | queryStream(query: SQLQuery): AsyncIterableIterator; 24 | } 25 | 26 | export interface DatabaseConnection extends DatabaseTransaction { 27 | tx(fn: (db: DatabaseTransaction) => Promise): Promise; 28 | dispose(): Promise; 29 | } 30 | 31 | async function* transactionalQueryStream( 32 | transaction: TransactionImplementation, 33 | query: SQLQuery, 34 | ): AsyncIterableIterator { 35 | const connection = transaction.connection; 36 | for (const row of connection.queryStream(query)) { 37 | if (transaction.aborted) { 38 | throw new Error("Transaction aborted"); 39 | } 40 | yield row; 41 | } 42 | } 43 | 44 | class TransactionImplementation implements DatabaseTransaction { 45 | connection: SyncDatabaseConnection; 46 | aborted: boolean = false; 47 | #onQuery: (query: SQLQuery) => void; 48 | 49 | constructor( 50 | connection: SyncDatabaseConnection, 51 | onQuery: (query: SQLQuery) => void, 52 | ) { 53 | this.connection = connection; 54 | this.#onQuery = onQuery; 55 | } 56 | 57 | async query(query: SQLQuery): Promise { 58 | if (this.aborted) { 59 | throw new Error("Transaction aborted"); 60 | } 61 | this.#onQuery(query); 62 | return this.connection.query(query); 63 | } 64 | 65 | queryStream(query: SQLQuery): AsyncIterableIterator { 66 | this.#onQuery(query); 67 | return transactionalQueryStream(this, query); 68 | } 69 | } 70 | 71 | async function* queryStream( 72 | maybePoolConnection: Promise< 73 | PoolConnection 74 | >, 75 | query: SQLQuery, 76 | ) { 77 | const poolConnection = await maybePoolConnection; 78 | try { 79 | for (const row of poolConnection.connection.queryStream(query)) { 80 | yield row; 81 | } 82 | } finally { 83 | poolConnection.release(); 84 | } 85 | } 86 | 87 | type PartialPoolOptions = Omit< 88 | PoolOptions, 89 | "openConnection" | "closeConnection" 90 | >; 91 | 92 | type onQueryParamters = { 93 | text: string; 94 | values: unknown[]; 95 | }; 96 | 97 | type ConnectionPoolOptions = PartialPoolOptions & { 98 | onQuery?(onQueryParamters): void; 99 | }; 100 | 101 | interface SyncDatabaseConnectionWithController extends SyncDatabaseConnection { 102 | controller?: AbortController; 103 | } 104 | 105 | class DatabaseConnectionImplementation implements DatabaseConnection { 106 | #pool: ConnectionPool; 107 | #onQuery: (query: SQLQuery) => void; 108 | 109 | constructor( 110 | filename?: string, 111 | options?: DatabaseOptions, 112 | poolOptions?: ConnectionPoolOptions, 113 | ) { 114 | this.#onQuery = (query) => { 115 | const formatted = query.format({ 116 | escapeIdentifier: escapeSQLiteIdentifier, 117 | formatValue: (value) => ({ placeholder: "?", value }), 118 | }); 119 | poolOptions?.onQuery?.(formatted); 120 | }; 121 | this.#pool = createBaseConnectionPool({ 122 | async openConnection() { 123 | return connect(filename, options); 124 | }, 125 | async closeConnection(connection) { 126 | connection.dispose(); 127 | return; 128 | }, 129 | async onReleaseTimeout(connection: SyncDatabaseConnectionWithController) { 130 | const controller = connection.controller; 131 | if (controller) { 132 | controller.abort(); 133 | } 134 | connection.dispose(); 135 | return; 136 | }, 137 | ...poolOptions, 138 | }); 139 | } 140 | 141 | async query(query: SQLQuery): Promise { 142 | const poolConnection = await this.#pool.getConnection(); 143 | try { 144 | this.#onQuery(query); 145 | const res = poolConnection.connection.query(query); 146 | return res; 147 | } finally { 148 | poolConnection.release(); 149 | } 150 | } 151 | 152 | queryStream(query: SQLQuery): AsyncIterableIterator { 153 | this.#onQuery(query); 154 | return queryStream(this.#pool.getConnection(), query); 155 | } 156 | 157 | async tx(fn: (db: DatabaseTransaction) => Promise): Promise { 158 | const poolConnection = await this.#pool.getConnection(); 159 | const connection = poolConnection.connection; 160 | try { 161 | connection.query(sql`BEGIN`); 162 | const controller = new AbortController(); 163 | const tx = new TransactionImplementation(connection, this.#onQuery); 164 | connection.controller = controller; 165 | const res = await Promise.race([ 166 | fn(tx), 167 | once(controller.signal, "abort").then(() => { 168 | throw new Error("Transaction aborted"); 169 | }), 170 | ]); 171 | connection.query(sql`COMMIT`); 172 | return res; 173 | } catch (e) { 174 | try { 175 | connection.query(sql`ROLLBACK`); 176 | } catch { 177 | // Deliberately swallow this error 178 | } 179 | throw e; 180 | } finally { 181 | poolConnection.release(); 182 | } 183 | } 184 | 185 | async dispose(): Promise { 186 | await this.#pool.drain(); 187 | } 188 | } 189 | 190 | export function createConnectionPool( 191 | filename?: string, 192 | options?: DatabaseOptions, 193 | poolOptions?: ConnectionPoolOptions, 194 | ): DatabaseConnection { 195 | return new DatabaseConnectionImplementation(filename, options, poolOptions); 196 | } 197 | 198 | export default createConnectionPool; 199 | --------------------------------------------------------------------------------