Skip to content

Latest commit

 

History

History
350 lines (237 loc) · 42.7 KB

File metadata and controls

350 lines (237 loc) · 42.7 KB

Changelog

All notable changes to this project will be documented in this file.

3.1.0 (2026-10-03)

API compatible with node:sqlite from Node.js v26.10.0, plus createModule() and the changes listed under 3.0.0 that are on Node.js v26.x-staging but not yet in a release. SQLite is unchanged at 3.53.4.

Added

  • createModule(): virtual tables whose rows come from a JavaScript iterable, via Database.prototype.createModule(name, { columns, rows, directOnly, useBigIntArguments }), wrapping sqlite3_create_module_v2(). Ports Node.js PR #65787, PR #66195, and PR #66215, which are on v26.x-staging but not yet in a Node.js release.

Fixed

  • process.exit() in a worker during exec(): a user-defined function that called process.exit() while exec() ran in a worker aborted the process with FATAL ERROR: Error::Error napi_define_properties. The worker now exits with the requested code.
  • Expanded SQL longer than V8's maximum string length: with a sqlite.db.query diagnostics subscriber, running a statement whose expanded SQL exceeded V8's maximum string length (for example, one with a bound blob over about 256 MB, which expands to hex) threw Error: Unknown failure from a C++ exception that unwound through SQLite, and leaked the expanded SQL. The statement now succeeds and publishes no event. Reading expandedSQL on such a statement threw Error: Unknown failure with no code; it now throws ERR_STRING_TOO_LONG, as oversized TEXT values do since 3.0.0. node:sqlite aborts the process when reading expandedSQL on such a statement (checked on v24.21.0).
  • C++ exceptions in aggregate callbacks: a node-addon-api failure inside an aggregate's step, inverse, or final callback unwound through SQLite. A step or inverse result whose then getter threw made exec() leave close() failing with ERR_INVALID_STATE (database cannot be closed while in a callback), and get()/all() threw an unrelated error instead of the getter's. Exiting the process or a worker while a window aggregate with a Uint8Array accumulator was mid-query aborted with terminate called after throwing an instance of 'Napi::Error'. Function and aggregate callbacks now catch these exceptions; the statement throws the original error, and exit proceeds normally.

3.0.0 (2026-10-02)

API compatible with node:sqlite from Node.js v26.10.0, plus the Database and Statement class rename from Node.js PR #65988, which landed on v26.x-staging, and two fixes from Node.js main (Node.js PR #66042, Node.js PR #66209). None of these is in a Node.js release yet. Database.prototype.createModule(), also only on v26.x-staging so far, is not ported yet. The rename changes the classes' name values, so this is a major release. SQLite is unchanged at 3.53.4.

Added

  • Database and Statement: the new names of DatabaseSync and StatementSync from Node.js PR #65988. The old names are still exported and are the same classes (DatabaseSync === Database, StatementSync === Statement); Node.js deprecates them in documentation only (DEP0210, DEP0211). The types DatabaseInstance, DatabaseOptions, DatabaseLimits, and StatementInstance are aliases of DatabaseSyncInstance, DatabaseSyncOptions, DatabaseSyncLimits, and StatementSyncInstance.

