├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.ts └── test │ ├── index.test.ts │ ├── limited.test.ts │ └── stream.test.ts └── tsconfig.json /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@matteo.collina/sqlite-pool", 3 | "version": "0.6.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@matteo.collina/sqlite-pool", 9 | "version": "0.6.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@databases/connection-pool": "^1.1.0", 13 | "@databases/escape-identifier": "^1.0.3", 14 | "@databases/sql": "^3.3.0", 15 | "@databases/sqlite-sync": "^3.0.0" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^22.4.1", 19 | "prettier": "^3.0.0", 20 | "typescript": "^5.1.3" 21 | } 22 | }, 23 | "node_modules/@databases/connection-pool": { 24 | "version": "1.1.0", 25 | "resolved": "https://registry.npmjs.org/@databases/connection-pool/-/connection-pool-1.1.0.tgz", 26 | "integrity": "sha512-/12/SNgl0V77mJTo5SX3yGPz4c9XGQwAlCfA0vlfs/0HcaErNpYXpmhj0StET07w6TmTJTnaUgX2EPcQK9ez5A==", 27 | "dependencies": { 28 | "@databases/queue": "^1.0.0", 29 | "is-promise": "^4.0.0" 30 | } 31 | }, 32 | "node_modules/@databases/escape-identifier": { 33 | "version": "1.0.3", 34 | "resolved": "https://registry.npmjs.org/@databases/escape-identifier/-/escape-identifier-1.0.3.tgz", 35 | "integrity": "sha512-Su36iSVzaHxpVdISVMViUX/32sLvzxVgjZpYhzhotxZUuLo11GVWsiHwqkvUZijTLUxcDmUqEwGJO3O/soLuZA==", 36 | "dependencies": { 37 | "@databases/validate-unicode": "^1.0.0" 38 | } 39 | }, 40 | "node_modules/@databases/queue": { 41 | "version": "1.0.1", 42 | "resolved": "https://registry.npmjs.org/@databases/queue/-/queue-1.0.1.tgz", 43 | "integrity": "sha512-dqRU+/aQ4lhFzjPIkIhjB0+UEKMb76FoBgHOJUTcEblgatr/IhdhHliT3VVwcImXh35Mz297PAXE4yFM4eYWUQ==" 44 | }, 45 | "node_modules/@databases/split-sql-query": { 46 | "version": "1.0.4", 47 | "resolved": "https://registry.npmjs.org/@databases/split-sql-query/-/split-sql-query-1.0.4.tgz", 48 | "integrity": "sha512-lDqDQvH34NNjLs0knaDvL6HKgPtishQlDYHfOkvbAd5VQOEhcDvvmG2zbBuFvS2HQAz5NsyLj5erGaxibkxhvQ==", 49 | "peerDependencies": { 50 | "@databases/sql": "*" 51 | } 52 | }, 53 | "node_modules/@databases/sql": { 54 | "version": "3.3.0", 55 | "resolved": "https://registry.npmjs.org/@databases/sql/-/sql-3.3.0.tgz", 56 | "integrity": "sha512-vj9huEy4mjJ48GS1Z8yvtMm4BYAnFYACUds25ym6Gd/gsnngkJ17fo62a6mmbNNwCBS/8467PmZR01Zs/06TjA==" 57 | }, 58 | "node_modules/@databases/sqlite-sync": { 59 | "version": "3.0.0", 60 | "resolved": "https://registry.npmjs.org/@databases/sqlite-sync/-/sqlite-sync-3.0.0.tgz", 61 | "integrity": "sha512-E/vzK7fH8P5aWk54DR/zEA6IHx65b4R5fWgGPDaBH39QcJvZm89C6q9tl+6E3t3kbCTdzhohZdFNbOEbV1s2WQ==", 62 | "license": "MIT", 63 | "dependencies": { 64 | "@databases/escape-identifier": "^1.0.3", 65 | "@databases/split-sql-query": "^1.0.4", 66 | "@databases/sql": "^3.3.0", 67 | "@types/better-sqlite3": "^7.6.9", 68 | "better-sqlite3": "^11.3.0" 69 | } 70 | }, 71 | "node_modules/@databases/validate-unicode": { 72 | "version": "1.0.0", 73 | "resolved": "https://registry.npmjs.org/@databases/validate-unicode/-/validate-unicode-1.0.0.tgz", 74 | "integrity": "sha512-dLKqxGcymeVwEb/6c44KjOnzaAafFf0Wxa8xcfEjx/qOl3rdijsKYBAtIGhtVtOlpPf/PFKfgTuFurSPn/3B/g==" 75 | }, 76 | "node_modules/@types/better-sqlite3": { 77 | "version": "7.6.10", 78 | "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.10.tgz", 79 | "integrity": "sha512-TZBjD+yOsyrUJGmcUj6OS3JADk3+UZcNv3NOBqGkM09bZdi28fNZw8ODqbMOLfKCu7RYCO62/ldq1iHbzxqoPw==", 80 | "dependencies": { 81 | "@types/node": "*" 82 | } 83 | }, 84 | "node_modules/@types/node": { 85 | "version": "22.13.16", 86 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.16.tgz", 87 | "integrity": "sha512-15tM+qA4Ypml/N7kyRdvfRjBQT2RL461uF1Bldn06K0Nzn1lY3nAPgHlsVrJxdZ9WhZiW0Fmc1lOYMtDsAuB3w==", 88 | "license": "MIT", 89 | "dependencies": { 90 | "undici-types": "~6.20.0" 91 | } 92 | }, 93 | "node_modules/base64-js": { 94 | "version": "1.5.1", 95 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 96 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 97 | "funding": [ 98 | { 99 | "type": "github", 100 | "url": "https://github.com/sponsors/feross" 101 | }, 102 | { 103 | "type": "patreon", 104 | "url": "https://www.patreon.com/feross" 105 | }, 106 | { 107 | "type": "consulting", 108 | "url": "https://feross.org/support" 109 | } 110 | ], 111 | "license": "MIT" 112 | }, 113 | "node_modules/better-sqlite3": { 114 | "version": "11.7.0", 115 | "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.7.0.tgz", 116 | "integrity": "sha512-mXpa5jnIKKHeoGzBrUJrc65cXFKcILGZpU3FXR0pradUEm9MA7UZz02qfEejaMcm9iXrSOCenwwYMJ/tZ1y5Ig==", 117 | "hasInstallScript": true, 118 | "license": "MIT", 119 | "dependencies": { 120 | "bindings": "^1.5.0", 121 | "prebuild-install": "^7.1.1" 122 | } 123 | }, 124 | "node_modules/bindings": { 125 | "version": "1.5.0", 126 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", 127 | "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", 128 | "license": "MIT", 129 | "dependencies": { 130 | "file-uri-to-path": "1.0.0" 131 | } 132 | }, 133 | "node_modules/bl": { 134 | "version": "4.1.0", 135 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", 136 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", 137 | "license": "MIT", 138 | "dependencies": { 139 | "buffer": "^5.5.0", 140 | "inherits": "^2.0.4", 141 | "readable-stream": "^3.4.0" 142 | } 143 | }, 144 | "node_modules/buffer": { 145 | "version": "5.7.1", 146 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 147 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 148 | "funding": [ 149 | { 150 | "type": "github", 151 | "url": "https://github.com/sponsors/feross" 152 | }, 153 | { 154 | "type": "patreon", 155 | "url": "https://www.patreon.com/feross" 156 | }, 157 | { 158 | "type": "consulting", 159 | "url": "https://feross.org/support" 160 | } 161 | ], 162 | "license": "MIT", 163 | "dependencies": { 164 | "base64-js": "^1.3.1", 165 | "ieee754": "^1.1.13" 166 | } 167 | }, 168 | "node_modules/chownr": { 169 | "version": "1.1.4", 170 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", 171 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", 172 | "license": "ISC" 173 | }, 174 | "node_modules/decompress-response": { 175 | "version": "6.0.0", 176 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 177 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 178 | "license": "MIT", 179 | "dependencies": { 180 | "mimic-response": "^3.1.0" 181 | }, 182 | "engines": { 183 | "node": ">=10" 184 | }, 185 | "funding": { 186 | "url": "https://github.com/sponsors/sindresorhus" 187 | } 188 | }, 189 | "node_modules/deep-extend": { 190 | "version": "0.6.0", 191 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", 192 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", 193 | "license": "MIT", 194 | "engines": { 195 | "node": ">=4.0.0" 196 | } 197 | }, 198 | "node_modules/detect-libc": { 199 | "version": "2.0.3", 200 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", 201 | "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", 202 | "license": "Apache-2.0", 203 | "engines": { 204 | "node": ">=8" 205 | } 206 | }, 207 | "node_modules/end-of-stream": { 208 | "version": "1.4.4", 209 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 210 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 211 | "license": "MIT", 212 | "dependencies": { 213 | "once": "^1.4.0" 214 | } 215 | }, 216 | "node_modules/expand-template": { 217 | "version": "2.0.3", 218 | "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", 219 | "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", 220 | "license": "(MIT OR WTFPL)", 221 | "engines": { 222 | "node": ">=6" 223 | } 224 | }, 225 | "node_modules/file-uri-to-path": { 226 | "version": "1.0.0", 227 | "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", 228 | "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", 229 | "license": "MIT" 230 | }, 231 | "node_modules/fs-constants": { 232 | "version": "1.0.0", 233 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", 234 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", 235 | "license": "MIT" 236 | }, 237 | "node_modules/github-from-package": { 238 | "version": "0.0.0", 239 | "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", 240 | "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", 241 | "license": "MIT" 242 | }, 243 | "node_modules/ieee754": { 244 | "version": "1.2.1", 245 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 246 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 247 | "funding": [ 248 | { 249 | "type": "github", 250 | "url": "https://github.com/sponsors/feross" 251 | }, 252 | { 253 | "type": "patreon", 254 | "url": "https://www.patreon.com/feross" 255 | }, 256 | { 257 | "type": "consulting", 258 | "url": "https://feross.org/support" 259 | } 260 | ], 261 | "license": "BSD-3-Clause" 262 | }, 263 | "node_modules/inherits": { 264 | "version": "2.0.4", 265 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 266 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 267 | "license": "ISC" 268 | }, 269 | "node_modules/ini": { 270 | "version": "1.3.8", 271 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", 272 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", 273 | "license": "ISC" 274 | }, 275 | "node_modules/is-promise": { 276 | "version": "4.0.0", 277 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 278 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" 279 | }, 280 | "node_modules/mimic-response": { 281 | "version": "3.1.0", 282 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 283 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 284 | "license": "MIT", 285 | "engines": { 286 | "node": ">=10" 287 | }, 288 | "funding": { 289 | "url": "https://github.com/sponsors/sindresorhus" 290 | } 291 | }, 292 | "node_modules/minimist": { 293 | "version": "1.2.8", 294 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 295 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 296 | "license": "MIT", 297 | "funding": { 298 | "url": "https://github.com/sponsors/ljharb" 299 | } 300 | }, 301 | "node_modules/mkdirp-classic": { 302 | "version": "0.5.3", 303 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", 304 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", 305 | "license": "MIT" 306 | }, 307 | "node_modules/napi-build-utils": { 308 | "version": "1.0.2", 309 | "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", 310 | "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", 311 | "license": "MIT" 312 | }, 313 | "node_modules/node-abi": { 314 | "version": "3.71.0", 315 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", 316 | "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", 317 | "license": "MIT", 318 | "dependencies": { 319 | "semver": "^7.3.5" 320 | }, 321 | "engines": { 322 | "node": ">=10" 323 | } 324 | }, 325 | "node_modules/once": { 326 | "version": "1.4.0", 327 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 328 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 329 | "license": "ISC", 330 | "dependencies": { 331 | "wrappy": "1" 332 | } 333 | }, 334 | "node_modules/prebuild-install": { 335 | "version": "7.1.2", 336 | "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", 337 | "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", 338 | "license": "MIT", 339 | "dependencies": { 340 | "detect-libc": "^2.0.0", 341 | "expand-template": "^2.0.3", 342 | "github-from-package": "0.0.0", 343 | "minimist": "^1.2.3", 344 | "mkdirp-classic": "^0.5.3", 345 | "napi-build-utils": "^1.0.1", 346 | "node-abi": "^3.3.0", 347 | "pump": "^3.0.0", 348 | "rc": "^1.2.7", 349 | "simple-get": "^4.0.0", 350 | "tar-fs": "^2.0.0", 351 | "tunnel-agent": "^0.6.0" 352 | }, 353 | "bin": { 354 | "prebuild-install": "bin.js" 355 | }, 356 | "engines": { 357 | "node": ">=10" 358 | } 359 | }, 360 | "node_modules/prettier": { 361 | "version": "3.5.1", 362 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", 363 | "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", 364 | "dev": true, 365 | "license": "MIT", 366 | "bin": { 367 | "prettier": "bin/prettier.cjs" 368 | }, 369 | "engines": { 370 | "node": ">=14" 371 | }, 372 | "funding": { 373 | "url": "https://github.com/prettier/prettier?sponsor=1" 374 | } 375 | }, 376 | "node_modules/pump": { 377 | "version": "3.0.2", 378 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", 379 | "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", 380 | "license": "MIT", 381 | "dependencies": { 382 | "end-of-stream": "^1.1.0", 383 | "once": "^1.3.1" 384 | } 385 | }, 386 | "node_modules/rc": { 387 | "version": "1.2.8", 388 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", 389 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", 390 | "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", 391 | "dependencies": { 392 | "deep-extend": "^0.6.0", 393 | "ini": "~1.3.0", 394 | "minimist": "^1.2.0", 395 | "strip-json-comments": "~2.0.1" 396 | }, 397 | "bin": { 398 | "rc": "cli.js" 399 | } 400 | }, 401 | "node_modules/readable-stream": { 402 | "version": "3.6.2", 403 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 404 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 405 | "license": "MIT", 406 | "dependencies": { 407 | "inherits": "^2.0.3", 408 | "string_decoder": "^1.1.1", 409 | "util-deprecate": "^1.0.1" 410 | }, 411 | "engines": { 412 | "node": ">= 6" 413 | } 414 | }, 415 | "node_modules/safe-buffer": { 416 | "version": "5.2.1", 417 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 418 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 419 | "funding": [ 420 | { 421 | "type": "github", 422 | "url": "https://github.com/sponsors/feross" 423 | }, 424 | { 425 | "type": "patreon", 426 | "url": "https://www.patreon.com/feross" 427 | }, 428 | { 429 | "type": "consulting", 430 | "url": "https://feross.org/support" 431 | } 432 | ], 433 | "license": "MIT" 434 | }, 435 | "node_modules/semver": { 436 | "version": "7.6.3", 437 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", 438 | "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", 439 | "license": "ISC", 440 | "bin": { 441 | "semver": "bin/semver.js" 442 | }, 443 | "engines": { 444 | "node": ">=10" 445 | } 446 | }, 447 | "node_modules/simple-concat": { 448 | "version": "1.0.1", 449 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", 450 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", 451 | "funding": [ 452 | { 453 | "type": "github", 454 | "url": "https://github.com/sponsors/feross" 455 | }, 456 | { 457 | "type": "patreon", 458 | "url": "https://www.patreon.com/feross" 459 | }, 460 | { 461 | "type": "consulting", 462 | "url": "https://feross.org/support" 463 | } 464 | ], 465 | "license": "MIT" 466 | }, 467 | "node_modules/simple-get": { 468 | "version": "4.0.1", 469 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", 470 | "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", 471 | "funding": [ 472 | { 473 | "type": "github", 474 | "url": "https://github.com/sponsors/feross" 475 | }, 476 | { 477 | "type": "patreon", 478 | "url": "https://www.patreon.com/feross" 479 | }, 480 | { 481 | "type": "consulting", 482 | "url": "https://feross.org/support" 483 | } 484 | ], 485 | "license": "MIT", 486 | "dependencies": { 487 | "decompress-response": "^6.0.0", 488 | "once": "^1.3.1", 489 | "simple-concat": "^1.0.0" 490 | } 491 | }, 492 | "node_modules/string_decoder": { 493 | "version": "1.3.0", 494 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 495 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 496 | "license": "MIT", 497 | "dependencies": { 498 | "safe-buffer": "~5.2.0" 499 | } 500 | }, 501 | "node_modules/strip-json-comments": { 502 | "version": "2.0.1", 503 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 504 | "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", 505 | "license": "MIT", 506 | "engines": { 507 | "node": ">=0.10.0" 508 | } 509 | }, 510 | "node_modules/tar-fs": { 511 | "version": "2.1.1", 512 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", 513 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", 514 | "license": "MIT", 515 | "dependencies": { 516 | "chownr": "^1.1.1", 517 | "mkdirp-classic": "^0.5.2", 518 | "pump": "^3.0.0", 519 | "tar-stream": "^2.1.4" 520 | } 521 | }, 522 | "node_modules/tar-stream": { 523 | "version": "2.2.0", 524 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", 525 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", 526 | "license": "MIT", 527 | "dependencies": { 528 | "bl": "^4.0.3", 529 | "end-of-stream": "^1.4.1", 530 | "fs-constants": "^1.0.0", 531 | "inherits": "^2.0.3", 532 | "readable-stream": "^3.1.1" 533 | }, 534 | "engines": { 535 | "node": ">=6" 536 | } 537 | }, 538 | "node_modules/tunnel-agent": { 539 | "version": "0.6.0", 540 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 541 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", 542 | "license": "Apache-2.0", 543 | "dependencies": { 544 | "safe-buffer": "^5.0.1" 545 | }, 546 | "engines": { 547 | "node": "*" 548 | } 549 | }, 550 | "node_modules/typescript": { 551 | "version": "5.7.2", 552 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 553 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 554 | "dev": true, 555 | "bin": { 556 | "tsc": "bin/tsc", 557 | "tsserver": "bin/tsserver" 558 | }, 559 | "engines": { 560 | "node": ">=14.17" 561 | } 562 | }, 563 | "node_modules/undici-types": { 564 | "version": "6.20.0", 565 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 566 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 567 | "license": "MIT" 568 | }, 569 | "node_modules/util-deprecate": { 570 | "version": "1.0.2", 571 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 572 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 573 | "license": "MIT" 574 | }, 575 | "node_modules/wrappy": { 576 | "version": "1.0.2", 577 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 578 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 579 | "license": "ISC" 580 | } 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /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": "^22.4.1", 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------