diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..794ebe34d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,53 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Matches multiple files with the same settings +[*.js;*.ts] +indent_size = 4 + +[*.json] +indent_size = 2 + +[*.yml] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false +indent_size = 2 + +[*.sql] +indent_size = 2 + +# C/C++ files +[*.cc] +indent_size = 2 +indent_style = tab +max_line_length = 100 + +[*.h] +indent_size = 2 +indent_style = tab +max_line_length = 100 + +# Makefile and GYP files use tabs +[{Makefile,*.gyp}] +indent_style = tab + +# Dockerfiles +[Dockerfile] +indent_size = 2 + +# YAML files +[*.yaml] +indent_size = 2 diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 2303c3d4b..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - "extends": "eslint:recommended", - "env": { - "es2017": true, - "node": true - }, - "rules": { - "indent": ["error", 4], - "linebreak-style": ["error", "unix"], - "semi": ["error", "always"], - "no-cond-assign": ["error", "always"] - } -}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..c660174d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Mark SQLite database files as binary to prevent line ending conversions +*.db binary +*.sqlite binary +*.sqlite3 binary diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b007fcc27..aea52f6ed 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,11 +4,10 @@ General guidelines for contributing to node-sqlite3 ## Install Help -If you've landed here due to a failed install of `node-sqlite3` then feel free to create a [new issue](https://github.com/tryghost/node-sqlite3/issues/new) to ask for help. The most likely problem is that we do not yet provide pre-built binaries for your particular platform and so the `node-sqlite3` install attempted a source compile but failed because you are missing the [dependencies for node-gyp](https://github.com/nodejs/node-gyp#installation). Provide as much detail on your problem as possible and we'll try to help. Include: +If you've landed here due to a failed install of `node-sqlite3` then feel free to create a [new issue](https://github.com/gms1/node-sqlite3/issues/new) to ask for help. The most likely problem is that we do not yet provide pre-built binaries for your particular platform and so the `node-sqlite3` install attempted a source compile but failed because you are missing the [dependencies for node-gyp](https://github.com/nodejs/node-gyp#installation). Provide as much detail on your problem as possible and we'll try to help. Include: - Logs of failed install (preferably from running `npm install sqlite3 --loglevel=info`) - Version of `node-sqlite3` you tried to install - Node version you are running - Operating system and architecture you are running, e.g. `Windows 7 64 bit`. -The release process is documented in the wiki: https://github.com/TryGhost/node-sqlite3/wiki/Release-process diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 729470a7d..1a49f62e6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,5 @@ blank_issues_enabled: false contact_links: - name: 📖 API documentation - url: https://github.com/TryGhost/node-sqlite3/wiki/API + url: https://github.com/gms1/node-sqlite3/wiki/API about: Documentation for the `node-sqlite3` API diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..fe3de7047 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "yarn" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99716e839..8bb015d59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,16 +4,61 @@ on: pull_request: push: branches: - - master + - main tags: - '*' +permissions: + contents: read env: FORCE_COLOR: 1 -concurrency: - group: ${{ github.head_ref || github.run_id }} - cancel-in-progress: true jobs: + verify-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Verify tag version matches package.json + run: | + # Check if we're running on a tag + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + # Extract tag name from GITHUB_REF (e.g., refs/tags/v6.0.2 -> v6.0.2) + TAG_NAME="${GITHUB_REF#refs/tags/}" + # Remove 'v' prefix if present to get the version number + TAG_VERSION="${TAG_NAME#v}" + + # Get version from package.json + PACKAGE_VERSION=$(node -p "require('./package.json').version") + + echo "Tag version: $TAG_VERSION" + echo "Package version: $PACKAGE_VERSION" + + if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then + echo "ERROR: Tag version ($TAG_VERSION) does not match package.json version ($PACKAGE_VERSION)" + echo "Please update package.json or create a new tag with the correct version" + echo "GitHub release will NOT be created." + exit 1 + fi + + echo "Version match verified!" + else + echo "Skipping version verification - not a tag event" + fi + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + - name: Install dependencies + run: yarn install --frozen-lockfile --ignore-scripts + - name: Run lint + run: yarn lint + build: + permissions: + contents: write + needs: [verify-version, lint] runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -69,13 +114,13 @@ jobs: architecture: ${{ matrix.host }} - name: Add msbuild to PATH - uses: microsoft/setup-msbuild@v1.3 + uses: microsoft/setup-msbuild@v2 if: contains(matrix.os, 'windows') with: msbuild-architecture: ${{ matrix.target }} - name: Install dependencies - run: yarn install --ignore-scripts + run: yarn install --frozen-lockfile --ignore-scripts - name: Check Node compatibility run: node tools/semver-check.js @@ -125,6 +170,9 @@ jobs: if: matrix.node == 24 && startsWith(github.ref, 'refs/tags/') build-musl: + permissions: + contents: write + needs: [verify-version] if: github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/') strategy: fail-fast: false @@ -161,5 +209,25 @@ jobs: retention-days: 7 - name: Upload binaries to GitHub Release - run: yarn install --ignore-scripts && yarn upload --upload-all ${{ github.token }} + run: yarn install --frozen-lockfile --ignore-scripts && yarn upload --upload-all ${{ github.token }} if: startsWith(github.ref, 'refs/tags/') + + publish-npm: + needs: [build, build-musl] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - name: Install dependencies + run: yarn install --frozen-lockfile --ignore-scripts + + - name: Publish to npm + run: npm publish diff --git a/.gitignore b/.gitignore index 4dadec34b..15f378d7a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,12 @@ local.env setup.sh /build-tmp-napi-v3 /build-tmp-napi-v6 +/.nyc_output *.tgz package-lock.json prebuilds +.vscode +.clinerules +.roomodes +/plans +/tools/temp diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..cafb7454d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,315 @@ +# Changelog + +Please check [GitHub Releases](https://github.com/gms1/node-sqlite3/releases) for notes on more recent releases. + +Please check [TryGhost/node-sqlite3 GitHub Releases](https://github.com/TryGhost/node-sqlite3/releases) for all releases between 5.0.3 and 6.0.1. + +## 5.0.2 +- build: rebuild binaries before publishing [#1426](https://github.com/mapbox/node-sqlite3/pull/1426) + +## 5.0.1 +- dep: node-addon-api to ^3.0.0 [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) +- bug: bad comparison of c string [#1347](https://github.com/mapbox/node-sqlite3/pull/1347) +- build: Install files to be deployed [#1352](https://github.com/mapbox/node-sqlite3/pull/1352) +- sqlite3: upgrade to 3.32.3 [#1351](https://github.com/mapbox/node-sqlite3/pull/1351) +- bug: worker threads crash [#1367](https://github.com/mapbox/node-sqlite3/pull/1367) +- bug: segfaults [#1368](https://github.com/mapbox/node-sqlite3/pull/1368) +- typo: broken link to MapBox site [#1369](https://github.com/mapbox/node-sqlite3/pull/1369) + +## 5.0.0 +- prebuilt: Node 14 support, dropped support for all version of Node < 10 [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) +- prebuilt: add electron 7.2 [#1324](https://github.com/mapbox/node-sqlite3/pull/1324) +- napi: refactor codebase to use N-API instead of NAN (+ various improvements) [#1304](https://github.com/mapbox/node-sqlite3/pull/1304) +- trace: don't require throw to add trace info for verbose [#1317](https://github.com/mapbox/node-sqlite3/pull/1317) +- ci: remove permission setting [#1319](https://github.com/mapbox/node-sqlite3/pull/1319) + +## 4.2.0 +- electron: Electron v8, v8.1.x & v8.2.x [#1294](https://github.com/mapbox/node-sqlite3/pull/1294) [#1308](https://github.com/mapbox/node-sqlite3/pull/1308) +- sqlite3: update to 3.31.1 (3310100) [#1289](https://github.com/mapbox/node-sqlite3/pull/1289) +- webpack: split sqlite3-binding.js out so that it could be override by webpack [#1268](https://github.com/mapbox/node-sqlite3/pull/1268) +- sqlite3: enable 'SQLITE_ENABLE_DBSTAT_VTAB=1' [#1281](https://github.com/mapbox/node-sqlite3/pull/1281) +- deps: remove request [#1287](https://github.com/mapbox/node-sqlite3/pull/1287) +- deps: alternative update of node-gyp for electron (v1 - v4), windows [#1283](https://github.com/mapbox/node-sqlite3/pull/1283) +- electron: fix dist url [#1282](https://github.com/mapbox/node-sqlite3/pull/1282) +- docs: Added json1 support note [#1303](https://github.com/mapbox/node-sqlite3/pull/1303) + +## 4.1.1 +- Electron v6.1 and v7 support [#1237](https://github.com/mapbox/node-sqlite3/pull/1237) +- Electron v7.1 support [#1254](https://github.com/mapbox/node-sqlite3/pull/1254) +- SQLite3 update to 3.30.1 [#1238](https://github.com/mapbox/node-sqlite3/pull/1238) +- Overwrite 'msbuild_toolset' only if 'toolset' is defined [#1242](https://github.com/mapbox/node-sqlite3/pull/1242) +- Upgrade CI to node-gyp 6.x for Windows Electron v5 & v6 builds [#1245](https://github.com/mapbox/node-sqlite3/pull/1245) +- Node v13 support [#1247](https://github.com/mapbox/node-sqlite3/pull/1247) +- Use minimum supported node version for Electron 7 [#1255](https://github.com/mapbox/node-sqlite3/pull/1255) + +## 4.1.0 + +- Electron v6 support [#1195](https://github.com/mapbox/node-sqlite3/pull/1195) +- Electron v4.1 and v4.2 support [#1180](https://github.com/mapbox/node-sqlite3/pull/1180) +- Custom file header with `--sqlite_magic` [#1144](https://github.com/mapbox/node-sqlite3/pull/1144) +- https everywhere [#1177](https://github.com/mapbox/node-sqlite3/pull/1177) + +## 4.0.9 +- Use trusty as the base for prebuilts [#1167](https://github.com/mapbox/node-sqlite3/pull/1167) + +## 4.0.8 +- Rerelease of 4.0.7 but removed excess .vscode files [0df90c7](https://github.com/mapbox/node-sqlite3/commit/0df90c7811331169ad5f8fbad396422e72757af3) + +## 4.0.7 + +- Node v12 support +- Electron v5 support +- Fix backup API tests +- HAVE_USLEEP=1 for all platforms +- docker suport + +## 4.0.6 +- Release of 4.0.5 (again due CI) + +## 4.0.5 +- **SECURITY:** Upgrade SQLite to 3.26.0 [#1088](https://github.com/mapbox/node-sqlite3/pull/1088) +- add constants for file open (shared databases) [#1078](https://github.com/mapbox/node-sqlite3/pull/1078) +- Allow specifying the python to use [#1089](https://github.com/mapbox/node-sqlite3/pull/1089) + +## 4.0.4 +- Add NodeJS 11 support [#1072](https://github.com/mapbox/node-sqlite3/pull/1072) +- Add electron osx 3.0.0 support [#1071](https://github.com/mapbox/node-sqlite3/pull/1071) + +## 4.0.3 + +- Increase electron/osx binary coverage [#1041](https://github.com/mapbox/node-sqlite3/pull/1041) (@kewde) + +## 4.0.2 + +- Fixed HTTP proxy support by using `request` over `needle` in node-pre-gyp + +## 4.0.1 + +- Node v10 support +- Upgrade to node-pre-gyp@0.10.1 +- Upgrade to nan@2.10.0 +- Upgrade to sqlite v3.24.0 +- Stopped bundling node-pre-gyp +- Upgrade to mocha@5 +- Now building electron binaries (@kewde) +- Add OPEN_FULLMUTEX constant + +## 4.0.0 + + - Drop support for Node v0.10 and v.12 + - Upgrade to node-pre-gyp@0.9.0 + - Upgrade to nan@2.9.2 + +## 3.1.13 + +- Attempt to fix regression of #866 + +## 3.1.12 + +- Fixed to ensure the binaries do not rely on `GLIBC_2.14` and only `GLIBC_2.2.5`. This regressed in v3.1.11. + +## 3.1.11 + +- Fixed building from source on alpine linux + +## 3.1.10 + +- Removed `npm ls` from `prepublish` hook per mapbox/node-pre-gyp#291 +- Upgraded node-pre-gyp to v0.6.37 +- Removed accidentally committed large file + +## 3.1.9 + +- Added support for node v8 and upgraded `nan`, `node-pre-gyp` deps. + +## 3.1.8 + +- Added support for node v7 (pre-compiled binaries available) + +## 3.1.7 + +- Upgrade sqlite to 3.15, enable FTS4, FTS5 (@wmertens) +- Upgrade to node-pre-gyp@0.6.31 and nan@2.4.0 + +## 3.1.6 + +- Starts bundling node-pre-gyp again to avoid #720 + +## 3.1.5 + +- [Added support for sqlite3_interrupt](https://github.com/mapbox/node-sqlite3/pull/518): this makes + it possible to interrupt a long-running query. +- [Fixes uv_ref race](https://github.com/mapbox/node-sqlite3/pull/705). + +## 3.1.4 + + - Added support for node v6 + +## 3.1.3 + + - Upgrade to node-pre-gyp@0.6.26 with better support for Electron + +## 3.1.2 + + - Only providing binaries for node v0.10x, v0.12.x, v4, and v5 + - Upgrade to nan@2.2.x + - Upgrade to node-pre-gyp@0.6.24 + + +## 3.1.1 + + - Support for node 5.x + - Upgraded SQLite to 3.9.1: https://www.sqlite.org/releaselog/3_9_1.html + - Enabled json1 extension by default + +## 3.1.0 + + - Support for node 3.x and 4.x + - Stopped producing binaries for node-webkit and 32 bit linux + +## 3.0.11 + + - Support for io.js 3.x (upgrade to Nan 2.x) @kkoopa + +## 3.0.10 + + - Upgraded SQLite to 3.8.11.1: https://www.sqlite.org/releaselog/3_8_11_1.html + - Fixed binary compatibility regression with old centos/rhel glibc GLIBC_2.14 (re-introduced alpine linux (musl) build regression) + - Now providing binaries against Visual Studio 2015 (pass --toolset=v140) and use binaries from https://github.com/mapbox/node-cpp11 + +## 3.0.9 + + - Fixed build regression against alpine linux (musl) + - Upgraded node-pre-gyp@0.6.8 + +## 3.0.8 + + - Fixed build regression against FreeBSD + - Upgraded node-pre-gyp@0.6.7 + +## 3.0.7 + + - Fixed build regression against ARM and i386 linux + - Upgraded node-pre-gyp@0.6.6 + - Added support for io.js 2.0.0 + +## 3.0.6 + + - Upgraded node-pre-gyp@0.6.5 + - Upgraded nan@1.8.4 + - Fixed binaries to work on older linux systems (circa GLIBC_2.2.5 like centos 6) @bnoordhuis + - Updated internal libsqlite3 from 3.8.7.1 -> 3.8.9 (https://www.sqlite.org/news.html) + +## 3.0.5 + + - IO.js and Node v0.12.x support. + - Node-webkit v0.11.x support regressed in this release, sorry (https://github.com/mapbox/node-sqlite3/issues/404). + +## 3.0.4 + + - Upgraded node-pre-gyp@0.6.1 + +## 3.0.3 + + - Upgraded to node-pre-gyp@0.6.0 which should fix crashes against node v0.11.14 + - Now providing binaries against Visual Studio 2014 (pass --toolset=v140) and use binaries from https://github.com/mapbox/node-cpp11 + +## 3.0.2 + + - Republish for possibly busted npm package. + +## 3.0.1 + + - Use ~ in node-pre-gyp semver for more flexible dep management. + +## 3.0.0 + +Released September 20nd, 2014 + + - Backwards-incompatible change: node versions 0.8.x are no longer supported. + - Updated to node-pre-gyp@0.5.27 + - Updated NAN to 1.3.0 + - Updated internal libsqlite3 to v3.8.6 + +## 2.2.7 + +Released August 6th, 2014 + + - Removed usage of `npm ls` with `prepublish` target (which breaks node v0.8.x) + +## 2.2.6 + +Released August 6th, 2014 + + - Fix bundled version of node-pre-gyp + +## 2.2.5 + +Released August 5th, 2014 + + - Fix leak in complete() callback of Database.each() (#307) + - Started using `engineStrict` and improved `engines` declaration to make clear only >= 0.11.13 is supported for the 0.11.x series. + +## 2.2.4 + +Released July 14th, 2014 + + - Now supporting node v0.11.x (specifically >=0.11.13) + - Fix db opening error with absolute path on windows + - Updated to node-pre-gyp@0.5.18 + - updated internal libsqlite3 from 3.8.4.3 -> 3.8.5 (https://www.sqlite.org/news.html) + +## 2.2.3 + + - Fixed regression in v2.2.2 for installing from binaries on windows. + +## 2.2.2 + + - Fixed packaging problem whereby a `config.gypi` was unintentially packaged and could cause breakages for OS X builds. + +## 2.2.1 + + - Now shipping with 64bit FreeBSD binaries against both node v0.10.x and node v0.8.x. + - Fixed solaris/smartos source compile by passing `-std=c99` when building internally bundled libsqlite3 (#201) + - Reduced size of npm package by ignoring tests and examples. + - Various fixes and improvements for building against node-webkit + - Upgraded to node-pre-gyp@0.5.x from node-pre-gyp@0.2.5 + - Improved ability to build from source against `sqlcipher` by passing custom library name: `--sqlite_libname=sqlcipher` + - No changes to C++ Core / Existing binaries are exactly the same + +## 2.2.0 + +Released Jan 13th, 2014 + + - updated internal libsqlite3 from 3.7.17 -> 3.8.2 (https://www.sqlite.org/news.html) which includes the next-generation query planner http://www.sqlite.org/queryplanner-ng.html + - improved binary deploy system using https://github.com/springmeyer/node-pre-gyp + - binary install now supports http proxies + - source compile now supports freebsd + - fixed support for node-webkit + +## 2.1.19 + +Released October 31st, 2013 + + - Started respecting `process.env.npm_config_tmp` as location to download binaries + - Removed uneeded `progress` dependency + +## 2.1.18 + +Released October 22nd, 2013 + + - `node-sqlite3` moved to mapbox github group + - Fixed reporting of node-gyp errors + - Fixed support for node v0.6.x + +## 2.1.17 + - Minor fixes to binary deployment + +## 2.1.16 + - Support for binary deployment + +## 2.1.15 + +Released August 7th, 2013 + + - Minor readme additions and code optimizations diff --git a/README.md b/README.md index 644664e02..57623b60a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# :no_entry: [DEPRECATED] node-sqlite3 +# gms1/node-sqlite3 -**Note:** This repository is currently unmaintained. We will not update any of its issues or pull requests. +**Note:** This repository is forked from [TryGhost/node-sqlite3](https://github.com/TryGhost/node-sqlite3) which was marked as deprecated/unmaintained. --- Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js](http://nodejs.org/). -[![Latest release](https://img.shields.io/github/release/TryGhost/node-sqlite3.svg)](https://www.npmjs.com/package/sqlite3) -![Build Status](https://github.com/TryGhost/node-sqlite3/workflows/CI/badge.svg?branch=master) +[![npm version](https://badge.fury.io/js/%40homeofthings%2Fsqlite3.svg)](https://badge.fury.io/js/%40homeofthings%2Fsqlite3) +[![Build Status](https://github.com/gms1/node-sqlite3/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/gms1/node-sqlite3/actions/workflows/ci.yml) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3?ref=badge_shield) [![N-API v3 Badge](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api) [![N-API v6 Badge](https://img.shields.io/badge/N--API-v6-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api) @@ -15,30 +15,30 @@ Asynchronous, non-blocking [SQLite3](https://sqlite.org/) bindings for [Node.js] - Straightforward query and parameter binding interface - Full Buffer/Blob support - - Extensive [debugging support](https://github.com/tryghost/node-sqlite3/wiki/Debugging) - - [Query serialization](https://github.com/tryghost/node-sqlite3/wiki/Control-Flow) API - - [Extension support](https://github.com/TryGhost/node-sqlite3/wiki/API#databaseloadextensionpath-callback), including bundled support for the [json1 extension](https://www.sqlite.org/json1.html) + - Extensive [debugging support](https://github.com/gms1/node-sqlite3/wiki/Debugging) + - [Query serialization](https://github.com/gms1/node-sqlite3/wiki/Control-Flow) API + - [Extension support](https://github.com/gms1/node-sqlite3/wiki/API#databaseloadextensionpath-callback), including bundled support for the [json1 extension](https://www.sqlite.org/json1.html) - Big test suite - Written in modern C++ and tested for memory leaks - - Bundles SQLite v3.52.0, or you can build using a local SQLite + - Bundles SQLite v3.51.3, or you can build using a local SQLite # Installing -You can use [`npm`](https://github.com/npm/cli) or [`yarn`](https://github.com/yarnpkg/yarn) to install `sqlite3`: +You can use [`npm`](https://github.com/npm/cli) or [`yarn`](https://github.com/yarnpkg/yarn) to install `@homeofthings/sqlite3`: * (recommended) Latest published package: ```bash -npm install sqlite3 +npm install @homeofthings/sqlite3 # or -yarn add sqlite3 +yarn add @homeofthings/sqlite3 ``` -* GitHub's `master` branch: `npm install https://github.com/tryghost/node-sqlite3/tarball/master` +* GitHub's `main` branch: `npm install https://github.com/gms1/node-sqlite3/tarball/main` ### Prebuilt binaries -`sqlite3` v5+ was rewritten to use [Node-API](https://nodejs.org/api/n-api.html) so prebuilt binaries do not need to be built for specific Node versions. `sqlite3` currently builds for both Node-API v3 and v6. Check the [Node-API version matrix](https://nodejs.org/api/n-api.html#node-api-version-matrix) to ensure your Node version supports one of these. The prebuilt binaries should be supported on Node v20.17.0+. +`@homeofthings/sqlite3` uses [Node-API](https://nodejs.org/api/n-api.html) so prebuilt binaries do not need to be built for specific Node versions. Prebuilt binaries are available for Node-API v3 and v6. Check the [Node-API version matrix](https://nodejs.org/api/n-api.html#node-api-version-matrix) to ensure your Node version supports one of these. Requires Node.js v20.17.0 or later. -The module uses [`prebuild-install`](https://github.com/prebuild/prebuild-install) to download the prebuilt binary for your platform, if it exists. These binaries are hosted on GitHub Releases for `sqlite3` versions above 5.0.2, and they are hosted on S3 otherwise. The following targets are currently provided: +The module uses [`prebuild-install`](https://github.com/prebuild/prebuild-install) to download the prebuilt binary for your platform, if it exists. These binaries are hosted on GitHub Releases. The following targets are currently provided: * `darwin-arm64` * `darwin-x64` @@ -64,14 +64,53 @@ SQLite's [SQLCipher extension](https://github.com/sqlcipher/sqlcipher) is also # API -See the [API documentation](https://github.com/TryGhost/node-sqlite3/wiki/API) in the wiki. +See the [API documentation](docs/API.md) for detailed documentation of both the callback-based and Promise-based APIs. + +## Quick Example + +### Callback-based API (Traditional) + +```js +const sqlite3 = require('@homeofthings/sqlite3').verbose(); +const db = new sqlite3.Database(':memory:'); + +db.serialize(() => { + db.run("CREATE TABLE lorem (info TEXT)"); + db.run("INSERT INTO lorem VALUES (?)", ['test']); + db.each("SELECT * FROM lorem", (err, row) => { + console.log(row); + }); +}); + +db.close(); +``` + +### Promise-based API (Modern) + +```js +const { SqliteDatabase } = require('@homeofthings/sqlite3'); + +async function main() { + const db = await SqliteDatabase.open(':memory:'); + + await db.run("CREATE TABLE lorem (info TEXT)"); + await db.run("INSERT INTO lorem VALUES (?)", ['test']); + + const rows = await db.all("SELECT * FROM lorem"); + console.log(rows); + + await db.close(); +} + +main().catch(console.error); +``` # Usage **Note:** the module must be [installed](#installing) before use. ``` js -const sqlite3 = require('sqlite3').verbose(); +const sqlite3 = require('@homeofthings/sqlite3').verbose(); const db = new sqlite3.Database(':memory:'); db.serialize(() => { @@ -95,14 +134,6 @@ db.close(); To skip searching for pre-compiled binaries, and force a build from source, use -```bash -npm install --build-from-source -``` - -The sqlite3 module depends only on libsqlite3. However, by default, an internal/bundled copy of sqlite will be built and statically linked, so an externally installed sqlite3 is not required. - -If you wish to install against an external sqlite then you need to pass the `--sqlite` argument to `npm` wrapper: - ```bash npm install --build-from-source --sqlite=/usr/local ``` @@ -137,10 +168,10 @@ To build `sqlite3` for node-webkit: ```bash NODE_WEBKIT_VERSION="0.8.6" # see latest version at https://github.com/rogerwang/node-webkit#downloads -npm install sqlite3 --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) +npm install @homeofthings/sqlite3 --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) ``` -You can also run this command from within a `sqlite3` checkout: +You can also run this command from within a `@homeofthings/sqlite3` checkout: ```bash npm install --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) @@ -162,9 +193,9 @@ For instructions on building SQLCipher, see [Building SQLCipher for Node.js](htt To run against SQLCipher, you need to compile `sqlite3` from source by passing build options like: ```bash -npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/ +npm install @homeofthings/sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/ -node -e 'require("sqlite3")' +node -e 'require("@homeofthings/sqlite3")' ``` If your SQLCipher is installed in a custom location (if you compiled and installed it yourself), you'll need to set some environment variables: @@ -176,9 +207,9 @@ Set the location where `brew` installed it: ```bash export LDFLAGS="-L`brew --prefix`/opt/sqlcipher/lib" export CPPFLAGS="-I`brew --prefix`/opt/sqlcipher/include/sqlcipher" -npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` +npm install @homeofthings/sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` -node -e 'require("sqlite3")' +node -e 'require("@homeofthings/sqlite3")' ``` ### On most Linuxes (including Raspberry Pi) @@ -189,23 +220,23 @@ Set the location where `make` installed it: export LDFLAGS="-L/usr/local/lib" export CPPFLAGS="-I/usr/local/include -I/usr/local/include/sqlcipher" export CXXFLAGS="$CPPFLAGS" -npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/local --verbose +npm install @homeofthings/sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=/usr/local --verbose -node -e 'require("sqlite3")' +node -e 'require("@homeofthings/sqlite3")' ``` ### Custom builds and Electron -Running `sqlite3` through [electron-rebuild](https://github.com/electron/electron-rebuild) does not preserve the SQLCipher extension, so some additional flags are needed to make this build Electron compatible. Your `npm install sqlite3 --build-from-source` command needs these additional flags (be sure to replace the target version with the current Electron version you are working with): +Running `sqlite3` through [electron-rebuild](https://github.com/electron/electron-rebuild) does not preserve the SQLCipher extension, so some additional flags are needed to make this build Electron compatible. Your `npm install @homeofthings/sqlite3 --build-from-source` command needs these additional flags (be sure to replace the target version with the current Electron version you are working with): ```bash ---runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers +npm install @homeofthings/sqlite3 --build-from-source --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers ``` In the case of MacOS with Homebrew, the command should look like the following: ```bash -npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers +npm install @homeofthings/sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`brew --prefix` --runtime=electron --target=18.2.1 --dist-url=https://electronjs.org/headers ``` # Testing @@ -214,6 +245,34 @@ npm install sqlite3 --build-from-source --sqlite_libname=sqlcipher --sqlite=`bre npm test ``` +# Benchmarks + +## Driver Comparison + +The `tools/benchmark-drivers` directory contains a comprehensive benchmark suite comparing different SQLite drivers for Node.js: + +```bash +cd tools/benchmark-drivers +npm install +node index.js +``` + +This compares `@homeofthings/sqlite3` against other popular SQLite drivers: +- `better-sqlite3` - Synchronous, high-performance +- `node:sqlite` - Built-in Node.js SQLite (v22.6.0+) + +See [tools/benchmark-drivers/README.md](tools/benchmark-drivers/README.md) for details. + +**Key insight**: Async drivers like `@homeofthings/sqlite3` show lower raw throughput but provide better event loop availability, allowing other operations to proceed concurrently. Sync drivers block the event loop completely. + +## Internal Benchmarks + +Internal performance benchmarks are available in `tools/benchmark-internal`: + +```bash +node tools/benchmark-internal/run.js +``` + # Contributors * [Daniel Lockyer](https://github.com/daniellockyer) @@ -238,16 +297,18 @@ Thanks to [Orlando Vazquez](https://github.com/orlandov), [Eric Fredricksen](https://github.com/grumdrig) and [Ryan Dahl](https://github.com/ry) for their SQLite bindings for node, and to mraleph on Freenode's #v8 for answering questions. -This module was originally created by [Mapbox](https://mapbox.com/) & is now maintained by [Ghost](https://ghost.org). +This module was originally created by [Mapbox](https://mapbox.com/), then it was taken over by [Ghost](https://ghost.org), but was then deprecated without prior notice, so that the original is no longer maintained. See [TryGhost/node-sqlite3](https://github.com/TryGhost/node-sqlite3) + +I still hope that it will eventually be taken over by a larger organization, but in the meantime, I'm trying to maintain this fork here. # Changelog -We use [GitHub releases](https://github.com/TryGhost/node-sqlite3/releases) for notes on the latest versions. See [CHANGELOG.md](https://github.com/TryGhost/node-sqlite3/blob/b05f4594cf8b0de64743561fcd2cfe6f4571754d/CHANGELOG.md) in git history for details on older versions. +We use [GitHub releases](https://github.com/gms1/node-sqlite3/releases) for notes on the latest versions. See [CHANGELOG.md](https://github.com/gms1/node-sqlite3/blob/main/CHANGELOG.md) in git history for details on older versions. # Copyright & license Copyright (c) 2013-2026 Mapbox & Ghost Foundation -`node-sqlite3` is [BSD licensed](https://github.com/tryghost/node-sqlite3/raw/master/LICENSE). +`node-sqlite3` is [BSD licensed](https://github.com/gms1/node-sqlite3/raw/main/LICENSE). [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Fmapbox%2Fnode-sqlite3?ref=badge_large) diff --git a/binding.gyp b/binding.gyp index a8fccd0c4..f32974e46 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,14 +8,10 @@ "targets": [ { "target_name": "<(module_name)", - "cflags!": [ "-fno-exceptions" ], - "cflags_cc!": [ "-fno-exceptions" ], - "xcode_settings": { "GCC_ENABLE_CPP_EXCEPTIONS": "YES", + "xcode_settings": { "CLANG_CXX_LIBRARY": "libc++", "MACOSX_DEPLOYMENT_TARGET": "10.7", - }, - "msvs_settings": { - "VCCLCompilerTool": { "ExceptionHandling": 1 }, + "OTHER_CFLAGS": [ "-fstack-protector-strong" ] }, "include_dirs": [ " { + db.run("CREATE TABLE users (id INT, name TEXT)"); + db.run("INSERT INTO users VALUES (1, 'Alice')"); + + db.each("SELECT * FROM users", (err, row) => { + console.log(row.id, row.name); + }); +}); + +db.close(); +``` + +### Promise-based API (Modern) + +```javascript +const { SqliteDatabase } = require('@homeofthings/sqlite3'); + +async function main() { + const db = await SqliteDatabase.open(':memory:'); + + await db.run("CREATE TABLE users (id INT, name TEXT)"); + await db.run("INSERT INTO users VALUES (1, 'Alice')"); + + const rows = await db.all("SELECT * FROM users"); + console.log(rows); + + await db.close(); +} + +main().catch(console.error); +``` + +--- + +## Callback API + +The traditional callback-based API is compatible with the original `node-sqlite3` API. + +### Database Class (Callback) + +#### Constructor + +```javascript +const sqlite3 = require('@homeofthings/sqlite3'); +const db = new sqlite3.Database(filename, [mode], [callback]); +``` + +**Parameters:** +- `filename` (string): Path to the database file, or `':memory:'` for an in-memory database +- `mode` (number, optional): Opening mode flags. Default is `OPEN_READWRITE | OPEN_CREATE` +- `callback` (function, optional): Called when the database is opened + +**Example:** +```javascript +const db = new sqlite3.Database('mydb.sqlite', (err) => { + if (err) console.error(err.message); + else console.log('Connected to database'); +}); +``` + +#### Database.open + +Opens a database connection. This is typically done via the constructor. + +```javascript +db.open(filename, [mode], [callback]); +``` + +#### Database.close + +Closes the database connection. + +```javascript +db.close([callback]); +``` + +**Example:** +```javascript +db.close((err) => { + if (err) console.error(err.message); + else console.log('Database closed'); +}); +``` + +#### Database.run + +Executes a SQL statement and returns the result. + +```javascript +db.run(sql, [params...], [callback]); +``` + +**Parameters:** +- `sql` (string): SQL query to execute +- `params` (any, optional): Parameters to bind to the query +- `callback` (function, optional): Called with `this.lastID` and `this.changes` + +**Example:** +```javascript +db.run("INSERT INTO users VALUES (?, ?)", [1, 'Alice'], function(err) { + if (err) return console.error(err); + console.log(`Inserted row with ID ${this.lastID}`); + console.log(`Rows affected: ${this.changes}`); +}); +``` + +#### Database.get + +Executes a SQL query and returns the first row. + +```javascript +db.get(sql, [params...], [callback]); +``` + +**Example:** +```javascript +db.get("SELECT * FROM users WHERE id = ?", [1], (err, row) => { + if (err) return console.error(err); + console.log(row); +}); +``` + +#### Database.all + +Executes a SQL query and returns all rows. + +```javascript +db.all(sql, [params...], [callback]); +``` + +**Example:** +```javascript +db.all("SELECT * FROM users", (err, rows) => { + if (err) return console.error(err); + rows.forEach(row => console.log(row)); +}); +``` + +#### Database.each + +Executes a SQL query and calls a callback for each row. + +```javascript +db.each(sql, [params...], callback, [complete]); +``` + +**Parameters:** +- `sql` (string): SQL query to execute +- `params` (any, optional): Parameters to bind to the query +- `callback` (function): Called for each row with `(err, row)` +- `complete` (function, optional): Called when all rows have been processed with `(err, count)` + +**Example:** +```javascript +db.each("SELECT * FROM users", (err, row) => { + if (err) console.error(err); + else console.log(row); +}, (err, count) => { + console.log(`Processed ${count} rows`); +}); +``` + +#### Database.exec + +Executes multiple SQL statements separated by semicolons. + +```javascript +db.exec(sql, [callback]); +``` + +**Example:** +```javascript +db.exec(` + CREATE TABLE users (id INT, name TEXT); + INSERT INTO users VALUES (1, 'Alice'); + INSERT INTO users VALUES (2, 'Bob'); +`, (err) => { + if (err) console.error(err); +}); +``` + +#### Database.prepare + +Prepares a SQL statement for repeated execution. + +```javascript +const stmt = db.prepare(sql, [params...], [callback]); +``` + +**Example:** +```javascript +const stmt = db.prepare("INSERT INTO users VALUES (?, ?)"); +stmt.run(1, 'Alice'); +stmt.run(2, 'Bob'); +stmt.finalize(); +``` + +#### Database.serialize + +Runs operations in serialized mode (one at a time). + +```javascript +db.serialize([callback]); +``` + +**Example:** +```javascript +db.serialize(() => { + db.run("CREATE TABLE users (id INT, name TEXT)"); + db.run("INSERT INTO users VALUES (1, 'Alice')"); +}); +``` + +#### Database.parallelize + +Runs operations in parallel mode. + +```javascript +db.parallelize([callback]); +``` + +#### Database.backup + +Creates a backup of the database. + +```javascript +const backup = db.backup(filename, [callback]); +// or with full control: +const backup = db.backup(filename, destName, sourceName, filenameIsDest, [callback]); +``` + +**Parameters:** +- `filename` (string): Path to the backup file +- `destName` (string): Name of the destination database (default: 'main') +- `sourceName` (string): Name of the source database (default: 'main') +- `filenameIsDest` (boolean): Whether filename is the destination (default: true) +- `callback` (function, optional): Called when backup is initialized + +#### Database.loadExtension + +Loads a SQLite extension. + +```javascript +db.loadExtension(filename, [callback]); +``` + +#### Database.interrupt + +Interrupts a running query. + +```javascript +db.interrupt(); +``` + +#### Database.wait + +Waits for the database to be ready. + +```javascript +db.wait(callback); +``` + +#### Database.configure + +Configures database options. + +```javascript +db.configure(option, [value]); +``` + +### Statement Class (Callback) + +A prepared statement object returned by `db.prepare()`. + +#### Statement.bind + +Binds parameters to the prepared statement. + +```javascript +stmt.bind([params...], [callback]); +``` + +**Example:** +```javascript +const stmt = db.prepare("SELECT * FROM users WHERE id = ?"); +stmt.bind(1, (err) => { + if (err) console.error(err); +}); +``` + +#### Statement.reset + +Resets the prepared statement cursor, preserving bound parameters. + +```javascript +stmt.reset([callback]); +``` + +#### Statement.run + +Executes the prepared statement. + +```javascript +stmt.run([params...], [callback]); +``` + +**Example:** +```javascript +const stmt = db.prepare("INSERT INTO users VALUES (?, ?)"); +stmt.run(1, 'Alice', function(err) { + console.log(`Inserted ID: ${this.lastID}`); +}); +``` + +#### Statement.get + +Executes the prepared statement and returns the first row. + +```javascript +stmt.get([params...], [callback]); +``` + +#### Statement.all + +Executes the prepared statement and returns all rows. + +```javascript +stmt.all([params...], [callback]); +``` + +#### Statement.each + +Executes the prepared statement and calls a callback for each row. + +```javascript +stmt.each([params...], callback, [complete]); +``` + +#### Statement.finalize + +Finalizes the prepared statement, freeing resources. + +> **Important:** You MUST finalize all prepared statements before closing the database. +> If you attempt to close a database with unfinalized statements, you will get: +> `SQLITE_BUSY: unable to close due to unfinalised statements` + +```javascript +stmt.finalize([callback]); +``` + +### Backup Class (Callback) + +A backup object returned by `db.backup()`. + +#### Backup.step + +Copies the next page or all remaining pages. + +```javascript +backup.step([pages], [callback]); +``` + +**Parameters:** +- `pages` (number, optional): Number of pages to copy. Default: -1 (all remaining) + +#### Backup.finish + +Finishes the backup operation. + +```javascript +backup.finish([callback]); +``` + +**Properties:** +- `idle` (boolean): True if backup is idle +- `completed` (boolean): True if backup is completed +- `failed` (boolean): True if backup has failed +- `remaining` (number): Remaining pages to copy +- `pageCount` (number): Total number of pages + +--- + +## Promise API + +The Promise-based API provides a modern interface using async/await syntax. + +### SqliteDatabase Class (Promise) + +A Promise-based wrapper around the Database class. + +#### SqliteDatabase.open (Static Factory Method) + +Creates and opens a new database connection. + +```javascript +const db = await SqliteDatabase.open(filename, [mode]); +``` + +**Parameters:** +- `filename` (string): Path to the database file, or `':memory:'` for in-memory +- `mode` (number, optional): Opening mode flags + +**Returns:** `Promise` + +**Example:** +```javascript +const { SqliteDatabase } = require('@homeofthings/sqlite3'); + +const db = await SqliteDatabase.open(':memory:'); +// or with mode +const db = await SqliteDatabase.open('mydb.sqlite', sqlite3.OPEN_READONLY); +``` + +#### SqliteDatabase Constructor + open + +Alternative way to create a database instance. + +```javascript +const db = new SqliteDatabase(); +await db.open(filename, [mode]); +``` + +#### SqliteDatabase.close + +Closes the database connection. + +```javascript +await db.close(); +``` + +**Returns:** `Promise` + +#### SqliteDatabase.isOpen + +Tests if the connection is open. + +```javascript +const open = db.isOpen(); // boolean +``` + +#### SqliteDatabase.run + +Executes a SQL statement. + +```javascript +const result = await db.run(sql, [params]); +``` + +**Parameters:** +- `sql` (string): SQL statement +- `params` (any, optional): Parameters to bind + +**Returns:** `Promise<{ lastID: number, changes: number }>` + +**Example:** +```javascript +const result = await db.run("INSERT INTO users VALUES (?, ?)", [1, 'Alice']); +console.log(`Inserted ID: ${result.lastID}, Changes: ${result.changes}`); +``` + +#### SqliteDatabase.get + +Executes a query and returns the first row. + +```javascript +const row = await db.get(sql, [params]); +``` + +**Returns:** `Promise` + +**Example:** +```javascript +const user = await db.get("SELECT * FROM users WHERE id = ?", [1]); +if (user) { + console.log(user.name); +} +``` + +#### SqliteDatabase.all + +Executes a query and returns all rows. + +```javascript +const rows = await db.all(sql, [params]); +``` + +**Returns:** `Promise` + +**Example:** +```javascript +const users = await db.all("SELECT * FROM users"); +users.forEach(user => console.log(user.name)); +``` + +#### SqliteDatabase.each + +Executes a query and calls a callback for each row. + +```javascript +const count = await db.each(sql, [params], callback); +``` + +**Parameters:** +- `sql` (string): SQL query +- `params` (any, optional): Parameters to bind +- `callback` (function): Called for each row with `(err, row)` + +**Returns:** `Promise` - Number of rows processed + +**Example:** +```javascript +const count = await db.each("SELECT * FROM users", (err, row) => { + console.log(row.name); +}); +console.log(`Processed ${count} rows`); +``` + +#### SqliteDatabase.exec + +Executes multiple SQL statements. + +```javascript +await db.exec(sql); +``` + +**Returns:** `Promise` + +#### SqliteDatabase.prepare + +Prepares a SQL statement. + +```javascript +const stmt = await db.prepare(sql, [params]); +``` + +**Returns:** `Promise` + +**Example:** +```javascript +const stmt = await db.prepare("INSERT INTO users VALUES (?, ?)"); +await stmt.run([1, 'Alice']); +await stmt.run([2, 'Bob']); +await stmt.finalize(); +``` + +#### SqliteDatabase.serialize + +Runs operations in serialized mode. + +```javascript +db.serialize([callback]); +``` + +#### SqliteDatabase.parallelize + +Runs operations in parallel mode. + +```javascript +db.parallelize([callback]); +``` + +#### SqliteDatabase.transactionalize + +Runs a callback inside a transaction. + +```javascript +const result = await db.transactionalize(async () => { + await db.run("INSERT INTO users VALUES (?, ?)", [1, 'Alice']); + await db.run("INSERT INTO logs VALUES (?, ?)", [1, 'created']); + return 'done'; +}); +``` + +**Returns:** `Promise` - The result of the callback + +#### Transaction Methods + +```javascript +await db.beginTransaction(); // BEGIN IMMEDIATE TRANSACTION +await db.commitTransaction(); // COMMIT TRANSACTION +await db.rollbackTransaction(); // ROLLBACK TRANSACTION +await db.endTransaction(commit); // COMMIT or ROLLBACK (ignores "no transaction" errors) +``` + +> **Important: Transaction Isolation Limitations** +> +> SQLite provides isolation between **different database connections**, but **NOT** between operations on the **same connection**. +> +> **Key behaviors:** +> - `beginTransaction()` uses `BEGIN IMMEDIATE TRANSACTION` which acquires a write lock immediately +> - If another connection tries to start a transaction while one is active, it will fail with `SQLITE_BUSY: database is locked` +> - This is expected SQLite behavior and prevents deadlocks +> +> **For concurrent transactions with isolation:** +> ```javascript +> // Option 1: Sequential on same connection (recommended) +> await db.transactionalize(async () => { /* ... */ }); +> await db.transactionalize(async () => { /* ... */ }); +> +> // Option 2: Separate connections with retry logic +> const db1 = await SqliteDatabase.open('my.db'); +> const db2 = await SqliteDatabase.open('my.db'); +> +> // Transactions on db1 and db2 are isolated from each other +> // But concurrent write transactions will fail with SQLITE_BUSY +> // You must handle this error in your application: +> try { +> await db2.beginTransaction(); +> } catch (err) { +> if (err.message.includes('SQLITE_BUSY') || err.message.includes('database is locked')) { +> // Wait and retry, or queue the operation +> await new Promise(resolve => setTimeout(resolve, 100)); +> // retry... +> } +> } +> ``` +> +> See [SQLite Isolation Documentation](https://www.sqlite.org/isolation.html) for more details. + +#### SqliteDatabase.backup + +Creates a backup of the database. + +```javascript +const backup = await db.backup(filename, [filenameIsDest], [destName], [sourceName]); +``` + +**Returns:** `Promise` + +#### SqliteDatabase.loadExtension + +Loads a SQLite extension. + +```javascript +await db.loadExtension(filename); +``` + +#### SqliteDatabase.wait + +Waits for the database to be ready. + +```javascript +await db.wait(); +``` + +#### SqliteDatabase.interrupt + +Interrupts a running query. + +```javascript +db.interrupt(); +``` + +#### SqliteDatabase.configure + +Configures database options. + +```javascript +db.configure(option, [value]); +``` + +#### Event Methods + +```javascript +db.on(event, listener); // Register event listener +db.off(event, listener); // Remove event listener +db.removeAllListeners([event]); // Remove all listeners +``` + +### SqliteStatement Class (Promise) + +A Promise-based wrapper around the Statement class. + +#### SqliteStatement.bind + +Binds parameters to the prepared statement. + +```javascript +stmt.bind(param1, param2, ...); // Returns this for chaining +``` + +#### SqliteStatement.reset + +Resets the statement cursor. + +```javascript +await stmt.reset(); +``` + +#### SqliteStatement.run + +Executes the prepared statement. + +```javascript +const result = await stmt.run([params]); +``` + +**Returns:** `Promise<{ lastID: number, changes: number }>` + +#### SqliteStatement.get + +Executes the statement and returns the first row. + +```javascript +const row = await stmt.get([params]); +``` + +**Returns:** `Promise` + +#### SqliteStatement.all + +Executes the statement and returns all rows. + +```javascript +const rows = await stmt.all([params]); +``` + +**Returns:** `Promise` + +#### SqliteStatement.each + +Executes the statement and calls a callback for each row. + +```javascript +const count = await stmt.each([params], callback); +``` + +**Returns:** `Promise` + +#### SqliteStatement.finalize + +Finalizes the statement. + +> **Important:** You MUST finalize all prepared statements before closing the database. +> If you attempt to close a database with unfinalized statements, you will get: +> `SQLITE_BUSY: unable to close due to unfinalised statements` + +```javascript +await stmt.finalize(); +``` + +### SqliteBackup Class (Promise) + +A Promise-based wrapper around the Backup class. + +#### Properties + +- `idle` (boolean): True if backup is idle +- `completed` (boolean): True if backup is completed +- `failed` (boolean): True if backup has failed +- `remaining` (number): Remaining pages to copy +- `pageCount` (number): Total number of pages +- `progress` (number): Progress as percentage (0-100) + +#### SqliteBackup.step + +Copies pages during backup. + +```javascript +await backup.step([pages]); +``` + +**Parameters:** +- `pages` (number, optional): Number of pages to copy. Default: -1 (all remaining) + +**Returns:** `Promise` + +#### SqliteBackup.finish + +Finishes the backup. + +```javascript +backup.finish(); // Synchronous +``` + +--- + +## Constants + +The following constants are available on the exported module: + +```javascript +const sqlite3 = require('@homeofthings/sqlite3'); + +// Open flags +sqlite3.OPEN_READONLY; // 0x00000001 +sqlite3.OPEN_READWRITE; // 0x00000002 +sqlite3.OPEN_CREATE; // 0x00000004 + +// Error codes +sqlite3.OK; // 0 +sqlite3.ERROR; // 1 +sqlite3.INTERNAL; // 2 +sqlite3.PERM; // 3 +sqlite3.ABORT; // 4 +sqlite3.BUSY; // 5 +sqlite3.LOCKED; // 6 +sqlite3.NOMEM; // 7 +sqlite3.READONLY; // 8 +sqlite3.INTERRUPT; // 9 +sqlite3.IOERR; // 10 +sqlite3.CORRUPT; // 11 +sqlite3.NOTFOUND; // 12 +sqlite3.FULL; // 13 +sqlite3.CANTOPEN; // 14 +sqlite3.PROTOCOL; // 15 +sqlite3.EMPTY; // 16 +sqlite3.SCHEMA; // 17 +sqlite3.TOOBIG; // 18 +sqlite3.CONSTRAINT; // 19 +sqlite3.MISMATCH; // 20 +sqlite3.MISUSE; // 21 +sqlite3.RANGE; // 25 +sqlite3.FORMAT; // 26 +``` + +--- + +## Events + +The Database class extends EventEmitter and emits the following events: + +### 'open' + +Emitted when the database is opened successfully. + +```javascript +db.on('open', () => { + console.log('Database opened'); +}); +``` + +### 'close' + +Emitted when the database is closed. + +```javascript +db.on('close', () => { + console.log('Database closed'); +}); +``` + +### 'error' + +Emitted when an error occurs. + +```javascript +db.on('error', (err) => { + console.error('Database error:', err); +}); +``` + +### 'trace' + +Emitted for each SQL statement executed (when tracing is enabled). + +```javascript +db.on('trace', (sql) => { + console.log('Executing:', sql); +}); +``` + +### 'profile' + +Emitted with timing information for each SQL statement (when profiling is enabled). + +```javascript +db.on('profile', (sql, time) => { + console.log(`Executed: ${sql} in ${time}ms`); +}); +``` + +### 'change' + +Emitted when data is modified (INSERT, UPDATE, DELETE). + +```javascript +db.on('change', (type, database, table, rowid) => { + console.log(`Change type: ${type}, Table: ${table}, RowID: ${rowid}`); +}); +``` + +--- + +## Cached Database + +For convenience, a cached version is available that reuses existing connections: + +```javascript +const sqlite3 = require('@homeofthings/sqlite3'); +const db = sqlite3.cached.Database('mydb.sqlite'); +``` + +This returns the same Database instance for the same filename, useful for connection pooling. + +--- + +## Verbose Mode + +Enable verbose mode for better error messages with stack traces: + +```javascript +const sqlite3 = require('@homeofthings/sqlite3').verbose(); +``` + +--- + +## TypeScript Support + +TypeScript definitions are included in the package: + +```typescript +import { Database, Statement, Backup } from '@homeofthings/sqlite3'; +import { SqliteDatabase, SqliteStatement, SqliteBackup } from '@homeofthings/sqlite3'; +``` + +--- + +## More Information + +- [SQLite Documentation](https://www.sqlite.org/docs.html) +- [Node.js SQLite3 Wiki](https://github.com/TryGhost/node-sqlite3/wiki) (upstream project) diff --git a/docs/DEVELOP.md b/docs/DEVELOP.md new file mode 100644 index 000000000..20092e6f5 --- /dev/null +++ b/docs/DEVELOP.md @@ -0,0 +1,78 @@ +# Development and Release Guide + +## Version Bumping + +This project uses [npm version](https://docs.npmjs.com/cli/v10/commands/npm-version) to manage version releases. + +### How to release a new version + +1. **Bump the version** (this will create a Git tag automatically): + ```bash + npm version + ``` + + For example: + ```bash + npm version patch + ``` + +2. **Push the changes and tags**: + ```bash + git push origin main --tags + ``` + + The CI workflow will automatically: + - Build binaries for all platforms + - Upload them as release assets + - Publish the package to npm + +3. ** Edit the generated Pre-release + - select "Generate release notes" and adjust them to your needs + - uncheck "Set as pre-release" + - check "Set as latest release" if it is + - click "Update Release + +### Version format + +- Versions follow [SemVer](https://semver.org/) format +- Tags should be prefixed with `v`, e.g., `v6.0.2` +- The version in `package.json` must match the Git tag version + +## Release process + +When you push a tag (e.g., `v6.0.2`), the CI workflow will: + +1. Build prebuilt binaries for: + - macOS (x64, arm64) + - Linux (x64, arm64) + - Windows (x64, ia32) + +2. Upload binaries to GitHub Release + +3. Publish to npm using trusted publishing (no NPM_TOKEN required) + +## Checking the release + +After releasing, you can verify: +- GitHub Release with binaries: https://github.com/gms1/node-sqlite3/releases +- npm package: https://www.npmjs.com/package/@homeofthings/sqlite3 + +## Code Quality + +**IMPORTANT**: After making any code changes, always run: +```bash +yarn lint --fix +yarn test +``` + +This ensures: +- Code follows project style guidelines +- No syntax errors are introduced +- All tests pass before committing + +### Pre-commit Checklist + +Before committing changes: +1. Run `yarn lint --fix` to fix code style issues +2. Run `yarn test` to ensure all tests pass +3. Review changes before pushing diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..a45388fc2 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,43 @@ +import js from '@eslint/js'; +import globals from 'globals'; + +export default [ + { + ignores: ['node_modules/', 'build/', 'prebuilds/', 'deps/'], + }, + { + files: ['**/*.js'], + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module', + globals: { + ...globals.node, + }, + }, + rules: { + ...js.configs.recommended.rules, + 'indent': ['error', 4], + 'linebreak-style': ['error', 'unix'], + 'semi': ['error', 'always'], + 'no-cond-assign': ['error', 'always'], + 'no-inner-declarations': 'off', + }, + }, + { + files: ['test/**/*.js'], + languageOptions: { + globals: { + ...globals.mocha, + }, + }, + rules: { + 'no-unused-vars': 'off', + }, + }, + { + files: ['tools/**/*.js'], + rules: { + 'no-unused-vars': 'off', + }, + }, +]; diff --git a/lib/promise/backup.js b/lib/promise/backup.js new file mode 100644 index 000000000..f51443ee5 --- /dev/null +++ b/lib/promise/backup.js @@ -0,0 +1,121 @@ +/** + * Promise-based wrapper for the Backup class from node-sqlite3 + * @module promise/backup + */ + +'use strict'; + +/** + * A thin wrapper for the 'Backup' class from 'node-sqlite3' using Promises + * instead of callbacks + * + * @see https://github.com/mapbox/node-sqlite3/wiki/API + */ +class SqliteBackup { + /** + * Creates an instance of SqliteBackup. + * + * @param {import('../sqlite3.js').Backup} backup - The underlying Backup instance + */ + constructor(backup) { + /** + * @type {import('../sqlite3.js').Backup} + * @private + */ + this._backup = backup; + } + + /** + * Returns true if the backup is idle (not actively copying) + * + * @returns {boolean} + */ + get idle() { + return this._backup.idle; + } + + /** + * Returns true if the backup is completed + * + * @returns {boolean} + */ + get completed() { + return this._backup.completed; + } + + /** + * Returns true if the backup has failed + * + * @returns {boolean} + */ + get failed() { + return this._backup.failed; + } + + /** + * Returns the remaining number of pages left to copy + * Returns -1 if `step` not yet called + * + * @returns {number} + */ + get remaining() { + return this._backup.remaining; + } + + /** + * Returns the total number of pages + * Returns -1 if `step` not yet called + * + * @returns {number} + */ + get pageCount() { + return this._backup.pageCount; + } + + /** + * Returns the progress (percentage completion) + * + * @returns {number} Progress as percentage (0-100) + */ + get progress() { + const pageCount = this.pageCount; + const remaining = this.remaining; + if (pageCount === -1 || remaining === -1) { + return 0; + } + return pageCount === 0 ? 100 : ((pageCount - remaining) / pageCount) * 100; + } + + /** + * Copy the next page or all remaining pages of the backup + * + * @param {number} [pages=-1] - Number of pages to copy (-1 for all remaining) + * @returns {Promise} + */ + step(pages) { + return new Promise((resolve, reject) => { + if (!this._backup) { + reject(new Error('backup handle not open')); + return; + } + this._backup.step(pages === undefined ? -1 : pages, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Finish the backup (synchronous) + */ + finish() { + if (this._backup) { + this._backup.finish(); + } + } +} + +module.exports = { SqliteBackup }; diff --git a/lib/promise/database.js b/lib/promise/database.js new file mode 100644 index 000000000..57e940cfe --- /dev/null +++ b/lib/promise/database.js @@ -0,0 +1,517 @@ +/** + * Promise-based wrapper for the Database class from node-sqlite3 + * @module promise/database + */ + +'use strict'; + +const { Database } = require('../sqlite3.js'); +const { SqliteStatement } = require('./statement'); +const { SqliteBackup } = require('./backup'); + +/** + * Result object returned by run() operations + * @typedef {Object} SqlRunResult + * @property {number} lastID - The ID of the last inserted row + * @property {number} changes - The number of rows affected + */ + +/** + * @typedef {import('../sqlite3-binding.js').Database} Database + */ + +/** + * A thin wrapper for the 'Database' class from 'node-sqlite3' using Promises + * instead of callbacks + * + * @see https://github.com/mapbox/node-sqlite3/wiki/API + */ +class SqliteDatabase { + /** + * Creates a new SqliteDatabase instance (without opening a database). + * Use open() to open a database connection, or use the static factory method open(). + * + * @example + * // Using constructor + open + * const db = new SqliteDatabase(); + * await db.open(':memory:'); + * + * // Using static factory method + * const db = await SqliteDatabase.open(':memory:'); + */ + constructor() { + /** + * @type {Database | undefined} + * @protected + */ + this.db = undefined; + } + + /** + * Static factory method to create and open a database connection. + * + * @param {string} filename - The path to the database file or ':memory:' for in-memory + * @param {number} [mode] - Optional mode flags (OPEN_READONLY, OPEN_READWRITE, OPEN_CREATE) + * @returns {Promise} A promise that resolves to the opened database + * @example + * const db = await SqliteDatabase.open(':memory:'); + * const db = await SqliteDatabase.open('mydb.sqlite', sqlite3.OPEN_READONLY); + */ + static async open(filename, mode) { + const db = new SqliteDatabase(); + await db.open(filename, mode); + return db; + } + + /** + * Open a database connection + * + * @param {string} filename - The path to the database file or ':memory:' for in-memory + * @param {number} [mode] - Optional mode flags (OPEN_READONLY, OPEN_READWRITE, OPEN_CREATE) + * @returns {Promise} + */ + open(filename, mode) { + return new Promise((resolve, reject) => { + // Default mode: OPEN_READWRITE | OPEN_CREATE + const defaultMode = 0x00000002 | 0x00000004; // OPEN_READWRITE | OPEN_CREATE + const db = new Database(filename, mode === undefined ? defaultMode : mode, (err) => { + if (err) { + reject(err); + } else { + this.db = db; + resolve(); + } + }); + }); + } + + /** + * Close the database connection + * + * @returns {Promise} + */ + close() { + return new Promise((resolve, reject) => { + if (!this.db) { + resolve(); + return; + } + const db = this.db; + this.db = undefined; + db.close((err) => { + db.removeAllListeners(); + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Test if a connection is open + * + * @returns {boolean} + */ + isOpen() { + return !!this.db; + } + + /** + * Runs a SQL statement with the specified parameters + * + * @param {string} sql - The SQL statement + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to the result object + */ + run(sql, params) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + // Use function() to get 'this' context with lastID and changes + this.db.run(sql, params, function (err) { + if (err) { + reject(err); + } else { + resolve({ + lastID: this.lastID, + changes: this.changes + }); + } + }); + }); + } + + /** + * Runs a SQL query with the specified parameters, fetching only the first row + * + * @template T + * @param {string} sql - The DQL statement + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to the row or undefined + */ + get(sql, params) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.get(sql, params, (err, row) => { + if (err) { + reject(err); + } else { + resolve(row); + } + }); + }); + } + + /** + * Runs a SQL query with the specified parameters, fetching all rows + * + * @template T + * @param {string} sql - The DQL statement + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to an array of rows + */ + all(sql, params) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.all(sql, params, (err, rows) => { + if (err) { + reject(err); + } else { + resolve(rows); + } + }); + }); + } + + /** + * Runs a SQL query with the specified parameters, fetching all rows + * using a callback for each row + * + * @param {string} sql - The DQL statement + * @param {any} [params] - The parameters referenced in the statement + * @param {(err: Error | null, row: any) => void} [callback] - The callback function for each row + * @returns {Promise} A promise that resolves to the number of rows retrieved + */ + each(sql, params, callback) { + // Handle case where params is actually the callback (no params provided) + if (typeof params === 'function') { + callback = params; + params = undefined; + } + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.each(sql, params, callback, (err, count) => { + if (err) { + reject(err); + } else { + resolve(count); + } + }); + }); + } + + /** + * Execute a SQL statement + * + * @param {string} sql - The SQL statement + * @returns {Promise} + */ + exec(sql) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.exec(sql, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Prepare a SQL statement + * + * @param {string} sql - The SQL statement + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to the prepared statement + */ + prepare(sql, params) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + const stmt = this.db.prepare(sql, params, (err) => { + if (err) { + reject(err); + } else { + resolve(new SqliteStatement(stmt)); + } + }); + }); + } + + /** + * Initiate online backup + * + * @param {string} filename - The database file to backup from or to + * @param {boolean} [filenameIsDest=true] - Whether filename is destination + * @param {string} [destName='main'] - The destination database name + * @param {string} [sourceName='main'] - The source database name + * @returns {Promise} A promise that resolves to the backup object + */ + backup(filename, filenameIsDest = true, destName = 'main', sourceName = 'main') { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + const backup = this.db.backup(filename, destName, sourceName, filenameIsDest, (err) => { + if (err) { + reject(err); + } else { + resolve(new SqliteBackup(backup)); + } + }); + }); + } + + /** + * Serialized sqlite3 calls + * If callback is provided, run callback in serialized mode + * Otherwise, switch connection to serialized mode + * + * @param {() => void} [callback] - Optional callback to run in serialized mode + */ + serialize(callback) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.serialize(callback); + } + + /** + * Parallelized sqlite3 calls + * If callback is provided, run callback in parallel mode + * Otherwise, switch connection to parallel mode + * + * @param {() => void} [callback] - Optional callback to run in parallel mode + */ + parallelize(callback) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.parallelize(callback); + } + + /** + * Run callback inside a database transaction + * + * IMPORTANT: SQLite provides isolation between different database connections, + * but NOT between operations on the same connection. If you need concurrent + * transactions with proper isolation, use separate SqliteDatabase instances. + * + * Note: This uses BEGIN IMMEDIATE TRANSACTION which acquires a write lock + * immediately. If another connection holds a write lock, this will fail + * with SQLITE_BUSY error. + * + * @template T + * @param {() => Promise} callback - The callback to run in transaction + * @returns {Promise} A promise that resolves to the callback result + * @see https://www.sqlite.org/isolation.html + */ + async transactionalize(callback) { + await this.beginTransaction(); + try { + const result = await callback(); + await this.commitTransaction(); + return result; + } catch (err) { + await this.rollbackTransaction(); + throw err; + } + } + + /** + * Begin a transaction using BEGIN IMMEDIATE TRANSACTION + * + * This acquires a write lock immediately, preventing deadlocks by + * failing fast if another connection holds the lock (SQLITE_BUSY). + * + * @returns {Promise} + */ + beginTransaction() { + return this.run('BEGIN IMMEDIATE TRANSACTION'); + } + + /** + * Commit a transaction + * + * @returns {Promise} + */ + commitTransaction() { + return this.run('COMMIT TRANSACTION'); + } + + /** + * Rollback a transaction + * + * @returns {Promise} + */ + rollbackTransaction() { + return this.run('ROLLBACK TRANSACTION'); + } + + /** + * End a transaction (commit or rollback) + * + * @param {boolean} commit - Whether to commit (true) or rollback (false) + * @returns {Promise} + */ + endTransaction(commit) { + const sql = commit ? 'COMMIT TRANSACTION' : 'ROLLBACK TRANSACTION'; + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.run(sql, (err) => { + // Ignore "no transaction" errors + if (err && !err.message.includes('no transaction')) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Load an extension + * + * @param {string} filename - The path to the extension + * @returns {Promise} + */ + loadExtension(filename) { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.loadExtension(filename, (err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Wait for the database to be ready + * + * @returns {Promise} + */ + wait() { + return new Promise((resolve, reject) => { + if (!this.db) { + reject(new Error('database connection not open')); + return; + } + this.db.wait((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Interrupt a running query + */ + interrupt() { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.interrupt(); + } + + /** + * Configure database options + * + * @param {string} option - The option name + * @param {number} [value] - The option value + */ + configure(option, value) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.configure(option, value); + } + + /** + * Register an event listener + * + * @param {'trace' | 'profile' | 'error' | 'close' | 'open' | 'change'} event - The event name + * @param {Function} listener - The listener function + * @returns {this} + */ + on(event, listener) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.on(event, listener); + return this; + } + + /** + * Remove an event listener + * + * @param {string} event - The event name + * @param {Function} listener - The listener function + * @returns {this} + */ + off(event, listener) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.off(event, listener); + return this; + } + + /** + * Remove all event listeners for an event + * + * @param {string} [event] - The event name + * @returns {this} + */ + removeAllListeners(event) { + if (!this.db) { + throw new Error('database connection not open'); + } + this.db.removeAllListeners(event); + return this; + } +} + +module.exports = { SqliteDatabase }; diff --git a/lib/promise/index.js b/lib/promise/index.js new file mode 100644 index 000000000..3669bc829 --- /dev/null +++ b/lib/promise/index.js @@ -0,0 +1,16 @@ +/** + * Promise-based wrappers for node-sqlite3 + * @module promise + */ + +'use strict'; + +const { SqliteDatabase } = require('./database'); +const { SqliteStatement } = require('./statement'); +const { SqliteBackup } = require('./backup'); + +module.exports = { + SqliteDatabase, + SqliteStatement, + SqliteBackup +}; diff --git a/lib/promise/statement.js b/lib/promise/statement.js new file mode 100644 index 000000000..d23322872 --- /dev/null +++ b/lib/promise/statement.js @@ -0,0 +1,185 @@ +/** + * Promise-based wrapper for the Statement class from node-sqlite3 + * @module promise/statement + */ + +'use strict'; + +/** + * Result object returned by run() operations + * @typedef {Object} SqlRunResult + * @property {number} lastID - The ID of the last inserted row + * @property {number} changes - The number of rows affected + */ + +/** + * A thin wrapper for the 'Statement' class from 'node-sqlite3' using Promises + * instead of callbacks + * + * @see https://github.com/mapbox/node-sqlite3/wiki/API + */ +class SqliteStatement { + /** + * Creates an instance of SqliteStatement. + * + * @param {import('../sqlite3.js').Statement} stmt - The underlying Statement instance + */ + constructor(stmt) { + /** + * @type {import('../sqlite3.js').Statement} + * @private + */ + this._stmt = stmt; + } + + /** + * Bind the given parameters to the prepared statement + * + * @param {...any} params - The parameters to bind + * @returns {this} Returns this for chaining + */ + bind(...params) { + this._stmt.bind(params); + return this; + } + + /** + * Reset a open cursor of the prepared statement preserving the parameter binding + * Allows re-execute of the same query + * + * @returns {Promise} + */ + reset() { + return new Promise((resolve) => { + this._stmt.reset(() => { + resolve(); + }); + }); + } + + /** + * Finalizes a prepared statement (freeing any resource used by this statement) + * + * IMPORTANT: You MUST finalize all prepared statements before closing the database. + * If you attempt to close a database with unfinalized statements, you will get: + * SQLITE_BUSY: unable to close due to unfinalised statements + * + * @returns {Promise} + */ + finalize() { + return new Promise((resolve, reject) => { + this._stmt.finalize((err) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /** + * Runs a prepared statement with the specified parameters + * + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to the result object + */ + run(params) { + return new Promise((resolve, reject) => { + // Use function() to get 'this' context with lastID and changes + const callback = function(err) { + if (err) { + reject(err); + } else { + resolve({ + lastID: this.lastID, + changes: this.changes + }); + } + }; + // Only pass params if provided - otherwise use bound parameters + if (params === undefined) { + this._stmt.run(callback); + } else { + this._stmt.run(params, callback); + } + }); + } + + /** + * Runs a prepared statement with the specified parameters, fetching only the first row + * + * @template T + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to the row or undefined + */ + get(params) { + return new Promise((resolve, reject) => { + const callback = (err, row) => { + if (err) { + reject(err); + } else { + resolve(row); + } + }; + // Only pass params if provided - otherwise use bound parameters + if (params === undefined) { + this._stmt.get(callback); + } else { + this._stmt.get(params, callback); + } + }); + } + + /** + * Runs a prepared statement with the specified parameters, fetching all rows + * + * @template T + * @param {any} [params] - The parameters referenced in the statement + * @returns {Promise} A promise that resolves to an array of rows + */ + all(params) { + return new Promise((resolve, reject) => { + const callback = (err, rows) => { + if (err) { + reject(err); + } else { + resolve(rows); + } + }; + // Only pass params if provided - otherwise use bound parameters + if (params === undefined) { + this._stmt.all(callback); + } else { + this._stmt.all(params, callback); + } + }); + } + + /** + * Runs a prepared statement with the specified parameters, fetching all rows + * using a callback for each row + * + * @param {any} [params] - The parameters referenced in the statement + * @param {(err: Error | null, row: any) => void} [callback] - The callback function for each row + * @returns {Promise} A promise that resolves to the number of rows retrieved + */ + each(params, callback) { + // Handle case where params is actually the callback (no params provided) + if (typeof params === 'function') { + callback = params; + params = undefined; + } + return new Promise((resolve, reject) => { + this._stmt.each(params, callback, (err, count) => { + if (err) { + reject(err); + } else { + resolve(count); + } + }); + }); + } +} + +module.exports = { SqliteStatement }; diff --git a/lib/sqlite3.d.ts b/lib/sqlite3.d.ts index 15e66230e..65fe614d8 100644 --- a/lib/sqlite3.d.ts +++ b/lib/sqlite3.d.ts @@ -1,5 +1,5 @@ // Type definitions for sqlite3 -// Project: http://github.com/tryghost/node-sqlite3 +// Project: http://github.com/gms1/node-sqlite3 /// @@ -26,23 +26,23 @@ export const BUSY: number; export const LOCKED: number; export const NOMEM: number; export const READONLY: number; -export const INTERRUPT: number +export const INTERRUPT: number; export const IOERR: number; -export const CORRUPT: number +export const CORRUPT: number; export const NOTFOUND: number; export const FULL: number; export const CANTOPEN: number; export const PROTOCOL: number; export const EMPTY: number; export const SCHEMA: number; -export const TOOBIG: number -export const CONSTRAINT: number +export const TOOBIG: number; +export const CONSTRAINT: number; export const MISMATCH: number; export const MISUSE: number; export const NOLFS: number; -export const AUTH: number +export const AUTH: number; export const FORMAT: number; -export const RANGE: number +export const RANGE: number; export const NOTADB: number; export const LIMIT_LENGTH: number; @@ -64,8 +64,8 @@ export const cached: { }; export interface RunResult extends Statement { - lastID: number; - changes: number; + lastID: number; + changes: number; } export class Statement extends events.EventEmitter { @@ -143,6 +143,17 @@ export class Database extends events.EventEmitter { export function verbose(): sqlite3; +export interface Backup { + idle: boolean; + completed: boolean; + failed: boolean; + remaining: number; + pageCount: number; + step(pages: number, callback: (err: Error | null) => void): void; + finish(): void; +} + + export interface sqlite3 { OPEN_READONLY: number; OPEN_READWRITE: number; @@ -165,23 +176,23 @@ export interface sqlite3 { LOCKED: number; NOMEM: number; READONLY: number; - INTERRUPT: number + INTERRUPT: number; IOERR: number; - CORRUPT: number + CORRUPT: number; NOTFOUND: number; FULL: number; CANTOPEN: number; PROTOCOL: number; EMPTY: number; SCHEMA: number; - TOOBIG: number - CONSTRAINT: number + TOOBIG: number; + CONSTRAINT: number; MISMATCH: number; MISUSE: number; NOLFS: number; - AUTH: number + AUTH: number; FORMAT: number; - RANGE: number + RANGE: number; NOTADB: number; LIMIT_LENGTH: number; @@ -201,5 +212,163 @@ export interface sqlite3 { RunResult: RunResult; Statement: typeof Statement; Database: typeof Database; + Backup: Backup; + SqlRunResult: SqlRunResult; + SqliteDatabase: typeof SqliteDatabase; + SqliteStatement: typeof SqliteStatement; + SqliteBackup: typeof SqliteBackup; verbose(): this; -} \ No newline at end of file +} + +// Promise-based wrapper classes + +/** + * Result object returned by run() operations + */ +export interface SqlRunResult { + lastID: number; + changes: number; +} + +/** + * Promise-based wrapper for the Database class + */ +export class SqliteDatabase { + protected db?: Database; + + constructor(); + + static open(filename: string, mode?: number): Promise; + + open(filename: string, mode?: number): Promise; + close(): Promise; + + run(sql: string, params?: any): Promise; + get(sql: string, params?: any): Promise; + all(sql: string, params?: any): Promise; + each( + sql: string, + params?: any, + callback?: (err: Error | null, row: T) => void, + ): Promise; + + exec(sql: string): Promise; + prepare(sql: string, params?: any): Promise; + + backup( + filename: string, + filenameIsDest?: boolean, + destName?: string, + sourceName?: string, + ): Promise; + + serialize(callback?: () => void): void; + parallelize(callback?: () => void): void; + + /** + * Run callback inside a database transaction. + * + * IMPORTANT: SQLite provides isolation between different database connections, + * but NOT between operations on the same connection. For concurrent transactions + * with proper isolation, use separate SqliteDatabase instances. + * + * Uses BEGIN IMMEDIATE TRANSACTION which acquires a write lock immediately. + * @see https://www.sqlite.org/isolation.html + */ + transactionalize(callback: () => Promise): Promise; + + /** + * Begin a transaction using BEGIN IMMEDIATE TRANSACTION. + * Acquires write lock immediately - fails with SQLITE_BUSY if lock unavailable. + */ + beginTransaction(): Promise; + commitTransaction(): Promise; + rollbackTransaction(): Promise; + endTransaction(commit: boolean): Promise; + + loadExtension(filename: string): Promise; + + wait(): Promise; + interrupt(): void; + + configure(option: string, ...args: any[]): void; + + on(event: string, listener: (...args: any[]) => void): this; + off(event: string, listener: (...args: any[]) => void): this; + removeAllListeners(event?: string): this; +} + +/** + * Promise-based wrapper for the Statement class + */ +export class SqliteStatement { + constructor(stmt: Statement); + + bind(...params: any[]): this; + reset(): Promise; + + run(params?: any): Promise; + get(params?: any): Promise; + all(params?: any): Promise; + each( + params?: any, + callback?: (err: Error | null, row: T) => void, + ): Promise; + + /** + * Finalizes the statement. + * IMPORTANT: You MUST finalize all prepared statements before closing the database. + * Otherwise you will get SQLITE_BUSY: unable to close due to unfinalised statements + */ + finalize(): Promise; +} + +/** + * Promise-based wrapper for the Backup class + */ +export class SqliteBackup { + constructor(backup: Backup); + + /** + * Returns true if the backup is idle (not actively copying) + */ + readonly idle: boolean; + + /** + * Returns true if the backup is completed + */ + readonly completed: boolean; + + /** + * Returns true if the backup has failed + */ + readonly failed: boolean; + + /** + * Returns the remaining number of pages left to copy + * Returns -1 if `step` not yet called + */ + readonly remaining: number; + + /** + * Returns the total number of pages + * Returns -1 if `step` not yet called + */ + readonly pageCount: number; + + /** + * Returns the progress (percentage completion) + */ + readonly progress: number; + + /** + * Copy the next page or all remaining pages of the backup + * @param pages - Number of pages to copy (-1 for all remaining) + */ + step(pages?: number): Promise; + + /** + * Finish the backup (synchronous) + */ + finish(): void; +} diff --git a/lib/sqlite3.js b/lib/sqlite3.js index 430a2b88a..843d4f6ae 100644 --- a/lib/sqlite3.js +++ b/lib/sqlite3.js @@ -205,3 +205,9 @@ sqlite3.verbose = function() { return sqlite3; }; + +// Export Promise-based wrapper classes +const { SqliteDatabase, SqliteStatement, SqliteBackup } = require('./promise'); +sqlite3.SqliteDatabase = SqliteDatabase; +sqlite3.SqliteStatement = SqliteStatement; +sqlite3.SqliteBackup = SqliteBackup; diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 000000000..5715de0a9 --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,38 @@ +# Active Context + +## Current Status + +**Last Updated**: 2026-03-29 + +## Current Work + +### Security Hardening Documentation + +**Status**: ✅ COMPLETE + +Documented the security hardening implementation in [`binding.gyp`](../binding.gyp): + +- Added comprehensive "Security Hardening" section to [`build-system.md`](build-system.md) +- Documented platform-specific hardening flags: + - **Linux**: `-fstack-protector-strong`, `-fPIC`, RELRO, `_FORTIFY_SOURCE=2`, CET + - **Windows**: BufferSecurityCheck, ControlFlowGuard, ASLR, DEP, /sdl + - **macOS**: `-fstack-protector-strong`, libc++ +- Added hardening decision entry to [`decisionLog.md`](decisionLog.md) +- Updated [`progress.md`](progress.md) with completed work + +## Pending Tasks + +No active tasks currently assigned. + +## Recent Changes + +- Security hardening documentation added to memory bank +- Memory-bank structure updated with hardening details + +## Open Questions + +None currently. + +## Next Steps + +Awaiting new task assignments. diff --git a/memory-bank/build-system.md b/memory-bank/build-system.md new file mode 100644 index 000000000..6b9c201b3 --- /dev/null +++ b/memory-bank/build-system.md @@ -0,0 +1,321 @@ +# Build System + +## Overview + +The project uses **node-gyp** to build the native SQLite3 addon. The build system supports both Debug and Release configurations. + +## Build Files + +### binding.gyp + +Main build configuration file: + +```python +{ + "includes": ["deps/common-sqlite.gypi"], + "variables": { + "sqlite%": "internal", # Use bundled SQLite + "sqlite_libname%": "sqlite3", + "module_name": "node_sqlite3", + }, + "targets": [ + { + "target_name": "<(module_name)", + "sources": [ + "src/backup.cc", + "src/database.cc", + "src/node_sqlite3.cc", + "src/statement.cc" + ], + "defines": [ + "NAPI_VERSION=<(napi_build_version)" + ] + // ... more config + } + ] +} +``` + +### deps/common-sqlite.gypi + +Common build configurations: + +| Configuration | Defines | Optimization | +|--------------|---------|--------------| +| Debug | `DEBUG`, `_DEBUG` | None (`-O0`) | +| Release | `NDEBUG` | Full (`-O3`) | + +### deps/sqlite3.gyp + +SQLite library build configuration: +- Compiles SQLite from source tarball +- Enables FTS3/4/5, R-Tree, math functions +- Thread-safe configuration + +## Build Commands + +### Standard Build + +```bash +# Install with prebuild or compile +yarn install + +# Explicit rebuild +yarn rebuild +# or +node-gyp rebuild +``` + +Output: `build/Release/node_sqlite3.node` + +### Debug Build + +```bash +node-gyp rebuild --debug +``` + +Output: `build/Debug/node_sqlite3.node` + +### Clean Build + +```bash +node-gyp clean +node-gyp rebuild +``` + +### Verbose Build + +```bash +node-gyp rebuild --verbose +``` + +## Build Configurations + +### Debug Configuration + +Enables: +- `DEBUG` and `_DEBUG` preprocessor macros +- Debug symbols (`GCC_GENERATE_DEBUGGING_SYMBOLS: YES`) +- No optimizations (`GCC_OPTIMIZATION_LEVEL: 0`) +- `ASSERT_STATUS()` macro checks (src/macros.h) + +### Release Configuration (Default) + +Enables: +- `NDEBUG` preprocessor macro +- Full optimizations (`GCC_OPTIMIZATION_LEVEL: 3`) +- No debug symbols + +## Custom Build Options + +### Using External SQLite + +```bash +node-gyp rebuild --sqlite=/path/to/sqlite --sqlite_libname=sqlite3 +``` + +### NAPI Version + +The `NAPI_VERSION` define is set via `napi_build_version` variable in binding.gyp: + +```python +"defines": [ "NAPI_VERSION=<(napi_build_version)" ] +``` + +**How it works**: +- The `napi_build_version` variable is automatically set by node-gyp based on the target Node.js version +- For local builds, it's stored in `build/config.gypi` (e.g., `"napi_build_version": "9"`) +- For prebuilds, the `prebuild` package passes it via `--napi_build_version=` flag + +### NAPI Versions Configuration + +The `package.json` specifies which NAPI versions to build prebuilt binaries for: + +```json +"binary": { + "napi_versions": [3, 6] +} +``` + +**Why multiple versions?** + +NAPI versions are independent of Node.js versions - they represent API feature tiers, not Node.js version numbers. Each NAPI version adds new capabilities: + +| NAPI Version | Key Features Added | +|--------------|-----------------------------------------| +| v3 | Instance data, cleanup hooks | +| v4 | Thread-safe functions | +| v5 | BigInt support | +| v6 | Instance data with finalizer hints | +| v7 | ArrayBuffer detaching | +| v8 | Type tagging, async cleanup | +| v9 | External strings, syntax error creation | +| v10 | Latin1 external strings | + +**Backward Compatibility**: + +NAPI is backward compatible - a binary built for NAPI v3 will run on any Node.js that supports v3 or higher. Since Node.js 20.17.0+ supports NAPI v9, it can run binaries built for v3, v6, or v9. + +**Code Conditionals**: + +The source code uses `#if NAPI_VERSION < 6` conditionals in [`src/database.h`](../src/database.h) and [`src/database.cc`](../src/database.cc) to provide backward compatibility for NAPI versions below v6. When building for NAPI v6+, these conditionals are disabled. + +**Current Configuration Rationale**: + +The `[3, 6]` configuration is historical from when this fork supported older Node.js versions. Since the project now requires Node.js >= 20.17.0 (which supports NAPI v9), both prebuilt variants work correctly. Future versions could simplify to a single NAPI version (e.g., v6 or v9). + +## Assert Control + +### Asserts in Debug Mode + +The `ASSERT_STATUS` macro in src/macros.h is enabled when `DEBUG` is defined: + +```c +#ifdef DEBUG + #define ASSERT_STATUS() assert(status == 0); +#else + #define ASSERT_STATUS() (void)status; +#endif +``` + +## Module Loading + +The native addon is loaded via lib/sqlite3-binding.js: + +```javascript +module.exports = require('bindings')('node_sqlite3.node'); +``` + +The `bindings` package searches: +1. `build/Debug/node_sqlite3.node` +2. `build/Release/node_sqlite3.node` + +**Note**: Debug builds take precedence if both exist. + +## Prebuilt Binaries + +### Downloading Prebuilts + +```bash +yarn install # Automatically downloads prebuilt if available +``` + +### Building Prebuilts + +```bash +yarn prebuild # Build for all NAPI versions +``` + +### Uploading Prebuilts + +```bash +yarn upload # Upload to GitHub releases +``` + +## Platform Support + +- Node.js >= 20.17.0 +- NAPI versions: 3, 6 +- Platforms: Linux, macOS, Windows (see prebuild configuration) + +## Security Hardening + +The build system includes platform-specific security hardening flags to protect against common vulnerability classes. + +### Linux Hardening + +Applied to all Linux builds (see `binding.gyp`): + +| Flag | Purpose | +|----------------------------|--------------------------------------------------------------------------------| +| `-fstack-protector-strong` | Stack overflow protection - inserts canaries into functions with local buffers | +| `-fPIC` | Position Independent Code - enables ASLR (Address Space Layout Randomization) | + +Linker flags: + +| Flag | Purpose | +|----------------|--------------------------------------------------------------------------------------| +| `-Wl,-z,relro` | Read-Only Relocations - makes some ELF sections read-only after load | +| `-Wl,-z,now` | Immediate binding - resolves all symbols at load time, prevents lazy binding attacks | + +Release-only hardening: + +| Flag | Purpose | Scope | +|------------------------|-------------------------------------------------------------------|-------------------| +| `_FORTIFY_SOURCE=2` | Source-level buffer overflow detection | All architectures | +| `-fcf-protection=full` | Intel CET (Control Flow Guard) - protects against ROP/JOP attacks | x86_64 only | + +### Windows Hardening + +Applied to all Windows builds (see `binding.gyp`): + +**Compiler settings:** + +| Setting | Purpose | +|-------------------------------|------------------------------------------------------| +| `ExceptionHandling: 1` | C++ exception handling support | +| `BufferSecurityCheck: "true"` | Stack buffer overrun detection (/GS) | +| `ControlFlowGuard: "Guard"` | Control Flow Guard - validates indirect call targets | + +**Linker settings:** + +| Setting | Purpose | +|----------------|----------------------------------------------------------------------| +| `/DYNAMICBASE` | ASLR - randomizes base address at load time | +| `/NXCOMPAT` | DEP (Data Execution Prevention) - marks stack/heap as non-executable | + +Release-only hardening: + +| Setting | Purpose | +|---------|-----------------------------------------| +| `/sdl` | Additional security checks and warnings | + +### macOS Hardening + +Applied to all macOS builds (see `binding.gyp`): + +| Flag | Purpose | +|------------------------------------|---------------------------------| +| `-fstack-protector-strong` | Stack overflow protection | +| `CLANG_CXX_LIBRARY: "libc++"` | Use modern C++ standard library | +| `MACOSX_DEPLOYMENT_TARGET: "10.7"` | Minimum deployment target | + +### Hardening Summary + +| Platform | Stack Protection | ASLR | Control Flow | Buffer Checks | +|----------|----------------------------------|-----------------------|--------------------------|---------------------------| +| Linux | Yes (`-fstack-protector-strong`) | Yes (`-fPIC` + RELRO) | Yes (CET on x86_64) | Yes (`_FORTIFY_SOURCE=2`) | +| Windows | Yes (`BufferSecurityCheck`) | Yes (`/DYNAMICBASE`) | Yes (`ControlFlowGuard`) | Yes (`/sdl`) | +| macOS | Yes (`-fstack-protector-strong`) | Yes (default) | No | No | + +### References + +- [OWASP Hardening Guide](https://owasp.org/www-project-web-security-testing-guide/) +- [GCC Security Features](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html) +- [MSVC Security Features](https://docs.microsoft.com/en-us/cpp/build/reference/security-best-practices) + +## Troubleshooting + +### Build Fails + +1. Check Node.js version: `node --version` (must be >= 20.17.0) +2. Check node-gyp version: `node-gyp --version` +3. Try clean rebuild: `node-gyp clean && node-gyp rebuild` +4. Check Python version (node-gyp requires Python 3) + +### Native Module Not Found + +1. Verify build output exists: `ls build/Release/node_sqlite3.node` +2. Check if bindings package is installed: `yarn install` +3. Try explicit rebuild: `yarn rebuild` + +### Debug Symbols Missing + +1. Build with `--debug` flag: `node-gyp rebuild --debug` +2. Verify output location: `build/Debug/node_sqlite3.node` + +## Related Files + +- [Project Overview](project-overview.md) - Architecture and components +- [Development Workflow](development.md) - Testing and contributing +- [Decision Log](decisionLog.md) - Technical decisions including hardening rationale diff --git a/memory-bank/custom-instructions.md b/memory-bank/custom-instructions.md new file mode 100644 index 000000000..9bdee4d5f --- /dev/null +++ b/memory-bank/custom-instructions.md @@ -0,0 +1,28 @@ +# AI Agent Instructions + +> These instructions are for AI agents (like Roo/Cline) working on this project. + +## Command Execution + +- Do not prefix commands with `cd /home/gms/gms/projects/hot/node-sqlite3 &&` when the command should run in the current workspace directory +- Use `yarn test`, `yarn lint` instead of npm run scripts (e.g., avoid `npm run test`, use `yarn test` instead) + +## Notes + +These instructions apply to all future sessions working in this project. + +## Memory Bank + +The most important part of the Memory Bank is keeping it updated. Currently, the community standard is the UMB (Update Memory Bank) routine: + +- Before you start a task: Ask your AI agent, "Read the memory bank and tell me the current status." + +- While working, the AI agent will occasionally suggest updates to activeContext.md. + +- Before you end a session: Simply type "UMB" or "Update memory bank". The AI agent will then: + + - Move completed items from activeContext.md to progress.md. + + - Log any new technical decisions in decisionLog.md. + + - Clear the activeContext.md for the next session. diff --git a/memory-bank/decisionLog.md b/memory-bank/decisionLog.md new file mode 100644 index 000000000..8b73fc953 --- /dev/null +++ b/memory-bank/decisionLog.md @@ -0,0 +1,188 @@ +# Decision Log + +## Technical Decisions + +### 2026-04-10: Queue Processing Deadlock Fix + +**Decision**: Track `pending` counter to detect synchronous operations in `Database::Process()` + +**Rationale**: +- Bug: When using `db.serialize()`, synchronous operations like `configure()` would cause subsequent operations to get stuck in the queue indefinitely +- Root cause: `Process()` loop breaks after exclusive operations, but synchronous operations don't increment `pending`, leaving `locked=true` with no async work pending +- Solution detects synchronous completion by checking if `pending` counter changed during callback execution + +**Implementation**: +```cpp +// Track pending before callback to detect synchronous operations +unsigned int before_pending = pending; +call->callback(call->baton); + +// If operation was synchronous (pending unchanged) and we're in exclusive mode, +// reset locked and continue processing the queue. +if (locked && pending == before_pending) { + locked = false; + continue; +} +``` + +**Files Changed**: +- `src/database.cc`: Modified `Database::Process()` function +- `test/serialization.test.js`: Added test cases for queue processing bug + +**Affected Synchronous Operations**: +- `SetLimit` - SQLite limit configuration +- `SetBusyTimeout` - Busy timeout setting +- `RegisterTraceCallback` - Trace callback registration +- `RegisterProfileCallback` - Profile callback registration +- `RegisterUpdateCallback` - Update hook registration +- `Work_Wait` - Wait operation + +**References**: +- GitHub Issue: https://github.com/TryGhost/node-sqlite3/issues/1838 + +--- + +### 2026-03-29: Security Hardening Implementation + +**Decision**: Implement platform-specific security hardening flags in binding.gyp + +**Rationale**: +- Native addons are potential attack vectors in Node.js applications +- Security hardening protects against common vulnerability classes: + - Buffer overflow attacks + - Control flow hijacking (ROP/JOP) + - Stack smashing attacks + - Memory corruption exploits +- Modern compilers and linkers provide built-in security features +- Minimal performance impact in Release builds + +**Implementation**: + +**Linux (all builds)**: +- `-fstack-protector-strong` - Stack canaries for functions with local buffers +- `-fPIC` - Position Independent Code for ASLR +- `-Wl,-z,relro,-z,now` - RELRO and immediate binding + +**Linux (Release only)**: +- `_FORTIFY_SOURCE=2` - Source-level buffer overflow detection +- `-fcf-protection=full` - Intel CET (x86_64 only) + +**Windows (all builds)**: +- `BufferSecurityCheck` - Stack buffer overrun detection +- `ControlFlowGuard` - Control Flow Guard +- `/DYNAMICBASE` - ASLR support +- `/NXCOMPAT` - DEP support + +**Windows (Release only)**: +- `/sdl` - Additional security checks + +**macOS (all builds)**: +- `-fstack-protector-strong` - Stack protection +- `libc++` - Modern C++ standard library + +**Files Changed**: +- `binding.gyp`: Added hardening flags for all platforms + +**References**: +- [OWASP Hardening](https://owasp.org/www-project-web-security-testing-guide/) +- [GCC Security Features](https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html) +- [MSVC Security Features](https://docs.microsoft.com/en-us/cpp/build/reference/security-best-practices) + +--- + +### 2026-03-29: NAPI Exception Handling + +**Decision**: Use `node_addon_api_except` instead of `NAPI_DISABLE_CPP_EXCEPTIONS=1` + +**Rationale**: +- Commit 48e95e8a0d32277449c269b41fba6419acb21418 changed the build configuration +- Using `node_addon_api_except` from node-addon-api provides proper exception handling support +- This is the recommended approach for modern node-addon-api versions + +**Files Changed**: +- `binding.gyp`: Changed from `node_addon_api` to `node_addon_api_except` dependency + +--- + +### 2026-03-28: Memory Bank Structure + +**Decision**: Adopt UMB (Update Memory Bank) workflow with standard file structure. + +**Rationale**: Following 2026 community standards for AI agent context management. + +**Files Created**: +- `activeContext.md` - Current work status +- `progress.md` - Completed work history +- `decisionLog.md` - Technical decisions record + +--- + +### Promisification Architecture + +**Decision**: Create Promise-based wrapper classes alongside callback-based classes. + +**Status**: ✅ IMPLEMENTED + +**Rationale**: +- Maintains backward compatibility with existing callback API +- Provides modern async/await support +- Follows established pattern from sqlite3orm reference implementation + +**Implementation**: +- New classes: `SqliteDatabase`, `SqliteStatement`, `SqliteBackup` +- Exported from `lib/promise/index.js` +- Generic type parameters for TypeScript type inference +- Transaction support with `beginTransaction()`, `commitTransaction()`, `rollbackTransaction()` +- Static factory method `SqliteDatabase.open()` + +**Alternatives Considered**: +1. Modify existing classes to return Promises - Rejected (breaks backward compatibility) +2. Create separate package - Deferred (can be refactored later if needed) + +--- + +### Node.js Version Requirement + +**Decision**: Require Node.js >= 20.17.0 + +**Rationale**: +- Modern NAPI support +- Latest performance improvements +- Simplified maintenance + +--- + +### Build System Choice + +**Decision**: Use node-gyp with prebuild for binary distribution + +**Rationale**: +- Established tool for native addons +- Prebuilt binaries reduce installation friction +- Supports multiple NAPI versions (3, 6) + +--- + +### SQLite Configuration + +**Decision**: Bundle SQLite with enabled extensions + +**Extensions Enabled**: +- `SQLITE_THREADSAFE=1` - Thread safety +- `SQLITE_ENABLE_FTS3/4/5` - Full-text search +- `SQLITE_ENABLE_RTREE` - R-Tree index +- `SQLITE_ENABLE_DBSTAT_VTAB=1` - Database stats +- `SQLITE_ENABLE_MATH_FUNCTIONS` - Math functions + +**Rationale**: Provides comprehensive SQLite functionality out of the box. + +--- + +### Test Framework + +**Decision**: Use mocha with 480s timeout + +**Rationale**: +- Established test suite +- Handles async operations well +- Compatible with existing tests diff --git a/memory-bank/development.md b/memory-bank/development.md new file mode 100644 index 000000000..682432f96 --- /dev/null +++ b/memory-bank/development.md @@ -0,0 +1,349 @@ +# Development Workflow + +## Setup + +### Prerequisites + +- Node.js >= 20.17.0 +- Python 3 (for node-gyp) +- C++ compiler (gcc, clang, or MSVC) +- yarn or npm + +### Installation + +```bash +# Clone repository +git clone https://github.com/gms1/node-sqlite3.git +cd node-sqlite3 + +# Install dependencies +yarn install + +# Build native addon (automatic during install, or manual) +yarn rebuild +``` + +## Testing + +### Run All Tests + +```bash +yarn test +``` + +This runs: +1. `node test/support/createdb.js` - Creates test database +2. `mocha -R spec --timeout 480000` - Runs test suite + +### Test Structure + +``` +test/ +├── support/ +│ ├── createdb.js # Test database setup +│ ├── helper.js # Test utilities +│ └── prepare.db # Pre-populated test database +├── affected.test.js # Affected rows tests +├── async_calls.test.js # Async call tests +├── backup.test.js # Backup API tests +├── blob.test.js # BLOB handling tests +├── cache.test.js # Cached database tests +├── constants.test.js # Constants tests +├── database_fail.test.js # Database failure tests +├── each.test.js # each() method tests +├── exec.test.js # exec() method tests +├── extension.test.js # Extension loading tests +├── fts-content.test.js # Full-text search tests +├── interrupt.test.js # Interrupt tests +├── issue-108.test.js # Regression tests +├── json.test.js # JSON handling tests +├── limit.test.js # LIMIT tests +├── map.test.js # map() method tests +├── named_columns.test.js # Named columns tests +├── named_params.test.js # Named parameters tests +├── null_error.test.js # NULL error handling tests +├── open_close.test.js # Open/close tests +├── other_objects.test.js # Other object tests +├── parallel_insert.test.js # Parallel insert tests +├── patching.test.js # Patching tests +├── prepare.test.js # prepare() method tests +├── profile.test.js # Profile API tests +├── rerun.test.js # Rerun tests +├── scheduling.test.js # Scheduling tests +├── serialization.test.js # Serialization tests +├── trace.test.js # Trace API tests +├── unicode.test.js # Unicode handling tests +├── update_hook.test.js # Update hook tests +├── upsert.test.js # UPSERT tests +├── verbose.test.js # Verbose mode tests +└── promise.test.js # Promise API tests +``` + +### Running Specific Tests + +```bash +# Run specific test file +npx mocha test/verbose.test.js + +# Run with specific reporter +npx mocha -R spec test/each.test.js + +# Run with increased timeout +npx mocha --timeout 10000 test/blob.test.js +``` + +## Debugging + +### JavaScript Debug Mode + +Enable extended stack traces: + +```javascript +const sqlite3 = require('@homeofthings/sqlite3').verbose(); +``` + +This adds context to error stack traces (see lib/trace.js). + +### Native Debug Build + +```bash +# Build with debug symbols +node-gyp rebuild --debug + +# Output: build/Debug/node_sqlite3.node +``` + +### Debugging with LLDB/GDB + +```bash +# Run Node with debugger +lldb -- node test/verbose.test.js + +# Set breakpoint on native function +(lldb) b Database::Open +``` + +### Verbose Logging + +The native addon supports trace and profile events: + +```javascript +const db = new sqlite3.Database(':memory:'); + +db.on('trace', (sql) => { + console.log('SQL:', sql); +}); + +db.on('profile', (sql, time) => { + console.log('SQL:', sql, 'Time:', time, 'ms'); +}); +``` + +## Promise API + +The project includes Promise-based wrappers for modern async/await support: + +### Using Promise API + +```javascript +const { SqliteDatabase } = require('@homeofthings/sqlite3/promise'); + +// Static factory method +const db = await SqliteDatabase.open(':memory:'); + +// Run queries +const result = await db.run('CREATE TABLE foo (id INT, name TEXT)'); +const row = await db.get('SELECT * FROM foo WHERE id = ?', [1]); +const rows = await db.all('SELECT * FROM foo'); + +// Transactions +await db.beginTransaction(); +await db.run('INSERT INTO foo VALUES (1, "test")'); +await db.commitTransaction(); + +// Prepared statements +const stmt = await db.prepare('INSERT INTO foo VALUES (?, ?)'); +await stmt.run(1, 'test'); +await stmt.finalize(); + +// Close +await db.close(); +``` + +### Promise Classes + +| Class | Description | +|-------------------|---------------------------------| +| `SqliteDatabase` | Promise wrapper for `Database` | +| `SqliteStatement` | Promise wrapper for `Statement` | +| `SqliteBackup` | Promise wrapper for `Backup` | + +See [`lib/promise/`](../lib/promise/) for implementation details. + +## Code Style + +### Linting + +```bash +yarn lint +``` + +Uses ESLint with configuration in `.eslintrc.js`. + +### Code Style Guidelines + +- Use `const` and `let` (not `var`) +- Use arrow functions for callbacks +- Use async/await where possible +- Follow existing naming conventions +- Add JSDoc comments for public APIs + +## Benchmarks + +### Driver Comparison Benchmarks + +The `tools/benchmark-drivers` directory contains a comprehensive benchmark suite comparing different SQLite drivers: + +```bash +cd tools/benchmark-drivers +npm install +node index.js +``` + +This compares `@homeofthings/sqlite3` against: +- `better-sqlite3` - Synchronous, high-performance +- `node:sqlite` - Built-in Node.js SQLite (v22.6.0+) + +**Event Loop Utilization**: The benchmarks measure event loop availability: +- Sync drivers (`better-sqlite3`, `node:sqlite`): 0% - blocks completely +- Async drivers (`@homeofthings/sqlite3`): 100% - non-blocking + +### Internal Benchmarks + +Internal performance benchmarks are in `tools/benchmark-internal`: + +```bash +node tools/benchmark-internal/run.js +``` + +See [Project Overview](project-overview.md) for benchmark details. + +### Critical: Statement.finalize() Requires Callback + +**IMPORTANT**: When using `stmt.finalize()` with queued operations (e.g., multiple `stmt.run()` calls inside `db.serialize()`), you **must** provide a callback to wait for operations to complete. + +**Without callback (WRONG):** +```javascript +db.serialize(() => { + const stmt = db.prepare('INSERT INTO foo VALUES (?, ?)'); + for (let i = 0; i < 10000; i++) { + stmt.run(i, 'Row ' + i); + } + stmt.finalize(); // Returns immediately, cancels queued operations! +}); +// Result: Only 1 row inserted, operations cancelled +``` + +**With callback (CORRECT):** +```javascript +db.serialize(() => { + const stmt = db.prepare('INSERT INTO foo VALUES (?, ?)'); + for (let i = 0; i < 10000; i++) { + stmt.run(i, 'Row ' + i); + } + stmt.finalize((err) => { + if (err) throw err; + // All 10,000 rows now inserted + }); +}); +// Result: All 10,000 rows inserted +``` + +**Why this matters:** +- `finalize()` without a callback returns immediately +- Queued operations (`stmt.run()` calls) are cancelled when the statement is finalized +- This caused benchmark results to appear ~10x faster than reality because operations never completed +- Always use callbacks with `finalize()` when operations are queued + +## Making Changes + +**IMPORTANT**: After making any code changes, always run: +```bash +yarn lint --fix +yarn test +``` + +### Native Code Changes + +1. Modify files in `src/` +2. Rebuild: `yarn rebuild` +3. Run lint: `yarn lint --fix` +4. Run tests: `yarn test` + +### JavaScript Code Changes + +1. Modify files in `lib/` +2. No rebuild needed +3. Run lint: `yarn lint --fix` +4. Run tests: `yarn test` + +### Adding New Tests + +1. Create `test/your-feature.test.js` +2. Follow existing test patterns +3. Import helper from `./support/helper.js` +4. Run: `yarn test` + +## Pull Request Guidelines + +1. Fork the repository +2. Create a feature branch +3. Make changes with tests +4. Run linting: `yarn lint --fix` +5. Run tests: `yarn test` +6. Submit pull request + +## Common Issues + +### Build Fails on macOS + +Ensure Xcode Command Line Tools are installed: + +```bash +xcode-select --install +``` + +### Build Fails on Linux + +Ensure build tools are installed: + +```bash +# Ubuntu/Debian +sudo apt-get install build-essential python3 + +# Fedora +sudo dnf install gcc-c++ make python3 +``` + +### Build Fails on Windows + +Ensure Visual Studio Build Tools are installed with: +- Desktop development with C++ +- Windows SDK + +### Tests Fail with "database is locked" + +Tests use in-memory databases by default. If using file-based databases, ensure proper cleanup. + +### Native Module Version Mismatch + +Rebuild for your Node.js version: + +```bash +yarn rebuild +``` + +## Related Files + +- [Project Overview](project-overview.md) - Architecture and components +- [Build System](build-system.md) - Build configuration and options diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 000000000..2213a5bf9 --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,86 @@ +# Progress Log + +## 2026-04-10: + +### v6.3.0 + +### fixed: queue processing deadlock in serialized mode + - Fixed bug where operations get stuck in queue when using `db.serialize()` with synchronous operations + - Issue: https://github.com/TryGhost/node-sqlite3/issues/1838 + - Modified `Database::Process()` in `src/database.cc` to detect synchronous operations + - Added test cases in `test/serialization.test.js` + - Solution: Track `pending` counter before/after callback to detect synchronous completion + +## 2026-04-04: + +### fixed: potential crash during shutdown + please see [microsoft/vscode-node-sqlite3/issues/67](https://github.com/microsoft/vscode-node-sqlite3/issues/67) + +## 2026-03-29 + +### Security Hardening Documentation +- Added comprehensive security hardening section to `build-system.md` +- Documented Linux hardening flags: `-fstack-protector-strong`, `-fPIC`, RELRO, `_FORTIFY_SOURCE=2`, CET +- Documented Windows hardening: BufferSecurityCheck, ControlFlowGuard, ASLR, DEP, /sdl +- Documented macOS hardening: `-fstack-protector-strong`, libc++ +- Added hardening decision entry to `decisionLog.md` +- Created hardening summary table comparing all platforms + +### Memory Bank Update +- Removed `NAPI_DISABLE_CPP_EXCEPTIONS` from documentation (commit 48e95e8a0d32277449c269b41fba6419acb21418) +- Updated `build-system.md` and `project-overview.md` to reflect current binding.gyp configuration + +## 2026-03-28 + +### Memory Bank Setup +- Created UMB-compliant memory-bank structure +- Added `activeContext.md` for current work tracking +- Added `progress.md` for completed work history +- Added `decisionLog.md` for technical decisions +- Updated to reflect actual project state + +### Promisification Implementation (VERIFIED COMPLETE) +- Promise-based wrapper classes implemented in [`lib/promise/`](../lib/promise/) +- `SqliteDatabase` class with full API coverage +- `SqliteStatement` class with all methods +- `SqliteBackup` class for backup operations +- Transaction support: `beginTransaction()`, `commitTransaction()`, `rollbackTransaction()` +- Static factory method `SqliteDatabase.open()` +- Tests in [`test/promise.test.js`](../test/promise.test.js) + +## v6.2.0 + +### feature: added hardening flags + +### fixed: exception handling + please see [microsoft/vscode-node-sqlite3/pull/47](https://github.com/microsoft/vscode-node-sqlite3/pull/47) + +## v6.1.1 + +### fixed: undefined behavior + please see [TryGhost/node-sqlite3/issues/1827](https://github.com/TryGhost/node-sqlite3/issues/1827) + +## 2026-03-20 + +### v6.1.0 + +### fixed: replace withdrawn SQLite 3.52.0 with stable 3.51.3 + please see [TryGhost/node-sqlite3/pull/1858](https://github.com/TryGhost/node-sqlite3/pull/1858) + +## Earlier Sessions + +### Project Setup +- Established Node.js >= 20.17.0 requirement +- Configured yarn package manager +- Set up ESLint with `.eslintrc.js` +- Configured node-gyp build system + +### Build System +- Configured Debug and Release builds +- Set up prebuild for binary distribution +- Enabled SQLite extensions: FTS3/4/5, R-Tree, math functions + +### Testing Infrastructure +- Set up mocha test framework +- Created test support utilities +- Established test database creation pattern diff --git a/memory-bank/project-overview.md b/memory-bank/project-overview.md new file mode 100644 index 000000000..992b360cb --- /dev/null +++ b/memory-bank/project-overview.md @@ -0,0 +1,173 @@ +# Project Overview + +## Project: @homeofthings/sqlite3 + +**Description**: Asynchronous, non-blocking SQLite3 bindings for Node.js + +**Repository**: https://github.com/gms1/node-sqlite3 (forked from TryGhost/node-sqlite3) + +**Package Name**: `@homeofthings/sqlite3` + +**Node.js Version**: >= 20.17.0 + +## Architecture + +``` +node-sqlite3/ +├── lib/ # JavaScript API layer +│ ├── sqlite3.js # Main module entry point +│ ├── sqlite3-binding.js # Native binding loader +│ ├── sqlite3.d.ts # TypeScript declarations +│ ├── trace.js # Stack trace augmentation for verbose mode +│ └── promise/ # Promise-based API wrappers +│ ├── index.js # Promise module exports +│ ├── database.js # SqliteDatabase class +│ ├── statement.js # SqliteStatement class +│ └── backup.js # SqliteBackup class +├── src/ # C++ native addon +│ ├── node_sqlite3.cc # Main addon entry +│ ├── database.cc/h # Database class +│ ├── statement.cc/h # Statement class +│ ├── backup.cc/h # Backup class +│ ├── async.h # Async work utilities +│ ├── macros.h # Helper macros (ASSERT_STATUS, etc.) +│ └── threading.h # Threading utilities +├── deps/ # SQLite dependency +│ ├── sqlite3.gyp # SQLite build config +│ ├── common-sqlite.gypi # Common build config +│ └── sqlite-autoconf-*.tar.gz # SQLite source +├── test/ # Test suite (mocha) +└── binding.gyp # node-gyp build configuration +``` + +## Key Components + +### JavaScript Layer (lib/) + +- **sqlite3.js**: Main module that wraps the native addon with a friendlier API + - `Database` class with methods: `prepare`, `run`, `get`, `all`, `each`, `map`, `exec`, `close` + - `Statement` class with methods: `bind`, `get`, `run`, `all`, `each`, `map`, `reset`, `finalize` + - `Backup` class for database backup operations + - Cached database support via `sqlite3.cached.Database` + - Event emitter integration for `trace`, `profile`, `change` events + +- **sqlite3-binding.js**: Loads the native addon using the `bindings` package + - Searches for `node_sqlite3.node` in build/Debug or build/Release + +- **trace.js**: Stack trace augmentation for verbose mode + - Extends error stack traces to include operation context + +- **promise/**: Promise-based API wrappers (modern async/await support) + - `SqliteDatabase` class: `open()`, `close()`, `run()`, `get()`, `all()`, `each()`, `exec()`, `prepare()`, `backup()` + - `SqliteStatement` class: `bind()`, `reset()`, `finalize()`, `run()`, `get()`, `all()`, `each()` + - `SqliteBackup` class: `step()`, `finish()` + - Transaction support: `beginTransaction()`, `commitTransaction()`, `rollbackTransaction()` + - Static factory: `SqliteDatabase.open(filename, mode)` + +### Native Layer (src/) + +- **node_sqlite3.cc**: Module initialization, exports `Database`, `Statement`, `Backup` classes +- **database.cc/h**: Database implementation with async operations +- **statement.cc/h**: Prepared statement implementation +- **backup.cc/h**: Database backup implementation +- **macros.h**: Contains `ASSERT_STATUS` macro (enabled in DEBUG mode) + +### Build System + +- **binding.gyp**: node-gyp configuration + - Builds `node_sqlite3` target + - Links against SQLite (internal or external) + - Defines `NAPI_VERSION` based on the target Node.js version + +- **deps/common-sqlite.gypi**: Common build configurations + - `Debug` configuration: disables `NDEBUG`, enables debug symbols + - `Release` configuration: enables `NDEBUG`, optimizations + +## Dependencies + +### Runtime +- `bindings`: ^1.5.0 - Native addon loader +- `node-addon-api`: ^8.0.0 - C++ NAPI wrapper +- `prebuild-install`: ^7.1.3 - Prebuilt binary downloader +- `tar`: ^7.5.10 - Tarball handling + +### Development +- `mocha`: 10.2.0 - Test framework +- `eslint`: 8.56.0 - Linting +- `prebuild`: 13.0.1 - Prebuilt binary builder +- `tinybench`: ^2.9.0 - Benchmarking + +### Peer +- `node-gyp`: 12.x - Native addon build tool + +## SQLite Configuration + +The SQLite build includes these extensions: +- `SQLITE_THREADSAFE=1` - Thread-safe +- `SQLITE_ENABLE_FTS3/4/5` - Full-text search +- `SQLITE_ENABLE_RTREE` - R-Tree index +- `SQLITE_ENABLE_DBSTAT_VTAB=1` - Database stats virtual table +- `SQLITE_ENABLE_MATH_FUNCTIONS` - Math functions + +## Debug Mode + +### JavaScript Debug Mode + +```javascript +const sqlite3 = require('@homeofthings/sqlite3').verbose(); +``` + +Enables extended stack traces for better error messages. + +### Native Debug Build + +```bash +# Build with debug configuration +node-gyp rebuild --debug + +# Output: build/Debug/node_sqlite3.node +``` + +This enables: +- `DEBUG` and `_DEBUG` preprocessor macros +- `ASSERT_STATUS()` macro checks (see src/macros.h:140) +- Debug symbols +- No optimizations + +## Module Resolution + +The `bindings` package searches for the native addon in this order: +1. `build/Debug/node_sqlite3.node` +2. `build/Release/node_sqlite3.node` + +If both exist, Debug takes precedence. + +## Common Commands + +```bash +# Install dependencies +yarn install + +# Build native addon (uses prebuild or falls back to node-gyp) +yarn install + +# Rebuild native addon +yarn rebuild + +# Build with debug configuration +node-gyp rebuild --debug + +# Run tests +yarn test + +# Run driver comparison benchmarks +cd tools/benchmark-drivers && npm install && node index.js + +# Run internal benchmarks +node tools/benchmark-internal/run.js +``` + +## Related Files + +- [Build System](build-system.md) - Detailed build configuration +- [Development Workflow](development.md) - Testing and contributing diff --git a/package.json b/package.json index 08f4b7e18..fe21b31dc 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "sqlite3", + "name": "@homeofthings/sqlite3", "description": "Asynchronous, non-blocking SQLite3 bindings", - "version": "6.0.1", - "homepage": "https://github.com/TryGhost/node-sqlite3", + "version": "6.3.1", + "homepage": "https://github.com/gms1/node-sqlite3", "author": { "name": "Mapbox", "url": "https://mapbox.com/" @@ -35,24 +35,28 @@ "files": [ "binding.gyp", "deps/", - "lib/*.js", - "lib/*.d.ts", + "lib/**/*.js", + "lib/**/*.d.ts", "src/" ], "repository": { "type": "git", - "url": "https://github.com/TryGhost/node-sqlite3.git" + "url": "https://github.com/gms1/node-sqlite3.git" }, "dependencies": { "bindings": "^1.5.0", - "node-addon-api": "^8.0.0", + "node-addon-api": "^8.7.0", "prebuild-install": "^7.1.3", - "tar": "^7.5.10" + "tar": "^7.5.13" }, "devDependencies": { - "eslint": "8.56.0", - "mocha": "10.2.0", - "prebuild": "13.0.1" + "@eslint/js": "^10.0.1", + "eslint": "^10.2.0", + "globals": "^17.4.0", + "mocha": "11.7.5", + "nyc": "^18.0.0", + "prebuild": "13.0.1", + "tinybench": "^6.0.0" }, "peerDependencies": { "node-gyp": "12.x" @@ -73,9 +77,14 @@ "prebuild": "prebuild --runtime napi --all --verbose", "rebuild": "node-gyp rebuild", "upload": "prebuild --verbose --prerelease", - "test": "node test/support/createdb.js && mocha -R spec --timeout 480000" + "frozen-install": "yarn install --frozen-lockfile", + "lint": "eslint lib/ test/ tools/", + "test": "node test/support/createdb.js && nyc mocha -R spec --timeout 480000" }, "license": "BSD-3-Clause", + "publishConfig": { + "access": "public" + }, "keywords": [ "sql", "sqlite", @@ -83,10 +92,5 @@ "database" ], "main": "./lib/sqlite3", - "types": "./lib/sqlite3.d.ts", - "renovate": { - "extends": [ - "@tryghost:base" - ] - } + "types": "./lib/sqlite3.d.ts" } diff --git a/src/database.cc b/src/database.cc index d495ce98f..f7fc5ea6f 100644 --- a/src/database.cc +++ b/src/database.cc @@ -14,7 +14,7 @@ Napi::FunctionReference Database::constructor; Napi::Object Database::Init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); // declare napi_default_method here as it is only available in Node v14.12.0+ - auto napi_default_method = static_cast(napi_writable | napi_configurable); + auto napi_default_method = static_cast(napi_writable | napi_configurable); auto t = DefineClass(env, "Database", { InstanceMethod("close", &Database::Close, napi_default_method), @@ -81,8 +81,21 @@ void Database::Process() { queue.pop(); std::unique_ptr call(c); locked = call->exclusive; + + // Track pending before callback to detect synchronous operations + unsigned int before_pending = pending; call->callback(call->baton); + // If operation was synchronous (pending unchanged) and we're in exclusive mode, + // reset locked and continue processing the queue. + // Synchronous operations don't increment pending, so if pending is unchanged + // and locked is true, the operation completed synchronously. + if (locked && pending == before_pending) { + locked = false; + // Continue processing - don't break + continue; + } + if (locked) break; } } @@ -340,7 +353,7 @@ Napi::Value Database::Configure(const Napi::CallbackInfo& info) { REQUIRE_ARGUMENTS(2); Napi::Function handle; - if (info[0].StrictEquals( Napi::String::New(env, "trace"))) { + if (info[0].StrictEquals( Napi::String::New(env, "trace"))) { auto* baton = new Baton(db, handle); db->Schedule(RegisterTraceCallback, baton); } diff --git a/src/macros.h b/src/macros.h index 3bcde83c6..0f2a03c8e 100644 --- a/src/macros.h +++ b/src/macros.h @@ -4,9 +4,14 @@ const char* sqlite_code_string(int code); const char* sqlite_authorizer_string(int type); #include +#include // TODO: better way to work around StringConcat? #include + +// Forward declaration of shutdown flag from node_sqlite3.cc +extern std::atomic g_env_shutting_down; + inline Napi::String StringConcat(Napi::Value str1, Napi::Value str2) { return Napi::String::New(str1.Env(), str1.As().Utf8Value() + str2.As().Utf8Value() ); @@ -128,8 +133,21 @@ inline bool OtherIsInt(Napi::Number source) { if ((argc != 0) && (passed_argv != NULL)) {\ args.assign(passed_argv, passed_argv + argc);\ }\ - Napi::Value res = (callback).Call(Napi::Value(context), args); \ - if (res.IsEmpty()) return __VA_ARGS__; + try {\ + Napi::Value res = (callback).Call(Napi::Value(context), args);\ + if (res.IsEmpty()) return __VA_ARGS__;\ + } catch (const Napi::Error&) {\ + /* During Node.js/Electron shutdown, when using NAPI_CPP_EXCEPTIONS,*/\ + /* we must take care to not throw any exceptions. */\ + /* But when napi_call_function returns napi_cannot_run_js */\ + /* this throws a C++ exception, when NAPI_CPP_EXCEPTIONS is enabled. */\ + if (g_env_shutting_down.load()) {\ + /* We need to silently ignore exceptions during shutdown. */\ + return __VA_ARGS__;\ + }\ + /* Real rror - re-throw to propagate it */\ + throw;\ + } #define WORK_DEFINITION(name) \ Napi::Value name(const Napi::CallbackInfo& info); \ @@ -204,4 +222,4 @@ inline bool OtherIsInt(Napi::Number source) { backup->Process(); \ backup->db->Process(); -#endif \ No newline at end of file +#endif diff --git a/src/node_sqlite3.cc b/src/node_sqlite3.cc index 6f47a68a8..a566ee738 100644 --- a/src/node_sqlite3.cc +++ b/src/node_sqlite3.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include #include "macros.h" @@ -11,11 +12,21 @@ using namespace node_sqlite3; +// Global flag set when the environment is shutting down. +std::atomic g_env_shutting_down{false}; + +static void EnvCleanupHook(void* /*data*/) { + g_env_shutting_down.store(true); +} + namespace { Napi::Object RegisterModule(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); + // Register cleanup hook to detect shutdown + napi_add_env_cleanup_hook(env, EnvCleanupHook, nullptr); + Database::Init(env, exports); Statement::Init(env, exports); Backup::Init(env, exports); diff --git a/src/statement.h b/src/statement.h index c522c0fdf..a35186b8a 100644 --- a/src/statement.h +++ b/src/statement.h @@ -57,7 +57,9 @@ namespace Values { Field(_name, SQLITE_BLOB), length(len) { value = new char[len]; assert(value != nullptr); - memcpy(value, val, len); + if (len > 0) { + memcpy(value, val, len); + } } inline virtual ~Blob() override { delete[] value; diff --git a/test/.eslintrc.js b/test/.eslintrc.js deleted file mode 100644 index 41371e2b6..000000000 --- a/test/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - env: { - mocha: true, - }, -}; diff --git a/test/async_calls.test.js b/test/async_calls.test.js index 9ce29a730..120aeee48 100644 --- a/test/async_calls.test.js +++ b/test/async_calls.test.js @@ -1,4 +1,4 @@ -"use strict" +"use strict"; var sqlite3 = require('..'); const assert = require("assert"); diff --git a/test/blob.test.js b/test/blob.test.js index 5c2b0d857..e09e033d2 100644 --- a/test/blob.test.js +++ b/test/blob.test.js @@ -51,4 +51,19 @@ describe('blob', function() { done(); }); }); + + it('should be able to select empty blobs', function (done) { + const empty = new sqlite3.Database(':memory:'); + empty.serialize(function () { + empty.run("CREATE TABLE files (id INTEGER PRIMARY KEY, data BLOB)"); + empty.run("INSERT INTO files (data) VALUES (X'')"); + }); + empty.get("SELECT data FROM files LIMIT 1", (err, row) => { + if (err) throw err; + assert.ok(Buffer.isBuffer(row.data)); + assert.equal(row.data.length, 0); + empty.close(done); + }); + }); + }); diff --git a/test/nw/Makefile b/test/nw/Makefile index 9c8223301..6b30809a8 100755 --- a/test/nw/Makefile +++ b/test/nw/Makefile @@ -2,11 +2,11 @@ NODE_WEBKIT_VERSION=0.8.4 all: app.nw -node_modules/sqlite3: - npm install https://github.com/mapbox/node-sqlite3/tarball/master --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) +node_modules/@homeofthings/sqlite3: + npm install https://github.com/gms1/node-sqlite3/tarball/main --build-from-source --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) rebuild: - cd node_modules/sqlite3 && ./node_modules/.bin/node-pre-gyp rebuild --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) + cd node_modules/@homeofthings/sqlite3 && ./node_modules/.bin/node-pre-gyp rebuild --runtime=node-webkit --target_arch=ia32 --target=$(NODE_WEBKIT_VERSION) node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip: wget https://s3.amazonaws.com/node-webkit/v$(NODE_WEBKIT_VERSION)/node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip @@ -14,13 +14,13 @@ node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip: ./node-webkit.app: node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip unzip -o node-webkit-v$(NODE_WEBKIT_VERSION)-osx-ia32.zip -app.nw: ./node-webkit.app Makefile package.json index.html node_modules/sqlite3 +app.nw: ./node-webkit.app Makefile package.json index.html node_modules/@homeofthings/sqlite3 zip app.nw index.html package.json node_modules test: ./node-webkit.app app.nw ./node-webkit.app/Contents/MacOS/node-webkit app.nw -package: ./node-webkit.app Makefile package.json index.html node_modules/sqlite3 +package: ./node-webkit.app Makefile package.json index.html node_modules/@homeofthings/sqlite3 rm -rf node-sqlite-test.app cp -r ./node-webkit.app node-sqlite-test.app mkdir ./node-sqlite-test.app/Contents/Resources/app.nw/ @@ -30,7 +30,7 @@ package: ./node-webkit.app Makefile package.json index.html node_modules/sqlite3 ./node-sqlite-test.app/Contents/MacOS/node-webkit clean: - rm -rf ./node_modules/sqlite3 + rm -rf ./node_modules/@homeofthings/sqlite3 rm -f ./app.nw rm -rf node-sqlite-test.app rm -f credits.html diff --git a/test/nw/index.html b/test/nw/index.html index 9a1bb906c..2b2aa51f2 100644 --- a/test/nw/index.html +++ b/test/nw/index.html @@ -7,8 +7,8 @@

