{"id":16834711,"url":"https://github.com/mscdex/esqlite","last_synced_at":"2025-08-23T09:06:52.945Z","repository":{"id":57230551,"uuid":"415446902","full_name":"mscdex/esqlite","owner":"mscdex","description":"An SQLite binding for node.js with built-in encryption, focused on simplicity and (async) performance","archived":false,"fork":false,"pushed_at":"2025-03-17T02:31:00.000Z","size":16518,"stargazers_count":41,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-12T14:23:23.964Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mscdex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-09T23:57:23.000Z","updated_at":"2025-04-23T07:00:27.000Z","dependencies_parsed_at":"2023-11-09T14:28:32.300Z","dependency_job_id":"dab38690-2e0d-4fe3-bafb-2cb2bbd715e4","html_url":"https://github.com/mscdex/esqlite","commit_stats":{"total_commits":53,"total_committers":1,"mean_commits":53.0,"dds":0.0,"last_synced_commit":"f06756d5a94f6d8c1af8163aad7fdcaa9c73c222"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/mscdex/esqlite","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Fesqlite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Fesqlite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Fesqlite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Fesqlite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mscdex","download_url":"https://codeload.github.com/mscdex/esqlite/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mscdex%2Fesqlite/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271746301,"owners_count":24813556,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-23T02:00:09.327Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-13T12:07:31.970Z","updated_at":"2025-08-23T09:06:52.821Z","avatar_url":"https://github.com/mscdex.png","language":"C++","funding_links":[],"categories":["Libraries"],"sub_categories":["Databases"],"readme":"Description\n===========\n\nAn SQLite (more accurately [SQLite3MultipleCiphers](https://utelle.github.io/SQLite3MultipleCiphers/)) binding for [node.js](https://nodejs.org) focused on simplicity and (async) performance.\n\nCurrent SQLite version: 3.49.1\n\nWhen dealing with encrypted sqlite databases, this binding only supports the\nChaCha20-Poly1305 cipher to keep things simple, secure, and working well across\nmultiple platforms.\n\nAvailable/Relevant special `PRAGMA`s:\n\n* [`PRAGMA kdf_iter`](https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_sql_pragmas/#pragma-kdf_iter)\n* [`PRAGMA key`](https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_sql_pragmas/#pragma-key)\n* [`PRAGMA rekey`](https://utelle.github.io/SQLite3MultipleCiphers/docs/configuration/config_sql_pragmas/#pragma-rekey)\n\nTable of Contents\n=================\n\n* [Implementation/Design Notes](#implementationdesign-notes)\n* [Performance](#performance)\n* [Requirements](#requirements)\n* [Installation](#installation)\n* [Examples](#examples)\n* [API](#api)\n\n# Implementation/Design Notes\n\nThe goal of this addon/binding is to provide a simple and consistent interface\nfor interacting with SQLite databases. What that means on a technical level is:\n\n  * Only synchronous opening and closing of databases\n    * **Why?** To simplify things. Opening and closing should be fast enough and\n      are typically not done that often anyway.\n\n  * Only async queries, which are processed in a queue\n    * **Why?** Async because queries could easily have the potential to disrupt\n      the node.js event loop. A per-connection queue is used because of the\n      threading model used with SQLite, which not only avoids a lot of extra\n      mutexes but also avoids various race conditions that can still occur even\n      with SQLite in a serialized/\"thread-safe\" threading model.\n\n  * Only strings, `null`, and `Buffer`s for column values\n    * **Why?** To provide a consistent set of data types without any \"gotchas.\"\n      In particular there is no awkward number value handling that plagues a lot\n      of node.js database bindings in general due to JavaScript's use of a\n      double type for its numbers (although there is built-in bigint now, it is\n      a separate type and can't be used with regular JavaScript numbers very\n      easily).\n\n      Some bindings deal with this problem by allowing you to configure\n      number-handling behavior, however in general that ultimately means you\n      will probably end up adding some kind of type checking and whatnot when\n      processing query results to support different configurations.\n\n  * Only SQLite's UTF-8 APIs are used/supported\n    * **Why?** To be clear, this doesn't mean databases utilizing UTF-16 can't\n      be used with this addon, it just means that SQLite will be forced to do\n      some transformations that would ordinarily be unnecessary with a database\n      that used UTF-8 for string values from the get-go. This incurs additional\n      overhead when executing queries. Also, SQLite has some APIs that only\n      accept UTF-8 strings anyway so it makes even more sense from a\n      consistency perspective.\n\n# Performance\n\nWhen discussing performance (particularly node.js sqlite driver performance),\nit's important to reiterate that your mileage may vary and that it mostly boils\ndown to how the sqlite database is accessed. Specifically I'm referring to\nsynchronous vs. asynchronous. Both have their advantages and disadvantages and\nhave different scaling properties.\n\nBecause `esqlite` only provides an async API and the fact that sqlite directly\naccesses the disk, it means queries run in the thread pool to ensure the main\nthread is not blocked. With other types of databases where you make a network\nconnection to the database, this is unnecessary and can be done without the\nthread pool (and without writing/using C/C++ code) because you're simply waiting\nfor I/O, which node.js can easily and more efficiently do.\n\nWith that in mind, what this means is that for some workloads, synchronous\nqueries will perform better than asynchronous queries because of the overhead\nof queueing work to the thread pool and the additional copying of results\nbecause you cannot access V8 APIs from threads in a *node addon*.\n\nFor benchmarking, I generated a single, unencrypted database with 100k records.\nThe schema looked like:\n\n```sql\nCREATE TABLE data (\n  ID INT,\n  EmailAddress VARCHAR(500),\n  FirstName VARCHAR(500),\n  LastName VARCHAR(500),\n  IPAddress VARCHAR(500),\n  Age INT\n)\n```\n\nThe node.js version benchmarked with here was **v20.14.0**.\n\nThe sqlite packages being benchmarked:\n\nPackage          | Version\n-----------------|--------:\n[better-sqlite3] | 11.7.0\n[esqlite]        | 0.0.19\n[sqlite3]        | 5.1.7\n\n[better-sqlite3]: https://github.com/WiseLibs/better-sqlite3\n[esqlite]: https://github.com/mscdex/esqlite\n[sqlite3]: https://github.com/TryGhost/node-sqlite3\n\nHere is the code and the results for a couple of different queries that I ran on\nmy Linux desktop:\n\n* `SELECT * FROM data` (retrieves all 100k rows)\n\n  * Code\n\n    * `better-sqlite3`\n        ```js\n        const openDB = require('better-sqlite3');\n        const db = openDB('/tmp/test.db', { readonly: true });\n\n        console.time('select');\n        db.prepare('SELECT * FROM data').all();\n        console.timeEnd('select');\n        db.close();\n        ```\n    * `esqlite`\n        ```js\n        const { Database, OPEN_FLAGS } = require('esqlite');\n        const db = new Database('/tmp/test.db');\n        db.open(OPEN_FLAGS.READONLY);\n\n        console.time('select');\n        db.query('SELECT * FROM data', () =\u003e {\n          console.timeEnd('select');\n          db.close();\n        });\n        ```\n    * `sqlite3`\n        ```js\n        const sqlite3 = require('sqlite3');\n        const db = new sqlite3.Database('/tmp/test.db', sqlite3.OPEN_READONLY);\n\n        console.time('select');\n        db.all('SELECT * FROM data', () =\u003e {\n          console.timeEnd('select');\n          db.close();\n        });\n        ```\n\n  * Results\n\n    Package        | Average time (ms) | Average max RSS (MB)\n    ---------------|------------------:|---------------------:\n    better-sqlite3 | `121`             | `101`\n    esqlite        | `88`              | `129`\n    sqlite3        | `189`             | `146`\n\n* `SELECT * FROM data LIMIT 1000`\n\n  * Code same as before, but with the SQL string changed appropriately\n\n  * Results\n\n    Package        | Average time (ms) | Average max RSS (MB)\n    ---------------|------------------:|---------------------:\n    better-sqlite3 | `1.5`             | `51`\n    esqlite        | `1.3`             | `50`\n    sqlite3        | `2.3`             | `47`\n\n* `SELECT * FROM data LIMIT 10`\n\n  * Code same as before, but with the SQL string changed appropriately\n\n  * Results\n\n    Package        | Average time (ms) | Average max RSS (MB)\n    ---------------|------------------:|---------------------:\n    better-sqlite3 | `0.185`           | `50`\n    esqlite        | `0.500`           | `46`\n    sqlite3        | `0.603`           | `47`\n\n\n# Requirements\n\n* [node.js](http://nodejs.org/)\n  * Windows: node v12.x or newer\n  * All other platforms: node v10.7.0 or newer\n* An appropriate build environment -- see [node-gyp's documentation](https://github.com/nodejs/node-gyp/blob/main/README.md)\n\n\n# Installation\n\n    npm install esqlite\n\n\n# Examples\n\n* Create/Open an encrypted database\n```js\nconst { Database } = require('esqlite');\n\nconst db = new Database('/path/to/database');\ndb.open();\ndb.query(`PRAGMA key = 'my passphrase'`, (err) =\u003e {\n  if (err) throw err;\n\n  // Perform queries as normal ...\n\n  // ... and eventually close the database\n  db.close();\n});\n```\n\n* Binding values\n```js\nconst { Database } = require('esqlite');\n\nconst db = new Database('/path/to/database');\ndb.open();\n\n// Using nameless/ordered parameters\ndb.query('SELECT * FROM posts WHERE id = ?', [1234], (err, rows) =\u003e {\n  if (err) throw err;\n\n  db.close();\n});\n\n// Using named parameters\nconst values = { id: 1234 };\ndb.query('SELECT * FROM posts WHERE id = :id', { values }, (err, rows) =\u003e {\n  if (err) throw err;\n\n  db.close();\n});\n```\n\n\n# API\n\n## Exports\n\n* `Database` - A class that represents a connection to an SQLite database.\n\n* `OPEN_FLAGS` - *object* - Contains various flags that can be passed to\n                            `database.open()`:\n\n    * `CREATE` - The database is created if it does not exist.\n    * `MEMORY` - The database will be opened as an in-memory database. The\n                 database is named by the `filename` argument passed to the\n                 `Database` constructor for the purposes of cache-sharing if\n                 shared cache mode is enabled, otherwise the `filename` is\n                 ignored.\n    * `NOFOLLOW` - When opening the database, the database path is not allowed\n                   to be a symbolic link.\n    * `PRIVATECACHE` - The database is opened with shared cache disabled.\n    * `READONLY` - The database is opened in read-only mode. If the database\n                   does not already exist, an error is thrown.\n    * `READWRITE` - The database is opened for reading and writing if possible,\n                    or reading only if the file is write protected by the\n                    operating system. In either case the database must already\n                    exist, otherwise an error is thrown.\n    * `SHAREDCACHE` - The database is opened with shared cache enabled.\n\n* `PREPARE_FLAGS` - *object* - Contains various flags related to query\n                               preparation that can be passed to `query()`:\n\n    * `NO_VTAB` - Causes the query to fail if the statement uses any virtual\n                  tables.\n\n* `version` - *string* - Contains the SQLite and SQLite3MultipleCiphers\n                         versions.\n\n---\n\n## `Database` methods\n\n* Database(\u003c _string_ \u003epath) - Creates a new `Database` object for operating\n  on the database located at `path`.\n\n* autoCommitEnabled() - *boolean* - Returns whether the opened database\n  currently has auto-commit enabled.\n\n* close() - *(void)* - Closes the database.\n\n* end() - *(void)* - Automatically closes the database when the query queue is\n  empty. If the queue is empty when `end()` is called, then the database is\n  immediately closed.\n\n* interrupt(\u003c _function_ \u003ecallback) - *(void)* -  Interrupts the currently\n  running query. `callback` has no arguments and is called after any query has\n  been interrupted.\n\n* open([ \u003c _integer_ \u003eflags ]) - *(void)* -  Opens the database with optional\n  flags whose values come from `OPEN_FLAGS`.\n  **Default `flags`:** `CREATE | READWRITE`\n\n* query(\u003c _string_ \u003esql[, \u003c _object_ \u003eoptions][, \u003c _array_ \u003evalues][, \u003c _function_ \u003ecallback) -\n  Executes the statement(s) in `sql`. `options` may contain:\n\n    * `prepareFlags` - *integer* - Flags to be used during preparation of the\n      statement(s) whose values come from `PREPARE_FLAGS`.\n      **Default:** (no flags)\n\n    * `single` - *boolean* - Whether only a single statement should be executed\n      from `sql`. This can be useful to help avoid some SQL injection attacks.\n      **Default:** `true`\n\n    * `values` - *mixed* - Either an object containing named bind parameters and\n      their associated values or an array containing values for nameless/ordered\n      bind parameters. **Default:** (none)\n\n  If using nameless/ordered values, then an array `values` may be passed\n  directly in `query()`.\n\n  If an error occurs while preparing/parsing a statement, further processing\n  of `sql` stops immediately (only relevant when `options.single === false`).\n\n  `callback` is called when zero or more of the statement(s) finish and has the\n  signature `(err, rows)`. In the case of a single statement, `err` is a\n  possible `Error` instance and `rows` is a possible array of rows returned from\n  the statement. In the case of multiple statements, if any one of the\n  statements ended in an error, then `err` will be an array. If there was no\n  error, `rows` will contain a 2D array of rows, one set of rows per statement.\n  It is possible that the length of `err` and/or `rows` will not equal the\n  number of statements if there was a fatal error that halted execution of any\n  further statements.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscdex%2Fesqlite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmscdex%2Fesqlite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmscdex%2Fesqlite/lists"}