Changed

  • BREAKING: Class names follow the rename: DatabaseSync.name and a database's constructor.name are now "Database", StatementSync.name and a statement's constructor.name are "Statement", and the iterator from iterate() is a StatementIterator (was StatementSyncIterator), as in node:sqlite from Node.js PR #65988. In 2.6.0 the exported constructors were named "DatabaseSync2" and "StatementSync2", because the bundler renamed them, while instances reported "DatabaseSync" and "StatementSync". Code that compares these names against the old strings must change; instanceof DatabaseSync and instanceof StatementSync still work.
  • open() after close() restores the connection's settings: a setAuthorizer() callback was not reinstalled, so after close() and open() a deny-all authorizer stopped denying anything; limits set through db.limits went back to the constructor's values; and extension loading turned on with enableLoadExtension(true) was off again. All three now carry over, as in node:sqlite from Node.js PR #66042.
  • TEXT longer than V8's maximum string length throws ERR_STRING_TOO_LONG: reading such a value with get(), all(), or iterate(), passing one to a user-defined or aggregate function, or reading one through DatabasePool threw Error: Unknown failure with no code. In a user-defined or aggregate function the error was a C++ exception that unwound through SQLite, after which close() always failed with ERR_INVALID_STATE (database cannot be closed while in a callback). It now throws ERR_STRING_TOO_LONG, as in node:sqlite from Node.js PR #66209, and the connection stays usable.
  • Experimental DatabasePool keeps its name: the bundler renamed the class, so DatabasePool.name was "_DatabasePool" and util.inspect() printed a pool as _DatabasePool {}. The build now keeps the source names of all exported classes and functions.
  • backup() runs one step per threadpool job: each sqlite3_backup_step() now returns to the main thread before the next one is queued, as in node:sqlite. The progress callback is therefore called after every step that leaves pages remaining; previously calls could be coalesced. Small rate values cost more: on tmpfs, a 128 MB backup took 580–690 ms at rate: 1 (was 195–210 ms; node:sqlite 580–670 ms) and 132–150 ms at the default rate: 100 (was 124–132 ms).
  • Strings with NUL bytes are no longer truncated: binding "a\0b" stored "a", and user-defined and aggregate functions received only the text before the first NUL byte of a TEXT argument. Both now use the full string, as node:sqlite and DatabasePool already did. Queries that bound such strings now store and match different values.
  • INT64_MIN passed to a function throws ERR_OUT_OF_RANGE: a user-defined or aggregate function without useBigIntArguments received -9223372036854775808 as the imprecise Number -9223372036854776000, because the range check used std::abs(), which is undefined for that value. It now throws like every other integer outside the safe range, as in node:sqlite.
  • Aggregate values that do not fit the stored state throw ERR_OUT_OF_RANGE: between steps, an aggregate's accumulator is stored in a 4096-byte buffer, and a BigInt as int64. A string or Buffer over 4095 bytes used to be truncated, an object or array whose JSON reached 4095 bytes was replaced with {"_truncated":true}, and a BigInt outside the int64 range wrapped (2n ** 64n + 5n became 5n), all without an error. A start or step value that does not fit now throws, and aggregate() throws for such a BigInt start. node:sqlite keeps the JavaScript value itself and has neither limit.
  • An unparsable URL href throws ERR_INVALID_URL: new Database({ href: "not a url" }) and backup(db, { href: "not a url" }) threw ERR_INVALID_URL_SCHEME; they now throw ERR_INVALID_URL, as node:sqlite does. Ports Node.js PR #66026.