Hello World!

Using node-sqlite3: . - \ No newline at end of file + diff --git a/test/patching.test.js b/test/patching.test.js index 3f9f68d59..6a88b25ce 100644 --- a/test/patching.test.js +++ b/test/patching.test.js @@ -1,5 +1,6 @@ var sqlite3 = require('..'); var assert = require('assert'); +var helper = require('./support/helper'); describe('patching', function() { describe("Database", function() { @@ -20,8 +21,8 @@ describe('patching', function() { it('allow patching native functions', function() { var myFun = function myFunction() { return "Success"; - } - + }; + assert.doesNotThrow(() => { sqlite3.Database.prototype.close = myFun; }); @@ -91,8 +92,8 @@ describe('patching', function() { it('allow patching native functions', function() { var myFun = function myFunction() { return "Success"; - } - + }; + assert.doesNotThrow(() => { sqlite3.Statement.prototype.bind = myFun; }); @@ -147,6 +148,10 @@ describe('patching', function() { var backup; var originalFunctions = {}; + before(function () { + helper.ensureExists('test/tmp'); + }); + before(function() { originalFunctions.step = sqlite3.Backup.prototype.step; originalFunctions.finish = sqlite3.Backup.prototype.finish; @@ -155,8 +160,8 @@ describe('patching', function() { it('allow patching native functions', function() { var myFun = function myFunction() { return "Success"; - } - + }; + assert.doesNotThrow(() => { sqlite3.Backup.prototype.step = myFun; }); @@ -165,7 +170,7 @@ describe('patching', function() { }); db = new sqlite3.Database(':memory:'); - backup = db.backup("somefile", myFun); + backup = db.backup("test/tmp/patching_backup.db", myFun); assert.strictEqual(backup.step(), "Success"); assert.strictEqual(backup.finish(), "Success"); }); @@ -176,6 +181,7 @@ describe('patching', function() { sqlite3.Backup.prototype.finish = originalFunctions.finish; backup.finish(); } + helper.deleteFile('test/tmp/patching_backup.db'); if(db != null) { db.close(); } diff --git a/test/promise.test.js b/test/promise.test.js new file mode 100644 index 000000000..7e6206536 --- /dev/null +++ b/test/promise.test.js @@ -0,0 +1,701 @@ +/** + * Tests for Promise-based wrapper classes + */ + +'use strict'; + +const assert = require('assert'); +const sqlite3 = require('..'); +const { SqliteDatabase, SqliteStatement, SqliteBackup } = sqlite3; +const helper = require('./support/helper'); + +describe('SqliteDatabase', () => { + describe('open/close', () => { + it('should open and close database with constructor + open', async () => { + const db = new SqliteDatabase(); + await db.open(':memory:'); + assert.strictEqual(db.isOpen(), true); + await db.close(); + assert.strictEqual(db.isOpen(), false); + }); + + it('should open and close database with static factory method', async () => { + const db = await SqliteDatabase.open(':memory:'); + assert.strictEqual(db.isOpen(), true); + await db.close(); + assert.strictEqual(db.isOpen(), false); + }); + + it('should open database with mode flags', async () => { + const db = await SqliteDatabase.open(':memory:', sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE); + assert.strictEqual(db.isOpen(), true); + await db.close(); + }); + + it('should handle close on unopened database', async () => { + const db = new SqliteDatabase(); + assert.strictEqual(db.isOpen(), false); + await db.close(); // Should not throw + assert.strictEqual(db.isOpen(), false); + }); + + it('should handle errors when opening database', async () => { + const db = new SqliteDatabase(); + await assert.rejects( + async () => db.open('/nonexistent/path/database.db', sqlite3.OPEN_READONLY), + /Error/ + ); + }); + }); + + describe('exec', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should execute SQL statements', async () => { + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + await db.exec("INSERT INTO test VALUES (1, 'hello')"); + const result = await db.get('SELECT * FROM test WHERE id = ?', 1); + assert.strictEqual(result.value, 'hello'); + }); + + it('should reject on SQL errors', async () => { + await assert.rejects( + async () => db.exec('INVALID SQL'), + /Error/ + ); + }); + }); + + describe('run', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should run INSERT and return lastID and changes', async () => { + const result = await db.run("INSERT INTO test (value) VALUES ('hello')"); + assert.strictEqual(result.lastID, 1); + assert.strictEqual(result.changes, 1); + }); + + it('should run UPDATE and return changes', async () => { + await db.run("INSERT INTO test (value) VALUES ('hello')"); + await db.run("INSERT INTO test (value) VALUES ('world')"); + const result = await db.run("UPDATE test SET value = 'updated' WHERE id = ?", 1); + assert.strictEqual(result.changes, 1); + }); + + it('should run DELETE and return changes', async () => { + await db.run("INSERT INTO test (value) VALUES ('hello')"); + const result = await db.run('DELETE FROM test WHERE id = ?', 1); + assert.strictEqual(result.changes, 1); + }); + }); + + describe('get', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + await db.run("INSERT INTO test (value) VALUES ('hello')"); + await db.run("INSERT INTO test (value) VALUES ('world')"); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should get a single row', async () => { + const row = await db.get('SELECT * FROM test WHERE id = ?', 1); + assert.strictEqual(row.id, 1); + assert.strictEqual(row.value, 'hello'); + }); + + it('should return undefined for no rows', async () => { + const row = await db.get('SELECT * FROM test WHERE id = ?', 999); + assert.strictEqual(row, undefined); + }); + + it('should work with named parameters', async () => { + const row = await db.get('SELECT * FROM test WHERE id = $id', { $id: 1 }); + assert.strictEqual(row.id, 1); + }); + }); + + describe('all', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + await db.run("INSERT INTO test (value) VALUES ('one')"); + await db.run("INSERT INTO test (value) VALUES ('two')"); + await db.run("INSERT INTO test (value) VALUES ('three')"); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should get all rows', async () => { + const rows = await db.all('SELECT * FROM test ORDER BY id'); + assert.strictEqual(rows.length, 3); + assert.strictEqual(rows[0].value, 'one'); + assert.strictEqual(rows[1].value, 'two'); + assert.strictEqual(rows[2].value, 'three'); + }); + + it('should return empty array for no rows', async () => { + const rows = await db.all('SELECT * FROM test WHERE id > ?', 999); + assert.strictEqual(rows.length, 0); + assert(Array.isArray(rows)); + }); + }); + + describe('each', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + await db.run("INSERT INTO test (value) VALUES ('one')"); + await db.run("INSERT INTO test (value) VALUES ('two')"); + await db.run("INSERT INTO test (value) VALUES ('three')"); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should iterate over rows with callback', async () => { + const values = []; + const count = await db.each('SELECT * FROM test ORDER BY id', (err, row) => { + if (err) throw err; + values.push(row.value); + }); + assert.strictEqual(count, 3); + assert.deepStrictEqual(values, ['one', 'two', 'three']); + }); + + it('should work with parameters', async () => { + const values = []; + const count = await db.each('SELECT * FROM test WHERE id <= ?', 2, (err, row) => { + if (err) throw err; + values.push(row.value); + }); + assert.strictEqual(count, 2); + assert.deepStrictEqual(values, ['one', 'two']); + }); + }); + + describe('prepare', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should prepare a statement', async () => { + const stmt = await db.prepare('SELECT * FROM test'); + assert(stmt instanceof SqliteStatement); + await stmt.finalize(); + }); + + it('should prepare and bind parameters', async () => { + const stmt = await db.prepare('INSERT INTO test (value) VALUES (?)'); + await stmt.run('hello'); + await stmt.finalize(); + + const row = await db.get('SELECT * FROM test WHERE id = ?', 1); + assert.strictEqual(row.value, 'hello'); + }); + }); + + describe('transaction helpers', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should begin, commit transaction', async () => { + await db.beginTransaction(); + await db.run("INSERT INTO test (value) VALUES ('test')"); + await db.commitTransaction(); + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 1); + }); + + it('should rollback transaction', async () => { + await db.beginTransaction(); + await db.run("INSERT INTO test (value) VALUES ('test')"); + await db.rollbackTransaction(); + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 0); + }); + + it('should use transactionalize helper', async () => { + await db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('test1')"); + await db.run("INSERT INTO test (value) VALUES ('test2')"); + }); + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 2); + }); + + it('should rollback on error in transactionalize', async () => { + await assert.rejects( + async () => db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('test1')"); + throw new Error('Test error'); + }), + /Test error/ + ); + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 0); + }); + }); + + describe('serialize/parallelize', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should serialize operations', (done) => { + db.serialize(async () => { + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY)'); + done(); + }); + }); + + it('should parallelize operations', (done) => { + db.parallelize(async () => { + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY)'); + done(); + }); + }); + }); + + describe('events', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should emit trace event', async () => { + let tracedSql = null; + db.on('trace', (sql) => { + tracedSql = sql; + }); + + await db.exec('CREATE TABLE test (id INTEGER)'); + + // Note: trace event may not be emitted in all configurations + // This test verifies the event listener can be registered + }); + + it('should remove event listener', async () => { + const listener = () => {}; + db.on('trace', listener); + db.off('trace', listener); + // Should not throw + }); + + it('should remove all listeners', async () => { + db.on('trace', () => {}); + db.on('error', () => {}); + db.removeAllListeners('trace'); + // Should not throw + }); + + it('should throw when calling methods on unopened database', async () => { + const unopened = new SqliteDatabase(); + assert.strictEqual(unopened.isOpen(), false); + + assert.throws(() => unopened.interrupt(), /database connection not open/); + assert.throws(() => unopened.configure('busyTimeout', 1000), /database connection not open/); + assert.throws(() => unopened.on('trace', () => {}), /database connection not open/); + assert.throws(() => unopened.off('trace', () => {}), /database connection not open/); + assert.throws(() => unopened.removeAllListeners('trace'), /database connection not open/); + }); + + it('should reject when calling async methods on unopened database', async () => { + const unopened = new SqliteDatabase(); + + // close() on unopened database resolves successfully (safe to call) + await unopened.close(); // Should not throw + + await assert.rejects(async () => unopened.run('SELECT 1'), /database connection not open/); + await assert.rejects(async () => unopened.get('SELECT 1'), /database connection not open/); + await assert.rejects(async () => unopened.all('SELECT 1'), /database connection not open/); + await assert.rejects(async () => unopened.each('SELECT 1', () => {}), /database connection not open/); + await assert.rejects(async () => unopened.exec('SELECT 1'), /database connection not open/); + await assert.rejects(async () => unopened.prepare('SELECT 1'), /database connection not open/); + await assert.rejects(async () => unopened.backup('test.db'), /database connection not open/); + await assert.rejects(async () => unopened.wait(), /database connection not open/); + }); + + it('should throw when calling serialize/parallelize on unopened database', async () => { + const unopened = new SqliteDatabase(); + assert.throws(() => unopened.serialize(), /database connection not open/); + assert.throws(() => unopened.parallelize(), /database connection not open/); + }); + + it('should handle close errors', async () => { + const db = await SqliteDatabase.open(':memory:'); + await db.close(); + // Second close should succeed (no error) + await db.close(); + }); + + it('should handle run/get/all/each errors', async () => { + const db = await SqliteDatabase.open(':memory:'); + + await assert.rejects(async () => db.run('INVALID SQL'), /Error/); + await assert.rejects(async () => db.get('INVALID SQL'), /Error/); + await assert.rejects(async () => db.all('INVALID SQL'), /Error/); + await assert.rejects(async () => db.each('INVALID SQL', () => {}), /Error/); + + await db.close(); + }); + + it('should handle prepare errors', async () => { + const db = await SqliteDatabase.open(':memory:'); + + await assert.rejects(async () => db.prepare('INVALID SQL'), /Error/); + + await db.close(); + }); + + it('should wait for database', async () => { + const db = await SqliteDatabase.open(':memory:'); + await db.wait(); + await db.close(); + }); + + it('should interrupt queries', async () => { + const db = await SqliteDatabase.open(':memory:'); + // interrupt() should not throw + db.interrupt(); + await db.close(); + }); + + it('should configure database', async () => { + const db = await SqliteDatabase.open(':memory:'); + db.configure('busyTimeout', 1000); + await db.close(); + }); + + it('should handle backup errors', async () => { + const db = await SqliteDatabase.open(':memory:'); + + // Try to backup to an invalid path + await assert.rejects(async () => db.backup('/nonexistent/path/backup.db'), /Error/); + + await db.close(); + }); + + it('should use endTransaction for commit', async () => { + const db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY)'); + + await db.beginTransaction(); + await db.run('INSERT INTO test VALUES (1)'); + await db.endTransaction(true); // commit + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 1); + + await db.close(); + }); + + it('should use endTransaction for rollback', async () => { + const db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY)'); + + await db.beginTransaction(); + await db.run('INSERT INTO test VALUES (1)'); + await db.endTransaction(false); // rollback + + const row = await db.get('SELECT COUNT(*) as count FROM test'); + assert.strictEqual(row.count, 0); + + await db.close(); + }); + + it('should reject endTransaction on unopened database', async () => { + const db = new SqliteDatabase(); + await assert.rejects(async () => db.endTransaction(true), /database connection not open/); + }); + + it('should reject loadExtension on unopened database', async () => { + const db = new SqliteDatabase(); + await assert.rejects(async () => db.loadExtension('test.ext'), /database connection not open/); + }); + }); +}); + +describe('SqliteStatement', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + describe('run', () => { + it('should run prepared statement', async () => { + const stmt = await db.prepare('INSERT INTO test (value) VALUES (?)'); + const result = await stmt.run('hello'); + assert.strictEqual(result.lastID, 1); + assert.strictEqual(result.changes, 1); + await stmt.finalize(); + }); + + it('should run multiple times', async () => { + const stmt = await db.prepare('INSERT INTO test (value) VALUES (?)'); + + await stmt.run('one'); + await stmt.run('two'); + await stmt.run('three'); + + await stmt.finalize(); + + const rows = await db.all('SELECT * FROM test ORDER BY id'); + assert.strictEqual(rows.length, 3); + }); + }); + + describe('get', () => { + it('should get single row', async () => { + await db.run("INSERT INTO test (value) VALUES ('hello')"); + + const stmt = await db.prepare('SELECT * FROM test WHERE id = ?'); + const row = await stmt.get(1); + assert.strictEqual(row.value, 'hello'); + await stmt.finalize(); + }); + + it('should return undefined for no rows', async () => { + const stmt = await db.prepare('SELECT * FROM test WHERE id = ?'); + const row = await stmt.get(999); + assert.strictEqual(row, undefined); + await stmt.finalize(); + }); + }); + + describe('all', () => { + it('should get all rows', async () => { + await db.run("INSERT INTO test (value) VALUES ('one')"); + await db.run("INSERT INTO test (value) VALUES ('two')"); + + const stmt = await db.prepare('SELECT * FROM test ORDER BY id'); + const rows = await stmt.all(); + assert.strictEqual(rows.length, 2); + await stmt.finalize(); + }); + }); + + describe('each', () => { + it('should iterate over rows', async () => { + await db.run("INSERT INTO test (value) VALUES ('one')"); + await db.run("INSERT INTO test (value) VALUES ('two')"); + + const values = []; + const stmt = await db.prepare('SELECT * FROM test ORDER BY id'); + const count = await stmt.each((err, row) => { + if (err) throw err; + values.push(row.value); + }); + assert.strictEqual(count, 2); + assert.deepStrictEqual(values, ['one', 'two']); + await stmt.finalize(); + }); + }); + + describe('bind', () => { + it('should bind parameters', async () => { + const stmt = await db.prepare('INSERT INTO test (value) VALUES (?)'); + stmt.bind('bound value'); + await stmt.run(); + await stmt.finalize(); + + const row = await db.get('SELECT * FROM test WHERE id = ?', 1); + assert.strictEqual(row.value, 'bound value'); + }); + }); + + describe('reset', () => { + it('should reset statement', async () => { + const stmt = await db.prepare('SELECT * FROM test'); + await stmt.reset(); + await stmt.finalize(); + // Should not throw + }); + }); + + describe('errors', () => { + it('should reject on statement finalize error when statement is invalid', async () => { + // This is hard to test without corrupting the database + // Just verify finalize works normally + const stmt = await db.prepare('SELECT * FROM test'); + await stmt.finalize(); + }); + }); +}); + +describe('SqliteBackup', () => { + before(function() { + helper.ensureExists('test/tmp'); + }); + + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + await db.run("INSERT INTO test (value) VALUES ('test data')"); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should create backup', async () => { + const backup = await db.backup('test/tmp/backup.db'); + assert(backup instanceof SqliteBackup); + backup.finish(); + + // Clean up + helper.deleteFile('test/tmp/backup.db'); + }); + + it('should step through backup', async () => { + const backup = await db.backup('test/tmp/backup2.db'); + + // Step through all pages + await backup.step(-1); + + assert.strictEqual(backup.completed, true); + assert.strictEqual(backup.idle, true); + + backup.finish(); + + // Clean up + helper.deleteFile('test/tmp/backup2.db'); + }); + + it('should report progress', async () => { + const backup = await db.backup('test/tmp/backup3.db'); + + // Before stepping + assert.strictEqual(backup.progress, 0); + + // Step through all pages + await backup.step(-1); + + // After completion + assert.strictEqual(backup.progress, 100); + + backup.finish(); + + // Clean up + helper.deleteFile('test/tmp/backup3.db'); + }); + + it('should handle backup errors', async () => { + // Close the database before backup finishes + const backup = await db.backup('test/tmp/backup4.db'); + await backup.step(-1); + backup.finish(); + + // Clean up + helper.deleteFile('test/tmp/backup4.db'); + }); +}); + +describe('TypeScript generics (demonstration)', () => { + // Note: TypeScript generics are only available in TypeScript files. + // This test demonstrates that the JavaScript API works correctly. + // In TypeScript, you would use: + // const user = await db.get('SELECT * FROM users WHERE id = ?', 1); + // const users = await db.all('SELECT * FROM users'); + + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should work with typed results (JS)', async () => { + await db.run("INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"); + + // In JavaScript, the result is just an object with the expected properties + const user = await db.get('SELECT * FROM users WHERE id = ?', 1); + assert.strictEqual(user.name, 'Alice'); + assert.strictEqual(user.email, 'alice@example.com'); + + const users = await db.all('SELECT * FROM users'); + assert.strictEqual(users.length, 1); + assert.strictEqual(users[0].name, 'Alice'); + }); + + it('should work with statement generics (JS)', async () => { + await db.run("INSERT INTO users (name, email) VALUES ('Bob', 'bob@example.com')"); + + // In JavaScript, the result is just an object with the expected properties + const stmt = await db.prepare('SELECT * FROM users WHERE id = ?'); + const user = await stmt.get(1); + assert.strictEqual(user.name, 'Bob'); + await stmt.finalize(); + }); +}); diff --git a/test/serialization.test.js b/test/serialization.test.js index ccc586734..4baddab6b 100644 --- a/test/serialization.test.js +++ b/test/serialization.test.js @@ -102,3 +102,80 @@ describe('serialize(fn)', function() { after(function(done) { db.close(done); }); }); + +describe('serialize() queue processing with synchronous operations', function () { + var db; + + beforeEach(function (done) { + db = new sqlite3.Database(':memory:', done); + }); + + afterEach(function (done) { + db.close(done); + }); + + it('should process queued operations after configure in serialized mode', function (done) { + // This test reproduces the bug where operations get stuck in the queue + // when db.configure() is called during serialized mode with pending async work. + // See: https://github.com/TryGhost/node-sqlite3/issues/1838 + + var LONG_QUERY = `WITH recursive recur(n) + AS (SELECT 1 + UNION ALL + SELECT n + 1 + FROM recur where n < 1000000 + ) + SELECT n FROM recur;`; + + var executed = false; + + db.serialize(); + + // Start a long-running async operation + db.exec(LONG_QUERY); + + // Queue a synchronous configure operation + db.configure('limit', sqlite3.LIMIT_ATTACHED, 1); + + // Queue another operation - this should execute after configure + db.exec("SELECT 1", function (err) { + if (err) return done(err); + executed = true; + }); + + // Give enough time for the bug to manifest + setTimeout(function () { + if (executed) { + done(); + } else { + done(new Error('Queue processing deadlock: SELECT 1 callback was never called')); + } + }, 2000); + }); + + it('should process multiple configure calls in serialized mode', function (done) { + var executed = false; + + db.serialize(); + + db.exec("CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"); + db.exec("INSERT INTO test VALUES (1, 'one')"); + + // Multiple configure calls should all be processed + db.configure('limit', sqlite3.LIMIT_ATTACHED, 1); + db.configure('busyTimeout', 1000); + + db.exec("SELECT * FROM test", function (err, rows) { + if (err) return done(err); + executed = true; + }); + + setTimeout(function () { + if (executed) { + done(); + } else { + done(new Error('Queue processing deadlock: SELECT callback was never called')); + } + }, 1000); + }); +}); diff --git a/test/support/createdb.js b/test/support/createdb.js index ee22d05e8..d1225d076 100755 --- a/test/support/createdb.js +++ b/test/support/createdb.js @@ -19,7 +19,7 @@ function createdb(callback) { return str; } -// Make sure the file exists and is also valid. + // Make sure the file exists and is also valid. if (existsSync(db_path) && statSync(db_path).size !== 0) { console.log('okay: database already created (' + db_path + ')'); if (callback) callback(); diff --git a/test/transaction_isolation.test.js b/test/transaction_isolation.test.js new file mode 100644 index 000000000..4a41a33e6 --- /dev/null +++ b/test/transaction_isolation.test.js @@ -0,0 +1,269 @@ +/** + * Tests for transaction isolation behavior + * + * Key insight from SQLite documentation: + * - Isolation EXISTS between different database connections + * - NO isolation between operations on the SAME database connection + */ + +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const sqlite3 = require('..'); +const { SqliteDatabase } = sqlite3; +const helper = require('./support/helper'); + +// Helper function to generate unique database paths +let dbCounter = 0; +function newDatabasePath() { + const tmpDir = path.join(__dirname, 'tmp'); + if (!fs.existsSync(tmpDir)) { + fs.mkdirSync(tmpDir, { recursive: true }); + } + return path.join(tmpDir, `transaction_test_${Date.now()}_${++dbCounter}.db`); +} + +describe('Transaction Isolation', () => { + describe('single connection behavior', () => { + let db; + + beforeEach(async () => { + db = await SqliteDatabase.open(':memory:'); + await db.exec('CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)'); + }); + + afterEach(async () => { + await db.close(); + }); + + it('should handle sequential transactions correctly', async () => { + // Sequential transactions on same connection should work fine + await db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('tx1')"); + }); + + await db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('tx2')"); + }); + + const rows = await db.all('SELECT * FROM test ORDER BY id'); + assert.strictEqual(rows.length, 2); + assert.strictEqual(rows[0].value, 'tx1'); + assert.strictEqual(rows[1].value, 'tx2'); + }); + + it('should handle nested transactionalize calls (same connection)', async () => { + // Note: This tests behavior but is NOT recommended practice + // Nested transactions on same connection share the same transaction + let insertedInNested = false; + + await db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('outer')"); + + // Inner transactionalize will start a new transaction + // but since we're on the same connection, it's actually the same transaction + try { + await db.transactionalize(async () => { + await db.run("INSERT INTO test (value) VALUES ('inner')"); + insertedInNested = true; + }); + } catch (err) { + // May fail because there's already an active transaction + } + }); + + const rows = await db.all('SELECT * FROM test ORDER BY id'); + // At minimum, outer should be inserted + assert.strictEqual(rows.length >= 1, true); + }); + + it('should demonstrate that concurrent operations on same connection are serialized', async () => { + // JavaScript is single-threaded, so even with Promise.all, + // operations on the same connection are serialized + const results = await Promise.all([ + db.run("INSERT INTO test (value) VALUES ('concurrent1')"), + db.run("INSERT INTO test (value) VALUES ('concurrent2')"), + ]); + + const rows = await db.all('SELECT * FROM test ORDER BY id'); + assert.strictEqual(rows.length, 2); + }); + }); + + describe('multiple connections behavior', () => { + let db1, db2; + + beforeEach(async () => { + // Create a file-based database for cross-connection tests + const dbPath = newDatabasePath(); + db1 = await SqliteDatabase.open(dbPath); + db2 = await SqliteDatabase.open(dbPath); + + await db1.exec('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, value TEXT)'); + // Clear any existing data + await db1.exec('DELETE FROM test'); + }); + + afterEach(async () => { + await db1.close(); + await db2.close(); + }); + + it('should provide isolation between concurrent transactions on different connections', async () => { + // Start transaction on db1 + await db1.beginTransaction(); + + // Insert in db1's transaction (not committed yet) + await db1.run("INSERT INTO test (value) VALUES ('from_db1')"); + + // db2 should NOT see uncommitted changes from db1 + const rowsBeforeCommit = await db2.all('SELECT * FROM test'); + assert.strictEqual(rowsBeforeCommit.length, 0, 'db2 should not see uncommitted changes'); + + // Commit db1's transaction + await db1.commitTransaction(); + + // Now db2 should see the committed changes + const rowsAfterCommit = await db2.all('SELECT * FROM test'); + assert.strictEqual(rowsAfterCommit.length, 1, 'db2 should see committed changes'); + assert.strictEqual(rowsAfterCommit[0].value, 'from_db1'); + }); + + it('should handle concurrent transactions on different connections', async () => { + // NOTE: This test demonstrates that concurrent write transactions + // on different connections to the same database will fail with SQLITE_BUSY + // because BEGIN IMMEDIATE TRANSACTION acquires a write lock. + // This is expected SQLite behavior + + // Start transaction on db1 first + await db1.beginTransaction(); + + // db2 trying to start a transaction while db1 has the lock should fail + await assert.rejects( + async () => await db2.beginTransaction(), + /SQLITE_BUSY|database is locked/, + 'db2 should fail to start transaction while db1 has write lock' + ); + + // db1 can still insert + await db1.run("INSERT INTO test (value) VALUES ('from_db1')"); + + // Commit db1 + await db1.commitTransaction(); + + // Now db2 can start its transaction + await db2.beginTransaction(); + await db2.run("INSERT INTO test (value) VALUES ('from_db2')"); + await db2.commitTransaction(); + + // Both should see all committed data + const rowsFinal1 = await db1.all('SELECT * FROM test ORDER BY value'); + const rowsFinal2 = await db2.all('SELECT * FROM test ORDER BY value'); + + assert.strictEqual(rowsFinal1.length, 2); + assert.strictEqual(rowsFinal2.length, 2); + }); + + it('should handle rollback isolation between connections', async () => { + // db1 starts and completes its transaction first + await db1.beginTransaction(); + await db1.run("INSERT INTO test (value) VALUES ('from_db1')"); + await db1.rollbackTransaction(); + + // Now db2 can start its transaction + await db2.beginTransaction(); + await db2.run("INSERT INTO test (value) VALUES ('from_db2')"); + await db2.commitTransaction(); + + // Both should only see db2's committed data (db1 was rolled back) + const rows1 = await db1.all('SELECT * FROM test'); + const rows2 = await db2.all('SELECT * FROM test'); + + assert.strictEqual(rows1.length, 1); + assert.strictEqual(rows2.length, 1); + assert.strictEqual(rows1[0].value, 'from_db2'); + }); + + it('should demonstrate proper isolation with transactionalize on separate connections', async () => { + // Run concurrent transactions on separate connections + const results = await Promise.all([ + db1.transactionalize(async () => { + await db1.run("INSERT INTO test (value) VALUES ('tx1')"); + // Small delay to increase chance of concurrent execution + await new Promise(resolve => setTimeout(resolve, 10)); + return 'tx1_done'; + }), + db2.transactionalize(async () => { + await db2.run("INSERT INTO test (value) VALUES ('tx2')"); + await new Promise(resolve => setTimeout(resolve, 10)); + return 'tx2_done'; + }), + ]); + + assert.deepStrictEqual(results, ['tx1_done', 'tx2_done']); + + // Both transactions should have committed + const rows = await db1.all('SELECT * FROM test ORDER BY value'); + assert.strictEqual(rows.length, 2); + }); + }); + + describe('write lock contention', () => { + let db1, db2; + + beforeEach(async () => { + const dbPath = newDatabasePath(); + db1 = await SqliteDatabase.open(dbPath); + db2 = await SqliteDatabase.open(dbPath); + + await db1.exec('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, value TEXT)'); + await db1.exec('DELETE FROM test'); + }); + + afterEach(async () => { + await db1.close(); + await db2.close(); + }); + + it('should handle write lock contention with BEGIN IMMEDIATE', async () => { + // BEGIN IMMEDIATE TRANSACTION acquires a write lock immediately + // This prevents deadlocks by failing fast if lock is not available + + await db1.beginTransaction(); // Uses BEGIN IMMEDIATE TRANSACTION + + // db2 trying to start a transaction should wait or fail + // depending on the busy timeout + let db2Started = false; + let db2Error = null; + + const db2Promise = db2.beginTransaction() + .then(() => { + db2Started = true; + }) + .catch(err => { + db2Error = err; + }); + + // Give db2 a chance to try + await new Promise(resolve => setTimeout(resolve, 50)); + + // db1 should still have the lock + await db1.run("INSERT INTO test (value) VALUES ('from_db1')"); + await db1.commitTransaction(); + + // Now db2 should be able to proceed + await db2Promise; + + if (db2Error) { + // If db2 timed out waiting for lock, that's acceptable behavior + assert.ok(db2Error.message.includes('locked') || db2Error.message.includes('busy'), + `Expected lock-related error, got: ${db2Error.message}`); + } else { + // db2 got the lock after db1 committed + assert.strictEqual(db2Started, true); + } + }); + }); +}); diff --git a/test/unicode.test.js b/test/unicode.test.js index 9a78c53d6..09224665e 100644 --- a/test/unicode.test.js +++ b/test/unicode.test.js @@ -71,7 +71,7 @@ describe('unicode', function() { // Generate random data. var data = []; var length = Math.floor(Math.random() * 1000) + 200; - for (var i = 0; i < length; i++) { + for (i = 0; i < length; i++) { data.push(randomString()); } diff --git a/tools/benchmark-drivers/.gitignore b/tools/benchmark-drivers/.gitignore new file mode 100644 index 000000000..c2658d7d1 --- /dev/null +++ b/tools/benchmark-drivers/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/tools/benchmark-drivers/README.md b/tools/benchmark-drivers/README.md new file mode 100644 index 000000000..7bc88b5fe --- /dev/null +++ b/tools/benchmark-drivers/README.md @@ -0,0 +1,242 @@ +# SQLite3 Benchmark + +A comprehensive benchmark suite comparing the performance and event loop utilization of different SQLite drivers for Node.js. + +Thanks to [better-sqlite3/benchmark](https://github.com/WiseLibs/better-sqlite3/tree/master/benchmark) for the initial work! + +This benchmark is using small tables with few columns and little data, therefore low I/O, so it's not reasonable to expect an asynchronous driver to perform in anyway better here. +But it is strange, though, that a brief review also highlighted some other “tricks” designed to make the async driver look worse. + +- In general, prepared statements were not used for the async driver, but for all others. + The performance improvements are significant, e.g 2.4 x for 'select-iterate', 1.5 x for 'insert' +- The async driver had to open an additional database connection for each isolated transaction, even though this is a limitation of SQLite that affects all drivers equally. + The performance improvements are significant, e.g 'transaction small' is now about 26x faster + +## Why Async Drivers are expected to be slower + +1. **Event Loop Integration**: Async drivers must integrate with Node.js's event loop, requiring context switches and queue management. + +2. **Thread Pool Usage**: Async SQLite operations are using libuv's thread pool, introducing thread scheduling overhead. + +Despite lower raw throughput, async drivers provide **Non-Blocking I/O**, by preventing the event loop from being blocked and provide **Concurrency**, by allowing other operations (network requests, file I/O, timers) to proceed while waiting for database operations to complete. + +## Supported Drivers + +| Driver | Type | Description | +|--------|------|-------------| +| `better-sqlite3` | Synchronous | High-performance synchronous SQLite bindings | +| `@homeofthings/sqlite3` | Asynchronous | Promise-based SQLite bindings (fork of node-sqlite3) | +| `node:sqlite` | Synchronous | Built-in Node.js SQLite (Node.js v22.6.0+) | + +## Requirements + +- **Node.js**: v20.17.0 or later (for N-API compatibility) +- **For `node:sqlite`**: Node.js v22.6.0+ (experimental) or v22.12.0+ (stable) + +## Installation + +```bash +npm install +``` + +## Usage + +### Run Default Benchmarks + +```bash +node index.js +``` + +This runs a general-purpose benchmark suite + +### Run Specific Benchmarks + +```bash +node index.js +``` + +Examples: +```bash +# Run only select benchmarks +node index.js select + +# Run benchmarks for specific tables +node index.js small + +# Run benchmarks for specific columns +node index.js integer text + +# Combine search terms +node index.js select small integer +``` + +### Using Local Development Version + +To benchmark the local development version of `@homeofthings/sqlite3` instead of the npm package: + +```bash +node index.js --use-local +``` + +This is useful for testing performance changes before publishing. Requires the native addon to be built: + +```bash +# From project root +npm run build +``` + +The `--use-local` flag can be combined with search terms: + +```bash +node index.js --use-local insert small +``` + +## Benchmark Types + +| Type | Description | +|------|-------------| +| `select` | Reading single rows by primary key | +| `select-all` | Reading 100 rows into an array | +| `select-iterate` | Iterating over 100 rows | +| `select-aggregate` | Aggregate functions (COUNT, SUM, AVG, MIN, MAX) with WHERE clause | +| `insert` | Inserting single rows | +| `update` | Updating single rows | +| `transaction` | Inserting 100 rows in a single transaction | +| `update-transaction` | Updating 100 rows in a single transaction | + +## Output Format + +Results are displayed as: +``` +driver-name x 471,255 ops/sec ±0.07% (event loop: 50%, 2.1μs/op) +``` + +- `x` - Separator (from original benchmark format) +- `ops/sec` - Operations per second (higher is better) +- `±X.XX%` - Relative margin of error +- `event loop: X%, Yμs/op` - Utilization percentage and blocking time per operation (lower is better) + +### Example Output + +Running `node index.js select` produces output like: + +``` +select small (nul) +better-sqlite3 x 638,075 ops/sec ±0.44% (event loop: 100%, 1.6μs/op) +@homeofthings/sqlite3 x 88,459 ops/sec ±0.82% (event loop: 47%, 5.3μs/op) +node:sqlite x 543,445 ops/sec ±0.53% (event loop: 100%, 1.8μs/op) +``` + +### Event Loop Metrics + +The **event loop** metrics show how the driver affects the event loop (measured using Node.js native `performance.eventLoopUtilization()` API): + +**Utilization Percentage:** How much of the benchmark time the event loop was busy (100% = completely blocked, 0% = completely free) + +**Time per Operation:** `(1,000,000 μs/sec ÷ ops/sec) × utilization = μs blocked per operation` + +| Driver | Utilization | Time per Op | Meaning | +|-------------------------|-------------|-------------|-------------------------------------------------| +| `better-sqlite3` | 100% | ~1.6μs/op | Blocks completely - all time is event loop time | +| `@homeofthings/sqlite3` | ~47% | ~5.3μs/op | 3.3x more blocking than sync drivers | +| `node:sqlite` | 100% | ~1.8μs/op | Blocks completely - all time is event loop time | + +Such metric shows the real cost. +For some operations, especially on very fast operations lasting only a few microseconds, async drivers **may** even block the event loop **longer in total**, even though they don't block it **completely** for the whole operation, like the sync drivers do. Here, for example, even for very short operations, the event loopis is less burdened by the async driver: + +``` +--- inserting rows individually --- +better-sqlite3 x 139,898 ops/sec ±21.94% (event loop: 100%, 7.1μs/op) +@homeofthings/sqlite3 x 47,619 ops/sec ±18.89% (event loop: 22%, 4.6μs/op) +node:sqlite x 128,465 ops/sec ±22.25% (event loop: 100%, 7.8μs/op) +``` + +### Large Data Performance + +For I/O-bound operations (large data reads), async drivers can actually **outperform** sync drivers: + +``` +--- reading large blobs (16MB each) --- +better-sqlite3 x 83 ops/sec ±7.99% (event loop: 100%, 12.07ms/op) +@homeofthings/sqlite3 x 94 ops/sec ±8.57% (event loop: 34%, 3.63ms/op) +node:sqlite x 127 ops/sec ±10.75% (event loop: 100%, 7.88ms/op) +``` + +**Why async wins for large data:** + +1. **Lower event loop blocking**: 3.63ms vs 12.07ms - async driver blocks 70% less +2. **Higher throughput**: 94 vs 83 ops/sec - async driver is 13% faster +3. **Event loop availability**: 66% free during async operations + +For I/O-bound operations, the async driver's overhead becomes negligible compared to disk I/O wait time. The ability to interleave other work becomes an advantage - the event loop can process other tasks while waiting for data. + +### Long Running Query Performance + +With such a small amount of data we are currently using for this benchmark, it's not so easy to simulate longer running queries. That's why I tried it here using simple aggregation. + +Aggregate functions (COUNT, SUM, AVG, MIN, MAX) with WHERE clauses show even more dramatic async advantages: + +``` +--- aggregate functions (COUNT, SUM, AVG, MIN, MAX) with WHERE clause --- +better-sqlite3 x 11,246 ops/sec ±0.27% (event loop: 100%, 88.9μs/op) +@homeofthings/sqlite3 x 68,779 ops/sec ±0.60% (event loop: 47%, 6.8μs/op) +node:sqlite x 10,982 ops/sec ±0.40% (event loop: 100%, 91.1μs/op) +``` + +**Why async wins for aggregation:** + +1. **6x higher throughput**: 68,779 vs 11,246 ops/sec +2. **13x less event loop blocking**: 6.8μs/op vs 88.9μs/op +3. **Same pattern as large data**: I/O-bound operations benefit from async + +Aggregation queries scan 1000 rows per operation. The async driver's ability to yield during I/O makes it significantly more efficient for these multi-row operations. + +## Project Structure + +``` +├── index.js # Main orchestrator +├── benchmark.js # Benchmark runner (tinybench) +├── drivers.js # SQLite driver configurations +├── trials.js # Benchmark trial definitions +├── seed.js # Database seeding +├── types/ +│ ├── insert.js # Insert benchmark +│ ├── select.js # Single row select benchmark +│ ├── select-all.js # Multi-row select benchmark +│ ├── select-aggregate.js # Aggregate functions benchmark +│ ├── select-iterate.js # Iteration benchmark +│ └── transaction.js # Transaction benchmark +└── temp/ # Temporary database files (auto-created) +``` + +## Architecture + +Each benchmark runs in an isolated child process to ensure: +- Clean state for each measurement +- Memory isolation between runs +- No interference between drivers + +## Adding a New Driver + +1. Add the driver to `package.json` dependencies +2. Add a connection function to `drivers.js`: +```javascript +['driver-name', async (filename, pragma) => { + const db = require('driver-package')(filename); + // Apply PRAGMA settings + for (const str of pragma) await db.exec(`PRAGMA ${str}`); + return db; +}] +``` +3. Add benchmark implementations in each `types/*.js` file: +```javascript +// Either return sync or async function + +exports['driver-name'] = (db, ctx) => { + return () => db.someOperation(); +}; + +exports['driver-name'] = async (db, ctx) => { + return () => db.someOperation(); +}; +``` diff --git a/tools/benchmark-drivers/benchmark.js b/tools/benchmark-drivers/benchmark.js new file mode 100755 index 000000000..d112473a3 --- /dev/null +++ b/tools/benchmark-drivers/benchmark.js @@ -0,0 +1,75 @@ +#!/usr/bin/env node +'use strict'; +const { Bench } = require('tinybench'); +const { performance } = require('perf_hooks'); + +const formatResult = (task, eventLoopUtilization) => { + // Format: 471,255 ops/sec ±0.07% (event loop: 50%, 2.1μs/op) + const hz = task.result?.hz || 0; + const rme = task.result?.rme || 0; + const ops = Math.round(hz).toLocaleString('en-US'); + // Calculate event loop time per operation in microseconds + // Formula: (1,000,000 μs/sec / ops/sec) × utilization = μs blocked per operation + const timePerOpUs = hz > 0 ? (1000000 / hz) * eventLoopUtilization : 0; + // Format: use μs for values < 1000, otherwise ms + let eluTimeStr; + if (timePerOpUs < 1000) { + eluTimeStr = `${timePerOpUs.toFixed(1)}μs/op`; + } else { + eluTimeStr = `${(timePerOpUs / 1000).toFixed(2)}ms/op`; + } + // Format utilization percentage + const eluPct = (eventLoopUtilization * 100).toFixed(0); + return `${ops} ops/sec ±${rme.toFixed(2)}% (event loop: ${eluPct}%, ${eluTimeStr})`; +}; + +const runWithEventLoopMeasurement = async (fn, isAsync) => { + const bench = new Bench({ time: 1000, warmupTime: 0 }); + + // Warmup run + const warmupBench = new Bench({ time: 100, warmupTime: 0 }); + warmupBench.add('warmup', isAsync ? async () => { await fn(); } : fn); + await warmupBench.run(); + + // Actual benchmark with event loop measurement using native API + const eluStart = performance.eventLoopUtilization(); + + bench.add('test', isAsync ? async () => { await fn(); } : fn); + await bench.run(); + + const eluEnd = performance.eventLoopUtilization(); + const eventLoopUtilization = performance.eventLoopUtilization(eluStart, eluEnd).utilization; + + return formatResult(bench.tasks[0], eventLoopUtilization); +}; + +const runSync = async (fn) => { + return runWithEventLoopMeasurement(fn, false); +}; + +const runAsync = async (fn) => { + return runWithEventLoopMeasurement(fn, true); +}; + +const display = (result) => { + process.stdout.write(result); + process.exit(); +}; + +(async () => { + process.on('unhandledRejection', (err) => { throw err; }); + const ctx = JSON.parse(process.argv[2]); + const type = require(`./types/${ctx.type}`); + const drivers = require('./drivers')(ctx.useLocal); + const db = await drivers.get(ctx.driver)('../temp/benchmark.db', ctx.pragma); + if (!type.readonly) { + for (const table of ctx.tables) await db.exec(`DELETE FROM ${table} WHERE rowid > 1;`); + await db.exec('VACUUM;'); + } + const fn = type[ctx.driver](db, ctx); + if (typeof fn === 'function') { + setImmediate(async () => { display(await runSync(fn)); }); + } else { + setImmediate(async () => { display(await runAsync(await fn)); }); + } +})(); diff --git a/tools/benchmark-drivers/drivers.js b/tools/benchmark-drivers/drivers.js new file mode 100644 index 000000000..23450f794 --- /dev/null +++ b/tools/benchmark-drivers/drivers.js @@ -0,0 +1,43 @@ +'use strict'; + +/* + Every benchmark trial will be executed once for each SQLite driver listed + below. Each driver has a function to open a new database connection on a + given filename and a list of PRAGMA statements. + + When useLocal is true, @homeofthings/sqlite3 is loaded from the local + development path (../../lib/sqlite3) instead of the npm package. + */ + +module.exports = (useLocal = false) => new Map([ + ['better-sqlite3', async (filename, pragma) => { + const db = require('better-sqlite3')(filename); + for (const str of pragma) db.pragma(str); + return db; + }], + ['@homeofthings/sqlite3', async (filename, pragma) => { + // Use local development path when --use-local flag is set + const modulePath = useLocal + ? require.resolve('../../lib/sqlite3') + : '@homeofthings/sqlite3'; + const { SqliteDatabase } = require(modulePath); + const db = await SqliteDatabase.open(filename); + for (const str of pragma) await db.run(`PRAGMA ${str}`); + return db; + }], + ...!moduleExists('node:sqlite') ? [] : [ + ['node:sqlite', async (filename, pragma) => { + const db = new (require('node:sqlite').DatabaseSync)(filename); + for (const str of pragma) db.exec(`PRAGMA ${str}`); + return db; + }] + ], +]); + +function moduleExists(moduleName) { + try { + return !!(require.resolve(moduleName)); + } catch (_) { + return false; + } +}; diff --git a/tools/benchmark-drivers/index.js b/tools/benchmark-drivers/index.js new file mode 100755 index 000000000..d54c9290f --- /dev/null +++ b/tools/benchmark-drivers/index.js @@ -0,0 +1,88 @@ +'use strict'; +const { execFileSync } = require('child_process'); +const clc = require('cli-color'); + +const getTrials = (searchTerms) => { + // Without any command-line arguments, we do a general-purpose benchmark. + if (!searchTerms.length) return require('./trials').default; + + // With command-line arguments, the user can run specific groups of trials. + return require('./trials').searchable.filter(filterBySearchTerms(searchTerms)); +}; + +const filterBySearchTerms = (searchTerms) => (trial) => { + const terms = [ + trial.type, + trial.table, + `(${trial.columns.join(', ')})`, + `(${trial.columns.join(',')})`, + ...trial.columns, + ...trial.customPragma, + ]; + return searchTerms.every(arg => terms.includes(arg)); +}; + +const sortTrials = (a, b) => { + const aRo = require(`./types/${a.type}`).readonly; + const bRo = require(`./types/${b.type}`).readonly; + if (typeof aRo !== 'boolean') throw new TypeError(`Missing readonly export in benchmark type ${a.type}`); + if (typeof bRo !== 'boolean') throw new TypeError(`Missing readonly export in benchmark type ${b.type}`); + return bRo - aRo; +}; + +const displayTrialName = (trial) => { + if (trial.description) return console.log(clc.magenta(`--- ${trial.description} ---`)); + const name = `${trial.type} ${trial.table} (${trial.columns.join(', ')})`; + const pragma = trial.customPragma.length ? ` | ${trial.customPragma.join('; ')}` : ''; + console.log(clc.magenta(name) + clc.yellow(pragma)); +}; + +const createContext = (trial, driver, useLocal) => { + const tableInfo = Object.assign({}, tables.get(trial.table), { data: undefined }); + return JSON.stringify(Object.assign({}, trial, tableInfo, { driver, tables: [...tables.keys()], useLocal })); +}; + +const erase = () => { + return clc.move(0, -1) + clc.erase.line; +}; + +// Parse command-line arguments +const args = process.argv.slice(2); +const useLocal = args.includes('--use-local'); +const searchTerms = args.filter(arg => !arg.startsWith('--')); + +// Determine which trials should be executed. +process.chdir(__dirname); +const trials = getTrials(searchTerms).sort(sortTrials); +if (!trials.length) { + console.log(clc.yellow('No matching benchmarks found!')); + process.exit(); +} + +// Create the temporary database needed to run the benchmark trials. +console.log('Generating tables...'); +const tables = require('./seed')(); +process.stdout.write(erase()); + +// Execute each trial for each available driver. +const drivers = require('./drivers')(useLocal); +const nameLength = [...drivers.keys()].reduce((m, d) => Math.max(m, d.length), 0); +for (const trial of trials) { + displayTrialName(trial); + for (const driver of drivers.keys()) { + const driverName = driver.padEnd(nameLength); + const ctx = createContext(trial, driver, useLocal); + process.stdout.write(`${driver} (running...)\n`); + try { + const result = execFileSync('node', [...process.execArgv, './benchmark.js', ctx], { stdio: 'pipe', encoding: 'utf8' }); + console.log(erase() + `${driverName} x ${result}`); + } catch (err) { + console.log(erase() + clc.red(`${driverName} ERROR (probably out of memory)`)); + process.stderr.write(clc.xterm(247)(clc.strip(err.stderr))); + } + } + console.log(''); +} + +console.log(clc.green('All benchmarks complete!')); +process.exit(); diff --git a/tools/benchmark-drivers/package.json b/tools/benchmark-drivers/package.json new file mode 100644 index 000000000..687c59e22 --- /dev/null +++ b/tools/benchmark-drivers/package.json @@ -0,0 +1,18 @@ +{ + "name": "benchmark", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@homeofthings/sqlite3": "^6.3.1", + "better-sqlite3": "^12.8.0", + "cli-color": "^2.0.4", + "fs-extra": "^11.3.4", + "tinybench": "^4.0.1" + } +} diff --git a/tools/benchmark-drivers/seed.js b/tools/benchmark-drivers/seed.js new file mode 100644 index 000000000..17f07ac63 --- /dev/null +++ b/tools/benchmark-drivers/seed.js @@ -0,0 +1,47 @@ +'use strict'; +const fs = require('fs-extra'); +const path = require('path'); + +const tables = new Map([ + ['small', { + schema: '(nul, integer INTEGER, real REAL, text TEXT, blob BLOB)', + data: [null, 0x7fffffff, 1 / 3, 'this is the text', Buffer.from('this is the blob')], + count: 10000, + }], + ['large_text', { + schema: '(text TEXT)', + data: ['this is the text'.repeat(65536)], + count: 1000, + }], + ['large_blob', { + schema: '(blob BLOB)', + data: [Buffer.from('this is the blob'.repeat(1048576))], + count: 1000, + }], +]); + +/* + This function creates a pre-populated database that is deleted when the + process exits. + */ + +module.exports = () => { + const tempDir = path.join(__dirname, '..', 'temp'); + process.on('exit', () => fs.removeSync(tempDir)); + fs.removeSync(tempDir); + fs.ensureDirSync(tempDir); + + const db = require('better-sqlite3')(path.join(tempDir, 'benchmark.db')); + db.pragma('journal_mode = OFF'); + db.pragma('synchronous = OFF'); + + for (const [name, ctx] of tables.entries()) { + db.exec(`CREATE TABLE ${name} ${ctx.schema}`); + const columns = db.pragma(`table_info(${name})`).map(() => '?'); + const insert = db.prepare(`INSERT INTO ${name} VALUES (${columns.join(', ')})`).bind(ctx.data); + for (let i = 0; i < ctx.count; ++i) insert.run(); + } + + db.close(); + return tables; +}; diff --git a/tools/benchmark-drivers/trials.js b/tools/benchmark-drivers/trials.js new file mode 100644 index 000000000..91e5cd9cc --- /dev/null +++ b/tools/benchmark-drivers/trials.js @@ -0,0 +1,103 @@ +'use strict'; + +exports.default = [ + { type: 'select', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'reading rows individually' }, + { + type: 'select', table: 'large_blob', columns: ['blob'], + description: 'reading large blobs' + }, + { type: 'select-all', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'reading 100 rows into an array' + }, + { type: 'insert', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'inserting rows individually' }, + { + type: 'insert', table: 'large_blob', columns: ['blob'], + description: 'inserting large blobs' + }, + { + type: 'update', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'updating rows individually' + }, + { + type: 'update', table: 'large_blob', columns: ['blob'], + description: 'updating large blobs' + }, + { type: 'transaction', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'inserting 100 rows in a single transaction' }, + { + type: 'update-transaction', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'updating 100 rows in a single transaction' + }, + { + type: 'select-aggregate', table: 'small', columns: ['nul', 'integer', 'real', 'text'], + description: 'aggregate functions (COUNT, SUM, AVG, MIN, MAX) with WHERE clause' + }, +]; + +exports.searchable = [ + { type: 'select', table: 'small', columns: ['nul'] }, + { type: 'select', table: 'small', columns: ['integer'] }, + { type: 'select', table: 'small', columns: ['real'] }, + { type: 'select', table: 'small', columns: ['text'] }, + { type: 'select', table: 'small', columns: ['blob'] }, + { type: 'select', table: 'large_text', columns: ['text'] }, + { type: 'select', table: 'large_blob', columns: ['blob'] }, + { type: 'select-all', table: 'small', columns: ['nul'] }, + { type: 'select-all', table: 'small', columns: ['integer'] }, + { type: 'select-all', table: 'small', columns: ['real'] }, + { type: 'select-all', table: 'small', columns: ['text'] }, + { type: 'select-all', table: 'small', columns: ['blob'] }, + { type: 'select-all', table: 'large_text', columns: ['text'] }, + { type: 'select-all', table: 'large_blob', columns: ['blob'] }, + { type: 'select-iterate', table: 'small', columns: ['nul'] }, + { type: 'select-iterate', table: 'small', columns: ['integer'] }, + { type: 'select-iterate', table: 'small', columns: ['real'] }, + { type: 'select-iterate', table: 'small', columns: ['text'] }, + { type: 'select-iterate', table: 'small', columns: ['blob'] }, + { type: 'select-iterate', table: 'large_text', columns: ['text'] }, + { type: 'select-iterate', table: 'large_blob', columns: ['blob'] }, + { type: 'insert', table: 'small', columns: ['nul'] }, + { type: 'insert', table: 'small', columns: ['integer'] }, + { type: 'insert', table: 'small', columns: ['real'] }, + { type: 'insert', table: 'small', columns: ['text'] }, + { type: 'insert', table: 'small', columns: ['blob'] }, + { type: 'insert', table: 'large_text', columns: ['text'] }, + { type: 'insert', table: 'large_blob', columns: ['blob'] }, + { type: 'transaction', table: 'small', columns: ['nul'] }, + { type: 'transaction', table: 'small', columns: ['integer'] }, + { type: 'transaction', table: 'small', columns: ['real'] }, + { type: 'transaction', table: 'small', columns: ['text'] }, + { type: 'transaction', table: 'small', columns: ['blob'] }, + { type: 'transaction', table: 'large_text', columns: ['text'] }, + { type: 'transaction', table: 'large_blob', columns: ['blob'] }, + { type: 'update', table: 'small', columns: ['nul'] }, + { type: 'update', table: 'small', columns: ['integer'] }, + { type: 'update', table: 'small', columns: ['real'] }, + { type: 'update', table: 'small', columns: ['text'] }, + { type: 'update', table: 'small', columns: ['blob'] }, + { type: 'update', table: 'large_text', columns: ['text'] }, + { type: 'update', table: 'large_blob', columns: ['blob'] }, + { type: 'update-transaction', table: 'small', columns: ['nul'] }, + { type: 'update-transaction', table: 'small', columns: ['integer'] }, + { type: 'update-transaction', table: 'small', columns: ['real'] }, + { type: 'update-transaction', table: 'small', columns: ['text'] }, + { type: 'update-transaction', table: 'small', columns: ['blob'] }, + { type: 'update-transaction', table: 'large_text', columns: ['text'] }, + { type: 'update-transaction', table: 'large_blob', columns: ['blob'] }, + { type: 'select-aggregate', table: 'small', columns: ['nul', 'integer', 'real', 'text'] }, +]; + +(() => { + const defaultPragma = []; + const yes = /^\s*(1|true|on|yes)\s*$/i; + if (yes.test(process.env.NO_CACHE)) defaultPragma.push('cache_size = 0'); + else defaultPragma.push('cache_size = -16000'); + if (yes.test(process.env.NO_WAL)) defaultPragma.push('journal_mode = DELETE', 'synchronous = FULL'); + else defaultPragma.push('journal_mode = WAL', 'synchronous = NORMAL'); + for (const trial of [].concat(...Object.values(exports))) { + trial.customPragma = trial.pragma || []; + trial.pragma = defaultPragma.concat(trial.customPragma); + } +})(); diff --git a/tools/benchmark-drivers/types/insert.js b/tools/benchmark-drivers/types/insert.js new file mode 100644 index 000000000..d3191e4d0 --- /dev/null +++ b/tools/benchmark-drivers/types/insert.js @@ -0,0 +1,22 @@ +'use strict'; +exports.readonly = false; // Inserting rows individually (`.run()`) + +exports['better-sqlite3'] = (db, { table, columns }) => { + const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + return () => stmt.run(row); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns }) => { + const stmt = await db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`)) + .filter(([k]) => columns.includes(k)) + .map(([k, v]) => ({ ['@' + k]: v }))); + return async () => await stmt.run(row); +}; + +exports['node:sqlite'] = (db, { table, columns }) => { + const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + return () => stmt.run(row); +}; diff --git a/tools/benchmark-drivers/types/select-aggregate.js b/tools/benchmark-drivers/types/select-aggregate.js new file mode 100644 index 000000000..7d7bb24db --- /dev/null +++ b/tools/benchmark-drivers/types/select-aggregate.js @@ -0,0 +1,44 @@ +'use strict'; +exports.readonly = true; // Aggregate functions (COUNT, SUM, AVG, MIN, MAX) with WHERE clause + +exports['better-sqlite3'] = (db, { table, columns }) => { + const stmt = db.prepare(` + SELECT COUNT(*), SUM(integer), AVG(real), MIN(text), MAX(text) + FROM ${table} + WHERE rowid >= ? AND rowid < ? + `); + let start = 0; + return () => { + const result = stmt.get(start, start + 1000); + start = (start + 1) % 9000; // Cycle through 0-8999 to stay within 10000 rows + return result; + }; +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns }) => { + const stmt = await db.prepare(` + SELECT COUNT(*), SUM(integer), AVG(real), MIN(text), MAX(text) + FROM ${table} + WHERE rowid >= ? AND rowid < ? + `); + let start = 0; + return async () => { + const result = await stmt.get(start, start + 1000); + start = (start + 1) % 9000; // Cycle through 0-8999 to stay within 10000 rows + return result; + }; +}; + +exports['node:sqlite'] = (db, { table, columns }) => { + const stmt = db.prepare(` + SELECT COUNT(*), SUM(integer), AVG(real), MIN(text), MAX(text) + FROM ${table} + WHERE rowid >= ? AND rowid < ? + `); + let start = 0; + return () => { + const result = stmt.get(start, start + 1000); + start = (start + 1) % 9000; // Cycle through 0-8999 to stay within 10000 rows + return result; + }; +}; diff --git a/tools/benchmark-drivers/types/select-all.js b/tools/benchmark-drivers/types/select-all.js new file mode 100644 index 000000000..6242ae9d8 --- /dev/null +++ b/tools/benchmark-drivers/types/select-all.js @@ -0,0 +1,20 @@ +'use strict'; +exports.readonly = true; // Reading 100 rows into an array (`.all()`) + +exports['better-sqlite3'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); + let rowid = -100; + return () => stmt.all((rowid += 100) % count + 1); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns, count }) => { + const stmt = await db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); + let rowid = -100; + return async () => await stmt.all((rowid += 100) % count + 1); +}; + +exports['node:sqlite'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); + let rowid = -100; + return () => stmt.all((rowid += 100) % count + 1); +}; diff --git a/tools/benchmark-drivers/types/select-iterate.js b/tools/benchmark-drivers/types/select-iterate.js new file mode 100644 index 000000000..dd49fbb4b --- /dev/null +++ b/tools/benchmark-drivers/types/select-iterate.js @@ -0,0 +1,33 @@ +'use strict'; +exports.readonly = true; // Iterating over 100 rows (`.iterate()`) + +exports['better-sqlite3'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); + let rowid = -100; + return () => { + for (const row of stmt.iterate((rowid += 100) % count + 1)) { + // iterate over rows - row intentionally unused + } + }; +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns, count }) => { + const stmt = await db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`); + let rowid = -100; + return async () => { + rowid += 100; + for (let index = 0; index < 100; index++) { + await stmt.get((rowid + index) % count + 1); + } + }; +}; + +exports['node:sqlite'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); + let rowid = -100; + return () => { + for (const row of stmt.iterate((rowid += 100) % count + 1)) { + // iterate over rows - row intentionally unused + } + }; +}; diff --git a/tools/benchmark-drivers/types/select.js b/tools/benchmark-drivers/types/select.js new file mode 100644 index 000000000..1b433f57b --- /dev/null +++ b/tools/benchmark-drivers/types/select.js @@ -0,0 +1,20 @@ +'use strict'; +exports.readonly = true; // Reading rows individually (`.get()`) + +exports['better-sqlite3'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`); + let rowid = -1; + return () => stmt.get(++rowid % count + 1); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns, count }) => { + const stmt = await db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`); + let rowid = -1; + return async () => await stmt.get(++rowid % count + 1); +}; + +exports['node:sqlite'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`); + let rowid = -1; + return () => stmt.get(++rowid % count + 1); +}; diff --git a/tools/benchmark-drivers/types/transaction.js b/tools/benchmark-drivers/types/transaction.js new file mode 100644 index 000000000..2d30a4c2e --- /dev/null +++ b/tools/benchmark-drivers/types/transaction.js @@ -0,0 +1,43 @@ +'use strict'; +exports.readonly = false; // Inserting 100 rows in a single transaction + +exports['better-sqlite3'] = (db, { table, columns }) => { + const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + const trx = db.transaction((row) => { + for (let i = 0; i < 100; ++i) stmt.run(row); + }); + return () => trx(row); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns }) => { + const stmt = await db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`)) + .filter(([k]) => columns.includes(k)) + .map(([k, v]) => ({ ['@' + k]: v }))); + return async () => { + await db.run('BEGIN'); + try { + for (let i = 0; i < 100; ++i) await stmt.run(row); + await db.run('COMMIT'); + } catch (err) { + await db.run('ROLLBACK'); + throw err; + } + }; +}; + +exports['node:sqlite'] = (db, { table, columns }) => { + const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + return () => { + db.exec('BEGIN'); + try { + for (let i = 0; i < 100; ++i) stmt.run(row); + db.exec('COMMIT'); + } catch (err) { + db.isTransaction && db.exec('ROLLBACK'); + throw err; + } + }; +}; diff --git a/tools/benchmark-drivers/types/update-transaction.js b/tools/benchmark-drivers/types/update-transaction.js new file mode 100644 index 000000000..37b6e0c69 --- /dev/null +++ b/tools/benchmark-drivers/types/update-transaction.js @@ -0,0 +1,43 @@ +'use strict'; +exports.readonly = false; // Updating 100 rows in a single transaction + +exports['better-sqlite3'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + const trx = db.transaction((row) => { + for (let i = 0; i < 100; ++i) stmt.run({ ...row, rowid: (i % count) + 1 }); + }); + return () => trx(row); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns, count }) => { + const stmt = await db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`)) + .filter(([k]) => columns.includes(k)) + .map(([k, v]) => ({ ['@' + k]: v }))); + return async () => { + await db.run('BEGIN'); + try { + for (let i = 0; i < 100; ++i) await stmt.run({ ...row, '@rowid': (i % count) + 1 }); + await db.run('COMMIT'); + } catch (err) { + await db.run('ROLLBACK'); + throw err; + } + }; +}; + +exports['node:sqlite'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + return () => { + db.exec('BEGIN'); + try { + for (let i = 0; i < 100; ++i) stmt.run({ ...row, rowid: (i % count) + 1 }); + db.exec('COMMIT'); + } catch (err) { + db.isTransaction && db.exec('ROLLBACK'); + throw err; + } + }; +}; diff --git a/tools/benchmark-drivers/types/update.js b/tools/benchmark-drivers/types/update.js new file mode 100644 index 000000000..02b5169a0 --- /dev/null +++ b/tools/benchmark-drivers/types/update.js @@ -0,0 +1,25 @@ +'use strict'; +exports.readonly = false; // Updating rows individually (`.run()`) + +exports['better-sqlite3'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + let rowid = -1; + return () => stmt.run({ ...row, rowid: ++rowid % count + 1 }); +}; + +exports['@homeofthings/sqlite3'] = async (db, { table, columns, count }) => { + const stmt = await db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = Object.assign({}, ...Object.entries(await db.get(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`)) + .filter(([k]) => columns.includes(k)) + .map(([k, v]) => ({ ['@' + k]: v }))); + let rowid = -1; + return async () => await stmt.run({ ...row, '@rowid': ++rowid % count + 1 }); +}; + +exports['node:sqlite'] = (db, { table, columns, count }) => { + const stmt = db.prepare(`UPDATE ${table} SET ${columns.map(c => `${c} = @${c}`).join(', ')} WHERE rowid = @rowid`); + const row = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} LIMIT 1`).get(); + let rowid = -1; + return () => stmt.run({ ...row, rowid: ++rowid % count + 1 }); +}; diff --git a/tools/benchmark-drivers/yarn.lock b/tools/benchmark-drivers/yarn.lock new file mode 100644 index 000000000..b9ec4e23e --- /dev/null +++ b/tools/benchmark-drivers/yarn.lock @@ -0,0 +1,800 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@gar/promise-retry@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz" + integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA== + +"@homeofthings/sqlite3@^6.3.1": + version "6.3.1" + resolved "https://registry.npmjs.org/@homeofthings/sqlite3/-/sqlite3-6.3.1.tgz" + integrity sha512-d+RqnFap9bfW64EXfcT7L7wB56DZuTL6z6DtBGVAuZc+Nr7LtH0R/qcjimsFoshEuMbtpHhlV/cx8wOBSEGtNw== + dependencies: + bindings "^1.5.0" + node-addon-api "^8.7.0" + prebuild-install "^7.1.3" + tar "^7.5.13" + optionalDependencies: + node-gyp "12.x" + +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + +"@npmcli/agent@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz" + integrity sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA== + dependencies: + agent-base "^7.1.0" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.1" + lru-cache "^11.2.1" + socks-proxy-agent "^8.0.3" + +"@npmcli/fs@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz" + integrity sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og== + dependencies: + semver "^7.3.5" + +"@npmcli/redact@^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz" + integrity sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q== + +abbrev@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz" + integrity sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA== + +agent-base@^7.1.0, agent-base@^7.1.2: + version "7.1.4" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== + +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +better-sqlite3@^12.8.0: + version "12.8.0" + resolved "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.8.0.tgz" + integrity sha512-RxD2Vd96sQDjQr20kdP+F+dK/1OUNiVOl200vKBZY8u0vTwysfolF6Hq+3ZK2+h8My9YvZhHsF+RSGZW2VYrPQ== + dependencies: + bindings "^1.5.0" + prebuild-install "^7.1.1" + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +brace-expansion@^5.0.5: + version "5.0.5" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz" + integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== + dependencies: + balanced-match "^4.0.2" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +cacache@^20.0.1: + version "20.0.4" + resolved "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz" + integrity sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA== + dependencies: + "@npmcli/fs" "^5.0.0" + fs-minipass "^3.0.0" + glob "^13.0.0" + lru-cache "^11.1.0" + minipass "^7.0.3" + minipass-collect "^2.0.1" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^7.0.2" + ssri "^13.0.0" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== + +cli-color@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/cli-color/-/cli-color-2.0.4.tgz" + integrity sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA== + dependencies: + d "^1.0.1" + es5-ext "^0.10.64" + es6-iterator "^2.0.3" + memoizee "^0.4.15" + timers-ext "^0.1.7" + +d@^1.0.1, d@^1.0.2, d@1: + version "1.0.2" + resolved "https://registry.npmjs.org/d/-/d-1.0.2.tgz" + integrity sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw== + dependencies: + es5-ext "^0.10.64" + type "^2.7.2" + +debug@^4.3.4, debug@4: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +detect-libc@^2.0.0: + version "2.1.2" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.5" + resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz" + integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== + dependencies: + once "^1.4.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2: + version "0.10.64" + resolved "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.4" + resolved "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz" + integrity sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg== + dependencies: + d "^1.0.2" + ext "^1.7.0" + +es6-weak-map@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + +event-emitter@^0.3.5: + version "0.3.5" + resolved "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz" + integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== + dependencies: + d "1" + es5-ext "~0.10.14" + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +exponential-backoff@^3.1.1: + version "3.1.3" + resolved "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz" + integrity sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA== + +ext@^1.7.0: + version "1.7.0" + resolved "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@^11.3.4: + version "11.3.4" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz" + integrity sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^3.0.0: + version "3.0.3" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz" + integrity sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw== + dependencies: + minipass "^7.0.3" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob@^13.0.0: + version "13.0.6" + resolved "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== + dependencies: + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +http-cache-semantics@^4.1.1: + version "4.2.0" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz" + integrity sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== + +http-proxy-agent@^7.0.0: + version "7.0.2" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + +https-proxy-agent@^7.0.1: + version "7.0.6" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" + integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== + dependencies: + agent-base "^7.1.2" + debug "4" + +iconv-lite@^0.7.2: + version "0.7.2" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz" + integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inherits@^2.0.3, inherits@^2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ip-address@^10.0.1: + version "10.1.0" + resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz" + integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== + +is-promise@^2.2.2: + version "2.2.2" + resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz" + integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== + +isexe@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz" + integrity sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw== + +jsonfile@^6.0.1: + version "6.2.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1: + version "11.3.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz" + integrity sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ== + +lru-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz" + integrity sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ== + dependencies: + es5-ext "~0.10.2" + +make-fetch-happen@^15.0.0: + version "15.0.5" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz" + integrity sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg== + dependencies: + "@gar/promise-retry" "^1.0.0" + "@npmcli/agent" "^4.0.0" + "@npmcli/redact" "^4.0.0" + cacache "^20.0.1" + http-cache-semantics "^4.1.1" + minipass "^7.0.2" + minipass-fetch "^5.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^1.0.0" + proc-log "^6.0.0" + ssri "^13.0.0" + +memoizee@^0.4.15: + version "0.4.17" + resolved "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz" + integrity sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA== + dependencies: + d "^1.0.2" + es5-ext "^0.10.64" + es6-weak-map "^2.0.3" + event-emitter "^0.3.5" + is-promise "^2.2.2" + lru-queue "^0.1.0" + next-tick "^1.1.0" + timers-ext "^0.1.7" + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^10.2.2: + version "10.2.5" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== + dependencies: + brace-expansion "^5.0.5" + +minimist@^1.2.0, minimist@^1.2.3: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz" + integrity sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw== + dependencies: + minipass "^7.0.3" + +minipass-fetch@^5.0.0: + version "5.0.2" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz" + integrity sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ== + dependencies: + minipass "^7.0.3" + minipass-sized "^2.0.0" + minizlib "^3.0.1" + optionalDependencies: + iconv-lite "^0.7.2" + +minipass-flush@^1.0.5: + version "1.0.7" + resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz" + integrity sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz" + integrity sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA== + dependencies: + minipass "^7.1.2" + +minipass@^3.0.0: + version "3.3.6" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3: + version "7.1.3" + resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +minizlib@^3.0.1, minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== + dependencies: + minipass "^7.1.2" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +ms@^2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +napi-build-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz" + integrity sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA== + +negotiator@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz" + integrity sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +node-abi@^3.3.0: + version "3.89.0" + resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz" + integrity sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA== + dependencies: + semver "^7.3.5" + +node-addon-api@^8.7.0: + version "8.7.0" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz" + integrity sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA== + +node-gyp@12.x: + version "12.2.0" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz" + integrity sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ== + dependencies: + env-paths "^2.2.0" + exponential-backoff "^3.1.1" + graceful-fs "^4.2.6" + make-fetch-happen "^15.0.0" + nopt "^9.0.0" + proc-log "^6.0.0" + semver "^7.3.5" + tar "^7.5.4" + tinyglobby "^0.2.12" + which "^6.0.0" + +nopt@^9.0.0: + version "9.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz" + integrity sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw== + dependencies: + abbrev "^4.0.0" + +once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-map@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz" + integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== + +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + +picomatch@^4.0.4: + version "4.0.4" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +prebuild-install@^7.1.1, prebuild-install@^7.1.3: + version "7.1.3" + resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz" + integrity sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^2.0.0" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +proc-log@^6.0.0: + version "6.1.0" + resolved "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz" + integrity sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ== + +pump@^3.0.0: + version "3.0.4" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz" + integrity sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^7.3.5: + version "7.7.4" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^8.0.3: + version "8.0.5" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz" + integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== + dependencies: + agent-base "^7.1.2" + debug "^4.3.4" + socks "^2.8.3" + +socks@^2.8.3: + version "2.8.7" + resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== + dependencies: + ip-address "^10.0.1" + smart-buffer "^4.2.0" + +ssri@^13.0.0: + version "13.0.1" + resolved "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz" + integrity sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ== + dependencies: + minipass "^7.0.3" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +tar-fs@^2.0.0: + version "2.1.4" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz" + integrity sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^7.5.13, tar@^7.5.4: + version "7.5.13" + resolved "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz" + integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== + dependencies: + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" + +timers-ext@^0.1.7: + version "0.1.8" + resolved "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz" + integrity sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww== + dependencies: + es5-ext "^0.10.64" + next-tick "^1.1.0" + +tinybench@^4.0.1: + version "4.1.0" + resolved "https://registry.npmjs.org/tinybench/-/tinybench-4.1.0.tgz" + integrity sha512-8JZoQRJgWWEIIeAmpiNmMHIREmUY3oGX8GRmlmNapLr/qtgMe+K76vM2qabh85hNScnE2lqTVTajVETjuD9Ixg== + +tinyglobby@^0.2.12: + version "0.2.16" + resolved "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz" + integrity sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +type@^2.7.2: + version "2.7.3" + resolved "https://registry.npmjs.org/type/-/type-2.7.3.tgz" + integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +which@^6.0.0: + version "6.0.1" + resolved "https://registry.npmjs.org/which/-/which-6.0.1.tgz" + integrity sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg== + dependencies: + isexe "^4.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== diff --git a/tools/benchmark-internal/README.md b/tools/benchmark-internal/README.md new file mode 100644 index 000000000..ae5957206 --- /dev/null +++ b/tools/benchmark-internal/README.md @@ -0,0 +1,190 @@ +# Benchmark Tools + +This directory contains benchmark scripts to measure and compare the performance of different SQLite operations in `@homeofthings/sqlite3` using [tinybench](https://github.com/tinylibs/tinybench). + +## Key Features + +- **Proper setup/teardown separation**: Database creation, table creation, and data population happen in `beforeEach` (not measured) +- **Only actual operations measured**: The benchmark functions contain only the INSERT/SELECT operations +- **Warmup support**: tinybench includes warmup iterations to allow V8 optimization +- **Statistical accuracy**: Multiple iterations with mean, variance, and margin of error + +## Running Benchmarks + +### Quick Start + +Install dependencies (from project root): + +```bash +yarn install +``` + +Run all benchmarks: + +```bash +node tools/benchmark-internal/run.js +``` + +Or from the benchmark directory: + +```bash +cd tools/benchmark-internal +node run.js +``` + +## Benchmark Scripts + +### run.js (Recommended) + +The main runner script executes all benchmarks and provides detailed timing statistics including: +- ops/sec (operations per second) +- Mean and margin of error +- Sample variance + +### insert.js + +Measures performance of different data insertion approaches. + +**Benchmark tests:** +- `insert: literal file`: Executes SQL from a file using `db.exec()` +- `insert: transaction with two statements`: Uses parallelized statements within a transaction +- `insert: with transaction`: Uses a single statement within a transaction +- `insert: without transaction`: Uses a single statement without explicit transaction + +**Usage:** +```bash +node tools/benchmark-internal/run.js +``` + +**Expected output:** +``` +Task Name ops/sec Average Time (ns) Margin Samples +------------------------------------------------------------------------------------------------------ +insert: literal file 101.79 9867669.32 1.36% 102 +insert: transaction with two statements 15.72 64101675.83 1.81% 100 +insert: with transaction 9.53 105168845.93 0.98% 100 +insert: without transaction 8.76 114706903.08 1.33% 100 +``` + +**Key findings:** +- `literal file` is fastest because it uses a single `db.exec()` call with all SQL in one batch +- `transaction with two statements` uses parallelized statements within a transaction for better throughput +- `with transaction` wraps all inserts in a single transaction +- `without transaction` is slowest because each INSERT is committed individually + +### select.js + +Measures performance of different data retrieval approaches. + +**Benchmark tests:** +- `select: db.each()`: Iterates through results row-by-row using a callback +- `select: db.all()`: Retrieves all results into an array at once +- `select: db.all with statement reset()`: Retrieves all results after resetting the statement + +**Usage:** +```bash +node tools/benchmark-internal/run.js +``` + +**Expected output:** +``` +Task Name ops/sec Average Time (ns) Margin Samples +------------------------------------------------------------------------------------------------------ +select: db.each 0.82 1232450196.30 5.37% 10 +select: db.all 0.86 1166833768.40 2.27% 10 +select: db.all with statement reset 0.42 2357234044.80 0.77% 10 +``` + +**Key findings:** +- `db.all()` and `db.each()` have similar performance for large result sets (1 million rows) +- `db.each()` is more memory-efficient for large result sets as it processes rows one at a time +- `db.all with statement reset` is slower because it executes the query twice + +## Benchmark Data Files + +### insert-transaction.sql + +Contains 10,000 INSERT statements wrapped in a transaction for the "insert literal file" benchmark. + +### select-data.sql + +Contains 10,000 INSERT statements to populate the test database for the select benchmarks. + +## Architecture + +### Setup/Teardown Pattern + +Each benchmark follows this pattern to ensure only the actual operation is measured: + +```javascript +{ + async beforeEach(ctx) { + // Setup: Create database, create table, populate data + // NOT measured + ctx.db = new sqlite3.Database(':memory:'); + await promisifyRun(ctx.db, 'CREATE TABLE foo ...'); + }, + + async fn(ctx) { + // Benchmark: Only the operation to measure + // MEASURED + await insertData(ctx.db); + }, + + async afterEach(ctx) { + // Teardown: Close database + // NOT measured + await promisifyClose(ctx.db); + }, +} +``` + +### Context Object + +A context object (`ctx`) is passed between setup, benchmark, and teardown: +- `ctx.db`: Database instance +- Custom properties can be added as needed + +## Adding New Benchmarks + +1. Create a new JavaScript file in this directory (e.g., `update.js`) +2. Export a `benchmarks` object with your benchmark functions: + +```javascript +module.exports = { + benchmarks: { + 'benchmark name': { + async beforeEach(ctx) { + // Setup - NOT measured + ctx.db = new sqlite3.Database(':memory:'); + // ... create tables, populate data + }, + + async fn(ctx) { + // Benchmark - MEASURED + // ... your operation here + }, + + async afterEach(ctx) { + // Teardown - NOT measured + await promisifyClose(ctx.db); + }, + }, + }, +}; +``` + +3. Import and add your benchmarks in `run.js` +4. Add documentation for your benchmark in this README + +## Notes + +- Benchmarks run in-memory (`:memory:`) by default for faster execution +- Results may vary based on your hardware and system configuration +- tinybench automatically determines the number of runs needed for statistical accuracy +- Warmup iterations allow V8 to optimize the code before measurement +- For meaningful comparisons, run benchmarks multiple times and compare the results + +## Dependencies + +- `tinybench`: ^6.0.0 - Modern benchmark library with proper setup/teardown support diff --git a/tools/benchmark/insert-transaction.sql b/tools/benchmark-internal/insert-transaction.sql similarity index 100% rename from tools/benchmark/insert-transaction.sql rename to tools/benchmark-internal/insert-transaction.sql diff --git a/tools/benchmark-internal/insert.js b/tools/benchmark-internal/insert.js new file mode 100644 index 000000000..36dc2b21b --- /dev/null +++ b/tools/benchmark-internal/insert.js @@ -0,0 +1,183 @@ +const sqlite3 = require('../../lib/sqlite3'); +const fs = require('fs'); + +const iterations = 10000; + +/** + * Helper to promisify sqlite3 operations + */ +function promisifyRun(db, sql, params = []) { + return new Promise((resolve, reject) => { + db.run(sql, params, function (err) { + if (err) reject(err); + else resolve(this); + }); + }); +} + +function promisifyExec(db, sql) { + return new Promise((resolve, reject) => { + db.exec(sql, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +function promisifyClose(db) { + return new Promise((resolve, reject) => { + db.close((err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +/** + * Insert benchmarks with proper setup/teardown separation. + * Setup (create database, create table) and teardown (close database) + * are NOT measured - only the actual insert operations are measured. + */ +module.exports = { + benchmarks: { + 'literal file': (() => { + let db = null; + let sqlFile = null; + + return { + async beforeEach() { + // Setup: Create in-memory database - NOT measured + db = new sqlite3.Database(':memory:'); + sqlFile = fs.readFileSync(__dirname + '/insert-transaction.sql', 'utf8'); + }, + + async fn() { + // Benchmark: Execute SQL file - MEASURED + await promisifyExec(db, sqlFile); + }, + + async afterEach() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + + 'transaction with two statements': (() => { + let db = null; + + return { + async beforeEach() { + // Setup: Create database and table - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyRun(db, 'CREATE TABLE foo (id INT, txt TEXT)'); + }, + + async fn() { + // Benchmark: Insert with transaction and two parallel statements - MEASURED + await new Promise((resolve, reject) => { + db.serialize(async () => { + await promisifyRun(db, 'BEGIN'); + + db.parallelize(() => { + const stmt1 = db.prepare('INSERT INTO foo VALUES (?, ?)'); + const stmt2 = db.prepare('INSERT INTO foo VALUES (?, ?)'); + for (let i = 0; i < iterations; i++) { + stmt1.run(i, 'Row ' + i); + i++; + stmt2.run(i, 'Row ' + i); + } + let finalized = 0; + const checkDone = (err) => { + if (err) reject(err); + finalized++; + if (finalized === 2) { + promisifyRun(db, 'COMMIT').then(resolve).catch(reject); + } + }; + stmt1.finalize(checkDone); + stmt2.finalize(checkDone); + }); + }); + }); + }, + + async afterEach() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + + 'with transaction': (() => { + let db = null; + + return { + async beforeEach() { + // Setup: Create database and table - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyRun(db, 'CREATE TABLE foo (id INT, txt TEXT)'); + }, + + async fn() { + // Benchmark: Insert with transaction - MEASURED + await new Promise((resolve, reject) => { + db.serialize(async () => { + await promisifyRun(db, 'BEGIN'); + const stmt = db.prepare('INSERT INTO foo VALUES (?, ?)'); + for (let i = 0; i < iterations; i++) { + stmt.run(i, 'Row ' + i); + } + stmt.finalize(async (err) => { + if (err) { + reject(err); + return; + } + await promisifyRun(db, 'COMMIT'); + resolve(); + }); + }); + }); + }, + + async afterEach() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + + 'without transaction': (() => { + let db = null; + + return { + async beforeEach() { + // Setup: Create database and table - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyRun(db, 'CREATE TABLE foo (id INT, txt TEXT)'); + }, + + async fn() { + // Benchmark: Insert without transaction - MEASURED + await new Promise((resolve, reject) => { + db.serialize(() => { + const stmt = db.prepare('INSERT INTO foo VALUES (?, ?)'); + for (let i = 0; i < iterations; i++) { + stmt.run(i, 'Row ' + i); + } + stmt.finalize((err) => { + if (err) reject(err); + else resolve(); + }); + }); + }); + }, + + async afterEach() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + }, +}; diff --git a/tools/benchmark-internal/run.js b/tools/benchmark-internal/run.js new file mode 100644 index 000000000..75246863a --- /dev/null +++ b/tools/benchmark-internal/run.js @@ -0,0 +1,106 @@ +const { Bench } = require('tinybench'); +const path = require('path'); + +// Set working directory to benchmark folder for relative path resolution +process.chdir(__dirname); + +// Load benchmarks +const insertBenchmarks = require('./insert'); +const selectBenchmarks = require('./select'); + +async function runBenchmarks() { + console.log('Running sqlite3 benchmarks...\n'); + + const results = []; + + // Run insert benchmarks + console.log('=== INSERT BENCHMARKS ===\n'); + for (const [name, benchmark] of Object.entries(insertBenchmarks.benchmarks)) { + console.log(`Running: insert: ${name}`); + const suite = new Bench({ + iterations: 100, + warmupIterations: 5, + }); + + suite.add(`insert: ${name}`, benchmark.fn, { + beforeEach: benchmark.beforeEach, + afterEach: benchmark.afterEach, + }); + + await suite.run(); + const task = suite.tasks[0]; + if (task.result && task.result.latency) { + results.push({ + name: `insert: ${name}`, + throughput: task.result.throughput?.mean, + latency: task.result.latency.mean, + rme: task.result.latency.rme, + samples: task.result.latency.samplesCount, + }); + } + } + + // Run select benchmarks (fewer iterations since they're slower) + console.log('\n=== SELECT BENCHMARKS ===\n'); + for (const [name, benchmark] of Object.entries(selectBenchmarks.benchmarks)) { + console.log(`Running: select: ${name}`); + const suite = new Bench({ + iterations: 10, + warmupIterations: 1, + }); + + suite.add(`select: ${name}`, benchmark.fn, { + beforeAll: benchmark.beforeAll, + afterAll: benchmark.afterAll, + }); + + await suite.run(); + const task = suite.tasks[0]; + if (task.result && task.result.latency) { + results.push({ + name: `select: ${name}`, + throughput: task.result.throughput?.mean, + latency: task.result.latency.mean, + rme: task.result.latency.rme, + samples: task.result.latency.samplesCount, + }); + } + } + + // Output results with full precision + console.log('\n=== RESULTS ===\n'); + + // Header + const header = [ + 'Task Name'.padEnd(45), + 'ops/sec'.padStart(15), + 'Average Time (ns)'.padStart(20), + 'Margin'.padStart(12), + 'Samples'.padStart(10) + ].join(''); + console.log(header); + console.log('-'.repeat(102)); + + // Results + for (const result of results) { + const opsSec = result.throughput?.toFixed(2) || 'N/A'; + const avgTime = (result.latency * 1e6).toFixed(2); // Convert ms to nanoseconds + const margin = result.rme?.toFixed(2) || 'N/A'; + const samples = result.samples || 0; + + const row = [ + result.name.padEnd(45), + opsSec.padStart(15), + avgTime.padStart(20), + (margin + '%').padStart(12), + samples.toString().padStart(10) + ].join(''); + console.log(row); + } +} + +runBenchmarks() + .catch((err) => { + console.error('Benchmark failed:', err); + process.exit(1); + }); diff --git a/tools/benchmark/select-data.sql b/tools/benchmark-internal/select-data.sql similarity index 100% rename from tools/benchmark/select-data.sql rename to tools/benchmark-internal/select-data.sql diff --git a/tools/benchmark-internal/select.js b/tools/benchmark-internal/select.js new file mode 100644 index 000000000..8221b09d9 --- /dev/null +++ b/tools/benchmark-internal/select.js @@ -0,0 +1,130 @@ +const sqlite3 = require('../../'); +const { readFileSync } = require('fs'); + +const setupSQL = readFileSync(`${__dirname}/select-data.sql`, 'utf8'); + +/** + * Helper to promisify sqlite3 operations + */ +function promisifyExec(db, sql) { + return new Promise((resolve, reject) => { + db.exec(sql, (err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +function promisifyClose(db) { + return new Promise((resolve, reject) => { + db.close((err) => { + if (err) reject(err); + else resolve(); + }); + }); +} + +/** + * Select benchmarks with proper setup/teardown separation. + * Setup (create database, populate data) and teardown (close database) + * are NOT measured - only the actual select operations are measured. + */ +module.exports = { + benchmarks: { + 'db.each': (() => { + let db = null; + + return { + async beforeAll() { + // Setup: Create and populate database - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyExec(db, setupSQL); + }, + + async fn() { + // Benchmark: Iterate through results row by row - MEASURED + await new Promise((resolve, reject) => { + const results = []; + db.each('SELECT * FROM foo', (err, row) => { + if (err) reject(err); + results.push(row); + }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + }, + + async afterAll() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + + 'db.all': (() => { + let db = null; + + return { + async beforeAll() { + // Setup: Create and populate database - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyExec(db, setupSQL); + }, + + async fn() { + // Benchmark: Retrieve all results at once - MEASURED + await new Promise((resolve, reject) => { + db.all('SELECT * FROM foo', (err, rows) => { + if (err) reject(err); + else resolve(); + }); + }); + }, + + async afterAll() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + + 'db.all with statement reset': (() => { + let db = null; + + return { + async beforeAll() { + // Setup: Create and populate database - NOT measured + db = new sqlite3.Database(':memory:'); + await promisifyExec(db, setupSQL); + }, + + async fn() { + // Benchmark: Retrieve all results with statement reset - MEASURED + await new Promise((resolve, reject) => { + const stmt = db.prepare('SELECT * FROM foo'); + stmt.all((err, rows) => { + if (err) { + stmt.finalize(() => reject(err)); + return; + } + stmt.reset(); + stmt.all((err2, rows2) => { + if (err2) { + stmt.finalize(() => reject(err2)); + return; + } + stmt.finalize(() => resolve()); + }); + }); + }); + }, + + async afterAll() { + // Teardown: Close database - NOT measured + await promisifyClose(db); + }, + }; + })(), + }, +}; diff --git a/tools/benchmark/insert.js b/tools/benchmark/insert.js deleted file mode 100644 index f2ebf7b90..000000000 --- a/tools/benchmark/insert.js +++ /dev/null @@ -1,68 +0,0 @@ -var sqlite3 = require('../../lib/sqlite3'); -var fs = require('fs'); - -var iterations = 10000; - -exports.compare = { - 'insert literal file': function(finished) { - var db = new sqlite3.Database(''); - var file = fs.readFileSync('benchmark/insert-transaction.sql', 'utf8'); - db.exec(file); - db.close(finished); - }, - - 'insert with transaction and two statements': function(finished) { - var db = new sqlite3.Database(''); - - db.serialize(function() { - db.run("CREATE TABLE foo (id INT, txt TEXT)"); - db.run("BEGIN"); - - db.parallelize(function() { - var stmt1 = db.prepare("INSERT INTO foo VALUES (?, ?)"); - var stmt2 = db.prepare("INSERT INTO foo VALUES (?, ?)"); - for (var i = 0; i < iterations; i++) { - stmt1.run(i, 'Row ' + i); - i++; - stmt2.run(i, 'Row ' + i); - } - stmt1.finalize(); - stmt2.finalize(); - }); - - db.run("COMMIT"); - }); - - db.close(finished); - }, - 'insert with transaction': function(finished) { - var db = new sqlite3.Database(''); - - db.serialize(function() { - db.run("CREATE TABLE foo (id INT, txt TEXT)"); - db.run("BEGIN"); - var stmt = db.prepare("INSERT INTO foo VALUES (?, ?)"); - for (var i = 0; i < iterations; i++) { - stmt.run(i, 'Row ' + i); - } - stmt.finalize(); - db.run("COMMIT"); - }); - - db.close(finished); - }, - 'insert without transaction': function(finished) { - var db = new sqlite3.Database(''); - - db.serialize(function() { - db.run("CREATE TABLE foo (id INT, txt TEXT)"); - var stmt = db.prepare("INSERT INTO foo VALUES (?, ?)"); - for (var i = 0; i < iterations; i++) { - stmt.run(i, 'Row ' + i); - } - stmt.finalize(); - }); - - db.close(finished); - } -}; \ No newline at end of file diff --git a/tools/benchmark/select.js b/tools/benchmark/select.js deleted file mode 100644 index be4b0e71d..000000000 --- a/tools/benchmark/select.js +++ /dev/null @@ -1,28 +0,0 @@ -const sqlite3 = require('../../'); -const { readFileSync } = require('fs'); -const db = new sqlite3.Database(':memory:'); - -db.serialize(() => { - db.exec(readFileSync(`${__dirname}/select-data.sql`, 'utf8'), (err) => { - if (err) throw err; - console.time('db.each'); - }); - - { - const results = []; - db.each('SELECT * FROM foo', (err, row) => { - if (err) throw err; - results.push(row); - }, () => { - console.timeEnd('db.each'); - console.time('db.all'); - }); - } - - db.all('SELECT * FROM foo', (err, rows) => { - console.timeEnd('db.all'); - if (err) throw err; - }); - - db.close(); -}); diff --git a/yarn.lock b/yarn.lock index 500dbb70a..7a9ef6593 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,63 +2,225 @@ # yarn lockfile v1 -"@eslint-community/eslint-utils@^4.2.0": +"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c" + integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw== + dependencies: + "@babel/helper-validator-identifier" "^7.28.5" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.28.6": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.0.tgz#00d03e8c0ac24dd9be942c5370990cbe1f17d88d" + integrity sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg== + +"@babel/core@^7.23.9": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.0.tgz#5286ad785df7f79d656e88ce86e650d16ca5f322" + integrity sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-compilation-targets" "^7.28.6" + "@babel/helper-module-transforms" "^7.28.6" + "@babel/helpers" "^7.28.6" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/traverse" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.29.0": + version "7.29.1" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50" + integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw== + dependencies: + "@babel/parser" "^7.29.0" + "@babel/types" "^7.29.0" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz#32c4a3f41f12ed1532179b108a4d746e105c2b25" + integrity sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA== + dependencies: + "@babel/compat-data" "^7.28.6" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" + integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== + dependencies: + "@babel/traverse" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/helper-module-transforms@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" + integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== + dependencies: + "@babel/helper-module-imports" "^7.28.6" + "@babel/helper-validator-identifier" "^7.28.5" + "@babel/traverse" "^7.28.6" + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.6": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.2.tgz#9cfbccb02b8e229892c0b07038052cc1a8709c49" + integrity sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw== + dependencies: + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + +"@babel/parser@^7.23.9", "@babel/parser@^7.28.6", "@babel/parser@^7.29.0": + version "7.29.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.2.tgz#58bd50b9a7951d134988a1ae177a35ef9a703ba1" + integrity sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA== + dependencies: + "@babel/types" "^7.29.0" + +"@babel/template@^7.28.6": + version "7.28.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57" + integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ== + dependencies: + "@babel/code-frame" "^7.28.6" + "@babel/parser" "^7.28.6" + "@babel/types" "^7.28.6" + +"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" + integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== + dependencies: + "@babel/code-frame" "^7.29.0" + "@babel/generator" "^7.29.0" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.29.0" + "@babel/template" "^7.28.6" + "@babel/types" "^7.29.0" + debug "^4.3.1" + +"@babel/types@^7.28.6", "@babel/types@^7.29.0": + version "7.29.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7" + integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@eslint-community/eslint-utils@^4.8.0": version "4.9.1" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.12.2": version "4.12.2" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/config-array@^0.23.4": + version "0.23.4" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.4.tgz#b4e160e668694011b355bfe9a89eb61a0eb641c4" + integrity sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow== dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" + "@eslint/object-schema" "^3.0.4" + debug "^4.3.1" + minimatch "^10.2.4" -"@eslint/js@8.56.0": - version "8.56.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" - integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== +"@eslint/config-helpers@^0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.4.tgz#0b16c091dd16766f27e41f09bd264e3585a45652" + integrity sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg== + dependencies: + "@eslint/core" "^1.2.0" -"@gar/promise-retry@^1.0.0": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@gar/promise-retry/-/promise-retry-1.0.2.tgz#90d01966a7fcb270ce6fb4211405ab828ae6e036" - integrity sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g== +"@eslint/core@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.2.0.tgz#3f741da0119058ad2a567a6f215677b5557a19e9" + integrity sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A== dependencies: - retry "^0.13.1" + "@types/json-schema" "^7.0.15" + +"@eslint/js@^10.0.1": + version "10.0.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-10.0.1.tgz#1e8a876f50117af8ab67e47d5ad94d38d6622583" + integrity sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA== + +"@eslint/object-schema@^3.0.4": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.4.tgz#8ce3aff08f6ca7c3bd9e1cec34530fc7fb44546a" + integrity sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw== -"@humanwhocodes/config-array@^0.11.13": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== +"@eslint/plugin-kit@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz#7442f663da4d61091d2af0b30c8a6b50949fb26d" + integrity sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg== dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" + "@eslint/core" "^1.2.0" + levn "^0.4.1" + +"@gar/promise-retry@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@gar/promise-retry/-/promise-retry-1.0.3.tgz#65e726428e794bc4453948e0a41e6de4215ce8b0" + integrity sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA== + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -79,26 +241,55 @@ dependencies: minipass "^7.0.4" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" "@npmcli/agent@^2.0.0": version "2.2.2" @@ -136,15 +327,30 @@ dependencies: semver "^7.3.5" +"@npmcli/redact@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/redact/-/redact-4.0.0.tgz#c91121e02b7559a997614a2c1057cd7fc67608c4" + integrity sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@^1.0.6", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== abbrev@1: version "1.1.1" @@ -166,7 +372,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +acorn@^8.16.0: version "8.16.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== @@ -189,7 +395,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv@^6.12.3, ajv@^6.12.4: +ajv@^6.12.3, ajv@^6.14.0: version "6.14.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== @@ -199,11 +405,6 @@ ajv@^6.12.3, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -236,13 +437,12 @@ ansi@^0.3.0, ansi@~0.3.1: resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" integrity sha512-iFY7JCgHbepc0b82yLaw4IMortylNb6wG4kL+4R0C3iv6i+RHGHux/yUX5BTiRvSX/shMnngjR1YyNMnXEFh5A== -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== +append-transform@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-2.0.0.tgz#99d9d29c7b38391e6f428d28ce136551f0b77e12" + integrity sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg== dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" + default-require-extensions "^3.0.0" aproba@^1.0.3: version "1.2.0" @@ -254,6 +454,11 @@ aproba@^1.0.3: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.1.0.tgz#75500a190313d95c64e871e7e4284c6ac219f0b1" integrity sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew== +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw== + are-we-there-yet@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz#679df222b278c64f2cdba1175cdc00b0d96164bd" @@ -275,6 +480,13 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -316,13 +528,13 @@ aws4@^1.8.0: integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== axios@^1.6.5: - version "1.13.6" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.13.6.tgz#c3f92da917dc209a15dd29936d20d5089b6b6c98" - integrity sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ== + version "1.15.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.15.0.tgz#0fcee91ef03d386514474904b27863b2c683bf4f" + integrity sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q== dependencies: follow-redirects "^1.15.11" form-data "^4.0.5" - proxy-from-env "^1.1.0" + proxy-from-env "^2.1.0" b4a@^1.6.4: version "1.8.0" @@ -345,9 +557,9 @@ bare-events@^2.5.4, bare-events@^2.7.0: integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== bare-fs@^4.5.5: - version "4.5.5" - resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.5.5.tgz#589a8f87a32af0266aa474413c8d7d11d50e4a65" - integrity sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w== + version "4.6.0" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-4.6.0.tgz#ff2f10c8238d3ff94f2704f0c581b197b47ef112" + integrity sha512-2YkS7NuiJceSEbyEOdSNLE9tsGd+f4+f7C+Nik/MCk27SYdwIMPT/yRKvg++FZhQXgk0KWJKJyXX9RhVV0RGqA== dependencies: bare-events "^2.5.4" bare-path "^3.0.0" @@ -356,9 +568,9 @@ bare-fs@^4.5.5: fast-fifo "^1.3.2" bare-os@^3.0.1: - version "3.8.0" - resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.8.0.tgz#888541d443914e861abed02657a6ef1c50bbf383" - integrity sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg== + version "3.8.7" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.8.7.tgz#09c7c4e8c817de750b0b69b65c929513f69ede65" + integrity sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w== bare-path@^3.0.0: version "3.0.0" @@ -368,17 +580,17 @@ bare-path@^3.0.0: bare-os "^3.0.1" bare-stream@^2.6.4: - version "2.8.1" - resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.8.1.tgz#a4551375bcb01484c5a66946652ebd718b47903d" - integrity sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg== + version "2.12.0" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.12.0.tgz#f1b6818768113790bbbfde90f47003f370c8778e" + integrity sha512-w28i8lkBgREV3rPXGbgK+BO66q+ZpKqRWrZLiCdmmUlLPrQ45CzkvRhN+7lnv00Gpi2zy5naRxnUFAxCECDm9g== dependencies: - streamx "^2.21.0" + streamx "^2.25.0" teex "^1.0.1" bare-url@^2.2.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.3.2.tgz#4aef382efa662b2180a6fe4ca07a71b39bdf7ca3" - integrity sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/bare-url/-/bare-url-2.4.0.tgz#1546d63057917189cab9b24629e946e1e8f7af31" + integrity sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA== dependencies: bare-path "^3.0.0" @@ -387,6 +599,11 @@ base64-js@^1.3.1: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +baseline-browser-mapping@^2.10.12: + version "2.10.14" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.14.tgz#d25463733a8f80bb59ab9f797c902dc88832d47c" + integrity sha512-fOVLPAsFTsQfuCkvahZkzq6nf8KvGWanlYoTh0SVA0A/PIUxQGU2AOZAoD95n2gFLVDW/jP6sbGLny95nmEuHA== + bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -394,11 +611,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" @@ -430,39 +642,43 @@ block-stream@*: inherits "~2.0.0" brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.13" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6" + integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1, brace-expansion@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== +brace-expansion@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.3.tgz#0493338bdd58e319b1039c67cf7ee439892c01d9" + integrity sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA== dependencies: balanced-match "^1.0.0" -brace-expansion@^5.0.2: - version "5.0.4" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.4.tgz#614daaecd0a688f660bbbc909a8748c3d80d4336" - integrity sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg== +brace-expansion@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" + integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== dependencies: balanced-match "^4.0.2" -braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +browserslist@^4.24.0: + version "4.28.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2" + integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg== + dependencies: + baseline-browser-mapping "^2.10.12" + caniuse-lite "^1.0.30001782" + electron-to-chromium "^1.5.328" + node-releases "^2.0.36" + update-browserslist-db "^1.2.3" + buffer-from@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-0.1.2.tgz#15f4b9bcef012044df31142c14333caf6e0260d0" @@ -495,9 +711,9 @@ cacache@^18.0.0: unique-filename "^3.0.0" cacache@^20.0.1: - version "20.0.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-20.0.3.tgz#bd65205d5e6d86e02bbfaf8e4ce6008f1b81d119" - integrity sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw== + version "20.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-20.0.4.tgz#9b547dc3db0c1f87cba6dbbff91fb17181b4bbb1" + integrity sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA== dependencies: "@npmcli/fs" "^5.0.0" fs-minipass "^3.0.0" @@ -509,7 +725,16 @@ cacache@^20.0.1: minipass-pipeline "^1.2.4" p-map "^7.0.2" ssri "^13.0.0" - unique-filename "^5.0.0" + +caching-transform@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-4.0.0.tgz#00d297a4206d71e2163c39eaffa8157ac0651f0f" + integrity sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA== + dependencies: + hasha "^5.0.0" + make-dir "^3.0.0" + package-hash "^4.0.0" + write-file-atomic "^3.0.0" call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" @@ -519,22 +744,27 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: es-errors "^1.3.0" function-bind "^1.1.2" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase@^5.0.0, camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +caniuse-lite@^1.0.30001782: + version "1.0.30001785" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001785.tgz#31f8e3ec1059430d2a7b04fff44c27672c4482df" + integrity sha512-blhOL/WNR+Km1RI/LCVAvA73xplXA7ZbjzI4YkMK9pa6T/P3F2GxjNpEkyw5repTw9IvkyrjyHpwjnhZ5FOvYQ== + caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -542,20 +772,12 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" +chokidar@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" chownr@^1.1.1: version "1.1.4" @@ -577,14 +799,14 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: string-width "^4.2.0" strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" + wrap-ansi "^6.2.0" cliui@^8.0.1: version "8.0.1" @@ -647,6 +869,11 @@ commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -657,6 +884,16 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== +convert-source-map@^1.7.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -667,7 +904,7 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== -cross-spawn@^7.0.2, cross-spawn@^7.0.6: +cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -691,20 +928,13 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@4, debug@^4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@4, debug@^4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" -debug@4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@^2.2.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -712,6 +942,11 @@ debug@^2.2.0: dependencies: ms "2.0.0" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" @@ -734,6 +969,13 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +default-require-extensions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-3.0.1.tgz#bfae00feeaeada68c2ae256c62540f60b80625bd" + integrity sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw== + dependencies: + strip-bom "^4.0.0" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -749,17 +991,10 @@ detect-libc@^2.0.0, detect-libc@^2.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" +diff@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" + integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== dunder-proto@^1.0.1: version "1.0.1" @@ -795,6 +1030,11 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" +electron-to-chromium@^1.5.328: + version "1.5.331" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz#3e4e845042d517c68b3c00be5fc33204f13b2058" + integrity sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -866,6 +1106,11 @@ es5-ext@^0.10.35, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14: esniff "^2.0.1" next-tick "^1.1.0" +es6-error@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -883,72 +1128,71 @@ es6-symbol@^3.0.2, es6-symbol@^3.1.1, es6-symbol@^3.1.3: d "^1.0.2" ext "^1.7.0" -escalade@^3.1.1: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.2.tgz#b9de6ace2fab1cff24d2e58d85b74c8fcea39802" + integrity sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ== dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@8.56.0: - version "8.56.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" - integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.56.0" - "@humanwhocodes/config-array" "^0.11.13" +eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.2.0.tgz#711c80d32fc3fdd3a575bb93977df43887c3ec8e" + integrity sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.4" + "@eslint/config-helpers" "^0.5.4" + "@eslint/core" "^1.2.0" + "@eslint/plugin-kit" "^0.7.0" + "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" - ajv "^6.12.4" - chalk "^4.0.0" - cross-spawn "^7.0.2" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^9.1.2" + eslint-visitor-keys "^5.0.1" + espree "^11.2.0" + esquery "^1.7.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" - is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" + minimatch "^10.2.4" natural-compare "^1.4.0" optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" esniff@^2.0.1: version "2.0.1" @@ -960,16 +1204,21 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== dependencies: - acorn "^8.9.0" + acorn "^8.16.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^5.0.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== @@ -1067,38 +1316,41 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fastq@^1.6.0: - version "1.20.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" - integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== - dependencies: - reusify "^1.0.4" - fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== +find-cache-dir@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - to-regex-range "^5.0.1" + locate-path "^5.0.0" + path-exists "^4.0.0" -find-up@5.0.0, find-up@^5.0.0: +find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== @@ -1106,14 +1358,13 @@ find-up@5.0.0, find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" + keyv "^4.5.4" flat@^5.0.2: version "5.0.2" @@ -1121,16 +1372,24 @@ flat@^5.0.2: integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^3.2.9: - version "3.4.1" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.1.tgz#84ccd9579e76e9cc0d246c11d8be0beb019143e6" - integrity sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ== + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== follow-redirects@^1.15.11: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" -foreground-child@^3.1.0: +foreground-child@^3.1.0, foreground-child@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== @@ -1163,6 +1422,11 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +fromentries@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.3.2.tgz#e4bca6808816bf8f93b52750f1127f5a6fd86e3a" + integrity sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg== + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -1196,11 +1460,6 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - fstream@^1.0.0, fstream@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" @@ -1269,7 +1528,12 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -get-caller-file@^2.0.5: +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== @@ -1290,6 +1554,11 @@ get-intrinsic@^1.2.6: hasown "^2.0.2" math-intrinsics "^1.1.0" +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" @@ -1344,13 +1613,6 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - "glob@3 || 4 || 5 || 6 || 7", glob@^7.0.3, glob@^7.1.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1363,19 +1625,7 @@ glob-parent@~5.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^10.2.2, glob@^10.3.10: +glob@^10.2.2, glob@^10.3.10, glob@^10.4.5: version "10.5.0" resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== @@ -1387,7 +1637,7 @@ glob@^10.2.2, glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^13.0.0: +glob@^13.0.0, glob@^13.0.3, glob@^13.0.6: version "13.0.6" resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== @@ -1396,28 +1646,21 @@ glob@^13.0.0: minipass "^7.1.3" path-scurry "^2.0.2" -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" +globals@^17.4.0: + version "17.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-17.4.0.tgz#33d7d297ed1536b388a0e2f4bcd0ff19c8ff91b5" + integrity sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw== gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -1453,6 +1696,14 @@ has-unicode@^2.0.0, has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== +hasha@^5.0.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/hasha/-/hasha-5.2.2.tgz#a48477989b3b327aea3c04f53096d816d97522a1" + integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== + dependencies: + is-stream "^2.0.0" + type-fest "^0.8.0" + hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" @@ -1460,11 +1711,16 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -he@1.2.0: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + http-cache-semantics@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" @@ -1528,14 +1784,6 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -1569,13 +1817,6 @@ ip-address@^10.0.1: resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.1.0.tgz#d8dcffb34d0e02eb241427444a6e23f5b0595aa4" integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1593,7 +1834,7 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1605,11 +1846,6 @@ is-lambda@^1.0.1: resolved "https://registry.yarnpkg.com/is-lambda/-/is-lambda-1.0.1.tgz#3d9877899e6a53efc0160504cde15f82e6f061d5" integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -1620,7 +1856,12 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-typedarray@~1.0.0: +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== @@ -1630,6 +1871,11 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -1660,6 +1906,67 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-hook@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz#8f84c9434888cc6b1d0a9d7092a76d239ebf0cc6" + integrity sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ== + dependencies: + append-transform "^2.0.0" + +istanbul-lib-instrument@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== + dependencies: + "@babel/core" "^7.23.9" + "@babel/parser" "^7.23.9" + "@istanbuljs/schema" "^0.1.3" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + +istanbul-lib-processinfo@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-processinfo/-/istanbul-lib-processinfo-3.0.0.tgz#1b95ce9ccbac979a718de17ab33b940e2ce5b75f" + integrity sha512-P7nLXRRlo7Sqinty6lNa7+4o9jBUYGpqtejqCOZKfgXlRoxY/QArflcB86YO500Ahj4pDJEG34JjMRbQgePLnQ== + dependencies: + archy "^1.0.0" + cross-spawn "^7.0.3" + istanbul-lib-coverage "^3.2.0" + p-map "^3.0.0" + rimraf "^6.1.3" + uuid "^8.3.2" + +istanbul-lib-report@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz#cb4535162b5784aa623cee21a7252cf2c807ac93" + integrity sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -1669,12 +1976,18 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -js-yaml@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.2" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: - argparse "^2.0.1" + argparse "^1.0.7" + esprima "^4.0.0" js-yaml@^4.1.0: version "4.1.1" @@ -1688,6 +2001,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -1713,6 +2031,11 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^6.0.1: version "6.2.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" @@ -1742,7 +2065,7 @@ jsprim@^1.2.2: json-schema "0.4.0" verror "1.10.0" -keyv@^4.5.3: +keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -1757,6 +2080,13 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -1764,10 +2094,10 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== lodash.pad@^4.1.0: version "4.5.1" @@ -1789,7 +2119,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -1807,6 +2137,27 @@ lru-cache@^11.0.0, lru-cache@^11.1.0, lru-cache@^11.2.1: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.7.tgz#9127402617f34cd6767b96daee98c28e74458d35" integrity sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA== +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^3.0.0, make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== + dependencies: + semver "^7.5.3" + make-fetch-happen@^13.0.0: version "13.0.1" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz#273ba2f78f45e1f3a6dca91cede87d9fa4821e36" @@ -1826,12 +2177,13 @@ make-fetch-happen@^13.0.0: ssri "^10.0.0" make-fetch-happen@^15.0.0: - version "15.0.4" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-15.0.4.tgz#6ff007b6d5f7b75c4cc26ec2d4adbfe05d6c0921" - integrity sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw== + version "15.0.5" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz#b0e3dd53d487b2733e4ea232c2bebf1bd16afb03" + integrity sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg== dependencies: "@gar/promise-retry" "^1.0.0" "@npmcli/agent" "^4.0.0" + "@npmcli/redact" "^4.0.0" cacache "^20.0.1" http-cache-semantics "^4.1.1" minipass "^7.0.2" @@ -1871,28 +2223,21 @@ mimic-response@^3.1.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@3, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@3, minimatch@^3.0.2, minimatch@^3.1.1: version "3.1.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^10.2.2: - version "10.2.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" - integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== +minimatch@^10.2.2, minimatch@^10.2.4: + version "10.2.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" + integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg== dependencies: - brace-expansion "^5.0.2" + brace-expansion "^5.0.5" -minimatch@^9.0.4: +minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.9" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== @@ -1934,9 +2279,9 @@ minipass-fetch@^5.0.0: iconv-lite "^0.7.2" minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.7.tgz#145c383d5ae294b36030aa80d4e872d08bebcb73" + integrity sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA== dependencies: minipass "^3.0.0" @@ -2010,53 +2355,43 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8" - integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "7.2.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - nanoid "3.3.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +mocha@11.7.5: + version "11.7.5" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.5.tgz#58f5bbfa5e0211ce7e5ee6128107cefc2515a627" + integrity sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig== + dependencies: + browser-stdout "^1.3.1" + chokidar "^4.0.1" + debug "^4.3.5" + diff "^7.0.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^10.4.5" + he "^1.2.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^9.0.5" + ms "^2.1.3" + picocolors "^1.1.1" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^9.2.0" + yargs "^17.7.2" + yargs-parser "^21.1.1" + yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.3: +ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@3.3.3: - version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" - integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== - napi-build-utils@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" @@ -2094,10 +2429,10 @@ node-abi@^3.3.0, node-abi@^3.54.0: dependencies: semver "^7.3.5" -node-addon-api@^8.0.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.6.0.tgz#b22497201b465cd0a92ef2c01074ee5068c79a6d" - integrity sha512-gBVjCaqDlRUk0EwoPNKzIr9KkS9041G/q31IBShPs1Xz6UTA+EXdZADbzqAJQrpDRq71CIMnOP5VMut3SL0z5Q== +node-addon-api@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-8.7.0.tgz#f64f8413456ecbe900221305a3f883c37666473f" + integrity sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA== node-api-headers@^1.1.0: version "1.8.0" @@ -2156,6 +2491,18 @@ node-ninja@^1.0.2: tar "^2.0.0" which "1" +node-preload@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/node-preload/-/node-preload-0.2.1.tgz#c03043bb327f417a18fee7ab7ee57b408a144301" + integrity sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ== + dependencies: + process-on-spawn "^1.0.0" + +node-releases@^2.0.36: + version "2.0.37" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.37.tgz#9bd4f10b77ba39c2b9402d4e8399c482a797f671" + integrity sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg== + noop-logger@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" @@ -2182,11 +2529,6 @@ nopt@^9.0.0: dependencies: abbrev "^4.0.0" -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - npm-path@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.4.tgz#c641347a5ff9d6a09e4d9bce5580c4f505278e64" @@ -2266,6 +2608,39 @@ nw-gyp@^3.6.6: tar "^2.0.0" which "1" +nyc@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/nyc/-/nyc-18.0.0.tgz#644c75c5cba4e8c571674016ac7f714fb143f20c" + integrity sha512-G5UyHinFkB1BxqGTrmZdB6uIYH0+v7ZnVssuflUDi+J+RhKWyAhRT1RCehBSI6jLFLuUUgFDyLt49mUtdO1XeQ== + dependencies: + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + caching-transform "^4.0.0" + convert-source-map "^1.7.0" + decamelize "^1.2.0" + find-cache-dir "^3.2.0" + find-up "^4.1.0" + foreground-child "^3.3.0" + get-package-type "^0.1.0" + glob "^13.0.6" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-hook "^3.0.0" + istanbul-lib-instrument "^6.0.2" + istanbul-lib-processinfo "^3.0.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + make-dir "^3.0.0" + node-preload "^0.2.1" + p-map "^3.0.0" + process-on-spawn "^1.0.0" + resolve-from "^5.0.0" + rimraf "^6.1.3" + signal-exit "^3.0.2" + spawn-wrap "^3.0.0" + test-exclude "^8.0.0" + yargs "^15.0.2" + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -2313,6 +2688,13 @@ osenv@0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-limit@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" @@ -2320,6 +2702,13 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + p-locate@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" @@ -2327,6 +2716,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" @@ -2339,18 +2735,26 @@ p-map@^7.0.2: resolved "https://registry.yarnpkg.com/p-map/-/p-map-7.0.4.tgz#b81814255f542e252d5729dca4d66e5ec14935b8" integrity sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ== -package-json-from-dist@^1.0.0: +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +package-hash@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-4.0.0.tgz#3537f654665ec3cc38827387fc904c163c54f506" + integrity sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ== + dependencies: + graceful-fs "^4.1.15" + hasha "^5.0.0" + lodash.flattendeep "^4.4.0" + release-zalgo "^1.0.0" + +package-json-from-dist@^1.0.0, package-json-from-dist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - path-array@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-array/-/path-array-1.0.1.tgz#7e2f0f35f07a2015122b868b7eac0eb2c4fec271" @@ -2394,15 +2798,22 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== -picomatch@^2.0.4, picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" - integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" prebuild-install@^7.1.3: version "7.1.3" @@ -2467,6 +2878,13 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-on-spawn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/process-on-spawn/-/process-on-spawn-1.1.0.tgz#9d5999ba87b3bf0a8acb05322d69f2f5aa4fb763" + integrity sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q== + dependencies: + fromentries "^1.2.0" + promise-retry@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22" @@ -2475,10 +2893,10 @@ promise-retry@^2.0.1: err-code "^2.0.2" retry "^0.12.0" -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== +proxy-from-env@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz#a7487568adad577cfaaa7e88c49cab3ab3081aba" + integrity sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA== psl@^1.1.28: version "1.15.0" @@ -2505,11 +2923,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.5.tgz#7c9442fc3f1c58bb52ac57ad09db63ba68916395" integrity sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ== -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -2569,12 +2982,17 @@ readable-stream@~1.1.9: isarray "0.0.1" string_decoder "~0.10.x" -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + integrity sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA== dependencies: - picomatch "^2.2.1" + es6-error "^4.0.1" request@2: version "2.88.2" @@ -2607,26 +3025,21 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -reusify@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" - integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== - rimraf@2: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -2634,19 +3047,13 @@ rimraf@2: dependencies: glob "^7.1.3" -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== +rimraf@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af" + integrity sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA== dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" + glob "^13.0.3" + package-json-from-dist "^1.0.1" run-waterfall@^1.1.7: version "1.1.7" @@ -2673,7 +3080,12 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^7.3.5, semver@^7.5.4: +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5, semver@^7.5.3, semver@^7.5.4: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== @@ -2683,10 +3095,10 @@ semver@~5.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" integrity sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw== -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +serialize-javascript@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== dependencies: randombytes "^2.1.0" @@ -2707,7 +3119,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -signal-exit@^3.0.0, signal-exit@^3.0.7: +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== @@ -2758,6 +3170,29 @@ socks@^2.8.3: ip-address "^10.0.1" smart-buffer "^4.2.0" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spawn-wrap@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-3.0.0.tgz#8f8c3e69e7f6afcf9404beacaa3241f645d6ea5c" + integrity sha512-z+s5vv4KzFPJVddGab0xX2n7kQPGMdNUX5l9T8EJqsXdKTWpcxmAqWHpsgHEXoC1taGBCc7b79bi62M5kdbrxQ== + dependencies: + cross-spawn "^7.0.6" + foreground-child "^2.0.0" + is-windows "^1.0.2" + make-dir "^3.0.0" + rimraf "^6.1.3" + signal-exit "^3.0.2" + which "^2.0.1" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + sshpk@^1.7.0: version "1.18.0" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.18.0.tgz#1663e55cddf4d688b86a46b77f0d5fe363aba028" @@ -2787,10 +3222,10 @@ ssri@^13.0.0: dependencies: minipass "^7.0.3" -streamx@^2.12.5, streamx@^2.15.0, streamx@^2.21.0: - version "2.23.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.23.0.tgz#7d0f3d00d4a6c5de5728aecd6422b4008d66fd0b" - integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== +streamx@^2.12.5, streamx@^2.15.0, streamx@^2.25.0: + version "2.25.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.25.0.tgz#cc967e99390fda8b918b1eeaf3bc437637c8c7af" + integrity sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg== dependencies: events-universal "^1.0.0" fast-fifo "^1.3.2" @@ -2879,7 +3314,12 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.2.2" -strip-json-comments@3.1.1, strip-json-comments@^3.1.1: +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -2889,13 +3329,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -supports-color@8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2903,6 +3336,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + tar-fs@^2.0.0: version "2.1.4" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.4.tgz#800824dbf4ef06ded9afea4acafe71c67c76b930" @@ -2955,10 +3395,10 @@ tar@^6.1.11, tar@^6.2.0, tar@^6.2.1: mkdirp "^1.0.3" yallist "^4.0.0" -tar@^7.5.10, tar@^7.5.4: - version "7.5.11" - resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.11.tgz#1250fae45d98806b36d703b30973fa8e0a6d8868" - integrity sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ== +tar@^7.5.13, tar@^7.5.4: + version "7.5.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.13.tgz#0d214ed56781a26edc313581c0e2d929ceeb866d" + integrity sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng== dependencies: "@isaacs/fs-minipass" "^4.0.0" chownr "^3.0.0" @@ -2973,6 +3413,15 @@ teex@^1.0.1: dependencies: streamx "^2.12.5" +test-exclude@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-8.0.0.tgz#85891add3fa46bb822b1b1579c7f8c9a3d04ebd8" + integrity sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^13.0.6" + minimatch "^10.2.2" + text-decoder@^1.1.0: version "1.2.7" resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.7.tgz#5d073a9a74b9c0a9d28dfadcab96b604af57d8ba" @@ -2980,11 +3429,6 @@ text-decoder@^1.1.0: dependencies: b4a "^1.6.4" -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - through2@~0.6.3: version "0.6.5" resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" @@ -2993,6 +3437,11 @@ through2@~0.6.3: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" +tinybench@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-6.0.0.tgz#890309d74e0c3f60b20def20295bfd937c1d7521" + integrity sha512-BWlWpVbbZXaYjRV0twGLNQO00Zj4HA/sjLOQP2IvzQqGwRGp+2kh7UU3ijyJ3ywFRogYDRbiHDMrUOfaMnN56g== + tinyglobby@^0.2.12: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" @@ -3001,13 +3450,6 @@ tinyglobby@^0.2.12: fdir "^6.5.0" picomatch "^4.0.3" -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -3035,16 +3477,23 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== type@^2.7.2: version "2.7.3" resolved "https://registry.yarnpkg.com/type/-/type-2.7.3.tgz#436981652129285cc3ba94f392886c2637ea0486" integrity sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ== +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + unique-filename@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-3.0.0.tgz#48ba7a5a16849f5080d26c760c86cf5cf05770ea" @@ -3052,13 +3501,6 @@ unique-filename@^3.0.0: dependencies: unique-slug "^4.0.0" -unique-filename@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-5.0.0.tgz#8b17bbde1a7ca322dd1a1d23fe17c2b798c43f8f" - integrity sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg== - dependencies: - unique-slug "^6.0.0" - unique-slug@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-4.0.0.tgz#6bae6bb16be91351badd24cdce741f892a6532e3" @@ -3066,18 +3508,19 @@ unique-slug@^4.0.0: dependencies: imurmurhash "^0.1.4" -unique-slug@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-6.0.0.tgz#f46fd688a9bd972fd356c23d95812a3a4862ed88" - integrity sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw== - dependencies: - imurmurhash "^0.1.4" - universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== +update-browserslist-db@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -3110,6 +3553,11 @@ uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" @@ -3119,6 +3567,11 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + which@1, which@^1.2.10: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -3159,10 +3612,10 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^9.2.0: + version "9.3.4" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41" + integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" @@ -3173,6 +3626,15 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3196,16 +3658,36 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + "xtend@>=4.0.0 <4.1.0-0", xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" @@ -3216,22 +3698,20 @@ yallist@^5.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -3241,18 +3721,22 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== +yargs@^15.0.2: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" yargs@^17.7.2: version "17.7.2"