Fixed

  • SQLite errors swallowed after a throwing function in exec(): when a user-defined function threw inside exec(), the next SQLite error on that connection was ignored, so for example an INSERT that violated a primary key returned undefined instead of throwing. Only an error with a JavaScript exception actually pending is now ignored, as in node:sqlite from Node.js PR #66209.
  • Statements starved during backup(): every backup step holds the source connection's mutex, and the whole backup ran as one threadpool loop, so a synchronous statement on the same Database waited for most of the backup (204 ms of a 208 ms backup of a 128 MB WAL database). It now waits for at most one step (under 1 ms at rate: 100).
  • Worker terminated during backup(): terminating a worker thread while it ran a backup with a progress callback aborted the process (terminate called after throwing an instance of 'Napi::Error'). The backup now stops at the next step without settling its promise, and the worker exits.
  • process.exit() in a worker's backup progress callback: aborted the process with FATAL ERROR: Error::Error napi_define_properties, because node-addon-api's conversion of the termination exception into a Napi::Error is fatal when JavaScript can no longer run. The worker now exits with the requested code. Rejection messages for a throwing progress callback are unchanged.
  • close() during backup(): closing the source database freed the SQLite backup handle while a backup step was using it on a worker thread, and before the first step it freed the source connection that step was about to attach to (heap use-after-free, confirmed with AddressSanitizer; present in 2.6.0). close() now waits for a running step to finish, and a step that has not started does nothing. The promise rejects with ERR_SQLITE_ERROR: errcode 1 (SQL logic error), or errcode 5 (database is locked) if the backup was waiting on a lock when close() was called.
  • Iterator used after its statement was collected: iterate() returned an iterator that did not keep its statement alive, so an iterator over a statement the caller no longer referenced (for example Readable.from(db.prepare(sql).iterate())) could segfault or return rows from a different statement once the statement was garbage-collected. The iterator now holds its statement, as in node:sqlite.
  • expand() wrote columns onto Object.prototype: with enhance(), a query whose columns came from a table named __proto__ (directly or through a view) set those columns on Object.prototype for the whole process, and a table named constructor set them on Object. Anyone who controls the schema of a database the application reads with .expand() could set properties on every object. Such tables now appear as ordinary own properties of the row, and a column named __proto__ keeps its value instead of being dropped.
  • Aggregate errors lost with an object or Buffer accumulator: when a step function threw, or an argument could not be converted, in an aggregate whose accumulator was an object, array, or Buffer, get() returned undefined instead of throwing. SQLite finalizes the aggregate after the failed step, and rebuilding the accumulator there cleared the pending error. The error now reaches the caller.
  • backup() spun a CPU core while waiting on a lock: while another connection held a lock on the source or destination, each step that returned SQLITE_BUSY or SQLITE_LOCKED queued the next one at once, so a waiting backup kept one core busy until the lock was released. Retries now wait 1 ms, doubling up to 100 ms, on a libuv timer, so fake timers that replace setTimeout do not affect them.
  • backup() resolved with 0 pages: if its first step found a lock, the backup copied every page but resolved with 0, because the page count was read only after that first step.
  • DataView aggregate accumulator: an aggregate whose start or step value was a DataView failed with Invalid argument, thrown as a C++ exception through SQLite's C code, which can crash on musl. Its bytes are now kept, and the next step receives them as a Uint8Array, as for a Buffer.
  • DatabasePool error without a code: a named parameter whose key contained a NUL byte was rejected with a TypeError that had no code; it now has ERR_INVALID_ARG_TYPE, like the pool's other argument errors. An unknown named parameter, including a bare key such as "t\0x" next to a "t" key, was rejected with an Error that had no code; it now has ERR_INVALID_STATE, as in Database and node:sqlite.
  • Unknown named parameter message cut off at a NUL: for a key such as "tenant\0x", the Database error message ended at the NUL (Unknown named parameter 'tenant); it now contains the whole key.
  • SECURITY.md read-only example opened read-write: it passed readonly: true, which Database ignores; it now passes readOnly: true. Its extension example called db.allowExtension(), which does not exist, instead of passing allowExtension: true to the constructor.
  • defensive documented as off by default: the type docs and API reference said defensive defaults to false. It defaults to true; only the docs changed.
  • NUL in named-parameter keys documented: SQLite matches a parameter name only up to a NUL, so a key such as ":tenant\0x" never binds its own value here, while node:sqlite binds it and can replace the ":tenant" binding. The Statement and DatabasePool type docs give the details. If parameter objects are built from untrusted keys, reject keys that contain NUL.
  • memory:check hung with clang's ASan runtime (developer tooling): the sanitizer harness preloaded libclang_rt.ubsan_standalone next to clang's ASan runtime, and with clang 21 node hung at startup, before any test ran. clang's ASan runtime already defines UBSan's *_abort handlers, so a separate UBSan runtime is now preloaded only with GCC's libasan.

2.6.0 (2026-09-17)

API compatible with node:sqlite from Node.js v26.9.0, plus the changes below that landed on v26.x-staging but are not yet in a Node.js release. Binding undefined now succeeds where it previously threw, so this is a minor release. SQLite is unchanged at 3.53.4.

Changed

  • undefined binds as SQL NULL: passing undefined for a parameter binds NULL instead of throwing ERR_INVALID_ARG_TYPE, so an explicit undefined and an omitted named parameter now agree. undefined is not an object, so it is still bound as an anonymous parameter rather than being treated as the named-parameter bag. This also removes a better-sqlite3 migration gotcha that surfaced with query builders like Knex, which emit undefined for columns missing from a multi-row insert. Ports Node.js PR #65709.

Fixed

  • Changeset detached by a user-defined SQL function: applyChangeset() copies every non-empty changeset before applying it, not just when a filter or onConflict callback is supplied. SQLite reaches JavaScript through a user-defined SQL function in a CHECK constraint or trigger too, so a changeset could previously be detached or zeroed while SQLite was still reading it. Ports Node.js PR #65870.

2.5.0 (2026-09-08)

API compatible with node:sqlite from Node.js v26.8.1, plus four changes landed on v26.x-staging but not yet in a Node.js release. Two of them make calls throw that previously succeeded, so this is a minor release. SQLite is unchanged at 3.53.4.

Changed

  • Closing a session from a callback now throws: session.close() and session[Symbol.dispose]() throw ERR_INVALID_STATE (session cannot be closed while in a callback) when called from any SQLite callback on the same connection: an authorizer, a user-defined function, or a sqlite.db.query diagnostics subscriber. Previously an idle session could be closed from an authorizer or user-defined function. SQLite's session module runs PRAGMA table_xinfo from inside its pre-update hook while it is still walking the connection's session list, so deleting a session there is a use-after-free, and Node cannot tell which callbacks run inside that hook. Disposing an already-closed session from a callback remains a no-op. Ports Node.js PR #65454.
  • Non-integer callback length now throws: function() and aggregate() throw ERR_INVALID_ARG_TYPE when fn.length, options.step.length, or options.inverse.length has been redefined to a non-integer. Previously a non-number was silently treated as varargs. Ports Node.js PR #65595.

Fixed

  • Options getters that close the database: a property getter on an options bag runs arbitrary JavaScript in the middle of the call, so prepare(), function(), aggregate(), deserialize(), createSession(), applyChangeset(), and backup() now re-check that the database is still open after reading their options (and, for function() and aggregate(), the callback's length) and throw ERR_INVALID_STATE if a getter closed it. Previously applyChangeset() segfaulted, createSession() returned a session on a closed connection, and the others surfaced SQLite misuse errors. Ports Node.js PR #65595.
  • deserialize() buffer resized from an options getter: if the getter shrinks or detaches the buffer argument's backing store, deserialize() throws ERR_INVALID_STATE instead of copying past the end of it (previously a segfault). Same upstream PR.
  • Changeset detached mid-apply: when a filter or onConflict callback is supplied, applyChangeset() copies the changeset before applying it, so a callback that detaches or overwrites the input buffer can no longer hand SQLite freed or zeroed memory. Changesets over 2 GiB are rejected with ERR_OUT_OF_RANGE. Ports Node.js PR #65286.
  • Double free when function() or aggregate() registration fails: when sqlite3_create_function_v2() or sqlite3_create_window_function() rejects a registration (for example a callback whose length exceeds SQLITE_MAX_FUNCTION_ARG, 1000 in this build), SQLite invokes the user data's xDestroy itself. The port freed it a second time and crashed the process instead of throwing ERR_SQLITE_ERROR. Found while porting the fixes above; not present in Node.js.
  • Portable Linux prebuild: scripts/prebuild-linux-glibc.sh no longer runs apt-get inside the node:22-bullseye image, which already ships GCC 10.2, make, and Python 3.9. Debian 11's bullseye-security Release file expired on 2026-09-07 (Bullseye LTS ended 2026-08-31), so apt-get update failed and blocked the glibc 2.31 prebuild locally and in both release workflows. The script now also removes its build container when a step fails. The glibc 2.31 target is unchanged.

2.4.0 (2026-09-02)

Added

  • Experimental DatabasePool (@photostructure/sqlite/experimental): a fixed-size pool of warm SQLite connections whose open, prepare, bind, step, finalize, and close work runs on libuv worker threads. It exposes only the connection-independent run, get, all, and batch operations; a strict authorizer (the default) rejects SQL that depends on which physical connection the pool leases. The stable root export is unchanged. Its compatibility policy is separate from the stable API — see the async pool guide for authorizer policy, connection setup, ordering, memory, and libuv sizing tradeoffs.

2.3.0 (2026-08-10)

API compatible with node:sqlite from Node.js v26.7.0, plus three APIs landed upstream but not yet in a Node.js release line. SQLite is unchanged at 3.53.4.

Added

  • StatementSync.prototype.close(): Finalizes a prepared statement deterministically instead of waiting for garbage collection or database close. Throws ERR_INVALID_STATE if the statement is already finalized, if it is currently executing, or if called from inside an authorizer callback — sqlite3_finalize() modifies the connection, which SQLite forbids there, so close() joins the same guard the other statement methods use. Ported from Node.js PR #64232.
  • StatementSync.prototype[Symbol.dispose](): Enables using stmt = db.prepare(...). Unlike close(), it is idempotent and never throws; the two cases close() rejects for safety become no-ops, leaving the statement to be finalized later by GC or database close. Also from Node.js PR #64232.
  • ArrayBuffer and SharedArrayBuffer parameter binding: Both now bind as BLOBs, matching Node.js PR #62061. Previously only ArrayBufferViews (Buffer, TypedArray, DataView) were accepted.

These three landed on nodejs/node@main but are not in the v26.x-staging line this package syncs from, so they ship here ahead of their Node.js release. They are covered by this package's own tests; the corresponding upstream tests will arrive with a future sync.

Fixed

  • Use-after-free when a session outlives its database: Session holds a raw DatabaseSync *, and N-API finalization order between the two wrappers is unspecified. If the database was finalized first, every surviving session was left pointing at freed memory and the next session method dereferenced it. Both sides now clear the link, and an orphaned session reports database is not open. Confirmed with Valgrind before and after. Ports Node.js PR #63797 and #64783 in the shape our N-API port allows — upstream keeps the database alive with a strong reference, which we cannot do: a Napi::Reference member on a GC-finalized ObjectWrap corrupts V8 JIT pages on Alpine/musl (see commit 4da0638).
  • ArrayBuffer bound as SQL NULL: An ArrayBuffer or SharedArrayBuffer passed as the sole argument to run()/get()/all() was treated as a named-parameter object rather than a value, leaving the real parameter unbound. The insert silently stored NULL instead of the blob.

Changed

  • Smaller published tarball: a files allowlist in package.json replaces .npmignore, dropping the package from 78 files to 46. Everything binding.gyp compiles still ships, so node-gyp-build's source fallback is unaffected on platforms without a prebuild. Gone are the TypeScript sources (the published source maps already embed sourcesContent), the reference copies of Node.js's own node_sqlite.cc/.h, Makefile, SECURITY.md, and osv-scanner.toml.
  • Releases are staged for approval instead of published directly (release process): Build & Release now signs and pushes the version commit and tag, then dispatches a tag-bound Stage npm Release workflow that rebuilds all eight prebuilds from the tag, packs one tarball, installs and loads it on every supported platform, and stages it on npm for a maintainer to approve with 2FA. Only the staging job holds npm publishing authority: it checks out no source, installs no dependencies, and runs no third-party action. See RELEASE.md.
  • Upstream sync: Node.js v26.x-staging@68dc114 → v26.x-staging@079339a. Beyond the session lifetime fix above, this range adds IsOpen() guards to enableLoadExtension() and setAuthorizer() (Node.js PR #64812) and marks the statement iterator done at exhaustion — all three already matched our port, which had them first. Upstream's BaseObjectPtr guards in Exec()/applyChangeset() (Node.js PR #64535) do not apply: an N-API ObjectWrap receiver is rooted by the handle scope for the whole synchronous call, verified under Valgrind.
  • Close-inside-callback error message: now database cannot be closed while in a callback, matching the wording upstream adopted in Node.js PR #64743. Previously database cannot be closed inside a user-defined function callback. The error code (ERR_INVALID_STATE) is unchanged; only the message text differs, so any test matching the old string needs updating.
  • Node.js compatibility tests sync from the same branch as the sources: sync:tests defaulted to main while sync:node tracks vNN.x-staging, so the suite ran the next major's tests against current-line sources and reported failures for APIs that did not exist in the baseline. Both now resolve the same staging branch. test-sqlite-udf-close.js had also been downloaded but never adapted, so its four cases — the ones that pin the close-inside-callback message below — were absent from npm run test:node; the adapted file is now generated, and sync-node-tests.ts only runs its sync when invoked directly, so its exports can be reused without triggering one.
  • memory:check runs again (developer tooling): the sanitizer harness had three independent faults, each masking the next. It exported LD_PRELOAD for the whole script, so binding.gyp's node -p helper ran under LeakSanitizer, exited non-zero on an unrelated leak, and failed configure; it drove the build through npx node-gyp, which races on creating the .deps directories; and it preloaded only an ASan runtime, so the UBSan *_abort handlers were missing at load. The preload is now applied to the test command alone, the build goes through npm run build:native:rebuild, and a UBSan runtime is preloaded next to the ASan runtime. (With clang's ASan runtime, that pairing hangs node at startup; fixed in 3.0.0.) It also probes candidate ASan runtimes and skips any that cannot complete a leak check — clang's compiler-rt runtime wedges in LSan's StopTheWorld on clang 21 + Linux 7.x, where GCC's libasan works.
  • Benchmark comparison refreshed (developer tooling): pinned better-sqlite3 13.0.3 and regenerated the published throughput table and charts.

2.2.0 (2026-07-25)

No API changes. Upstream refresh and dependency updates.

Changed

  • SQLite 3.53.4: Updated from 3.53.3. A bug-fix release addressing defects found in 3.53.0–3.53.3, largely by automated analysis — bounds hardening in the JSON/JSONB parsers plus fixes in the session and RBU modules (release notes). No API changes, but the amalgamation is compiled into the shipped binary, so any SQLite bump gets a minor release: consumers choose when to take it.
  • Upstream sync: Node.js v26.x-staging@955e669 → v26.x-staging@68dc114, now API compatible with node:sqlite from Node.js v26.5.0. The only node_sqlite.cc change in this range reads the column count after the first step() in StatementSync.all() (Node.js PR #64219); our port already resolved column metadata lazily on the first row, so no change was needed.
  • TypeScript held at 6.x: .ncurc.cjs now pins typescript to the 6.x line. TypeScript 7 is not yet supported by typedoc (0.28.20 peers <= 6.0.x) or typescript-eslint (8.63.0 peers < 6.1.0).

2.1.0 (2026-07-13)

No API changes. Build hardening, supply-chain verification, and one undefined-behavior fix.

Changed

  • Compiler and linker hardening: POSIX builds now follow the OpenSSF hardening baseline — stack protector, _FORTIFY_SOURCE=2, format-string hardening, full RELRO, non-executable stack, and arch-gated control-flow integrity (Intel CET on x64, PAC/BTI on arm64). Windows ARM64 gains /Qspectre and /guard:signret, the backward-edge protection it previously lacked.
  • Vendored SQLite integrity: the amalgamation sync now verifies the download against a SHA3-256 pinned in-tree and refuses to vendor a mismatch, instead of compiling whatever it fetched.

Fixed

  • Empty changeset undefined behavior: session.changeset() / .patchset() on a session with no recorded changes called memcpy(NULL, NULL, 0), which is undefined behavior even at zero length. Results are unchanged (still a zero-length Uint8Array); the UB is gone. Surfaced by the new UndefinedBehaviorSanitizer pass in CI.

2.0.0 (2026-07-11)

API compatible with node:sqlite from Node.js v26.4.0.

Added

  • DatabaseSync.prototype.serialize([dbName]) and DatabaseSync.prototype.deserialize(buffer, [options]): Serialize a database to a Uint8Array and load one back, matching the node:sqlite APIs added in Node.js PR #59967. Wraps sqlite3_serialize / sqlite3_deserialize and finalizes any open prepared statements before replacing database content.

Changed

  • BREAKING: Dropped support for Node.js 20 (end-of-life April 2026); @photostructure/sqlite now requires Node.js 22 or newer (package.json engines is >=22). This is why this release is 2.0.0 rather than a 1.x minor.
  • SQLite 3.53.3: Updated from 3.53.0. Three patch releases (3.53.1–3.53.3), bug fixes only, no API impact (release notes).
  • Upstream sync: Node.js v25.x-staging@ffa9b8f → v26.x-staging@c96c838. Beyond serialize()/deserialize(), upstream added a column-name caching path and a simdutf fast path for ASCII column text in StatementSync — both V8/internal-only optimizations with no N-API equivalent, so not ported. Subsequent node:sqlite bug fixes — closing the connection after a failed open(), changeset xFilter/callback-lifetime hardening, and reading the column count after the first step() in all() — are already covered by our port's structure and needed no change.
  • Statement finalization on db.close(): Live StatementSync instances are now eagerly detached when their database closes, so further method calls throw ERR_INVALID_STATE with "statement has been finalized" (matching node:sqlite) instead of "Database connection is closed". Statement error messages were also normalized to lowercase "statement has been finalized" throughout.
  • Build hardening (SQLITE_ENABLE_API_ARMOR): The bundled SQLite is now compiled with API armor, so misuse of the C API — for example by a loaded extension such as sqlite-vec — returns SQLITE_MISUSE instead of risking undefined behavior or a process abort the caller cannot catch. Negligible runtime cost; the public JavaScript API is unaffected.
  • Callback reentrancy hardening: operations SQLite forbids from inside its own callbacks (notably close/deserialize, plus prepare/exec/step/serialize/setAuthorizer from an authorizer) now throw ERR_INVALID_STATE instead of corrupting connection state. Intentional divergence from node:sqlite (nodejs/node#63207).
  • Config setters frozen mid-step: setReadBigInts, setReturnArrays, and the setAllow* parameter setters throw ERR_INVALID_STATE if called while the statement is executing.

Fixed

  • Backup teardown stability: In-flight backup() operations are now safe when a Node environment is shutting down. Backup jobs avoid resolving/rejecting promises or routing expected SQLite failures through node-addon-api's async worker error path after teardown begins.
  • Authorizer error identity: the exact value thrown by an authorizer callback (subclass, code, message, thrown primitives) now propagates unchanged through prepare/exec/step/serialize/deserialize/changeset/extension load, instead of being replaced by a generic error.
  • TEXT with embedded NUL bytes: returned in full via byte-length conversion instead of being truncated at the first NUL.

Performance

  • Faster multi-row reads: per-statement column-key caching, byte-length string conversion, per-column exception checks removed from the row builder, and a native iterator fast path for flat/raw modes.
  • -fno-plt on Linux removes PLT indirection from Node-API calls in the hot path.

Internal

  • Docs: bulk-read performance tradeoff documented honestly; Node 22 requirement propagated across docs and examples.
  • Benchmark suite reworked for fair, reproducible driver comparison (deterministic workloads, median confidence intervals, per-scenario ratios, SVG charts) plus correlation-gated memory-leak detection.
  • Dependencies: node-addon-api 8.9.0, TypeScript 6, ESLint 10, prettier 3.8.5, @types/node 26.
  • CI: pinned-action updates (CodeQL, TruffleHog, OSV-Scanner, actions/checkout).

1.2.1 (2026-04-27)

Fixed

  • Packaging: Excluded test extension artifact (test_extension.so) from published npm tarball. The CI pipeline's download-artifact steps lacked a pattern filter, causing the musl test fixture to be merged into prebuilds/ alongside production binaries.

1.2.0 (2026-04-15)

API compatible with node:sqlite from Node.js v25.9.0.

Changed

  • SQLite 3.53.0: Updated from 3.52.0. Adds json_array_insert() / jsonb_array_insert() SQL functions, ALTER TABLE support for adding/removing NOT NULL and CHECK constraints, REINDEX EXPRESSIONS to rebuild expression indexes, VACUUM INTO reserve=N URI parameter, and new C APIs (sqlite3_str_truncate, sqlite3_str_free, sqlite3_carray_bind_v2, SQLITE_PREPARE_FROM_DDL, SQLITE_DBCONFIG_FP_DIGITS). Floating-point text conversion default changed from 15 to 17 significant digits. Full release notes.
  • Upstream sync: Node.js v25.x-staging@ca2d6ea → ffa9b8f (includes content through Node.js v25.9.0). Upstream made a cosmetic lambda-capture fix in ApplyChangeset's filter callback; our port already used equivalent by-value captures.

Fixed

  • Docs: corrected stale "DataView parameter binding is not currently supported" note; BLOB binding accepts TypedArray or DataView input and returns Uint8Array.

Internal

  • Test sync: skip test-sqlite-serialize.js — Node.js DatabaseSync.prototype.serialize() / deserialize() APIs are not yet ported.

1.1.0 (2026-03-13)

Changed

  • SQLite 3.51.3: Reverted from 3.52.0 (retracted by the SQLite team)

1.0.0 (2026-03-07)

Promotion to v1.0.0 following API stabilization and 0.5.0 release.

API compatible with node:sqlite from Node.js v25.8.0.

Added

  • db.limits property: Get and set SQLite limits (length, sqlLength, column, exprDepth, compoundSelect, vdbeOp, functionArg, attach, likePatternLength, variableNumber, triggerDepth) at runtime. Supports Infinity to reset to compile-time maximum. Also accepts limits option in DatabaseSync constructor.
  • Statement iterator invalidation: Calling stmt.run(), stmt.get(), stmt.all(), or stmt.iterate() now invalidates any active iterator on the same statement, throwing ERR_INVALID_STATE

Changed

  • SQLite 3.52.0: Updated from 3.51.2

0.5.0 (2026-02-06)

Added

  • Statement modes via enhance(): stmt.pluck(), stmt.raw(), stmt.expand() for better-sqlite3 compatibility
    • .pluck() returns only the first column value from queries
    • .raw() returns rows as arrays instead of objects
    • .expand() returns rows namespaced by table, correctly handling duplicate column names across JOINs
    • All three modes are mutually exclusive, matching better-sqlite3's toggle semantics
  • stmt.database: Back-reference from prepared statements to their parent database instance
  • EnhancedStatementMethods type: TypeScript interface for pluck(), raw(), expand(), and database

0.4.0 (2026-02-04)

API compatible with node:sqlite from Node.js v25.6.1.

Added

  • enhance() function: Adds better-sqlite3-style .pragma() and .transaction() methods to any compatible database instance
  • isEnhanced() type guard: Check if a database has enhanced methods
  • Transaction helper: Automatic BEGIN/COMMIT/ROLLBACK with savepoint support for nested transactions
  • Pragma convenience method: Simple API for reading and setting SQLite pragmas with simple option
  • Node.js test sync script: npm run sync:tests downloads and adapts upstream Node.js SQLite tests
  • Percentile extension: SQLITE_ENABLE_PERCENTILE now enabled, adding percentile(), median(), percentile_cont(), percentile_disc() SQL functions (Node.js v25+)
  • Prepare options: db.prepare(sql, options) now accepts per-statement options (readBigInts, returnArrays, allowBareNamedParameters, allowUnknownNamedParameters) to override database-level defaults. This is a Node.js v25+ feature; node:sqlite on v24 and earlier silently ignores these options.
  • StatementColumnMetadata type: stmt.columns() now returns richer metadata including column, database, table, and type properties alongside name
  • SQLite 3.51.2: Updated from 3.51.1

Changed

  • BREAKING: Removed API extensions to achieve exact parity with node:sqlite:
    • Removed stmt.finalize() method (use database close for cleanup)
    • Removed stmt.finalized property
    • Removed stmt[Symbol.dispose] (still available on DatabaseSync and Session)
    • Removed db.backup() instance method (use standalone backup(db, path) function instead)
  • BREAKING: Session.changeset() and Session.patchset() now return Uint8Array instead of Buffer to match node:sqlite API
  • BREAKING: Defensive mode now defaults to true instead of false to match Node.js v25+ behavior. Use { defensive: false } to restore old behavior.

Fixed

  • Alpine Linux / musl stability: Fixed native crashes by removing N-API reference cleanup from destructors that corrupted V8 JIT state
  • Session lifecycle management: Fixed use-after-free, double-free, and mutex deadlock when databases are garbage collected before their sessions
  • Worker thread stability: Added cleanup hooks and exception handling for worker thread termination
  • Callback error preservation: applyChangeset() now preserves the original error message when JavaScript callbacks throw
  • createTagStore() now throws errors with code: 'ERR_INVALID_STATE' property when database is closed, matching Node.js error format

0.3.0 (2025-12-17)

Changed

  • BREAKING: SQLTagStore.size changed from method to getter for Node.js API parity (Node.js PR #60246)
    • Before: sql.size()
    • After: sql.size
    • Note: This change was merged into Node.js main on December 11, 2025 and will appear in a future Node.js release. Current Node.js v24.x still uses sql.size() as a method.

0.2.1 (2025-12-01)

Added

  • Windows ARM64 prebuilt binaries

Fixed

  • Error message handling on Windows ARM64 (ABI compatibility)
  • Error handling consistency across platforms

0.2.0 (2025-12-01)

Added

  • Node.js v25 API sync: SQLite 3.51.1, native Symbol.dispose in C++, Session class exposed in public API
  • New database open options: readBigInts, returnArrays, allowBareNamedParameters, allowUnknownNamedParameters, defensive, open
  • Defensive mode: enableDefensive() method to prevent SQL from deliberately corrupting the database
  • Statement enhancements: setAllowUnknownNamedParameters() method, finalized property
  • Type identification: sqlite-type symbol property on DatabaseSync (Node.js PR #59405)
  • SQLite error properties: sqliteCode, sqliteExtendedCode, code, sqliteErrorString, systemErrno
  • ARM64 prebuilds: macOS Apple Silicon and Windows ARM64 binaries
  • Tagged template literals: db.createTagStore() for cached prepared statements (Node.js PR #58748)
  • Authorization API: db.setAuthorizer() for security callbacks (Node.js PR #59928)
  • Standalone backup: backup(srcDb, destFile, options?) for database backups with progress callbacks

Fixed

  • DataView parameter binding (previously returned garbage data)
  • DataView and TypedArray return values in user-defined functions
  • RETURNING clause metadata handling
  • Null and empty values in user function return value conversion
  • Native stability: N-API reference cleanup in aggregates/destructors, thread-local napi_env storage, statement-to-database reference tracking, deferred exception handling in authorizers

0.0.1 (2025-06-13)

Added

  • Initial release of @photostructure/sqlite, standalone SQLite for Node.js 20+
  • Compatible with Node.js built-in SQLite module API
  • Core SQLite operations with DatabaseSync and StatementSync classes
  • User-defined scalar and aggregate functions with window function support
  • Database backup and restoration
  • SQLite sessions and changesets for change tracking
  • Extension loading with automatic platform-specific file resolution
  • TypeScript definitions
  • Cross-platform prebuilt binaries for Windows, macOS, and Linux (x64, ARM64)
  • Test suite with 89+ tests
  • Memory safety validation with Valgrind and sanitizers
  • Performance benchmarking suite comparing to better-sqlite3
  • Automated synchronization from Node.js upstream SQLite implementation
  • CI/CD pipeline with security scanning and multi-platform builds

Features

  • Synchronous API: Blocking database operations for scripts and tools
  • Parameter binding: All SQLite data types including BigInt
  • Error handling: Detailed error messages with SQLite error codes
  • Resource limits: Control memory usage and query complexity
  • Safe integer handling: JavaScript-safe integer conversion with overflow detection
  • Multi-process support: Concurrent access from multiple Node.js processes
  • Worker thread support: Works in worker threads
  • URI filename support: SQLite URI syntax for advanced database configuration
  • Strict tables: SQLite strict table mode
  • Double-quoted strings: Configurable SQL syntax compatibility

Platform Support

  • Node.js 20.0.0 and later
  • Windows (x64, ARM64)
  • macOS (x64, ARM64)
  • Linux (x64, ARM64), (glibc 2.28+, musl)