diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index edc9cc529..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - parserOptions: { - sourceType: 'script' - }, - extends: ['semistandard'] -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 331778bf3..a420a61ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,14 +3,26 @@ updates: - package-ecosystem: github-actions directory: / schedule: - interval: daily + interval: weekly + groups: + all: + patterns: + - '*' - package-ecosystem: npm directory: / schedule: - interval: daily + interval: weekly + groups: + all: + patterns: + - '*' - package-ecosystem: npm directory: /test/addon_build/tpl schedule: - interval: daily + interval: weekly + groups: + all: + patterns: + - '*' diff --git a/.github/workflows/ci-win.yml b/.github/workflows/ci-win.yml index 13fd005c0..4b0ebfb23 100644 --- a/.github/workflows/ci-win.yml +++ b/.github/workflows/ci-win.yml @@ -12,21 +12,44 @@ jobs: test: timeout-minutes: 30 strategy: + fail-fast: false matrix: - node-version: [ 16.x, 18.x, 20.x ] + api_version: + - standard + - experimental + node-version: + - 20.x + - 22.x + - 24.x + - 25.x + - 26.x architecture: [x64, x86] os: - - windows-2019 - windows-2022 + - windows-2025 + exclude: + # Skip when node 24.x or 25.x AND architecture is x86 since there is + # no published Node.js x86 build for those versions. + - node-version: 24.x + architecture: x86 + - node-version: 25.x + architecture: x86 + - node-version: 26.x + architecture: x86 runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} architecture: ${{ matrix.architecture }} @@ -38,6 +61,10 @@ jobs: run: | npm install - name: npm test + shell: bash run: | + if [ "${{ matrix.api_version }}" = "experimental" ]; then + export NAPI_VERSION=2147483647 + fi npm run pretest -- --verbose node test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64bda90dd..3813c3d62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,8 +12,17 @@ jobs: test: timeout-minutes: 30 strategy: + fail-fast: false matrix: - node-version: [ 16.x, 18.x, 20.x ] + api_version: + - standard + - experimental + node-version: + - 20.x + - 22.x + - 24.x + - 25.x + - 26.x os: - macos-latest - ubuntu-latest @@ -25,13 +34,18 @@ jobs: compiler: gcc # GCC is an alias for clang on the MacOS image. runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ env.PYTHON_VERSION }} - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} - name: Check Node.js installation @@ -43,6 +57,9 @@ jobs: npm install - name: npm test run: | + if [ "${{ matrix.api_version }}" = "experimental" ]; then + export NAPI_VERSION=2147483647 + fi if [ "${{ matrix.compiler }}" = "gcc" ]; then export CC="gcc" CXX="g++" fi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 256dc9d2b..8758fdcef 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,16 +41,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -70,7 +70,7 @@ jobs: - name: Use Node.js v18.x if: matrix.language == 'cpp' - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: 18.x @@ -80,6 +80,6 @@ jobs: npx node-gyp rebuild -C test - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml new file mode 100644 index 000000000..3d116e405 --- /dev/null +++ b/.github/workflows/coverage-linux.yml @@ -0,0 +1,68 @@ +name: Coverage Linux + +on: + pull_request: + types: [opened, synchronize, reopened] + paths-ignore: + - '**.md' + - benchmark/** + - doc/** + - tools/** + - unit-test/** + - .github/** + - '!.github/workflows/coverage-linux.yml' + push: + branches: + - main + paths-ignore: + - '**.md' + - benchmark/** + - doc/** + - tools/** + - unit-test/** + - .github/** + - '!.github/workflows/coverage-linux.yml' + +env: + PYTHON_VERSION: '3.11' + NODE_VERSION: '22.x' + +permissions: + contents: read + +jobs: + coverage-linux: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ env.NODE_VERSION }} + - name: Environment Information + run: npx envinfo + - name: Install gcovr + run: pip install gcovr==6.0 + - name: Install dependencies + run: npm install + - name: Test with coverage + run: | + npm run create-coverage + - name: Generate coverage report (XML) + run: | + npm run report-coverage-xml + - name: Upload + uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + with: + directory: ./coverage-xml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fbaf8acfd..8d6513bed 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: 'Checkout Repository' - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@01bc87099ba56df1e897b6874784491ea6309bc4 # v3.1.4 + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index d855061a6..4cd921796 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -10,17 +10,22 @@ jobs: if: github.repository == 'nodejs/node-addon-api' strategy: matrix: - node-version: [16.x] + node-version: [22.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - run: git branch -a - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version: ${{ matrix.node-version }} - run: npm install diff --git a/.github/workflows/node-api-headers.yml b/.github/workflows/node-api-headers.yml new file mode 100644 index 000000000..59c686348 --- /dev/null +++ b/.github/workflows/node-api-headers.yml @@ -0,0 +1,71 @@ +name: Node.js CI with node-api-headers + +on: [push, pull_request] + +env: + PYTHON_VERSION: '3.11' + +permissions: + contents: read + +jobs: + test: + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + api_version: + - '9' + node-version: + - 22.x + node-api-headers-version: + - '1.1.0' + - '1.2.0' + - '1.3.0' + os: + - ubuntu-latest + compiler: + - gcc + - clang + runs-on: ${{ matrix.os }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ matrix.node-version }} + - name: Check Node.js installation + run: | + node --version + npm --version + - name: Install dependencies + run: | + npm install + npm install "node-api-headers@${{ matrix.node-api-headers-version }}" + - name: npm test + run: | + export NAPI_VERSION=${{ matrix.api_version }} + if [ "${{ matrix.compiler }}" = "gcc" ]; then + export CC="gcc" CXX="g++" + fi + if [ "${{ matrix.compiler }}" = "clang" ]; then + export CC="clang" CXX="clang++" + fi + echo "CC=\"$CC\" CXX=\"$CXX\"" + echo "$CC --version" + $CC --version + echo "$CXX --version" + $CXX --version + export CFLAGS="$CFLAGS -O3 --coverage" LDFLAGS="$LDFLAGS --coverage" + export use_node_api_headers=true + echo "CFLAGS=\"$CFLAGS\" LDFLAGS=\"$LDFLAGS\"" + npm run pretest -- --verbose diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 000000000..a6f09719a --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,51 @@ +name: release-please + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + id-token: write # Required for OIDC + contents: read + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + permissions: + contents: write + pull-requests: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + npm-publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 # npm >= 11.5.1 + registry-url: 'https://registry.npmjs.org' + - run: npm publish --provenance --access public diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index f0afbab87..d902982dc 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -31,17 +31,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@eb238b55efaa70779f274895e782ed17c84f2895 # v2.6.1 + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: "Checkout code" - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -63,7 +63,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif @@ -71,6 +71,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 + uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6 with: sarif_file: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 759e9474c..7554cf154 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,12 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + - name: Harden Runner + uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1 + with: + egress-policy: audit + + - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.' diff --git a/.gitignore b/.gitignore index bcded4821..a154db646 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,20 @@ /benchmark/build /benchmark/src /test/addon_build/addons +/test/require_basic_finalizers/addons /.vscode # ignore node-gyp generated files outside its build directory /test/*.Makefile /test/*.mk + +# ignore node-gyp generated Visual Studio files +*.vcxproj +*.vcxproj.filters +*.vcxproj.user +*.vsidx +*.sln +*.suo +/test/.vs/ +/test/Release/ +/test/Debug/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 000000000..a23c78a67 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "8.9.0" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index e0477814e..e3c30a2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,216 @@ # node-addon-api Changelog +## [8.9.0](https://github.com/nodejs/node-addon-api/compare/v8.8.0...v8.9.0) (2026-05-24) + + +### Features + +* add support for SharedArrayBuffer in TypedArray and TypedArrayOf<T> ([#1731](https://github.com/nodejs/node-addon-api/issues/1731)) ([00b95ef](https://github.com/nodejs/node-addon-api/commit/00b95efea6522980e9661a729a59b926ecf5c6b6)) + +## [8.8.0](https://github.com/nodejs/node-addon-api/compare/v8.7.0...v8.8.0) (2026-05-13) + + +### Features + +* add std::string_view overload for Symbol::For ([#1722](https://github.com/nodejs/node-addon-api/issues/1722)) ([f65113b](https://github.com/nodejs/node-addon-api/commit/f65113b6ce54271b0a26f97fc624b5574b64a048)) +* add String::New overload for string_view ([#1706](https://github.com/nodejs/node-addon-api/issues/1706)) ([0add130](https://github.com/nodejs/node-addon-api/commit/0add1306f60b81432da94d13683aa0b06aa52925)) + +## [8.7.0](https://github.com/nodejs/node-addon-api/compare/v8.6.0...v8.7.0) (2026-03-23) + + +### Features + +* add Date::New overload for a std::chrono::system_clock::time_point ([#1705](https://github.com/nodejs/node-addon-api/issues/1705)) ([7fb063d](https://github.com/nodejs/node-addon-api/commit/7fb063d95ff5ef816d1616f9acf4afac854cfd4c)) +* add Object::GetPrototype and Object::SetPrototype ([#1715](https://github.com/nodejs/node-addon-api/issues/1715)) ([967bbd5](https://github.com/nodejs/node-addon-api/commit/967bbd5911c7e90b428d4769b9ab1b1a0cee4451)), closes [#1691](https://github.com/nodejs/node-addon-api/issues/1691) +* add support for SharedArrayBuffer in DataViews ([#1714](https://github.com/nodejs/node-addon-api/issues/1714)) ([7b8d69e](https://github.com/nodejs/node-addon-api/commit/7b8d69e0ba912291aea0b337f7f1814b1032f7f0)) + + +### Bug Fixes + +* add missing const to ObjectReference::Set string parameter ([#1713](https://github.com/nodejs/node-addon-api/issues/1713)) ([845ba8e](https://github.com/nodejs/node-addon-api/commit/845ba8e4b0888ca20ed3f7c95f9d461cbce338c5)) +* fix -Wextra-semi ([#1718](https://github.com/nodejs/node-addon-api/issues/1718)) ([7fef973](https://github.com/nodejs/node-addon-api/commit/7fef9739166ebb89263459e9f4c3363678cd6367)) + +## [8.6.0](https://github.com/nodejs/node-addon-api/compare/v8.5.0...v8.6.0) (2026-01-30) + + +### Features + +* add SharedArrayBuffer ([#1688](https://github.com/nodejs/node-addon-api/issues/1688)) ([220bee2](https://github.com/nodejs/node-addon-api/commit/220bee244fae2e36405bf2bda33cb3985a846912)) +* silence a legitimate vfptr sanitizer warning that is on by default in Android NDK 29 ([#1692](https://github.com/nodejs/node-addon-api/issues/1692)) ([46673f4](https://github.com/nodejs/node-addon-api/commit/46673f403adf799cc73419427dd3cf166badff22)) + +## [8.5.0](https://github.com/nodejs/node-addon-api/compare/v8.4.0...v8.5.0) (2025-07-04) + + +### Features + +* add Then and Catch methods to Promise ([#1668](https://github.com/nodejs/node-addon-api/issues/1668)) ([ab3e5fe](https://github.com/nodejs/node-addon-api/commit/ab3e5fe59570cbb5ed7cc9891b3f25fe373f028f)) + +## [8.4.0](https://github.com/nodejs/node-addon-api/compare/v8.3.1...v8.4.0) (2025-06-11) + + +### Features + +* add sugar method for PropertyLValue ([#1651](https://github.com/nodejs/node-addon-api/issues/1651)) ([#1655](https://github.com/nodejs/node-addon-api/issues/1655)) ([1e57a0a](https://github.com/nodejs/node-addon-api/commit/1e57a0ae82786c320c784ec6b67f357c85733132)) + +## [8.3.1](https://github.com/nodejs/node-addon-api/compare/v8.3.0...v8.3.1) (2025-02-18) + + +### Bug Fixes + +* add missing `stdexcept` include to test ([#1634](https://github.com/nodejs/node-addon-api/issues/1634)) ([14c1a4f](https://github.com/nodejs/node-addon-api/commit/14c1a4f28278c5b02d0ea910061aad4312bb701e)) +* node-api version 10 support ([#1641](https://github.com/nodejs/node-addon-api/issues/1641)) ([932ad15](https://github.com/nodejs/node-addon-api/commit/932ad1503f7a3402716178a91879b5ab850a61b0)) + +## [8.3.0](https://github.com/nodejs/node-addon-api/compare/v8.2.2...v8.3.0) (2024-11-29) + + +### Features + +* allow catching all exceptions ([#1593](https://github.com/nodejs/node-addon-api/issues/1593)) ([c679f6f](https://github.com/nodejs/node-addon-api/commit/c679f6f4c9dc6bf9fc0d99cbe5982bd24a5e2c7b)) + +## [8.2.2](https://github.com/nodejs/node-addon-api/compare/v8.2.1...v8.2.2) (2024-11-07) + + +### Bug Fixes + +* mark external memory and version APIs as basic ([#1597](https://github.com/nodejs/node-addon-api/issues/1597)) ([78da4fa](https://github.com/nodejs/node-addon-api/commit/78da4fa2251af1e4de16efac94d92388f117ae6e)) +* missing napi_delete_reference on ObjectWrap ref ([#1607](https://github.com/nodejs/node-addon-api/issues/1607)) ([98aae33](https://github.com/nodejs/node-addon-api/commit/98aae3343c3af36b4befd6b67c4cb19ba49b8d20)) + +## [8.2.1](https://github.com/nodejs/node-addon-api/compare/v8.2.0...v8.2.1) (2024-10-09) + + +### Bug Fixes + +* failed type cast checks in Symbol::WellKnown ([#1581](https://github.com/nodejs/node-addon-api/issues/1581)) ([d8523a7](https://github.com/nodejs/node-addon-api/commit/d8523a708030a0a3abb9d7832051c70e2dafac3d)) +* missing node_api_nogc_env definition ([#1585](https://github.com/nodejs/node-addon-api/issues/1585)) ([6ba3891](https://github.com/nodejs/node-addon-api/commit/6ba3891954d8b56215d133e54a86cb621e476b9e)) + +## [8.2.0](https://github.com/nodejs/node-addon-api/compare/v8.1.0...v8.2.0) (2024-09-19) + + +### Features + +* add support for nogc types via `BasicEnv` ([#1514](https://github.com/nodejs/node-addon-api/issues/1514)) ([b4aeecb](https://github.com/nodejs/node-addon-api/commit/b4aeecb046480eeaaf1c578a140f71ac0e77094f)) +* add support for requiring basic finalizers ([#1568](https://github.com/nodejs/node-addon-api/issues/1568)) ([7bcb826](https://github.com/nodejs/node-addon-api/commit/7bcb826aa4323f450b3c58f9c7fb34243ff13f77)) + + +### Bug Fixes + +* call base basic finalizer if none defined ([#1574](https://github.com/nodejs/node-addon-api/issues/1574)) ([294a43f](https://github.com/nodejs/node-addon-api/commit/294a43f8c6a4c79b3295a8f1b83d4782d44cfe74)) + +## [8.1.0](https://github.com/nodejs/node-addon-api/compare/node-addon-api-v8.0.0...node-addon-api-v8.1.0) (2024-07-05) + + +### Features + +* Expose version property in public API ([#1479](https://github.com/nodejs/node-addon-api/issues/1479)) ([23bb42b](https://github.com/nodejs/node-addon-api/commit/23bb42b5e47630c9082dddbabea555626571926e)) +* improve messages on CheckCast ([#1507](https://github.com/nodejs/node-addon-api/issues/1507)) ([bf49519](https://github.com/nodejs/node-addon-api/commit/bf49519a4ce08ee5320327c9a0199cd89d5b87b3)) + + +### Bug Fixes + +* fix compilation for Visual Studio 2022 ([#1492](https://github.com/nodejs/node-addon-api/issues/1492)) ([e011720](https://github.com/nodejs/node-addon-api/commit/e011720010af26ed66638ceac822e5f1c5e43cde)) +* restore ability to run under NAPI_EXPERIMENTAL ([#1409](https://github.com/nodejs/node-addon-api/issues/1409)) ([40bcb09](https://github.com/nodejs/node-addon-api/commit/40bcb09e6b82e7a1164cb3de56cb503d9b5a3d37)) + +## 2024-03-01 Version 8.0.0, @legendecas + +### Notable changes + +- Support for Node.js v16.x is no longer maintained. + +### Commits + +* \[[`df2147a2b6`](https://github.com/nodejs/node-addon-api/commit/df2147a2b6)] - build(deps): bump github/codeql-action from 3.24.3 to 3.24.5 (dependabot\[bot]) [#1455](https://github.com/nodejs/node-addon-api/pull/1455) +* \[[`eb4fa9b55a`](https://github.com/nodejs/node-addon-api/commit/eb4fa9b55a)] - build(deps): bump actions/dependency-review-action from 4.1.0 to 4.1.3 (dependabot\[bot]) [#1452](https://github.com/nodejs/node-addon-api/pull/1452) +* \[[`f85e8146bb`](https://github.com/nodejs/node-addon-api/commit/f85e8146bb)] - build(deps): bump github/codeql-action from 3.23.2 to 3.24.3 (dependabot\[bot]) [#1448](https://github.com/nodejs/node-addon-api/pull/1448) +* \[[`b84deb0d2f`](https://github.com/nodejs/node-addon-api/commit/b84deb0d2f)] - build(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.0 (dependabot\[bot]) [#1447](https://github.com/nodejs/node-addon-api/pull/1447) +* \[[`7dcee380cd`](https://github.com/nodejs/node-addon-api/commit/7dcee380cd)] - build(deps): bump actions/setup-node from 4.0.1 to 4.0.2 (dependabot\[bot]) [#1444](https://github.com/nodejs/node-addon-api/pull/1444) +* \[[`a727b629fe`](https://github.com/nodejs/node-addon-api/commit/a727b629fe)] - build(deps): bump actions/upload-artifact from 4.3.0 to 4.3.1 (dependabot\[bot]) [#1443](https://github.com/nodejs/node-addon-api/pull/1443) +* \[[`ea712094e3`](https://github.com/nodejs/node-addon-api/commit/ea712094e3)] - build(deps): bump step-security/harden-runner from 2.6.1 to 2.7.0 (dependabot\[bot]) [#1440](https://github.com/nodejs/node-addon-api/pull/1440) +* \[[`898e5006a5`](https://github.com/nodejs/node-addon-api/commit/898e5006a5)] - build(deps): bump github/codeql-action from 3.23.1 to 3.23.2 (dependabot\[bot]) [#1439](https://github.com/nodejs/node-addon-api/pull/1439) +* \[[`66e6e0e4b6`](https://github.com/nodejs/node-addon-api/commit/66e6e0e4b6)] - build(deps): bump actions/upload-artifact from 4.0.0 to 4.3.0 (dependabot\[bot]) [#1438](https://github.com/nodejs/node-addon-api/pull/1438) +* \[[`f1ca4ccd7f`](https://github.com/nodejs/node-addon-api/commit/f1ca4ccd7f)] - build(deps): bump actions/dependency-review-action from 3.1.5 to 4.0.0 (dependabot\[bot]) [#1433](https://github.com/nodejs/node-addon-api/pull/1433) +* \[[`c58112d52e`](https://github.com/nodejs/node-addon-api/commit/c58112d52e)] - build(deps): bump github/codeql-action from 3.23.0 to 3.23.1 (dependabot\[bot]) [#1430](https://github.com/nodejs/node-addon-api/pull/1430) +* \[[`f1b9c0bc24`](https://github.com/nodejs/node-addon-api/commit/f1b9c0bc24)] - **chore**: remove v16.x regular CI runs (Chengzhong Wu) [#1437](https://github.com/nodejs/node-addon-api/pull/1437) +* \[[`c6561d90d6`](https://github.com/nodejs/node-addon-api/commit/c6561d90d6)] - **chore**: reduce dependabot noise (Chengzhong Wu) [#1436](https://github.com/nodejs/node-addon-api/pull/1436) +* \[[`42931eeba6`](https://github.com/nodejs/node-addon-api/commit/42931eeba6)] - **doc**: reorganize readme (Chengzhong Wu) [#1441](https://github.com/nodejs/node-addon-api/pull/1441) +* \[[`3b9f3db14e`](https://github.com/nodejs/node-addon-api/commit/3b9f3db14e)] - **doc**: update changelog maker commands (Chengzhong Wu) [#1431](https://github.com/nodejs/node-addon-api/pull/1431) +* \[[`034c039298`](https://github.com/nodejs/node-addon-api/commit/034c039298)] - **test**: heed npm\_config\_debug (Gabriel Schulhof) [#1445](https://github.com/nodejs/node-addon-api/pull/1445) + +## 2024-01-18 Version 7.1.0, @legendecas + +### Notable changes + +#### API + +- Add Env::GetModuleFileName +- Add SyntaxError +- Allow NAPI\_VERSION env var and templatize AttachData callback +- Add common gyp dependency targets. + +### Commits + +* \[[`864fed488c`](https://github.com/nodejs/node-addon-api/commit/864fed488c)] - build(deps): bump github/codeql-action from 3.22.12 to 3.23.0 (dependabot\[bot]) [#1428](https://github.com/nodejs/node-addon-api/pull/1428) +* \[[`81a8d43130`](https://github.com/nodejs/node-addon-api/commit/81a8d43130)] - build(deps): bump actions/dependency-review-action from 3.1.4 to 3.1.5 (dependabot\[bot]) [#1427](https://github.com/nodejs/node-addon-api/pull/1427) +* \[[`e20088941b`](https://github.com/nodejs/node-addon-api/commit/e20088941b)] - build(deps): bump github/codeql-action from 3.22.11 to 3.22.12 (dependabot\[bot]) [#1426](https://github.com/nodejs/node-addon-api/pull/1426) +* \[[`76c7b12e4e`](https://github.com/nodejs/node-addon-api/commit/76c7b12e4e)] - build(deps): bump actions/setup-node from 4.0.0 to 4.0.1 (dependabot\[bot]) [#1425](https://github.com/nodejs/node-addon-api/pull/1425) +* \[[`cd58edde1d`](https://github.com/nodejs/node-addon-api/commit/cd58edde1d)] - build(deps): bump actions/upload-artifact from 3.1.3 to 4.0.0 (dependabot\[bot]) [#1424](https://github.com/nodejs/node-addon-api/pull/1424) +* \[[`0fd1b9e0e1`](https://github.com/nodejs/node-addon-api/commit/0fd1b9e0e1)] - build(deps): bump github/codeql-action from 2.22.8 to 3.22.11 (dependabot\[bot]) [#1423](https://github.com/nodejs/node-addon-api/pull/1423) +* \[[`c181b19d68`](https://github.com/nodejs/node-addon-api/commit/c181b19d68)] - build(deps): bump actions/stale from 8.0.0 to 9.0.0 (dependabot\[bot]) [#1418](https://github.com/nodejs/node-addon-api/pull/1418) +* \[[`6fa67791a1`](https://github.com/nodejs/node-addon-api/commit/6fa67791a1)] - build(deps): bump actions/setup-python from 4.7.1 to 5.0.0 (dependabot\[bot]) [#1417](https://github.com/nodejs/node-addon-api/pull/1417) +* \[[`1fff346fa6`](https://github.com/nodejs/node-addon-api/commit/1fff346fa6)] - build(deps): bump actions/dependency-review-action from 3.1.3 to 3.1.4 (dependabot\[bot]) [#1415](https://github.com/nodejs/node-addon-api/pull/1415) +* \[[`ecb9690fe5`](https://github.com/nodejs/node-addon-api/commit/ecb9690fe5)] - build(deps): bump github/codeql-action from 2.22.7 to 2.22.8 (dependabot\[bot]) [#1414](https://github.com/nodejs/node-addon-api/pull/1414) +* \[[`969547b871`](https://github.com/nodejs/node-addon-api/commit/969547b871)] - build(deps): bump github/codeql-action from 2.22.5 to 2.22.7 (dependabot\[bot]) [#1413](https://github.com/nodejs/node-addon-api/pull/1413) +* \[[`183d1522a9`](https://github.com/nodejs/node-addon-api/commit/183d1522a9)] - build(deps): bump step-security/harden-runner from 2.6.0 to 2.6.1 (dependabot\[bot]) [#1412](https://github.com/nodejs/node-addon-api/pull/1412) +* \[[`25f977724a`](https://github.com/nodejs/node-addon-api/commit/25f977724a)] - build(deps): bump actions/dependency-review-action from 3.1.0 to 3.1.3 (dependabot\[bot]) [#1410](https://github.com/nodejs/node-addon-api/pull/1410) +* \[[`f6d125a407`](https://github.com/nodejs/node-addon-api/commit/f6d125a407)] - build(deps): bump actions/setup-python from 4.7.0 to 4.7.1 (dependabot\[bot]) [#1406](https://github.com/nodejs/node-addon-api/pull/1406) +* \[[`ce78a39ec7`](https://github.com/nodejs/node-addon-api/commit/ce78a39ec7)] - build(deps): bump github/codeql-action from 2.22.4 to 2.22.5 (dependabot\[bot]) [#1400](https://github.com/nodejs/node-addon-api/pull/1400) +* \[[`dc211ebb48`](https://github.com/nodejs/node-addon-api/commit/dc211ebb48)] - build(deps): bump actions/setup-node from 3.8.1 to 4.0.0 (dependabot\[bot]) [#1398](https://github.com/nodejs/node-addon-api/pull/1398) +* \[[`cab559e3bd`](https://github.com/nodejs/node-addon-api/commit/cab559e3bd)] - build(deps): bump ossf/scorecard-action from 2.3.0 to 2.3.1 (dependabot\[bot]) [#1397](https://github.com/nodejs/node-addon-api/pull/1397) +* \[[`f71ff5582d`](https://github.com/nodejs/node-addon-api/commit/f71ff5582d)] - build(deps): bump github/codeql-action from 2.22.3 to 2.22.4 (dependabot\[bot]) [#1396](https://github.com/nodejs/node-addon-api/pull/1396) +* \[[`21c1d08680`](https://github.com/nodejs/node-addon-api/commit/21c1d08680)] - build(deps): bump actions/checkout from 4.1.0 to 4.1.1 (dependabot\[bot]) [#1394](https://github.com/nodejs/node-addon-api/pull/1394) +* \[[`e4eec0939c`](https://github.com/nodejs/node-addon-api/commit/e4eec0939c)] - build(deps): bump github/codeql-action from 2.21.9 to 2.22.3 (dependabot\[bot]) [#1393](https://github.com/nodejs/node-addon-api/pull/1393) +* \[[`94f3459474`](https://github.com/nodejs/node-addon-api/commit/94f3459474)] - build(deps): bump ossf/scorecard-action from 2.2.0 to 2.3.0 (dependabot\[bot]) [#1388](https://github.com/nodejs/node-addon-api/pull/1388) +* \[[`90a741ef10`](https://github.com/nodejs/node-addon-api/commit/90a741ef10)] - build(deps): bump step-security/harden-runner from 2.5.1 to 2.6.0 (dependabot\[bot]) [#1386](https://github.com/nodejs/node-addon-api/pull/1386) +* \[[`7e1aa06132`](https://github.com/nodejs/node-addon-api/commit/7e1aa06132)] - Update LICENSE.md (Michael Dawson) [#1385](https://github.com/nodejs/node-addon-api/pull/1385) +* \[[`0a0612362e`](https://github.com/nodejs/node-addon-api/commit/0a0612362e)] - build(deps): bump github/codeql-action from 2.21.7 to 2.21.9 (dependabot\[bot]) [#1384](https://github.com/nodejs/node-addon-api/pull/1384) +* \[[`47bd430da2`](https://github.com/nodejs/node-addon-api/commit/47bd430da2)] - build(deps): bump actions/checkout from 4.0.0 to 4.1.0 (dependabot\[bot]) [#1383](https://github.com/nodejs/node-addon-api/pull/1383) +* \[[`b3f7f73cb9`](https://github.com/nodejs/node-addon-api/commit/b3f7f73cb9)] - build(deps): bump actions/dependency-review-action from 3.0.8 to 3.1.0 (dependabot\[bot]) [#1377](https://github.com/nodejs/node-addon-api/pull/1377) +* \[[`12c1655387`](https://github.com/nodejs/node-addon-api/commit/12c1655387)] - build(deps): bump github/codeql-action from 2.21.6 to 2.21.7 (dependabot\[bot]) [#1380](https://github.com/nodejs/node-addon-api/pull/1380) +* \[[`6abed318e4`](https://github.com/nodejs/node-addon-api/commit/6abed318e4)] - build(deps): bump github/codeql-action from 2.21.5 to 2.21.6 (dependabot\[bot]) [#1378](https://github.com/nodejs/node-addon-api/pull/1378) +* \[[`89eda59930`](https://github.com/nodejs/node-addon-api/commit/89eda59930)] - build(deps): bump actions/upload-artifact from 3.1.2 to 3.1.3 (dependabot\[bot]) [#1376](https://github.com/nodejs/node-addon-api/pull/1376) +* \[[`90870dbffa`](https://github.com/nodejs/node-addon-api/commit/90870dbffa)] - build(deps): bump actions/checkout from 3.6.0 to 4.0.0 (dependabot\[bot]) [#1375](https://github.com/nodejs/node-addon-api/pull/1375) +* \[[`b860793eff`](https://github.com/nodejs/node-addon-api/commit/b860793eff)] - build(deps): bump github/codeql-action from 2.21.2 to 2.21.5 (dependabot\[bot]) [#1372](https://github.com/nodejs/node-addon-api/pull/1372) +* \[[`f9b9974b4a`](https://github.com/nodejs/node-addon-api/commit/f9b9974b4a)] - build(deps): bump actions/checkout from 3.5.3 to 3.6.0 (dependabot\[bot]) [#1371](https://github.com/nodejs/node-addon-api/pull/1371) +* \[[`9596e3de2d`](https://github.com/nodejs/node-addon-api/commit/9596e3de2d)] - build(deps): bump actions/setup-node from 3.7.0 to 3.8.1 (dependabot\[bot]) [#1370](https://github.com/nodejs/node-addon-api/pull/1370) +* \[[`e969210747`](https://github.com/nodejs/node-addon-api/commit/e969210747)] - build(deps): bump actions/dependency-review-action from 3.0.6 to 3.0.8 (dependabot\[bot]) [#1368](https://github.com/nodejs/node-addon-api/pull/1368) +* \[[`13ef96a5a9`](https://github.com/nodejs/node-addon-api/commit/13ef96a5a9)] - build(deps): bump step-security/harden-runner from 2.5.0 to 2.5.1 (dependabot\[bot]) [#1364](https://github.com/nodejs/node-addon-api/pull/1364) +* \[[`9776d148b3`](https://github.com/nodejs/node-addon-api/commit/9776d148b3)] - build(deps): bump github/codeql-action from 2.21.1 to 2.21.2 (dependabot\[bot]) [#1358](https://github.com/nodejs/node-addon-api/pull/1358) +* \[[`59dc6be097`](https://github.com/nodejs/node-addon-api/commit/59dc6be097)] - build(deps): bump github/codeql-action from 2.21.0 to 2.21.1 (dependabot\[bot]) [#1357](https://github.com/nodejs/node-addon-api/pull/1357) +* \[[`5e72796cd5`](https://github.com/nodejs/node-addon-api/commit/5e72796cd5)] - build(deps): bump step-security/harden-runner from 2.4.1 to 2.5.0 (dependabot\[bot]) [#1356](https://github.com/nodejs/node-addon-api/pull/1356) +* \[[`4e62db45e4`](https://github.com/nodejs/node-addon-api/commit/4e62db45e4)] - build(deps): bump github/codeql-action from 2.20.3 to 2.21.0 (dependabot\[bot]) [#1353](https://github.com/nodejs/node-addon-api/pull/1353) +* \[[`0c093a33e8`](https://github.com/nodejs/node-addon-api/commit/0c093a33e8)] - build(deps): bump github/codeql-action from 2.20.1 to 2.20.3 (dependabot\[bot]) [#1349](https://github.com/nodejs/node-addon-api/pull/1349) +* \[[`5523b2d3fa`](https://github.com/nodejs/node-addon-api/commit/5523b2d3fa)] - build(deps): bump actions/setup-node from 3.6.0 to 3.7.0 (dependabot\[bot]) [#1348](https://github.com/nodejs/node-addon-api/pull/1348) +* \[[`afa494ef7f`](https://github.com/nodejs/node-addon-api/commit/afa494ef7f)] - Add Node.js version restrictions (Ingo Fischer) [#1340](https://github.com/nodejs/node-addon-api/pull/1340) +* \[[`ac4c87f660`](https://github.com/nodejs/node-addon-api/commit/ac4c87f660)] - build(deps): bump ossf/scorecard-action from 2.0.6 to 2.2.0 (dependabot\[bot]) [#1344](https://github.com/nodejs/node-addon-api/pull/1344) +* \[[`47aeb6689d`](https://github.com/nodejs/node-addon-api/commit/47aeb6689d)] - build(deps): bump github/codeql-action from 2.2.12 to 2.20.1 (dependabot\[bot]) [#1343](https://github.com/nodejs/node-addon-api/pull/1343) +* \[[`bd45a8fffc`](https://github.com/nodejs/node-addon-api/commit/bd45a8fffc)] - build(deps): bump step-security/harden-runner from 2.3.0 to 2.4.1 (dependabot\[bot]) [#1342](https://github.com/nodejs/node-addon-api/pull/1342) +* \[[`343a1e1708`](https://github.com/nodejs/node-addon-api/commit/343a1e1708)] - build(deps-dev): bump fs-extra from 9.1.0 to 11.1.1 (dependabot\[bot]) [#1335](https://github.com/nodejs/node-addon-api/pull/1335) +* \[[`4168c10182`](https://github.com/nodejs/node-addon-api/commit/4168c10182)] - build(deps): bump actions/stale from 5.2.1 to 8.0.0 (dependabot\[bot]) [#1333](https://github.com/nodejs/node-addon-api/pull/1333) +* \[[`1c182abd1f`](https://github.com/nodejs/node-addon-api/commit/1c182abd1f)] - build(deps): bump actions/dependency-review-action from 2.5.1 to 3.0.6 (dependabot\[bot]) [#1331](https://github.com/nodejs/node-addon-api/pull/1331) +* \[[`717a61931d`](https://github.com/nodejs/node-addon-api/commit/717a61931d)] - build(deps): bump actions/checkout from 3.5.2 to 3.5.3 (dependabot\[bot]) [#1329](https://github.com/nodejs/node-addon-api/pull/1329) +* \[[`d605d62c89`](https://github.com/nodejs/node-addon-api/commit/d605d62c89)] - **chore**: lock python version in actions (Chengzhong Wu) [#1403](https://github.com/nodejs/node-addon-api/pull/1403) +* \[[`734e3f2509`](https://github.com/nodejs/node-addon-api/commit/734e3f2509)] - **doc**: fix rendering of code blocks in list (Tobias Nießen) [#1401](https://github.com/nodejs/node-addon-api/pull/1401) +* \[[`dfdf6eb6e6`](https://github.com/nodejs/node-addon-api/commit/dfdf6eb6e6)] - **doc**: add missing title IsBigInt (Marx) [#1352](https://github.com/nodejs/node-addon-api/pull/1352) +* \[[`8850997f38`](https://github.com/nodejs/node-addon-api/commit/8850997f38)] - **doc**: fix typo AsyncProgressWorker::ExecutionProgress (JerryZhongJ) [#1350](https://github.com/nodejs/node-addon-api/pull/1350) +* \[[`8192a471a1`](https://github.com/nodejs/node-addon-api/commit/8192a471a1)] - **docs**: fixed Broken Links (Ömer AKGÜL) [#1405](https://github.com/nodejs/node-addon-api/pull/1405) +* \[[`16a18c047a`](https://github.com/nodejs/node-addon-api/commit/16a18c047a)] - **fix**: handle c++ exception in TSFN callback (Chengzhong Wu) [#1345](https://github.com/nodejs/node-addon-api/pull/1345) +* \[[`ab14347080`](https://github.com/nodejs/node-addon-api/commit/ab14347080)] - **gyp**: add common targets (Chengzhong Wu) [#1389](https://github.com/nodejs/node-addon-api/pull/1389) +* \[[`fa3518bc08`](https://github.com/nodejs/node-addon-api/commit/fa3518bc08)] - **src**: remove duplicate buffer info calls (Chengzhong Wu) [#1354](https://github.com/nodejs/node-addon-api/pull/1354) +* \[[`b83e453e6e`](https://github.com/nodejs/node-addon-api/commit/b83e453e6e)] - **src**: add Env::GetModuleFileName (Kevin Eady) [#1327](https://github.com/nodejs/node-addon-api/pull/1327) +* \[[`d9828c6264`](https://github.com/nodejs/node-addon-api/commit/d9828c6264)] - **src**: add SyntaxError (Kevin Eady) [#1326](https://github.com/nodejs/node-addon-api/pull/1326) +* \[[`c52e764bb2`](https://github.com/nodejs/node-addon-api/commit/c52e764bb2)] - **src,test,build**: allow NAPI\_VERSION env var and templatize AttachData callback (Gabriel Schulhof) [#1399](https://github.com/nodejs/node-addon-api/pull/1399) +* \[[`8f028d630a`](https://github.com/nodejs/node-addon-api/commit/8f028d630a)] - **test**: remove experimental flag from bigint (Gabriel Schulhof) [#1395](https://github.com/nodejs/node-addon-api/pull/1395) +* \[[`414be9e000`](https://github.com/nodejs/node-addon-api/commit/414be9e000)] - **test**: run interfering tests in their own process (Gabriel Schulhof) [#1325](https://github.com/nodejs/node-addon-api/pull/1325) + ## 2023-06-13 Version 7.0.0, @KevinEady ### Notable changes @@ -1097,5 +1308,3 @@ yet backported in the previous Node.js version. * [0a899bf1c5] - doc: update indication of latest version (Michael Dawson) https://github.com/nodejs/node-addon-api/pull/211 * [17c74e5a5e] - n-api: RangeError in napi_create_dataview() (Jinho Bang) https://github.com/nodejs/node-addon-api/pull/214 * [4058a29989] - n-api: fix memory leak in napi_async_destroy() (Jinho Bang) https://github.com/nodejs/node-addon-api/pull/213 - - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c6151d70..663fc2304 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,22 +1,41 @@ +# Contributing to **node-addon-api** -# Developer's Certificate of Origin 1.1 +* [Code of Conduct](#code-of-conduct) +* [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) +* [Tests](#tests) +* [Debug](#debug) +* [Benchmarks](#benchmarks) +* [node-addon-api Contribution Philosophy](#node-addon-api-contribution-philosophy) +## Code of Conduct + +The Node.js project has a +[Code of Conduct](https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md) +to which all contributors must adhere. + +See [details on our policy on Code of Conduct](https://github.com/nodejs/node/blob/main/doc/contributing/code-of-conduct.md). + + + +## Developer's Certificate of Origin 1.1 + +
 By making a contribution to this project, I certify that:
 
  (a) The contribution was created in whole or in part by me and I
-     have the right to submit it under the open-source license
+     have the right to submit it under the open source license
      indicated in the file; or
 
  (b) The contribution is based upon previous work that, to the best
      of my knowledge, is covered under an appropriate open source
      license and I have the right under that license to submit that
      work with modifications, whether created in whole or in part
-     by me, under the same open-source license (unless I am
+     by me, under the same open source license (unless I am
      permitted to submit under a different license), as indicated
      in the file; or
 
  (c) The contribution was provided directly to me by some other
-     person who certified (a), (b), or (c) and I have not modified
+     person who certified (a), (b) or (c) and I have not modified
      it.
 
  (d) I understand and agree that this project and the contribution
@@ -24,17 +43,118 @@ By making a contribution to this project, I certify that:
      personal information I submit with it, including my sign-off) is
      maintained indefinitely and may be redistributed consistent with
      this project or the open source license(s) involved.
+
+ + +## Tests + +To run the **node-addon-api** tests do: + +``` +npm install +npm test +``` + +To avoid testing the deprecated portions of the API run +``` +npm install +npm test --disable-deprecated +``` + +To run the tests targeting a specific version of Node-API run +``` +npm install +export NAPI_VERSION=X +npm test --NAPI_VERSION=X +``` + +where X is the version of Node-API you want to target. + +To run a subset of the test suite, filter conditions are available. +The `--filter` option limits which JavaScript test modules are executed by +`node test`. The default `pretest` step is still `node-gyp rebuild -C test`, +so `npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. + +**Example:** + perform the default test rebuild, then run only the `objectwrap` test module + ``` + npm test --filter=objectwrap + ``` + +Multiple test modules can be selected with wildcards. + +**Example:** +perform the default test rebuild, then run all test modules ending with +`reference`: +`function_reference`, `object_reference`, and `reference` + ``` + npm test --filter=*reference + ``` + +Multiple filter conditions can be joined to broaden the test selection. + +**Example:** + perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module + ``` + npm test --filter='*function objectwrap' + ``` -# **node-addon-api** Contribution Philosophy +As an alternative, `ninja` can be used to build the tests. Please +follow the instructions in [Build with ninja](doc/contributing/build_with_ninja.md). + +## Debug + +To run the **node-addon-api** tests with `--debug` option: + +``` +npm run-script dev +``` + +If you want a faster build, you might use the following option: + +``` +npm run-script dev:incremental +``` + +Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/HEAD/test)** + +## Benchmarks + +You can run the available benchmarks using the following command: + +``` +npm run-script benchmark +``` + +See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. + +## **node-addon-api** Contribution Philosophy The **node-addon-api** team loves contributions. There are many ways in which you can contribute to **node-addon-api**: -- Source code fixes +- [New APIs](#new-apis) +- [Source code fixes](#source-changes) - Additional tests - Documentation improvements - Joining the Node-API working group and participating in meetings -## Source changes +### New APIs + +As new APIs are added to Node-API, node-addon-api must be updated to provide +wrappers for those new APIs. For this reason, node-addon-api provides +methods that allow callers to obtain the underlying Node-API handles so +direct calls to Node-API and the use of the objects/methods provided by +node-addon-api can be used together. For example, in order to be able +to use an API for which the node-addon-api does not yet provide a wrapper. + +APIs exposed by node-addon-api are generally used to create and +manipulate JavaScript values. Concepts and operations generally map +to ideas specified in the **ECMA262 Language Specification**. + +### Source changes **node-addon-api** is meant to be a thin convenience wrapper around Node-API. With this in mind, contributions of any new APIs that wrap around a core Node-API API will @@ -56,6 +176,7 @@ idioms while writing native addons with **node-addon-api**. where folks can build on top of it. #### Larger Core + This is probably our simplest option in terms of immediate action needed. It would involve landing any open PRs against **node-addon-api**, and continuing to encourage folks to make PRs for utility helpers against the same repository. @@ -65,6 +186,7 @@ The downside of the approach is the following: - More maintenance burden on the Node-API WG core team. #### Extras Package + This involves us spinning up a new package that contains the utility classes and methods. This has the benefit of having a separate module where helpers make it easier to implement certain patterns and idioms for native addons @@ -78,6 +200,7 @@ belongs in **node-addon-api** vs **node-addon-api-extras**) - Unclear if the maintenance burden on the Node-API WG is reduced or not #### Ecosystem + This doesn't require a ton of up-front work from the Node-API WG. Instead of accepting utility PRs into **node-addon-api** or creating and maintaining a new module, the WG will encourage the creation of an ecosystem of modules that @@ -90,4 +213,3 @@ The downside of this approach is the following: authors might not find the right patterns and instead implement things themselves - There might be greater friction for the Node-API WG in evolving APIs since the ecosystem would have taken dependencies on the API shape of **node-addon-api** - diff --git a/README.md b/README.md index 8c43818ff..3259d7743 100644 --- a/README.md +++ b/README.md @@ -1,266 +1,37 @@ -NOTE: The default branch has been renamed! -master is now named main +# **node-addon-api module** -If you have a local clone, you can update it by running: +[![codecov](https://codecov.io/gh/nodejs/node-addon-api/branch/main/graph/badge.svg)](https://app.codecov.io/gh/nodejs/node-addon-api/tree/main) -```shell -git branch -m master main -git fetch origin -git branch -u origin/main main -``` +[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) -# **node-addon-api module** This module contains **header-only C++ wrapper classes** which simplify the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) provided by Node.js when using C++. It provides a C++ object model and exception handling semantics with low overhead. -There are three options for implementing addons: Node-API, nan, or direct -use of internal V8, libuv, and Node.js libraries. Unless there is a need for -direct access to functionality that is not exposed by Node-API as outlined -in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html) -in Node.js core, use Node-API. Refer to -[C/C++ addons with Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) -for more information on Node-API. - -Node-API is an ABI stable C interface provided by Node.js for building native -addons. It is independent of the underlying JavaScript runtime (e.g. V8 or ChakraCore) -and is maintained as part of Node.js itself. It is intended to insulate -native addons from changes in the underlying JavaScript engine and allow -modules compiled for one version to run on later versions of Node.js without -recompilation. - -The `node-addon-api` module, which is not part of Node.js, preserves the benefits -of the Node-API as it consists only of inline code that depends only on the stable API -provided by Node-API. As such, modules built against one version of Node.js -using node-addon-api should run without having to be rebuilt with newer versions -of Node.js. - -It is important to remember that *other* Node.js interfaces such as -`libuv` (included in a project via `#include `) are not ABI-stable across -Node.js major versions. Thus, an addon must use Node-API and/or `node-addon-api` -exclusively and build against a version of Node.js that includes an -implementation of Node-API (meaning an active LTS version of Node.js) in -order to benefit from ABI stability across Node.js major versions. Node.js -provides an [ABI stability guide][] containing a detailed explanation of ABI -stability in general, and the Node-API ABI stability guarantee in particular. - -As new APIs are added to Node-API, node-addon-api must be updated to provide -wrappers for those new APIs. For this reason, node-addon-api provides -methods that allow callers to obtain the underlying Node-API handles so -direct calls to Node-API and the use of the objects/methods provided by -node-addon-api can be used together. For example, in order to be able -to use an API for which the node-addon-api does not yet provide a wrapper. - -APIs exposed by node-addon-api are generally used to create and -manipulate JavaScript values. Concepts and operations generally map -to ideas specified in the **ECMA262 Language Specification**. +- [API References](doc/README.md) +- [Badges](#badges) +- [Contributing](#contributing) +- [License](#license) -The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers an -excellent orientation and tips for developers just getting started with Node-API -and node-addon-api. +## API References -- **[Setup](#setup)** -- **[API Documentation](#api)** -- **[Examples](#examples)** -- **[Tests](#tests)** -- **[More resource and info about native Addons](#resources)** -- **[Badges](#badges)** -- **[Code of Conduct](CODE_OF_CONDUCT.md)** -- **[Contributors](#contributors)** -- **[License](#license)** +API references are available in the [doc](doc/README.md) directory. -## **Current version: 7.0.0** + +## Current version: 8.9.0 + (See [CHANGELOG.md](CHANGELOG.md) for complete Changelog) -[![NPM](https://nodei.co/npm/node-addon-api.png?downloads=true&downloadRank=true)](https://nodei.co/npm/node-addon-api/) [![NPM](https://nodei.co/npm-dl/node-addon-api.png?months=6&height=1)](https://nodei.co/npm/node-addon-api/) - - - node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions. This allows addons built with it to run with Node.js versions which support the targeted Node-API version. **However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that every year there will be a new major which drops support for the Node.js LTS version which has gone out of service. -The oldest Node.js version supported by the current version of node-addon-api is Node.js 16.x. - -## Setup - - [Installation and usage](doc/setup.md) - - [node-gyp](doc/node-gyp.md) - - [cmake-js](doc/cmake-js.md) - - [Conversion tool](doc/conversion-tool.md) - - [Checker tool](doc/checker-tool.md) - - [Generator](doc/generator.md) - - [Prebuild tools](doc/prebuild_tools.md) - - - -### **API Documentation** - -The following is the documentation for node-addon-api. - - - [Full Class Hierarchy](doc/hierarchy.md) - - [Addon Structure](doc/addon.md) - - Data Types: - - [Env](doc/env.md) - - [CallbackInfo](doc/callbackinfo.md) - - [Reference](doc/reference.md) - - [Value](doc/value.md) - - [Name](doc/name.md) - - [Symbol](doc/symbol.md) - - [String](doc/string.md) - - [Number](doc/number.md) - - [Date](doc/date.md) - - [BigInt](doc/bigint.md) - - [Boolean](doc/boolean.md) - - [External](doc/external.md) - - [Object](doc/object.md) - - [Array](doc/array.md) - - [ObjectReference](doc/object_reference.md) - - [PropertyDescriptor](doc/property_descriptor.md) - - [Function](doc/function.md) - - [FunctionReference](doc/function_reference.md) - - [ObjectWrap](doc/object_wrap.md) - - [ClassPropertyDescriptor](doc/class_property_descriptor.md) - - [Buffer](doc/buffer.md) - - [ArrayBuffer](doc/array_buffer.md) - - [TypedArray](doc/typed_array.md) - - [TypedArrayOf](doc/typed_array_of.md) - - [DataView](doc/dataview.md) - - [Error Handling](doc/error_handling.md) - - [Error](doc/error.md) - - [TypeError](doc/type_error.md) - - [RangeError](doc/range_error.md) - - [SyntaxError](doc/syntax_error.md) - - [Object Lifetime Management](doc/object_lifetime_management.md) - - [HandleScope](doc/handle_scope.md) - - [EscapableHandleScope](doc/escapable_handle_scope.md) - - [Memory Management](doc/memory_management.md) - - [Async Operations](doc/async_operations.md) - - [AsyncWorker](doc/async_worker.md) - - [AsyncContext](doc/async_context.md) - - [AsyncWorker Variants](doc/async_worker_variants.md) - - [Thread-safe Functions](doc/threadsafe.md) - - [ThreadSafeFunction](doc/threadsafe_function.md) - - [TypedThreadSafeFunction](doc/typed_threadsafe_function.md) - - [Promises](doc/promises.md) - - [Version management](doc/version_management.md) - - - -### **Examples** - -Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)** - -- **[Hello World](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/1_hello_world)** -- **[Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/2_function_arguments/node-addon-api)** -- **[Callbacks](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/3_callbacks/node-addon-api)** -- **[Object factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/4_object_factory/node-addon-api)** -- **[Function factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/5_function_factory/node-addon-api)** -- **[Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/6_object_wrap/node-addon-api)** -- **[Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/7_factory_wrap/node-addon-api)** -- **[Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api)** - - - -### **Tests** - -To run the **node-addon-api** tests do: - -``` -npm install -npm test -``` - -To avoid testing the deprecated portions of the API run -``` -npm install -npm test --disable-deprecated -``` - -To run the tests targeting a specific version of Node-API run -``` -npm install -export NAPI_VERSION=X -npm test --NAPI_VERSION=X -``` - -where X is the version of Node-API you want to target. - -To run a specific unit test, filter conditions are available - -**Example:** - compile and run only tests on objectwrap.cc and objectwrap.js - ``` - npm run unit --filter=objectwrap - ``` +The oldest Node.js version supported by the current version of node-addon-api is Node.js 18.x. -Multiple unit tests cane be selected with wildcards - -**Example:** -compile and run all test files ending with "reference" -> function_reference.cc, object_reference.cc, reference.cc - ``` - npm run unit --filter=*reference - ``` - -Multiple filter conditions can be joined to broaden the test selection - -**Example:** - compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file - npm run unit --filter='*function objectwrap' - -### **Debug** - -To run the **node-addon-api** tests with `--debug` option: - -``` -npm run-script dev -``` - -If you want a faster build, you might use the following option: - -``` -npm run-script dev:incremental -``` - -Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/HEAD/test)** - -### **Benchmarks** - -You can run the available benchmarks using the following command: - -``` -npm run-script benchmark -``` - -See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks. - - - -### **More resource and info about native Addons** -- **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)** -- **[Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html)** -- **[Node-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)** -- **[How We Migrated Realm JavaScript From NAN to Node-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api)** - -As node-addon-api's core mission is to expose the plain C Node-API as C++ -wrappers, tools that facilitate n-api/node-addon-api providing more -convenient patterns for developing a Node.js add-on with n-api/node-addon-api -can be published to NPM as standalone packages. It is also recommended to tag -such packages with `node-addon-api` to provide more visibility to the community. - -Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). - - - -### **Other bindings** - -- **[napi-rs](https://napi.rs)** - (`Rust`) - - - -### **Badges** +## Badges The use of badges is recommended to indicate the minimum version of Node-API required for the module. This helps to determine which Node.js major versions are @@ -279,16 +50,15 @@ available: ![Node-API v9 Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20v9%20Badge.svg) ![Node-API Experimental Version Badge](https://github.com/nodejs/abi-stable-node/blob/doc/assets/Node-API%20Experimental%20Version%20Badge.svg) -## **Contributing** +## Contributing We love contributions from the community to **node-addon-api**! See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module. - - ## Team members ### Active + | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Anna Henningsen | [addaleax](https://github.com/addaleax) | @@ -299,7 +69,12 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around | Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) | | Vladimir Morozov | [vmoroz](https://github.com/vmoroz) | +
+ +Emeritus + ### Emeritus + | Name | GitHub Link | | ------------------- | ----------------------------------------------------- | | Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) | @@ -311,9 +86,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around | Sampson Gao | [sampsongao](https://github.com/sampsongao) | | Taylor Woll | [boingoing](https://github.com/boingoing) | - +
+ +## License Licensed under [MIT](./LICENSE.md) -[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ -[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix +[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#node-api-version-matrix diff --git a/common.gypi b/common.gypi index 06c0176b2..5fda7e77a 100644 --- a/common.gypi +++ b/common.gypi @@ -5,6 +5,7 @@ }, 'conditions': [ ['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ], + ['NAPI_VERSION==2147483647', { 'defines': ['NAPI_EXPERIMENTAL', 'NODE_API_EXPERIMENTAL_NO_WARNING'] } ], ['disable_deprecated=="true"', { 'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED'] }], diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..e5e24dfbe --- /dev/null +++ b/doc/README.md @@ -0,0 +1,146 @@ +# node-addon-api Documents + +* [Setup](#setup) +* [API Documentation](#api) +* [Examples](#examples) +* [ABI Stability Guideline](#abi-stability-guideline) +* [More resource and info about native Addons](#resources) + +Node-API is an ABI stable C interface provided by Node.js for building native +addons. It is independent of the underlying JavaScript runtime (e.g. V8 or ChakraCore) +and is maintained as part of Node.js itself. It is intended to insulate +native addons from changes in the underlying JavaScript engine and allow +modules compiled for one version to run on later versions of Node.js without +recompilation. + +The `node-addon-api` module, which is not part of Node.js, preserves the benefits +of the Node-API as it consists only of inline code that depends only on the stable API +provided by Node-API. As such, modules built against one version of Node.js +using node-addon-api should run without having to be rebuilt with newer versions +of Node.js. + +## Setup + - [Installation and usage](setup.md) + - [node-gyp](node-gyp.md) + - [cmake-js](cmake-js.md) + - [Conversion tool](conversion-tool.md) + - [Checker tool](checker-tool.md) + - [Generator](generator.md) + - [Prebuild tools](prebuild_tools.md) + + + +## API Documentation + +The following is the documentation for node-addon-api. + + - [Full Class Hierarchy](hierarchy.md) + - [Addon Structure](addon.md) + - Data Types: + - [BasicEnv](basic_env.md) + - [Env](env.md) + - [CallbackInfo](callbackinfo.md) + - [Reference](reference.md) + - [Value](value.md) + - [Name](name.md) + - [Symbol](symbol.md) + - [String](string.md) + - [Number](number.md) + - [Date](date.md) + - [BigInt](bigint.md) + - [Boolean](boolean.md) + - [External](external.md) + - [Object](object.md) + - [Array](array.md) + - [ObjectReference](object_reference.md) + - [PropertyDescriptor](property_descriptor.md) + - [Function](function.md) + - [FunctionReference](function_reference.md) + - [ObjectWrap](object_wrap.md) + - [ClassPropertyDescriptor](class_property_descriptor.md) + - [Buffer](buffer.md) + - [ArrayBuffer](array_buffer.md) + - [SharedArrayBuffer](shared_array_buffer.md) + - [TypedArray](typed_array.md) + - [TypedArrayOf](typed_array_of.md) + - [DataView](dataview.md) + - [Error Handling](error_handling.md) + - [Error](error.md) + - [TypeError](type_error.md) + - [RangeError](range_error.md) + - [SyntaxError](syntax_error.md) + - [Object Lifetime Management](object_lifetime_management.md) + - [HandleScope](handle_scope.md) + - [EscapableHandleScope](escapable_handle_scope.md) + - [Finalization](finalization.md) + - [Memory Management](memory_management.md) + - [Async Operations](async_operations.md) + - [AsyncWorker](async_worker.md) + - [AsyncContext](async_context.md) + - [AsyncWorker Variants](async_worker_variants.md) + - [Thread-safe Functions](threadsafe.md) + - [ThreadSafeFunction](threadsafe_function.md) + - [TypedThreadSafeFunction](typed_threadsafe_function.md) + - [Promises](promises.md) + - [Version management](version_management.md) + + + +## Examples + +Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)** + +- [Hello World](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/1_hello_world) +- [Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/2_function_arguments/node-addon-api) +- [Callbacks](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/3_callbacks/node-addon-api) +- [Object factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/4_object_factory/node-addon-api) +- [Function factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/5_function_factory/node-addon-api) +- [Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/6_object_wrap/node-addon-api) +- [Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/7_factory_wrap/node-addon-api) +- [Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api) + + + +## ABI Stability Guideline + +It is important to remember that *other* Node.js interfaces such as +`libuv` (included in a project via `#include `) are not ABI-stable across +Node.js major versions. Thus, an addon must use Node-API and/or `node-addon-api` +exclusively and build against a version of Node.js that includes an +implementation of Node-API (meaning an active LTS version of Node.js) in +order to benefit from ABI stability across Node.js major versions. Node.js +provides an [ABI stability guide][] containing a detailed explanation of ABI +stability in general, and the Node-API ABI stability guarantee in particular. + + + +## More resource and info about native Addons + +There are three options for implementing addons: Node-API, nan, or direct +use of internal V8, libuv, and Node.js libraries. Unless there is a need for +direct access to functionality that is not exposed by Node-API as outlined +in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html) +in Node.js core, use Node-API. Refer to +[C/C++ addons with Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +for more information on Node-API. + +- [C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html) +- [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html) +- [Node-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs) +- [How We Migrated Realm JavaScript From NAN to Node-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api) + +As node-addon-api's core mission is to expose the plain C Node-API as C++ +wrappers, tools that facilitate n-api/node-addon-api providing more +convenient patterns for developing a Node.js add-on with n-api/node-addon-api +can be published to NPM as standalone packages. It is also recommended to tag +such packages with `node-addon-api` to provide more visibility to the community. + +Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api). + + + +## Other bindings + +- [napi-rs](https://napi.rs) - (`Rust`) + +[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/ diff --git a/doc/array_buffer.md b/doc/array_buffer.md index 3be6b42a2..de05e55b3 100644 --- a/doc/array_buffer.md +++ b/doc/array_buffer.md @@ -31,13 +31,13 @@ Wraps the provided external data into a new `Napi::ArrayBuffer` instance. The `Napi::ArrayBuffer` instance does not assume ownership for the data and expects it to be valid for the lifetime of the instance. Since the `Napi::ArrayBuffer` is subject to garbage collection this overload is only -suitable for data which is static and never needs to be freed. -This factory method will not provide the caller with an opportunity to free the -data when the `Napi::ArrayBuffer` gets garbage-collected. If you need to free -the data retained by the `Napi::ArrayBuffer` object please use other -variants of the `Napi::ArrayBuffer::New` factory method that accept -`Napi::Finalizer`, which is a function that will be invoked when the -`Napi::ArrayBuffer` object has been destroyed. +suitable for data which is static and never needs to be freed. This factory +method will not provide the caller with an opportunity to free the data when the +`Napi::ArrayBuffer` gets garbage-collected. If you need to free the data +retained by the `Napi::ArrayBuffer` object please use other variants of the +`Napi::ArrayBuffer::New` factory method that accept `Napi::Finalizer`, which is +a function that will be invoked when the `Napi::ArrayBuffer` object has been +destroyed. See [Finalization][] for more details. ```cpp static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, void* externalData, size_t byteLength); @@ -72,9 +72,9 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: A function to be called when the `Napi::ArrayBuffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `void*` (which is the - `externalData` pointer), and return `void`. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*)`. + See [Finalization][] for more details. Returns a new `Napi::ArrayBuffer` instance. @@ -102,11 +102,10 @@ static Napi::ArrayBuffer Napi::ArrayBuffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::ArrayBuffer` instance. - `[in] externalData`: The pointer to the external data to wrap. - `[in] byteLength`: The length of the `externalData`, in bytes. -- `[in] finalizeCallback`: The function to be called when the `Napi::ArrayBuffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `void*` (which is the - `externalData` pointer) and `Hint*`, and return `void`. -- `[in] finalizeHint`: The hint to be passed as the second parameter of the - finalize callback. +- `[in] finalizeCallback`: A function called when the engine destroys the + `Napi::ArrayBuffer` object, implementing `operator()(Napi::BasicEnv, void*, + Hint*)`. See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns a new `Napi::ArrayBuffer` instance. @@ -163,3 +162,4 @@ Returns `true` if this `ArrayBuffer` has been detached. [`Napi::Object`]: ./object.md [External Buffer]: ./external_buffer.md +[Finalization]: ./finalization.md diff --git a/doc/basic_env.md b/doc/basic_env.md new file mode 100644 index 000000000..7a5b430f1 --- /dev/null +++ b/doc/basic_env.md @@ -0,0 +1,200 @@ +# BasicEnv + +The data structure containing the environment in which the request is being run. + +The `Napi::BasicEnv` object is usually created and passed by the Node.js runtime +or node-addon-api infrastructure. + +The `Napi::BasicEnv` object represents an environment that has a limited subset +of APIs when compared to `Napi::Env` and can be used in basic finalizers. See +[Finalization][] for more details. + +## Methods + +### Constructor + +```cpp +Napi::BasicEnv::BasicEnv(node_api_nogc_env env); +``` + +- `[in] env`: The `node_api_nogc_env` environment from which to construct the + `Napi::BasicEnv` object. + +### node_api_nogc_env + +```cpp +operator node_api_nogc_env() const; +``` + +Returns the `node_api_nogc_env` opaque data structure representing the +environment. + +### GetInstanceData +```cpp +template T* GetInstanceData() const; +``` + +Returns the instance data that was previously associated with the environment, +or `nullptr` if none was associated. + +### SetInstanceData + + +```cpp +template using Finalizer = void (*)(Env, T*); +template fini = Env::DefaultFini> +void SetInstanceData(T* data) const; +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If +not given, the default finalizer will be used, which simply uses the `delete` +operator to destroy `T*` when the add-on instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle. + +Associates a data item stored at `T* data` with the current instance of the +add-on. The item will be passed to the function `fini` which gets called when an +instance of the add-on is unloaded. + +### SetInstanceData + +```cpp +template +using FinalizerWithHint = void (*)(Env, DataType*, HintType*); +template fini = + Env::DefaultFiniWithHint> +void SetInstanceData(DataType* data, HintType* hint) const; +``` + +- `[template] fini`: A function to call when the instance data is to be deleted. +Accepts a function of the form `void CleanupData(Napi::Env env, DataType* data, +HintType* hint)`. If not given, the default finalizer will be used, which simply +uses the `delete` operator to destroy `T*` when the add-on instance is unloaded. +- `[in] data`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle. +- `[in] hint`: A pointer to data that will be associated with the instance of +the add-on for the duration of its lifecycle and will be passed as a hint to +`fini` when the add-on instance is unloaded. + +Associates a data item stored at `T* data` with the current instance of the +add-on. The item will be passed to the function `fini` which gets called when an +instance of the add-on is unloaded. This overload accepts an additional hint to +be passed to `fini`. + +### GetModuleFileName + +```cpp +const char* Napi::Env::GetModuleFileName() const; +``` + +Returns a URL containing the absolute path of the location from which the add-on +was loaded. For a file on the local file system it will start with `file://`. +The string is null-terminated and owned by env and must thus not be modified or +freed. It is only valid while the add-on is loaded. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook); +``` + +- `[in] hook`: A function to call when the environment exits. Accepts a function + of the form `void ()`. + +Registers `hook` as a function to be run once the current Node.js environment +exits. Unlike the underlying C-based Node-API, providing the same `hook` +multiple times **is** allowed. The hooks will be called in reverse order, i.e. +the most recently added one will be called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env)`. See [Finalization][] for + more details. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env, T*)`. See [Finalization][] + for more details. +- `[in] data`: The data to associate with the object. + +### PostFinalizer + +```cpp +template +inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +``` + +- `[in] finalizeCallback`: The function to queue for execution outside of the GC + finalization, implementing `operator()(Napi::Env, T*, Hint*)`. See + [Finalization][] for more details. +- `[in] data`: The data to associate with the object. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. + +### AddCleanupHook + +```cpp +template +CleanupHook AddCleanupHook(Hook hook, Arg* arg); +``` + +- `[in] hook`: A function to call when the environment exits. Accepts a function + of the form `void (Arg* arg)`. +- `[in] arg`: A pointer to data that will be passed as the argument to `hook`. + +Registers `hook` as a function to be run with the `arg` parameter once the +current Node.js environment exits. Unlike the underlying C-based Node-API, +providing the same `hook` and `arg` pair multiple times **is** allowed. The +hooks will be called in reverse order, i.e. the most recently added one will be +called first. + +Returns an `Env::CleanupHook` object, which can be used to remove the hook via +its `Remove()` method. + +# Env::CleanupHook + +The `Env::CleanupHook` object allows removal of the hook added via +`Env::AddCleanupHook()` + +## Methods + +### IsEmpty + +```cpp +bool IsEmpty(); +``` + +Returns `true` if the cleanup hook was **not** successfully registered. + +### Remove + +```cpp +bool Remove(Env env); +``` + +Unregisters the hook from running once the current Node.js environment exits. + +Returns `true` if the hook was successfully removed from the Node.js +environment. + +[Finalization]: ./finalization.md diff --git a/doc/buffer.md b/doc/buffer.md index 427eeee2f..548400481 100644 --- a/doc/buffer.md +++ b/doc/buffer.md @@ -27,16 +27,15 @@ Returns a new `Napi::Buffer` object. Wraps the provided external data into a new `Napi::Buffer` object. -The `Napi::Buffer` object does not assume ownership for the data and expects it to be -valid for the lifetime of the object. Since the `Napi::Buffer` is subject to garbage -collection this overload is only suitable for data which is static and never -needs to be freed. -This factory method will not provide the caller with an opportunity to free the -data when the `Napi::Buffer` gets garbage-collected. If you need to free the -data retained by the `Napi::Buffer` object please use other variants of the -`Napi::Buffer::New` factory method that accept `Napi::Finalizer`, which is a -function that will be invoked when the `Napi::Buffer` object has been -destroyed. +The `Napi::Buffer` object does not assume ownership for the data and expects it +to be valid for the lifetime of the object. Since the `Napi::Buffer` is subject +to garbage collection this overload is only suitable for data which is static +and never needs to be freed. This factory method will not provide the caller +with an opportunity to free the data when the `Napi::Buffer` gets +garbage-collected. If you need to free the data retained by the `Napi::Buffer` +object please use other variants of the `Napi::Buffer::New` factory method that +accept `Finalizer`, which is a function that will be invoked when the +`Napi::Buffer` object has been destroyed. See [Finalization][] for more details. ```cpp static Napi::Buffer Napi::Buffer::New(napi_env env, T* data, size_t length); @@ -70,9 +69,9 @@ static Napi::Buffer Napi::Buffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the - external data pointer), and return `void`. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. Returns a new `Napi::Buffer` object. @@ -99,11 +98,10 @@ static Napi::Buffer Napi::Buffer::New(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the - external data pointer) and `Hint*`, and return `void`. -- `[in] finalizeHint`: The hint to be passed as the second parameter of the - finalize callback. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns a new `Napi::Buffer` object. @@ -157,9 +155,9 @@ static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the - external data pointer), and return `void`. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. Returns a new `Napi::Buffer` object. @@ -186,11 +184,10 @@ static Napi::Buffer Napi::Buffer::NewOrCopy(napi_env env, - `[in] env`: The environment in which to create the `Napi::Buffer` object. - `[in] data`: The pointer to the external data to expose. - `[in] length`: The number of `T` elements in the external data. -- `[in] finalizeCallback`: The function to be called when the `Napi::Buffer` is - destroyed. It must implement `operator()`, accept an Napi::Env, a `T*` (which is the - external data pointer) and `Hint*`, and return `void`. -- `[in] finalizeHint`: The hint to be passed as the second parameter of the - finalize callback. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::Buffer` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns a new `Napi::Buffer` object. @@ -245,3 +242,4 @@ Returns the number of `T` elements in the external data. [`Napi::Uint8Array`]: ./typed_array_of.md [External Buffer]: ./external_buffer.md +[Finalization]: ./finalization.md diff --git a/doc/cmake-js.md b/doc/cmake-js.md index 85bdf9412..1d5df91ca 100644 --- a/doc/cmake-js.md +++ b/doc/cmake-js.md @@ -45,6 +45,25 @@ The following line in the `CMakeLists.txt` file will enable Node-API experimenta add_definitions(-DNAPI_EXPERIMENTAL) ``` +### Exception Handling + +To enable C++ exception handling (for more info see: [Setup](setup.md)), define +the corresponding preprocessor directives depending on which exception handling +behavior is desired. + +To enable C++ exception handling with `Napi::Error` objects only: + +``` +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS) +``` + +To enable C++ exception handling for all exceptions thrown: + +``` +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS) +add_definitions(-DNODE_ADDON_API_CPP_EXCEPTIONS_ALL) +``` + ### node-addon-api If your Node-API native add-on uses the optional [**node-addon-api**](https://github.com/nodejs/node-addon-api#node-addon-api-module) C++ wrapper, the `CMakeLists.txt` file requires additional configuration information as described on the [CMake.js README file](https://github.com/cmake-js/cmake-js#node-api-and-node-addon-api). diff --git a/doc/contributing/build_with_ninja.md b/doc/contributing/build_with_ninja.md new file mode 100644 index 000000000..d59e9bbef --- /dev/null +++ b/doc/contributing/build_with_ninja.md @@ -0,0 +1,16 @@ +# Build Test with Ninja + +Ninja can be used to speed up building tests with optimized parallelism. + +To build the tests with ninja and node-gyp, run the following commands: + +```sh +/node-addon-api $ node-gyp configure -C test -- -f ninja +/node-addon-api $ ninja -C test/build/Release +# Run tests +/node-addon-api $ node ./test/index.js + +# Run tests with debug addon +/node-addon-api $ ninja -C test/build/Debug +/node-addon-api $ NODE_API_BUILD_CONFIG=Debug node ./test/index.js +``` diff --git a/doc/creating_a_release.md b/doc/contributing/creating_a_release.md similarity index 82% rename from doc/creating_a_release.md rename to doc/contributing/creating_a_release.md index 8b7bdd3fd..02e9cbc53 100644 --- a/doc/creating_a_release.md +++ b/doc/contributing/creating_a_release.md @@ -6,7 +6,17 @@ collaborators to add you. If necessary you can ask the build Working Group who manages the Node.js npm user to add you if there are no other active collaborators. -## Prerequisites +Generally, the release is handled by the +[release-please](https://github.com/nodejs/node-addon-api/blob/main/.github/workflows/release-please.yml) +GitHub action. It will bump the version in `package.json` and publish +node-addon-api to npm. + +In cases that the release-please action is not working, please follow the steps +below to publish node-addon-api manually. + +## Publish new release manually + +### Prerequisites Before to start creating a new release check if you have installed the following tools: @@ -16,7 +26,7 @@ tools: If not please follow the instruction reported in the tool's documentation to install it. -## Publish new release +### Steps These are the steps to follow to create a new release: @@ -34,14 +44,16 @@ to show the new version as the latest. the route folder of the repo launch the following command: ```bash - > changelog-maker + > changelog-maker --md --group --filter-release ``` * Use the output generated by **changelog maker** to update the [CHANGELOG.md](https://github.com/nodejs/node-addon-api/blob/main/CHANGELOG.md) following the style used in publishing the previous release. * Add any new contributors to the "contributors" section in the package.json -* Validate all tests pass by running `npm test` on the `main` branch. +* Commit with a message containing _only_ an x.y.z semver designator. "x.y.z" (so that the commit can be filtered by changelog-maker) + +* Create a release proposal pull request. * Use **[CI](https://ci.nodejs.org/view/x%20-%20Abi%20stable%20module%20API/job/node-test-node-addon-api-new/)** to validate tests pass (note there are still some issues on SmartOS and diff --git a/doc/dataview.md b/doc/dataview.md index 66fb28919..619ceecca 100644 --- a/doc/dataview.md +++ b/doc/dataview.md @@ -6,6 +6,11 @@ The `Napi::DataView` class corresponds to the [JavaScript `DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) class. +**NOTE**: The support for `Napi::DataView::New()` overloads accepting an +`Napi::SharedArrayBuffer` parameter is only available when using +`NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ## Methods ### New @@ -50,6 +55,48 @@ static Napi::DataView Napi::DataView::New(napi_env env, Napi::ArrayBuffer arrayB Returns a new `Napi::DataView` instance. +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + +### New + +Allocates a new `Napi::DataView` instance with a given `Napi::SharedArrayBuffer`. + +```cpp +static Napi::DataView Napi::DataView::New(napi_env env, Napi::SharedArrayBuffer sharedArrayBuffer, size_t byteOffset, size_t byteLength); +``` + +- `[in] env`: The environment in which to create the `Napi::DataView` instance. +- `[in] sharedArrayBuffer` : `Napi::SharedArrayBuffer` underlying the `Napi::DataView`. +- `[in] byteOffset` : The byte offset within the `Napi::SharedArrayBuffer` from which to start projecting the `Napi::DataView`. +- `[in] byteLength` : Number of elements in the `Napi::DataView`. + +Returns a new `Napi::DataView` instance. + ### Constructor Initializes an empty instance of the `Napi::DataView` class. @@ -75,7 +122,22 @@ Napi::DataView(napi_env env, napi_value value); Napi::ArrayBuffer Napi::DataView::ArrayBuffer() const; ``` -Returns the backing array buffer. +Returns the backing array buffer as an `Napi::ArrayBuffer`. + +**NOTE**: If the `Napi::DataView` is not backed by an `Napi::ArrayBuffer`, this +method will terminate the process with a fatal error when using +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior +otherwise. Use `Buffer()` instead to get the backing buffer without assuming its +type. + +### Buffer + +```cpp +Napi::Value Napi::DataView::Buffer() const; +``` + +Returns the backing array buffer as a generic `Napi::Value`, allowing optional +type-checking with `Is*()` and type-casting with `As<>()` methods. ### ByteOffset diff --git a/doc/date.md b/doc/date.md index 4c5fefa5e..7131b016f 100644 --- a/doc/date.md +++ b/doc/date.md @@ -37,6 +37,20 @@ static Napi::Date Napi::Date::New(Napi::Env env, double value); Returns a new instance of `Napi::Date` object. +### New + +Creates a new instance of a `Napi::Date` object. + +```cpp +static Napi::Date Napi::Date::New(napi_env env, std::chrono::system_clock::time_point time_point); +``` + + - `[in] env`: The environment in which to construct the `Napi::Date` object. + - `[in] value`: The point in time, represented by an + `std::chrono::system_clock::time_point`. + +Returns a new instance of `Napi::Date` object. + ### ValueOf ```cpp diff --git a/doc/env.md b/doc/env.md index 29aa4459e..7773275ee 100644 --- a/doc/env.md +++ b/doc/env.md @@ -1,8 +1,15 @@ # Env -The opaque data structure containing the environment in which the request is being run. +Class `Napi::Env` inherits from class [`Napi::BasicEnv`][]. -The Env object is usually created and passed by the Node.js runtime or node-addon-api infrastructure. +The data structure containing the environment in which the request is being run. + +The `Napi::Env` object is usually created and passed by the Node.js runtime or +node-addon-api infrastructure. + +The `Napi::Env` object represents an environment that has a superset of APIs +when compared to `Napi::BasicEnv` and therefore _cannot_ be used in basic +finalizers. See [Finalization][] for more details. ## Methods @@ -76,132 +83,5 @@ The `script` can be any of the following types: - `const char *` - `const std::string &` -### GetInstanceData -```cpp -template T* GetInstanceData() const; -``` - -Returns the instance data that was previously associated with the environment, -or `nullptr` if none was associated. - -### SetInstanceData - -```cpp -template using Finalizer = void (*)(Env, T*); -template fini = Env::DefaultFini> -void SetInstanceData(T* data) const; -``` - -- `[template] fini`: A function to call when the instance data is to be deleted. -Accepts a function of the form `void CleanupData(Napi::Env env, T* data)`. If -not given, the default finalizer will be used, which simply uses the `delete` -operator to destroy `T*` when the addon instance is unloaded. -- `[in] data`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle. - -Associates a data item stored at `T* data` with the current instance of the -addon. The item will be passed to the function `fini` which gets called when an -instance of the addon is unloaded. - -### SetInstanceData - -```cpp -template -using FinalizerWithHint = void (*)(Env, DataType*, HintType*); -template fini = - Env::DefaultFiniWithHint> -void SetInstanceData(DataType* data, HintType* hint) const; -``` - -- `[template] fini`: A function to call when the instance data is to be deleted. -Accepts a function of the form -`void CleanupData(Napi::Env env, DataType* data, HintType* hint)`. If not given, -the default finalizer will be used, which simply uses the `delete` operator to -destroy `T*` when the addon instance is unloaded. -- `[in] data`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle. -- `[in] hint`: A pointer to data that will be associated with the instance of -the addon for the duration of its lifecycle and will be passed as a hint to -`fini` when the addon instance is unloaded. - -Associates a data item stored at `T* data` with the current instance of the -addon. The item will be passed to the function `fini` which gets called when an -instance of the addon is unloaded. This overload accepts an additional hint to -be passed to `fini`. - -### GetModuleFileName - -```cpp -const char* Napi::Env::GetModuleFileName() const; -``` - -Returns a A URL containing the absolute path of the location from which the -add-on was loaded. For a file on the local file system it will start with -`file://`. The string is null-terminated and owned by env and must thus not be -modified or freed. It is only valid while the add-on is loaded. - -### AddCleanupHook - -```cpp -template -CleanupHook AddCleanupHook(Hook hook); -``` - -- `[in] hook`: A function to call when the environment exits. Accepts a - function of the form `void ()`. - -Registers `hook` as a function to be run once the current Node.js environment -exits. Unlike the underlying C-based Node-API, providing the same `hook` -multiple times **is** allowed. The hooks will be called in reverse order, i.e. -the most recently added one will be called first. - -Returns an `Env::CleanupHook` object, which can be used to remove the hook via -its `Remove()` method. - -### AddCleanupHook - -```cpp -template -CleanupHook AddCleanupHook(Hook hook, Arg* arg); -``` - -- `[in] hook`: A function to call when the environment exits. Accepts a - function of the form `void (Arg* arg)`. -- `[in] arg`: A pointer to data that will be passed as the argument to `hook`. - -Registers `hook` as a function to be run with the `arg` parameter once the -current Node.js environment exits. Unlike the underlying C-based Node-API, -providing the same `hook` and `arg` pair multiple times **is** allowed. The -hooks will be called in reverse order, i.e. the most recently added one will be -called first. - -Returns an `Env::CleanupHook` object, which can be used to remove the hook via -its `Remove()` method. - -# Env::CleanupHook - -The `Env::CleanupHook` object allows removal of the hook added via -`Env::AddCleanupHook()` - -## Methods - -### IsEmpty - -```cpp -bool IsEmpty(); -``` - -Returns `true` if the cleanup hook was **not** successfully registered. - -### Remove - -```cpp -bool Remove(Env env); -``` - -Unregisters the hook from running once the current Node.js environment exits. - -Returns `true` if the hook was successfully removed from the Node.js -environment. +[`Napi::BasicEnv`]: ./basic_env.md +[Finalization]: ./finalization.md diff --git a/doc/error_handling.md b/doc/error_handling.md index 5bb7481aa..b4b4ca238 100644 --- a/doc/error_handling.md +++ b/doc/error_handling.md @@ -48,6 +48,18 @@ method. If a C++ exception of type `Napi::Error` escapes from a Node-API C++ callback, then the Node-API wrapper automatically converts and throws it as a JavaScript exception. +If other types of C++ exceptions are thrown, node-addon-api will either abort +the process or wrap the exception in an `Napi::Error` in order to throw it as a +JavaScript exception. This behavior is determined by which node-gyp dependency +used: + +- When using the `node_addon_api_except` dependency, only `Napi::Error` objects + will be handled. +- When using the `node_addon_api_except_all` dependency, all exceptions will be +handled. For exceptions derived from `std::exception`, an `Napi::Error` will be +created with the message of the exception's `what()` member function. For all +other exceptions, an `Napi::Error` will be created with a generic error message. + On return from a native method, node-addon-api will automatically convert a pending `Napi::Error` C++ exception to a JavaScript exception. diff --git a/doc/external.md b/doc/external.md index ce42e112a..4b4603e8e 100644 --- a/doc/external.md +++ b/doc/external.md @@ -4,7 +4,11 @@ Class `Napi::External` inherits from class [`Napi::TypeTaggable`][]. The `Napi::External` template class implements the ability to create a `Napi::Value` object with arbitrary C++ data. It is the user's responsibility to manage the memory for the arbitrary C++ data. -`Napi::External` objects can be created with an optional Finalizer function and optional Hint value. The Finalizer function, if specified, is called when your `Napi::External` object is released by Node's garbage collector. It gives your code the opportunity to free any dynamically created data. If you specify a Hint value, it is passed to your Finalizer function. +`Napi::External` objects can be created with an optional Finalizer function and +optional Hint value. The `Finalizer` function, if specified, is called when your +`Napi::External` object is released by Node's garbage collector. It gives your +code the opportunity to free any dynamically created data. If you specify a Hint +value, it is passed to your `Finalizer` function. See [Finalization][] for more details. Note that `Napi::Value::IsExternal()` will return `true` for any external value. It does not differentiate between the templated parameter `T` in @@ -38,7 +42,9 @@ static Napi::External Napi::External::New(napi_env env, - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. -- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting a T* and returning void. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*)`. See + [Finalization][] for more details. Returns the created `Napi::External` object. @@ -54,8 +60,10 @@ static Napi::External Napi::External::New(napi_env env, - `[in] env`: The `napi_env` environment in which to construct the `Napi::External` object. - `[in] data`: The arbitrary C++ data to be held by the `Napi::External` object. -- `[in] finalizeCallback`: A function called when the `Napi::External` object is released by the garbage collector accepting T* and Hint* parameters and returning void. -- `[in] finalizeHint`: A hint value passed to the `finalizeCallback` function. +- `[in] finalizeCallback`: The function called when the engine destroys the + `Napi::External` object, implementing `operator()(Napi::BasicEnv, T*, Hint*)`. + See [Finalization][] for more details. +- `[in] finalizeHint`: The hint value passed to the `finalizeCallback` function. Returns the created `Napi::External` object. @@ -67,4 +75,5 @@ T* Napi::External::Data() const; Returns a pointer to the arbitrary C++ data held by the `Napi::External` object. +[Finalization]: ./finalization.md [`Napi::TypeTaggable`]: ./type_taggable.md diff --git a/doc/finalization.md b/doc/finalization.md new file mode 100644 index 000000000..3dc4e7860 --- /dev/null +++ b/doc/finalization.md @@ -0,0 +1,153 @@ +# Finalization + +Various node-addon-api methods accept a templated `Finalizer finalizeCallback` +parameter. This parameter represents a native callback function that runs in +response to a garbage collection event. A finalizer is considered a _basic_ +finalizer if the callback only utilizes a certain subset of APIs, which may +provide more efficient memory management, optimizations, improved execution, or +other benefits. + +In general, it is best to use basic finalizers whenever possible (eg. when +access to JavaScript is _not_ needed). The +`NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS` preprocessor directive can be defined +to ensure that all finalizers are basic. + +## Finalizers + +The callback takes `Napi::Env` as its first argument: + +### Example + +```cpp +Napi::External::New(Env(), new int(1), [](Napi::Env env, int* data) { + env.RunScript("console.log('Finalizer called')"); + delete data; +}); +``` + +## Basic Finalizers + +Use of basic finalizers may allow the engine to perform optimizations when +scheduling or executing the callback. For example, V8 does not allow access to +the engine heap during garbage collection. Restricting finalizers from accessing +the engine heap allows the callback to execute during garbage collection, +providing a chance to free native memory eagerly. + +In general, APIs that access engine heap are not allowed in basic finalizers. + +The callback takes `Napi::BasicEnv` as its first argument: + +### Example + +```cpp +Napi::ArrayBuffer::New( + Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) { + delete[] static_cast(finalizeData); + }); +``` + +## Scheduling Finalizers + +In addition to passing finalizers to `Napi::External`s and other Node-API +constructs, `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` can be +used to schedule a callback to run outside of the garbage collector +finalization. Since the associated native memory may already be freed by the +basic finalizer, any additional data may be passed eg. via the finalizer's +parameters (`T data*`, `Hint hint*`) or via lambda capture. This allows for +freeing native data in a basic finalizer, while executing any JavaScript code in +an additional finalizer. + +### Example + +```cpp +// Native Add-on + +#include +#include +#include "napi.h" + +using namespace Napi; + +// A structure representing some data that uses a "large" amount of memory. +class LargeData { + public: + LargeData() : id(instances++) {} + size_t id; + + static size_t instances; +}; + +size_t LargeData::instances = 0; + +// Basic finalizer to free `LargeData`. Takes ownership of the pointer and +// frees its memory after use. +void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) { + std::unique_ptr instance(data); + std::cout << "Basic finalizer for instance " << instance->id + << " called\n"; + + // Register a finalizer. Since the instance will be deleted by + // the time this callback executes, pass the instance's `id` via lambda copy + // capture and _not_ a reference capture that accesses `this`. + env.PostFinalizer([instanceId = instance->id](Napi::Env env) { + env.RunScript("console.log('Finalizer for instance " + + std::to_string(instanceId) + " called');"); + }); + + // Free the `LargeData` held in `data` once `instance` goes out of scope. +} + +Value CreateExternal(const CallbackInfo& info) { + // Create a new instance of LargeData. + auto instance = std::make_unique(); + + // Wrap the instance in an External object, registering a basic + // finalizer that will delete the instance to free the "large" amount of + // memory. + return External::New(info.Env(), instance.release(), MyBasicFinalizer); +} + +Object Init(Napi::Env env, Object exports) { + exports["createExternal"] = Function::New(env, CreateExternal); + return exports; +} + +NODE_API_MODULE(addon, Init) +``` + +```js +// JavaScript + +const { createExternal } = require('./addon.node'); + +for (let i = 0; i < 5; i++) { + const ext = createExternal(); + // ... do something with `ext` .. +} + +console.log('Loop complete'); +await new Promise(resolve => setImmediate(resolve)); +console.log('Next event loop cycle'); +``` + +Possible output: + +``` +Basic finalizer for instance 0 called +Basic finalizer for instance 1 called +Basic finalizer for instance 2 called +Basic finalizer for instance 3 called +Basic finalizer for instance 4 called +Loop complete +Finalizer for instance 3 called +Finalizer for instance 4 called +Finalizer for instance 1 called +Finalizer for instance 2 called +Finalizer for instance 0 called +Next event loop cycle +``` + +If the garbage collector runs during the loop, the basic finalizers execute and +display their logging message synchronously during the loop execution. The +additional finalizers execute at some later point after the garbage collection +cycle. diff --git a/doc/memory_management.md b/doc/memory_management.md index afa622550..882c0f802 100644 --- a/doc/memory_management.md +++ b/doc/memory_management.md @@ -17,7 +17,7 @@ more often than it would otherwise in an attempt to garbage collect the JavaScri objects that keep the externally allocated memory alive. ```cpp -static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::Env env, int64_t change_in_bytes); +static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::BasicEnv env, int64_t change_in_bytes); ``` - `[in] env`: The environment in which the API is invoked under. diff --git a/doc/node-gyp.md b/doc/node-gyp.md index 529aa0ea2..a39d5b8c0 100644 --- a/doc/node-gyp.md +++ b/doc/node-gyp.md @@ -4,19 +4,19 @@ C++ code needs to be compiled into executable form whether it be as an object file to linked with others, a shared library, or a standalone executable. The main reason for this is that we need to link to the Node.js dependencies and -headers correctly, another reason is that we need a cross platform way to build +headers correctly. Another reason is that we need a cross-platform way to build C++ source into binary for the target platform. -Until now **node-gyp** is the **de-facto** standard build tool for writing -Node.js addons. It's based on Google's **gyp** build tool, which abstract away -many of the tedious issues related to cross platform building. +**node-gyp** remains the **de-facto** standard build tool for writing +Node.js addons. It's based on Google's **gyp** build tool, which abstracts away +many of the tedious issues related to cross-platform building. -**node-gyp** uses a file called ```binding.gyp``` that is located on the root of +**node-gyp** uses a file called `binding.gyp` that is located in the root of your addon project. -```binding.gyp``` file, contains all building configurations organized with a -JSON like syntax. The most important parameter is the **target** that must be -set to the same value used on the initialization code of the addon as in the +The `binding.gyp` file contains all building configurations organized with a +JSON-like syntax. The most important parameter is the **target** that must be +set to the same value used in the initialization code of the addon, as in the examples reported below: ### **binding.gyp** @@ -41,8 +41,8 @@ examples reported below: // ... /** -* This code is our entry-point. We receive two arguments here, the first is the -* environment that represent an independent instance of the JavaScript runtime, +* This code is our entry point. We receive two arguments here: the first is the +* environment that represent an independent instance of the JavaScript runtime; * the second is exports, the same as module.exports in a .js file. * You can either add properties to the exports object passed in or create your * own exports object. In either case you must return the object to be used as @@ -56,7 +56,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) { } /** -* This code defines the entry-point for the Node addon, it tells Node where to go +* This code defines the entry point for the Node addon. It tells Node where to go * once the library has been loaded into active memory. The first argument must * match the "target" in our *binding.gyp*. Using NODE_GYP_MODULE_NAME ensures * that the argument will be correct, as long as the module is built with @@ -75,8 +75,8 @@ NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) - [Command options](https://www.npmjs.com/package/node-gyp#command-options) - [Configuration](https://www.npmjs.com/package/node-gyp#configuration) -Sometimes finding the right settings for ```binding.gyp``` is not easy so to -accomplish at most complicated task please refer to: +Sometimes finding the right settings for `binding.gyp` is not easy, so to +accomplish the most complicated tasks, please refer to: - [GYP documentation](https://gyp.gsrc.io/index.md) -- [node-gyp wiki](https://github.com/nodejs/node-gyp/wiki) +- [node-gyp wiki](https://github.com/nodejs/node-gyp/tree/main/docs) diff --git a/doc/object.md b/doc/object.md index a4d3280b8..fb7d53ad1 100644 --- a/doc/object.md +++ b/doc/object.md @@ -241,6 +241,28 @@ from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable. +### GetPrototype() + +```cpp +Napi::Object Napi::Object::GetPrototype() const; +``` + +The `Napi::Object::GetPrototype()` method returns the prototype of the object. + +### SetPrototype() + +```cpp +bool Napi::Object::SetPrototype(const Napi::Object& value) const; +``` + +- `[in] value`: The prototype value. + +The `Napi::Object::SetPrototype()` method sets the prototype of the object. + +**NOTE**: The support for `Napi::Object::SetPrototype` is only available when +using `NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ### operator\[\]() ```cpp diff --git a/doc/object_wrap.md b/doc/object_wrap.md index 43546646a..40fb3bf12 100644 --- a/doc/object_wrap.md +++ b/doc/object_wrap.md @@ -241,9 +241,24 @@ request being made. ### Finalize -Provides an opportunity to run cleanup code that requires access to the -`Napi::Env` before the wrapped native object instance is freed. Override to -implement. +Provides an opportunity to run cleanup code that only utilizes basic Node APIs, if any. +Override to implement. See [Finalization][] for more details. + +```cpp +virtual void Finalize(Napi::BasicEnv env); +``` + +- `[in] env`: `Napi::Env`. + +### Finalize + +Provides an opportunity to run cleanup code that utilizes non-basic Node APIs. +Override to implement. + +*NOTE*: Defining this method causes the deletion of the underlying `T* data` to +be postponed until _after_ the garbage collection cycle. Since an `Napi::Env` +has access to non-basic Node APIs, it cannot run in the same current tick as the +garbage collector. ```cpp virtual void Finalize(Napi::Env env); @@ -586,3 +601,4 @@ Returns `Napi::PropertyDescriptor` object that represents an static value property of a JavaScript class [`Napi::InstanceWrap`]: ./instance_wrap.md +[Finalization]: ./finalization.md diff --git a/doc/promises.md b/doc/promises.md index 21594c6b8..b4ab83389 100644 --- a/doc/promises.md +++ b/doc/promises.md @@ -75,5 +75,56 @@ Rejects the Promise object held by the `Napi::Promise::Deferred` object. * `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`. +## Promise Methods + +### Then + +```cpp +Napi::Promise Napi::Promise::Then(napi_value onFulfilled) const; +Napi::Promise Napi::Promise::Then(const Function& onFulfilled) const; +``` + +Attaches a fulfillment handler to the promise and returns a new promise. + +**Parameters:** +* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is fulfilled. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled. + +**Returns:** A new `Napi::Promise` that resolves or rejects based on the handler's result. + +### Then + +```cpp +Napi::Promise Napi::Promise::Then(napi_value onFulfilled, napi_value onRejected) const; +Napi::Promise Napi::Promise::Then(const Function& onFulfilled, + const Function& onRejected) const; +``` + +Attaches a fulfillment and rejection handlers to the promise and returns a new promise. + +**Parameters:** +* `[in] onFulfilled`: The fulfillment handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is fulfilled. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is fulfilled. +* `[in] onRejected` (optional): The rejection handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is rejected. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected. + +### Catch +```cpp +Napi::Promise Napi::Promise::Catch(napi_value onRejected) const; +Napi::Promise Napi::Promise::Catch(const Function& onRejected) const; +``` + +Attaches a rejection handler to the promise and returns a new promise. + +**Parameters:** +* `[in] onRejected`: The rejection handler for the promise. May be any of: + - `napi_value` – a JavaScript function to be called when the promise is rejected. + - `const Function&` – the [`Napi::Function`](function.md) to be called when the promise is rejected. + +**Returns:** A new `Napi::Promise` that handles rejection cases. [`Napi::Object`]: ./object.md +[`Napi::Function`]: ./function.md diff --git a/doc/setup.md b/doc/setup.md index 29bb1a743..b3b7effc6 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -38,7 +38,10 @@ To use **Node-API** in a native module: ], ``` - To enable that capability, add an alternative dependency in `binding.gyp`: + To enable that capability, add an alternative dependency in `binding.gyp` + depending on if you want to integrate C++ exception handling for exceptions + derived from `Napi::Error` or all C++ exceptions. To catch only + `Napi::Error` exceptions, use: ```gyp 'dependencies': [ @@ -46,6 +49,15 @@ To use **Node-API** in a native module: ], ``` + Or, to allow catching all native C++ exceptions, use the + `node_addon_api_except_all` dependency: + + ```gyp + 'dependencies': [ + "()` methods. + ### ElementSize ```cpp diff --git a/doc/typed_array_of.md b/doc/typed_array_of.md index a982b80e5..4ced5841c 100644 --- a/doc/typed_array_of.md +++ b/doc/typed_array_of.md @@ -77,6 +77,34 @@ static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, Returns a new `Napi::TypedArrayOf` instance. +### New + +Wraps the provided `Napi::SharedArrayBuffer` into a new `Napi::TypedArray` instance. + +The array `type` parameter can normally be omitted (because it is inferred from +the template parameter `T`), except when creating a "clamped" array. + +```cpp +static Napi::TypedArrayOf Napi::TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type); +``` + +- `[in] env`: The environment in which to create the `Napi::TypedArrayOf` instance. +- `[in] elementLength`: The length to array, in elements. +- `[in] arrayBuffer`: The backing `Napi::SharedArrayBuffer` instance. +- `[in] bufferOffset`: The offset into the `Napi::SharedArrayBuffer` where the array starts, + in bytes. +- `[in] type`: The type of array to allocate (optional). + +Returns a new `Napi::TypedArrayOf` instance. + +**NOTE**: The support for this overload of `Napi::TypedArrayOf::New()` is only +available when using `NAPI_EXPERIMENTAL` and building against Node.js headers +that supports this feature. + ### Constructor Initializes an empty instance of the `Napi::TypedArrayOf` class. diff --git a/doc/value.md b/doc/value.md index 90e358236..f61a36ecf 100644 --- a/doc/value.md +++ b/doc/value.md @@ -86,6 +86,19 @@ In order to enforce expected type, use `Napi::Value::Is*()` methods to check the type before calling `Napi::Value::As()`, or compile with definition `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` to enforce type checks. +### UnsafeAs + +```cpp +template T Napi::Value::UnsafeAs() const; +``` + +Casts to another type of `Napi::Value`, when the actual type is known or +assumed. + +This conversion does not coerce the type. This does not check the type even if +`NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` is defined. This indicates intentional +unsafe type cast. Use `Napi::Value::As()` if possible. + ### Env ```cpp @@ -255,6 +268,19 @@ bool Napi::Value::IsPromise() const; Returns `true` if the underlying value is a JavaScript `Napi::Promise` or `false` otherwise. +### IsSharedArrayBuffer + +```cpp +bool Napi::Value::IsSharedArrayBuffer() const; +``` + +Returns `true` if the underlying value is a JavaScript +`Napi::IsSharedArrayBuffer` or `false` otherwise. + +**NOTE**: The support for `Napi::SharedArrayBuffer` is only available when using +`NAPI_EXPERIMENTAL` and building against Node.js headers that support this +feature. + ### IsString ```cpp diff --git a/doc/version_management.md b/doc/version_management.md index 1cdc48321..b289f1b1d 100644 --- a/doc/version_management.md +++ b/doc/version_management.md @@ -11,7 +11,7 @@ important to make decisions based on different versions of the system. Retrieves the highest Node-API version supported by Node.js runtime. ```cpp -static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); +static uint32_t Napi::VersionManagement::GetNapiVersion(Napi::BasicEnv env); ``` - `[in] env`: The environment in which the API is invoked under. @@ -34,7 +34,7 @@ typedef struct { ```` ```cpp -static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Env env); +static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Napi::BasicEnv env); ``` - `[in] env`: The environment in which the API is invoked under. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..d02c6f529 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = require('neostandard')({ + semi: true, +}); diff --git a/index.js b/index.js index 4877abc97..e235cc3e9 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ const path = require('path'); +const { version } = require('./package.json'); const includeDir = path.relative('.', __dirname); @@ -7,6 +8,7 @@ module.exports = { include_dir: includeDir, gyp: path.join(includeDir, 'node_api.gyp:nothing'), // deprecated. targets: path.join(includeDir, 'node_addon_api.gyp'), + version, isNodeApiBuiltin: true, needsFlag: false }; diff --git a/napi-inl.h b/napi-inl.h index a5ae7af72..9c0c47859 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -9,15 +9,25 @@ //////////////////////////////////////////////////////////////////////////////// // Note: Do not include this file directly! Include "napi.h" instead. +// This should be a no-op and is intended for better IDE integration. +#include "napi.h" #include +#include #include #if NAPI_HAS_THREADS #include #endif // NAPI_HAS_THREADS +#include #include #include +#if defined(__clang__) || defined(__GNUC__) +#define NAPI_NO_SANITIZE_VPTR __attribute__((no_sanitize("vptr"))) +#else +#define NAPI_NO_SANITIZE_VPTR +#endif + namespace Napi { #ifdef NAPI_CPP_CUSTOM_NAMESPACE @@ -32,7 +42,9 @@ namespace details { constexpr int napi_no_external_buffers_allowed = 22; template -inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) { +inline void default_basic_finalizer(node_addon_api_basic_env /*env*/, + void* data, + void* /*hint*/) { delete static_cast(data); } @@ -40,8 +52,9 @@ inline void default_finalizer(napi_env /*env*/, void* data, void* /*hint*/) { // garbage-collected. // TODO: Replace this code with `napi_add_finalizer()` whenever it becomes // available on all supported versions of Node.js. -template > +template < + typename FreeType, + node_addon_api_basic_finalize finalizer = default_basic_finalizer> inline napi_status AttachData(napi_env env, napi_value obj, FreeType* data, @@ -73,19 +86,33 @@ inline napi_status AttachData(napi_env env, // For use in JS to C++ callback wrappers to catch any Napi::Error exceptions // and rethrow them as JavaScript exceptions before returning from the callback. template -inline napi_value WrapCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline napi_value WrapCallback(napi_env env, Callable callback) { +#else +inline napi_value WrapCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { return callback(); } catch (const Error& e) { e.ThrowAsJavaScriptException(); return nullptr; } -#else // NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + return nullptr; + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + return nullptr; + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else // NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are disabled, errors are immediately thrown as JS // exceptions, so there is no need to catch and rethrow them here. return callback(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } // For use in JS to C++ void callback wrappers to catch any Napi::Error @@ -93,7 +120,7 @@ inline napi_value WrapCallback(Callable callback) { // the callback. template inline void WrapVoidCallback(Callable callback) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { callback(); } catch (const Error& e) { @@ -106,10 +133,41 @@ inline void WrapVoidCallback(Callable callback) { #endif // NAPI_CPP_EXCEPTIONS } +// For use in JS to C++ void callback wrappers to catch _any_ thrown exception +// and rethrow them as JavaScript exceptions before returning from the callback, +// wrapping in an Napi::Error as needed. +template +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL +inline void WrapVoidCallback(napi_env env, Callable callback) { +#else +inline void WrapVoidCallback(napi_env, Callable callback) { +#endif +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS + try { + callback(); + } catch (const Error& e) { + e.ThrowAsJavaScriptException(); + } +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS_ALL + catch (const std::exception& e) { + Napi::Error::New(env, e.what()).ThrowAsJavaScriptException(); + } catch (...) { + Napi::Error::New(env, "A native exception was thrown") + .ThrowAsJavaScriptException(); + } +#endif // NODE_ADDON_API_CPP_EXCEPTIONS_ALL +#else + // When C++ exceptions are disabled, there is no need to catch and rethrow C++ + // exceptions. JS errors should be thrown with + // `Error::ThrowAsJavaScriptException`. + callback(); +#endif // NODE_ADDON_API_CPP_EXCEPTIONS +} + template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -125,7 +183,7 @@ struct CallbackData { template struct CallbackData { static inline napi_value Wrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); CallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -142,7 +200,7 @@ struct CallbackData { template napi_value TemplatedVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); Callback(cbInfo); return nullptr; @@ -152,9 +210,12 @@ napi_value TemplatedVoidCallback(napi_env env, template napi_value TemplatedCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); - return Callback(cbInfo); + // MSVC requires to copy 'Callback' function pointer to a local variable + // before invoking it. + auto callback = Callback; + return callback(cbInfo); }); } @@ -162,7 +223,7 @@ template napi_value TemplatedInstanceCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); return instance ? (instance->*UnwrapCallback)(cbInfo) : Napi::Value(); @@ -172,7 +233,7 @@ napi_value TemplatedInstanceCallback(napi_env env, template napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); if (instance) (instance->*UnwrapCallback)(cbInfo); @@ -182,23 +243,102 @@ napi_value TemplatedInstanceVoidCallback(napi_env env, napi_callback_info info) template struct FinalizeData { - static inline void Wrapper(napi_env env, +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void Wrapper(node_addon_api_basic_env env, void* data, void* finalizeHint) NAPI_NOEXCEPT { WrapVoidCallback([&] { FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback(Env(env), static_cast(data)); + finalizeData->callback(env, static_cast(data)); delete finalizeData; }); } - static inline void WrapperWithHint(napi_env env, +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void Wrapper(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGC, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >> +#endif + static inline void WrapperWithHint(node_addon_api_basic_env env, void* data, void* finalizeHint) NAPI_NOEXCEPT { WrapVoidCallback([&] { FinalizeData* finalizeData = static_cast(finalizeHint); - finalizeData->callback( - Env(env), static_cast(data), finalizeData->hint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); + delete finalizeData; + }); + } + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template >, + typename = void> + static inline void WrapperWithHint(node_addon_api_basic_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { +#ifdef NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS + static_assert(false, + "NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS defined: Finalizer " + "must be basic."); +#endif + napi_status status = + node_api_post_finalizer(env, WrapperGCWithHint, data, finalizeHint); + NAPI_FATAL_IF_FAILED( + status, "FinalizeData::Wrapper", "node_api_post_finalizer failed"); + } +#endif + + static inline void WrapperGCWithoutData(napi_env env, + void* /*data*/, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env); + delete finalizeData; + }); + } + + static inline void WrapperGC(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data)); + delete finalizeData; + }); + } + + static inline void WrapperGCWithHint(napi_env env, + void* data, + void* finalizeHint) NAPI_NOEXCEPT { + WrapVoidCallback(env, [&] { + FinalizeData* finalizeData = static_cast(finalizeHint); + finalizeData->callback(env, static_cast(data), finalizeData->hint); delete finalizeData; }); } @@ -263,7 +403,7 @@ struct ThreadSafeFinalize { template inline typename std::enable_if(nullptr)>::type CallJsWrapper(napi_env env, napi_value jsCallback, void* context, void* data) { - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { call(env, Function(env, jsCallback), static_cast(context), @@ -277,7 +417,7 @@ CallJsWrapper(napi_env env, napi_value jsCallback, void* /*context*/, void* /*data*/) { - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { if (jsCallback != nullptr) { Function(env, jsCallback).Call(0, nullptr); } @@ -311,7 +451,7 @@ template struct AccessorCallbackData { static inline napi_value GetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -322,7 +462,7 @@ struct AccessorCallbackData { static inline napi_value SetterWrapper(napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); AccessorCallbackData* callbackData = static_cast(callbackInfo.Data()); @@ -337,6 +477,46 @@ struct AccessorCallbackData { void* data; }; +// Debugging-purpose C++-style variant of sprintf(). +inline std::string StringFormat(const char* format, ...) { + std::string result; + va_list args; + va_start(args, format); + int len = vsnprintf(nullptr, 0, format, args); + result.resize(len); + vsnprintf(&result[0], len + 1, format, args); + va_end(args); + return result; +} + +template +class HasExtendedFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + +template +class HasBasicFinalizer { + private: + template + struct SFINAE {}; + template + static char test(SFINAE*); + template + static int test(...); + + public: + static constexpr bool value = sizeof(test(0)) == sizeof(char); +}; + } // namespace details #ifndef NODE_ADDON_API_DISABLE_DEPRECATED @@ -373,7 +553,7 @@ struct AccessorCallbackData { inline napi_value RegisterModule(napi_env env, napi_value exports, ModuleRegisterCallback registerCallback) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { return napi_value( registerCallback(Napi::Env(env), Napi::Object(env, exports))); }); @@ -446,15 +626,21 @@ inline Maybe Just(const T& t) { } //////////////////////////////////////////////////////////////////////////////// -// Env class +// BasicEnv / Env class //////////////////////////////////////////////////////////////////////////////// -inline Env::Env(napi_env env) : _env(env) {} +inline BasicEnv::BasicEnv(node_addon_api_basic_env env) : _env(env) {} -inline Env::operator napi_env() const { +inline BasicEnv::operator node_addon_api_basic_env() const { return _env; } +inline Env::Env(napi_env env) : BasicEnv(env) {} + +inline Env::operator napi_env() const { + return const_cast(_env); +} + inline Object Env::Global() const { napi_value value; napi_status status = napi_get_global(*this, &value); @@ -478,7 +664,7 @@ inline Value Env::Null() const { inline bool Env::IsExceptionPending() const { bool result; - napi_status status = napi_is_exception_pending(_env, &result); + napi_status status = napi_is_exception_pending(*this, &result); if (status != napi_ok) result = false; // Checking for a pending exception shouldn't throw. return result; @@ -486,16 +672,16 @@ inline bool Env::IsExceptionPending() const { inline Error Env::GetAndClearPendingException() const { napi_value value; - napi_status status = napi_get_and_clear_last_exception(_env, &value); + napi_status status = napi_get_and_clear_last_exception(*this, &value); if (status != napi_ok) { // Don't throw another exception when failing to get the exception! return Error(); } - return Error(_env, value); + return Error(*this, value); } inline MaybeOrValue Env::RunScript(const char* utf8script) const { - String script = String::New(_env, utf8script); + String script = String::New(*this, utf8script); return RunScript(script); } @@ -505,46 +691,46 @@ inline MaybeOrValue Env::RunScript(const std::string& utf8script) const { inline MaybeOrValue Env::RunScript(String script) const { napi_value result; - napi_status status = napi_run_script(_env, script, &result); + napi_status status = napi_run_script(*this, script, &result); NAPI_RETURN_OR_THROW_IF_FAILED( - _env, status, Napi::Value(_env, result), Napi::Value); + *this, status, Napi::Value(*this, result), Napi::Value); } #if NAPI_VERSION > 2 template -void Env::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { - auto* cleanupData = - static_cast::CleanupData*>( - data); +void BasicEnv::CleanupHook::Wrapper(void* data) NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); cleanupData->hook(); delete cleanupData; } template -void Env::CleanupHook::WrapperWithArg(void* data) NAPI_NOEXCEPT { - auto* cleanupData = - static_cast::CleanupData*>( - data); +void BasicEnv::CleanupHook::WrapperWithArg(void* data) + NAPI_NOEXCEPT { + auto* cleanupData = static_cast< + typename Napi::BasicEnv::CleanupHook::CleanupData*>(data); cleanupData->hook(static_cast(cleanupData->arg)); delete cleanupData; } #endif // NAPI_VERSION > 2 #if NAPI_VERSION > 5 -template fini> -inline void Env::SetInstanceData(T* data) const { +template fini> +inline void BasicEnv::SetInstanceData(T* data) const { napi_status status = napi_set_instance_data( _env, data, [](napi_env env, void* data, void*) { fini(env, static_cast(data)); }, nullptr); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); } template fini> -inline void Env::SetInstanceData(DataType* data, HintType* hint) const { + Napi::BasicEnv::FinalizerWithHint fini> +inline void BasicEnv::SetInstanceData(DataType* data, HintType* hint) const { napi_status status = napi_set_instance_data( _env, data, @@ -552,35 +738,38 @@ inline void Env::SetInstanceData(DataType* data, HintType* hint) const { fini(env, static_cast(data), static_cast(hint)); }, hint); - NAPI_THROW_IF_FAILED_VOID(_env, status); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::SetInstanceData", "invalid arguments"); } template -inline T* Env::GetInstanceData() const { +inline T* BasicEnv::GetInstanceData() const { void* data = nullptr; napi_status status = napi_get_instance_data(_env, &data); - NAPI_THROW_IF_FAILED(_env, status, nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetInstanceData", "invalid arguments"); return static_cast(data); } template -void Env::DefaultFini(Env, T* data) { +void BasicEnv::DefaultFini(Env, T* data) { delete data; } template -void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) { +void BasicEnv::DefaultFiniWithHint(Env, DataType* data, HintType*) { delete data; } #endif // NAPI_VERSION > 5 #if NAPI_VERSION > 8 -inline const char* Env::GetModuleFileName() const { +inline const char* BasicEnv::GetModuleFileName() const { const char* result; napi_status status = node_api_get_module_file_name(_env, &result); - NAPI_THROW_IF_FAILED(*this, status, nullptr); + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::GetModuleFileName", "invalid arguments"); return result; } #endif // NAPI_VERSION > 8 @@ -752,6 +941,19 @@ inline bool Value::IsExternal() const { return Type() == napi_external; } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +inline bool Value::IsSharedArrayBuffer() const { + if (IsEmpty()) { + return false; + } + + bool result; + napi_status status = node_api_is_sharedarraybuffer(_env, _value, &result); + NAPI_THROW_IF_FAILED(_env, status, false); + return result; +} +#endif + template inline T Value::As() const { #ifdef NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS @@ -760,6 +962,16 @@ inline T Value::As() const { return T(_env, _value); } +template +inline T Value::UnsafeAs() const { + return T(_env, _value); +} + +// static +inline void Value::CheckCast(napi_env /* env */, napi_value value) { + NAPI_CHECK(value != nullptr, "Value::CheckCast", "empty value"); +} + inline MaybeOrValue Value::ToBoolean() const { napi_value result; napi_status status = napi_coerce_to_bool(_env, _value, &result); @@ -805,8 +1017,7 @@ inline void Boolean::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Boolean::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_boolean, "Boolean::CheckCast", "value is not napi_boolean"); + NAPI_INTERNAL_CHECK_EQ(type, napi_boolean, "%d", "Boolean::CheckCast"); } inline Boolean::Boolean() : Napi::Value() {} @@ -842,8 +1053,7 @@ inline void Number::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Number::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_number, "Number::CheckCast", "value is not napi_number"); + NAPI_INTERNAL_CHECK_EQ(type, napi_number, "%d", "Number::CheckCast"); } inline Number::Number() : Value() {} @@ -938,8 +1148,7 @@ inline void BigInt::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "BigInt::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_bigint, "BigInt::CheckCast", "value is not napi_bigint"); + NAPI_INTERNAL_CHECK_EQ(type, napi_bigint, "%d", "BigInt::CheckCast"); } inline BigInt::BigInt() : Value() {} @@ -991,6 +1200,13 @@ inline Date Date::New(napi_env env, double val) { return Date(env, value); } +inline Date Date::New(napi_env env, std::chrono::system_clock::time_point tp) { + using namespace std::chrono; + auto ms = static_cast( + duration_cast(tp.time_since_epoch()).count()); + return Date::New(env, ms); +} + inline void Date::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "Date::CheckCast", "empty value"); @@ -1025,9 +1241,10 @@ inline void Name::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Name::CheckCast", "napi_typeof failed"); - NAPI_CHECK(type == napi_string || type == napi_symbol, - "Name::CheckCast", - "value is not napi_string or napi_symbol"); + NAPI_INTERNAL_CHECK(type == napi_string || type == napi_symbol, + "Name::CheckCast", + "value is not napi_string or napi_symbol, got %d.", + type); } inline Name::Name() : Value() {} @@ -1046,6 +1263,10 @@ inline String String::New(napi_env env, const std::u16string& val) { return String::New(env, val.c_str(), val.size()); } +inline String String::New(napi_env env, std::string_view val) { + return String::New(env, val.data(), val.size()); +} + inline String String::New(napi_env env, const char* val) { // TODO(@gabrielschulhof) Remove if-statement when core's error handling is // available in all supported versions. @@ -1094,8 +1315,7 @@ inline void String::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "String::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_string, "String::CheckCast", "value is not napi_string"); + NAPI_INTERNAL_CHECK_EQ(type, napi_string, "%d", "String::CheckCast"); } inline String::String() : Name() {} @@ -1156,6 +1376,11 @@ inline Symbol Symbol::New(napi_env env, const std::string& description) { return Symbol::New(env, descriptionValue); } +inline Symbol Symbol::New(napi_env env, std::string_view description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::New(env, descriptionValue); +} + inline Symbol Symbol::New(napi_env env, String description) { napi_value descriptionValue = description; return Symbol::New(env, descriptionValue); @@ -1170,12 +1395,15 @@ inline Symbol Symbol::New(napi_env env, napi_value description) { inline MaybeOrValue Symbol::WellKnown(napi_env env, const std::string& name) { + // No need to check if the return value is a symbol or undefined. + // Well known symbols are definite and it is an develop time error + // if the symbol does not exist. #if defined(NODE_ADDON_API_ENABLE_MAYBE) Value symbol_obj; Value symbol_value; if (Napi::Env(env).Global().Get("Symbol").UnwrapTo(&symbol_obj) && symbol_obj.As().Get(name).UnwrapTo(&symbol_value)) { - return Just(symbol_value.As()); + return Just(symbol_value.UnsafeAs()); } return Nothing(); #else @@ -1184,7 +1412,7 @@ inline MaybeOrValue Symbol::WellKnown(napi_env env, .Get("Symbol") .As() .Get(name) - .As(); + .UnsafeAs(); #endif } @@ -1194,6 +1422,12 @@ inline MaybeOrValue Symbol::For(napi_env env, return Symbol::For(env, descriptionValue); } +inline MaybeOrValue Symbol::For(napi_env env, + std::string_view description) { + napi_value descriptionValue = String::New(env, description); + return Symbol::For(env, descriptionValue); +} + inline MaybeOrValue Symbol::For(napi_env env, const char* description) { napi_value descriptionValue = String::New(env, description); return Symbol::For(env, descriptionValue); @@ -1231,8 +1465,7 @@ inline void Symbol::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Symbol::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_symbol, "Symbol::CheckCast", "value is not napi_symbol"); + NAPI_INTERNAL_CHECK_EQ(type, napi_symbol, "%d", "Symbol::CheckCast"); } inline Symbol::Symbol() : Name() {} @@ -1386,6 +1619,11 @@ inline Object::PropertyLValue& Object::PropertyLValue::operator=( return *this; } +template +inline Value Object::PropertyLValue::AsValue() const { + return Value(*this); +} + template inline Object::PropertyLValue::PropertyLValue(Object object, Key key) : _env(object.Env()), _object(object), _key(key) {} @@ -1403,8 +1641,10 @@ inline void Object::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Object::CheckCast", "napi_typeof failed"); - NAPI_CHECK( - type == napi_object, "Object::CheckCast", "value is not napi_object"); + NAPI_INTERNAL_CHECK(type == napi_object || type == napi_function, + "Object::CheckCast", + "Expect napi_object or napi_function, but got %d.", + type); } inline Object::Object() : TypeTaggable() {} @@ -1660,7 +1900,7 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS inline Object::const_iterator::const_iterator(const Object* object, const Type type) { _object = object; @@ -1735,7 +1975,7 @@ Object::iterator::operator*() { PropertyLValue value = (*_object)[key]; return {key, value}; } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #if NAPI_VERSION >= 8 inline MaybeOrValue Object::Freeze() const { @@ -1749,6 +1989,19 @@ inline MaybeOrValue Object::Seal() const { } #endif // NAPI_VERSION >= 8 +inline MaybeOrValue Object::GetPrototype() const { + napi_value result; + napi_status status = napi_get_prototype(_env, _value, &result); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, Object(_env, result), Object); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +inline MaybeOrValue Object::SetPrototype(const Object& value) const { + napi_status status = node_api_set_prototype(_env, _value, value); + NAPI_RETURN_OR_THROW_IF_FAILED(_env, status, status == napi_ok, bool); +} +#endif + //////////////////////////////////////////////////////////////////////////////// // External class //////////////////////////////////////////////////////////////////////////////// @@ -1814,9 +2067,7 @@ inline void External::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "External::CheckCast", "napi_typeof failed"); - NAPI_CHECK(type == napi_external, - "External::CheckCast", - "value is not napi_external"); + NAPI_INTERNAL_CHECK_EQ(type, napi_external, "%d", "External::CheckCast"); } template @@ -1872,6 +2123,55 @@ inline uint32_t Array::Length() const { return result; } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +//////////////////////////////////////////////////////////////////////////////// +// SharedArrayBuffer class +//////////////////////////////////////////////////////////////////////////////// + +inline SharedArrayBuffer::SharedArrayBuffer() : Object() {} + +inline SharedArrayBuffer::SharedArrayBuffer(napi_env env, napi_value value) + : Object(env, value) {} + +inline void SharedArrayBuffer::CheckCast(napi_env env, napi_value value) { + NAPI_CHECK(value != nullptr, "SharedArrayBuffer::CheckCast", "empty value"); + + bool result; + napi_status status = node_api_is_sharedarraybuffer(env, value, &result); + NAPI_CHECK(status == napi_ok, + "SharedArrayBuffer::CheckCast", + "node_api_is_sharedarraybuffer failed"); + NAPI_CHECK( + result, "SharedArrayBuffer::CheckCast", "value is not sharedarraybuffer"); +} + +inline SharedArrayBuffer SharedArrayBuffer::New(napi_env env, + size_t byteLength) { + napi_value value; + void* data; + napi_status status = + node_api_create_sharedarraybuffer(env, byteLength, &data, &value); + NAPI_THROW_IF_FAILED(env, status, SharedArrayBuffer()); + + return SharedArrayBuffer(env, value); +} + +inline void* SharedArrayBuffer::Data() { + void* data; + napi_status status = napi_get_arraybuffer_info(_env, _value, &data, nullptr); + NAPI_THROW_IF_FAILED(_env, status, nullptr); + return data; +} + +inline size_t SharedArrayBuffer::ByteLength() { + size_t length; + napi_status status = + napi_get_arraybuffer_info(_env, _value, nullptr, &length); + NAPI_THROW_IF_FAILED(_env, status, 0); + return length; +} +#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + //////////////////////////////////////////////////////////////////////////////// // ArrayBuffer class //////////////////////////////////////////////////////////////////////////////// @@ -2025,6 +2325,39 @@ inline DataView DataView::New(napi_env env, return DataView(env, value); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer) { + return New(env, arrayBuffer, 0, arrayBuffer.ByteLength()); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset) { + if (byteOffset > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New( + env, "Start offset is outside the bounds of the buffer"), + DataView()); + } + return New( + env, arrayBuffer, byteOffset, arrayBuffer.ByteLength() - byteOffset); +} + +inline DataView DataView::New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength) { + if (byteOffset + byteLength > arrayBuffer.ByteLength()) { + NAPI_THROW(RangeError::New(env, "Invalid DataView length"), DataView()); + } + napi_value value; + napi_status status = + napi_create_dataview(env, byteLength, arrayBuffer, byteOffset, &value); + NAPI_THROW_IF_FAILED(env, status, DataView()); + return DataView(env, value); +} +#endif // NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + inline void DataView::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(value != nullptr, "DataView::CheckCast", "empty value"); @@ -2048,6 +2381,10 @@ inline DataView::DataView(napi_env env, napi_value value) : Object(env, value) { } inline Napi::ArrayBuffer DataView::ArrayBuffer() const { + return Buffer().As(); +} + +inline Napi::Value DataView::Buffer() const { napi_value arrayBuffer; napi_status status = napi_get_dataview_info(_env, _value /* dataView */, @@ -2055,8 +2392,8 @@ inline Napi::ArrayBuffer DataView::ArrayBuffer() const { nullptr /* data */, &arrayBuffer /* arrayBuffer */, nullptr /* byteOffset */); - NAPI_THROW_IF_FAILED(_env, status, Napi::ArrayBuffer()); - return Napi::ArrayBuffer(_env, arrayBuffer); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); } inline size_t DataView::ByteOffset() const { @@ -2257,6 +2594,14 @@ inline Napi::ArrayBuffer TypedArray::ArrayBuffer() const { return Napi::ArrayBuffer(_env, arrayBuffer); } +inline Napi::Value TypedArray::Buffer() const { + napi_value arrayBuffer; + napi_status status = napi_get_typedarray_info( + _env, _value, nullptr, nullptr, nullptr, &arrayBuffer, nullptr); + NAPI_THROW_IF_FAILED(_env, status, Napi::Value()); + return Napi::Value(_env, arrayBuffer); +} + //////////////////////////////////////////////////////////////////////////////// // TypedArrayOf class //////////////////////////////////////////////////////////////////////////////// @@ -2270,12 +2615,13 @@ inline void TypedArrayOf::CheckCast(napi_env env, napi_value value) { "TypedArrayOf::CheckCast", "napi_is_typedarray failed"); - NAPI_CHECK( + NAPI_INTERNAL_CHECK( (type == TypedArrayTypeForPrimitiveType() || (type == napi_uint8_clamped_array && std::is_same::value)), "TypedArrayOf::CheckCast", - "Array type must match the template parameter. (Uint8 arrays may " - "optionally have the \"clamped\" array type.)"); + "Array type must match the template parameter, (Uint8 arrays may " + "optionally have the \"clamped\" array type.), got %d.", + type); } template @@ -2307,6 +2653,28 @@ inline TypedArrayOf TypedArrayOf::New(napi_env env, bufferOffset)); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +template +inline TypedArrayOf TypedArrayOf::New(napi_env env, + size_t elementLength, + Napi::SharedArrayBuffer arrayBuffer, + size_t bufferOffset, + napi_typedarray_type type) { + napi_value value; + napi_status status = napi_create_typedarray( + env, type, elementLength, arrayBuffer, bufferOffset, &value); + NAPI_THROW_IF_FAILED(env, status, TypedArrayOf()); + + return TypedArrayOf( + env, + value, + type, + elementLength, + reinterpret_cast(reinterpret_cast(arrayBuffer.Data()) + + bufferOffset)); +} +#endif + template inline TypedArrayOf::TypedArrayOf() : TypedArray(), _data(nullptr) {} @@ -2456,9 +2824,7 @@ inline void Function::CheckCast(napi_env env, napi_value value) { napi_valuetype type; napi_status status = napi_typeof(env, value, &type); NAPI_CHECK(status == napi_ok, "Function::CheckCast", "napi_typeof failed"); - NAPI_CHECK(type == napi_function, - "Function::CheckCast", - "value is not napi_function"); + NAPI_INTERNAL_CHECK_EQ(type, napi_function, "%d", "Function::CheckCast"); } inline Function::Function() : Object() {} @@ -2616,8 +2982,95 @@ inline void Promise::CheckCast(napi_env env, napi_value value) { NAPI_CHECK(result, "Promise::CheckCast", "value is not promise"); } +inline Promise::Promise() : Object() {} + inline Promise::Promise(napi_env env, napi_value value) : Object(env, value) {} +inline MaybeOrValue Promise::Then(napi_value onFulfilled) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = thenMethod.Call(*this, {onFulfilled}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(napi_value onFulfilled, + napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value thenMethod; + if (!Get("then").UnwrapTo(&thenMethod)) { + return Nothing(); + } + MaybeOrValue result = + thenMethod.As().Call(*this, {onFulfilled, onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function thenMethod = Get("then").As(); + MaybeOrValue result = + thenMethod.Call(*this, {onFulfilled, onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Catch(napi_value onRejected) const { + EscapableHandleScope scope(_env); +#ifdef NODE_ADDON_API_ENABLE_MAYBE + Value catchMethod; + if (!Get("catch").UnwrapTo(&catchMethod)) { + return Nothing(); + } + MaybeOrValue result = + catchMethod.As().Call(*this, {onRejected}); + if (result.IsJust()) { + return Just(scope.Escape(result.Unwrap()).As()); + } + return Nothing(); +#else + Function catchMethod = Get("catch").As(); + MaybeOrValue result = catchMethod.Call(*this, {onRejected}); + if (scope.Env().IsExceptionPending()) { + return Promise(); + } + return scope.Escape(result).As(); +#endif +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled) const { + return Then(static_cast(onFulfilled)); +} + +inline MaybeOrValue Promise::Then(const Function& onFulfilled, + const Function& onRejected) const { + return Then(static_cast(onFulfilled), + static_cast(onRejected)); +} + +inline MaybeOrValue Promise::Catch(const Function& onRejected) const { + return Catch(static_cast(onRejected)); +} + //////////////////////////////////////////////////////////////////////////////// // Buffer class //////////////////////////////////////////////////////////////////////////////// @@ -2731,7 +3184,7 @@ inline Buffer Buffer::NewOrCopy(napi_env env, #endif // NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED // If we can't create an external buffer, we'll just copy the data. Buffer ret = Buffer::Copy(env, data, length); - details::FinalizeData::Wrapper(env, data, finalizeData); + details::FinalizeData::WrapperGC(env, data, finalizeData); return ret; #ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED } @@ -2766,7 +3219,7 @@ inline Buffer Buffer::NewOrCopy(napi_env env, #endif // If we can't create an external buffer, we'll just copy the data. Buffer ret = Buffer::Copy(env, data, length); - details::FinalizeData::WrapperWithHint( + details::FinalizeData::WrapperGCWithHint( env, data, finalizeData); return ret; #ifndef NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED @@ -3014,14 +3467,14 @@ inline Error& Error::operator=(const Error& other) { inline const std::string& Error::Message() const NAPI_NOEXCEPT { if (_message.size() == 0 && _env != nullptr) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { _message = Get("message").As(); } catch (...) { // Catch all errors here, to include e.g. a std::bad_alloc from // the std::string::operator=, because this method may not throw. } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS #if defined(NODE_ADDON_API_ENABLE_MAYBE) Napi::Value message_val; if (Get("message").UnwrapTo(&message_val)) { @@ -3030,7 +3483,7 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT { #else _message = Get("message").As(); #endif -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } return _message; } @@ -3054,7 +3507,12 @@ inline void Error::ThrowAsJavaScriptException() const { status = napi_throw(_env, Value()); - if (status == napi_pending_exception) { +#if (NAPI_VERSION >= 10) + napi_status expected_failure_mode = napi_cannot_run_js; +#else + napi_status expected_failure_mode = napi_pending_exception; +#endif + if (status == expected_failure_mode) { // The environment must be terminating as we checked earlier and there // was no pending exception. In this case continuing will result // in a fatal error and there is nothing the author has done incorrectly @@ -3072,24 +3530,24 @@ inline void Error::ThrowAsJavaScriptException() const { napi_status status = napi_throw(_env, Value()); #endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS if (status != napi_ok) { throw Error::New(_env); } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS NAPI_FATAL_IF_FAILED( status, "Error::ThrowAsJavaScriptException", "napi_throw"); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } } -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS inline const char* Error::what() const NAPI_NOEXCEPT { return Message().c_str(); } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS inline const char* Error::ERROR_WRAP_VALUE() NAPI_NOEXCEPT { return "4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal"; @@ -3191,7 +3649,15 @@ template inline Reference::~Reference() { if (_ref != nullptr) { if (!_suppressDestruct) { + // TODO(legendecas): napi_delete_reference should be invoked immediately. + // Fix this when https://github.com/nodejs/node/pull/55620 lands. +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + Env().PostFinalizer( + [](Napi::Env env, napi_ref ref) { napi_delete_reference(env, ref); }, + _ref); +#else napi_delete_reference(_env, _ref); +#endif } _ref = nullptr; @@ -3446,8 +3912,8 @@ inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, return Value().Set(utf8name, value); } -inline MaybeOrValue ObjectReference::Set(const std::string& utf8name, - std::string& utf8value) const { +inline MaybeOrValue ObjectReference::Set( + const std::string& utf8name, const std::string& utf8value) const { HandleScope scope(_env); return Value().Set(utf8name, utf8value); } @@ -4350,7 +4816,7 @@ inline ClassPropertyDescriptor InstanceWrap::InstanceValue( template inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4365,7 +4831,7 @@ inline napi_value InstanceWrap::InstanceVoidMethodCallbackWrapper( template inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4379,7 +4845,7 @@ inline napi_value InstanceWrap::InstanceMethodCallbackWrapper( template inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4393,7 +4859,7 @@ inline napi_value InstanceWrap::InstanceGetterCallbackWrapper( template inline napi_value InstanceWrap::InstanceSetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); InstanceAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4409,7 +4875,7 @@ template template ::InstanceSetterCallback method> inline napi_value InstanceWrap::WrappedMethod( napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); T* instance = T::Unwrap(cbInfo.This().As()); if (instance) (instance->*method)(cbInfo, cbInfo[0]); @@ -4422,7 +4888,8 @@ inline napi_value InstanceWrap::WrappedMethod( //////////////////////////////////////////////////////////////////////////////// template -inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { +inline NAPI_NO_SANITIZE_VPTR ObjectWrap::ObjectWrap( + const Napi::CallbackInfo& callbackInfo) { napi_env env = callbackInfo.Env(); napi_value wrapper = callbackInfo.This(); napi_status status; @@ -4436,10 +4903,10 @@ inline ObjectWrap::ObjectWrap(const Napi::CallbackInfo& callbackInfo) { } template -inline ObjectWrap::~ObjectWrap() { +inline NAPI_NO_SANITIZE_VPTR ObjectWrap::~ObjectWrap() { // If the JS object still exists at this point, remove the finalizer added // through `napi_wrap()`. - if (!IsEmpty()) { + if (!IsEmpty() && !_finalized) { Object object = Value(); // It is not valid to call `napi_remove_wrap()` with an empty `object`. // This happens e.g. during garbage collection. @@ -4449,8 +4916,12 @@ inline ObjectWrap::~ObjectWrap() { } } +// with RTTI turned on, modern compilers check to see if virtual function +// pointers are stripped of RTTI by void casts. this is intrinsic to how Unwrap +// works, so we inject a compiler pragma to turn off that check just for the +// affected methods. this compiler check is on by default in Android NDK 29. template -inline T* ObjectWrap::Unwrap(Object wrapper) { +inline NAPI_NO_SANITIZE_VPTR T* ObjectWrap::Unwrap(Object wrapper) { void* unwrapped; napi_status status = napi_unwrap(wrapper.Env(), wrapper, &unwrapped); NAPI_THROW_IF_FAILED(wrapper.Env(), status, nullptr); @@ -4791,6 +5262,9 @@ inline Value ObjectWrap::OnCalledAsFunction( template inline void ObjectWrap::Finalize(Napi::Env /*env*/) {} +template +inline void ObjectWrap::Finalize(BasicEnv /*env*/) {} + template inline napi_value ObjectWrap::ConstructorCallbackWrapper( napi_env env, napi_callback_info info) { @@ -4801,13 +5275,13 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( bool isConstructCall = (new_target != nullptr); if (!isConstructCall) { return details::WrapCallback( - [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); + env, [&] { return T::OnCalledAsFunction(CallbackInfo(env, info)); }); } - napi_value wrapper = details::WrapCallback([&] { + napi_value wrapper = details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); T* instance = new T(callbackInfo); -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS instance->_construction_failed = false; #else if (callbackInfo.Env().IsExceptionPending()) { @@ -4818,7 +5292,7 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( } else { instance->_construction_failed = false; } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS return callbackInfo.This(); }); @@ -4828,7 +5302,7 @@ inline napi_value ObjectWrap::ConstructorCallbackWrapper( template inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticVoidMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4841,7 +5315,7 @@ inline napi_value ObjectWrap::StaticVoidMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticMethodCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticMethodCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4853,7 +5327,7 @@ inline napi_value ObjectWrap::StaticMethodCallbackWrapper( template inline napi_value ObjectWrap::StaticGetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4865,7 +5339,7 @@ inline napi_value ObjectWrap::StaticGetterCallbackWrapper( template inline napi_value ObjectWrap::StaticSetterCallbackWrapper( napi_env env, napi_callback_info info) { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { CallbackInfo callbackInfo(env, info); StaticAccessorCallbackData* callbackData = reinterpret_cast(callbackInfo.Data()); @@ -4876,10 +5350,61 @@ inline napi_value ObjectWrap::StaticSetterCallbackWrapper( } template -inline void ObjectWrap::FinalizeCallback(napi_env env, +inline void ObjectWrap::FinalizeCallback(node_addon_api_basic_env env, void* data, void* /*hint*/) { - HandleScope scope(env); + // If the child class does not override _any_ Finalize() method, `env` will be + // unused because of the constexpr guards. Explicitly reference it here to + // bypass compiler warnings. + (void)env; + T* instance = static_cast(data); + + // Prevent ~ObjectWrap from calling napi_remove_wrap. + // The instance->_ref should be deleted with napi_delete_reference in + // ~Reference. + instance->_finalized = true; + + // If class overrides the basic finalizer, execute it. + if constexpr (details::HasBasicFinalizer::value) { +#ifndef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + HandleScope scope(env); +#endif + + instance->Finalize(Napi::BasicEnv(env)); + } + + // If class overrides the (extended) finalizer, either schedule it or + // execute it immediately (depending on experimental features enabled). + if constexpr (details::HasExtendedFinalizer::value) { +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + // In experimental, attach via node_api_post_finalizer. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + napi_status status = + node_api_post_finalizer(env, PostFinalizeCallback, data, nullptr); + NAPI_FATAL_IF_FAILED(status, + "ObjectWrap::FinalizeCallback", + "node_api_post_finalizer failed"); +#else + // In non-experimental, this `FinalizeCallback` already executes from a + // non-basic environment. Execute the override directly. + // `PostFinalizeCallback` is responsible for deleting the `T* instance`, + // after calling the user-provided finalizer. + HandleScope scope(env); + PostFinalizeCallback(env, data, static_cast(nullptr)); +#endif + } + // If the instance does _not_ override the (extended) finalizer, delete the + // `T* instance` immediately. + else { + delete instance; + } +} + +template +inline void ObjectWrap::PostFinalizeCallback(napi_env env, + void* data, + void* /*hint*/) { T* instance = static_cast(data); instance->Finalize(Napi::Env(env)); delete instance; @@ -4889,9 +5414,12 @@ template template ::StaticSetterCallback method> inline napi_value ObjectWrap::WrappedMethod( napi_env env, napi_callback_info info) NAPI_NOEXCEPT { - return details::WrapCallback([&] { + return details::WrapCallback(env, [&] { const CallbackInfo cbInfo(env, info); - method(cbInfo, cbInfo[0]); + // MSVC requires to copy 'method' function pointer to a local variable + // before invoking it. + auto m = method; + m(cbInfo, cbInfo[0]); return nullptr; }); } @@ -5187,15 +5715,15 @@ inline void AsyncWorker::OnAsyncWorkExecute(napi_env env, void* asyncworker) { // must not run any method that would cause JavaScript to run. In practice, // this means that almost any use of napi_env will be incorrect. inline void AsyncWorker::OnExecute(Napi::Env /*DO_NOT_USE*/) { -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS try { Execute(); } catch (const std::exception& e) { SetError(e.what()); } -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS Execute(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS } inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, @@ -5204,10 +5732,10 @@ inline void AsyncWorker::OnAsyncWorkComplete(napi_env env, AsyncWorker* self = static_cast(asyncworker); self->OnWorkComplete(env, status); } -inline void AsyncWorker::OnWorkComplete(Napi::Env /*env*/, napi_status status) { +inline void AsyncWorker::OnWorkComplete(Napi::Env env, napi_status status) { if (status != napi_cancelled) { HandleScope scope(_env); - details::WrapCallback([&] { + details::WrapCallback(env, [&] { if (_error.size() == 0) { OnOK(); } else { @@ -5321,19 +5849,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - nullptr, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -5365,19 +5895,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - nullptr, - resource, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + nullptr, + resource, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -5481,19 +6013,21 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); - napi_status status = napi_create_threadsafe_function( - env, - callback, - nullptr, - String::From(env, resourceName), - maxQueueSize, - initialThreadCount, - finalizeData, + auto fini = details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, - context, - CallJsInternal, - &tsfn._tsfn); + FinalizeFinalizeWrapperWithDataAndContext; + napi_status status = + napi_create_threadsafe_function(env, + callback, + nullptr, + String::From(env, resourceName), + maxQueueSize, + initialThreadCount, + finalizeData, + fini, + context, + CallJsInternal, + &tsfn._tsfn); if (status != napi_ok) { delete finalizeData; NAPI_THROW_IF_FAILED( @@ -5527,6 +6061,9 @@ TypedThreadSafeFunction::New( auto* finalizeData = new details:: ThreadSafeFinalize( {data, finalizeCallback}); + auto fini = + details::ThreadSafeFinalize:: + FinalizeFinalizeWrapperWithDataAndContext; napi_status status = napi_create_threadsafe_function( env, details::DefaultCallbackWrapper< @@ -5538,8 +6075,7 @@ TypedThreadSafeFunction::New( maxQueueSize, initialThreadCount, finalizeData, - details::ThreadSafeFinalize:: - FinalizeFinalizeWrapperWithDataAndContext, + fini, context, CallJsInternal, &tsfn._tsfn); @@ -6111,7 +6647,7 @@ inline void ThreadSafeFunction::CallJS(napi_env env, return; } - details::WrapVoidCallback([&]() { + details::WrapVoidCallback(env, [&]() { if (data != nullptr) { auto* callbackWrapper = static_cast(data); (*callbackWrapper)(env, Function(env, jsCallback)); @@ -6480,12 +7016,14 @@ inline void AsyncProgressQueueWorker::ExecutionProgress::Send( // Memory Management class //////////////////////////////////////////////////////////////////////////////// -inline int64_t MemoryManagement::AdjustExternalMemory(Env env, +inline int64_t MemoryManagement::AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes) { int64_t result; napi_status status = napi_adjust_external_memory(env, change_in_bytes, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + NAPI_FATAL_IF_FAILED(status, + "MemoryManagement::AdjustExternalMemory", + "napi_adjust_external_memory"); return result; } @@ -6493,17 +7031,20 @@ inline int64_t MemoryManagement::AdjustExternalMemory(Env env, // Version Management class //////////////////////////////////////////////////////////////////////////////// -inline uint32_t VersionManagement::GetNapiVersion(Env env) { +inline uint32_t VersionManagement::GetNapiVersion(BasicEnv env) { uint32_t result; napi_status status = napi_get_version(env, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNapiVersion", "napi_get_version"); return result; } -inline const napi_node_version* VersionManagement::GetNodeVersion(Env env) { +inline const napi_node_version* VersionManagement::GetNodeVersion( + BasicEnv env) { const napi_node_version* result; napi_status status = napi_get_node_version(env, &result); - NAPI_THROW_IF_FAILED(env, status, 0); + NAPI_FATAL_IF_FAILED( + status, "VersionManagement::GetNodeVersion", "napi_get_node_version"); return result; } @@ -6548,12 +7089,12 @@ inline Napi::Object Addon::DefineProperties( #if NAPI_VERSION > 2 template -Env::CleanupHook Env::AddCleanupHook(Hook hook, Arg* arg) { +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook, Arg* arg) { return CleanupHook(*this, hook, arg); } template -Env::CleanupHook Env::AddCleanupHook(Hook hook) { +Env::CleanupHook BasicEnv::AddCleanupHook(Hook hook) { return CleanupHook(*this, hook); } @@ -6563,7 +7104,7 @@ Env::CleanupHook::CleanupHook() { } template -Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook) +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, Hook hook) : wrapper(Env::CleanupHook::Wrapper) { data = new CleanupData{std::move(hook), nullptr}; napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); @@ -6574,7 +7115,9 @@ Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook) } template -Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook, Arg* arg) +Env::CleanupHook::CleanupHook(Napi::BasicEnv env, + Hook hook, + Arg* arg) : wrapper(Env::CleanupHook::WrapperWithArg) { data = new CleanupData{std::move(hook), arg}; napi_status status = napi_add_env_cleanup_hook(env, wrapper, data); @@ -6585,7 +7128,7 @@ Env::CleanupHook::CleanupHook(Napi::Env env, Hook hook, Arg* arg) } template -bool Env::CleanupHook::Remove(Env env) { +bool Env::CleanupHook::Remove(BasicEnv env) { napi_status status = napi_remove_env_cleanup_hook(env, wrapper, data); delete data; data = nullptr; @@ -6598,10 +7141,71 @@ bool Env::CleanupHook::IsEmpty() const { } #endif // NAPI_VERSION > 2 +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback) const { + using T = void*; + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithoutData, + static_cast(nullptr), + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), nullptr}); + + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGC, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} + +template +inline void BasicEnv::PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const { + details::FinalizeData* finalizeData = + new details::FinalizeData( + {std::move(finalizeCallback), finalizeHint}); + napi_status status = node_api_post_finalizer( + _env, + details::FinalizeData::WrapperGCWithHint, + data, + finalizeData); + if (status != napi_ok) { + delete finalizeData; + NAPI_FATAL_IF_FAILED( + status, "BasicEnv::PostFinalizer", "invalid arguments"); + } +} +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + #ifdef NAPI_CPP_CUSTOM_NAMESPACE } // namespace NAPI_CPP_CUSTOM_NAMESPACE #endif } // namespace Napi +#undef NAPI_NO_SANITIZE_VPTR + #endif // SRC_NAPI_INL_H_ diff --git a/napi.h b/napi.h index 9f20cb884..870a5c290 100644 --- a/napi.h +++ b/napi.h @@ -17,7 +17,9 @@ #if NAPI_HAS_THREADS #include #endif // NAPI_HAS_THREADS +#include #include +#include #include // VS2015 RTM has bugs with constexpr, so require min of VS2015 Update 3 (known @@ -37,22 +39,40 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_WIDE_TEXT(x) u##x #endif +// Backwards-compatibility to handle the rename of this macro definition, in +// case they are used within userland code. +#ifdef NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && !defined(NAPI_CPP_EXCEPTIONS) +#define NAPI_CPP_EXCEPTIONS +#endif +#ifdef NAPI_DISABLE_CPP_EXCEPTIONS +#define NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS +#endif +#if defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) && \ + !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#define NAPI_DISABLE_CPP_EXCEPTIONS +#endif + // If C++ exceptions are not explicitly enabled or disabled, enable them // if exceptions were enabled in the compiler settings. -#if !defined(NAPI_CPP_EXCEPTIONS) && !defined(NAPI_DISABLE_CPP_EXCEPTIONS) +#if !defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + !defined(NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS) #if defined(_CPPUNWIND) || defined(__EXCEPTIONS) -#define NAPI_CPP_EXCEPTIONS +#define NODE_ADDON_API_CPP_EXCEPTIONS #else #error Exception support not detected. \ - Define either NAPI_CPP_EXCEPTIONS or NAPI_DISABLE_CPP_EXCEPTIONS. + Define either NODE_ADDON_API_CPP_EXCEPTIONS or NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS. #endif #endif -// If C++ NAPI_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE should -// not be set -#if defined(NAPI_CPP_EXCEPTIONS) && defined(NODE_ADDON_API_ENABLE_MAYBE) +// If C++ NODE_ADDON_API_CPP_EXCEPTIONS are enabled, NODE_ADDON_API_ENABLE_MAYBE +// should not be set +#if defined(NODE_ADDON_API_CPP_EXCEPTIONS) && \ + defined(NODE_ADDON_API_ENABLE_MAYBE) #error NODE_ADDON_API_ENABLE_MAYBE should not be set when \ - NAPI_CPP_EXCEPTIONS is defined. + NODE_ADDON_API_CPP_EXCEPTIONS is defined. #endif #ifdef _NOEXCEPT @@ -61,7 +81,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_NOEXCEPT noexcept #endif -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are enabled, Errors are thrown directly. There is no need // to return anything after the throw statements. The variadic parameter is an @@ -78,7 +98,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), #define NAPI_THROW_IF_FAILED_VOID(env, status) \ if ((status) != napi_ok) throw Napi::Error::New(env); -#else // NAPI_CPP_EXCEPTIONS +#else // NODE_ADDON_API_CPP_EXCEPTIONS // When C++ exceptions are disabled, Errors are thrown as JavaScript exceptions, // which are pending until the callback returns to JS. The variadic parameter @@ -110,7 +130,7 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), return; \ } -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #ifdef NODE_ADDON_API_ENABLE_MAYBE #define NAPI_MAYBE_THROW_IF_FAILED(env, status, type) \ @@ -142,6 +162,26 @@ static_assert(sizeof(char16_t) == sizeof(wchar_t), } \ } while (0) +// Internal check helper. Be careful that the formatted message length should be +// max 255 size and null terminated. +#define NAPI_INTERNAL_CHECK(expr, location, ...) \ + do { \ + if (!(expr)) { \ + std::string msg = Napi::details::StringFormat(__VA_ARGS__); \ + Napi::Error::Fatal(location, msg.c_str()); \ + } \ + } while (0) + +#define NAPI_INTERNAL_CHECK_EQ(actual, expected, value_format, location) \ + do { \ + auto actual_value = (actual); \ + NAPI_INTERNAL_CHECK(actual_value == (expected), \ + location, \ + "Expected " #actual " to be equal to " #expected \ + ", but got " value_format ".", \ + actual_value); \ + } while (0) + #define NAPI_FATAL_IF_FAILED(status, location, message) \ NAPI_CHECK((status) == napi_ok, location, message) @@ -279,6 +319,14 @@ template using MaybeOrValue = T; #endif +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +using node_addon_api_basic_env = node_api_nogc_env; +using node_addon_api_basic_finalize = node_api_nogc_finalize; +#else +using node_addon_api_basic_env = napi_env; +using node_addon_api_basic_finalize = napi_finalize; +#endif + /// Environment for Node-API values and operations. /// /// All Node-API values and operations must be associated with an environment. @@ -287,14 +335,13 @@ using MaybeOrValue = T; /// Node-API operations within the callback. (Many methods infer the /// environment from the `this` instance that the method is called on.) /// -/// In the future, multiple environments per process may be supported, -/// although current implementations only support one environment per process. +/// Multiple environments may co-exist in a single process or a thread. /// /// In the V8 JavaScript engine, a Node-API environment approximately /// corresponds to an Isolate. -class Env { +class BasicEnv { private: - napi_env _env; + node_addon_api_basic_env _env; #if NAPI_VERSION > 5 template static void DefaultFini(Env, T* data); @@ -302,20 +349,22 @@ class Env { static void DefaultFiniWithHint(Env, DataType* data, HintType* hint); #endif // NAPI_VERSION > 5 public: - Env(napi_env env); - - operator napi_env() const; - - Object Global() const; - Value Undefined() const; - Value Null() const; - - bool IsExceptionPending() const; - Error GetAndClearPendingException() const; - - MaybeOrValue RunScript(const char* utf8script) const; - MaybeOrValue RunScript(const std::string& utf8script) const; - MaybeOrValue RunScript(String script) const; + BasicEnv(node_addon_api_basic_env env); + + operator node_addon_api_basic_env() const; + + // Without these operator overloads, the error: + // + // Use of overloaded operator '==' is ambiguous (with operand types + // 'Napi::Env' and 'Napi::Env') + // + // ... occurs when comparing foo.Env() == bar.Env() or foo.Env() == nullptr + bool operator==(const BasicEnv& other) const { + return _env == other._env; + } + bool operator==(std::nullptr_t /*other*/) const { + return _env == nullptr; + } #if NAPI_VERSION > 2 template @@ -334,7 +383,7 @@ class Env { template using Finalizer = void (*)(Env, T*); - template fini = Env::DefaultFini> + template fini = BasicEnv::DefaultFini> void SetInstanceData(T* data) const; template @@ -342,7 +391,7 @@ class Env { template fini = - Env::DefaultFiniWithHint> + BasicEnv::DefaultFiniWithHint> void SetInstanceData(DataType* data, HintType* hint) const; #endif // NAPI_VERSION > 5 @@ -351,9 +400,9 @@ class Env { class CleanupHook { public: CleanupHook(); - CleanupHook(Env env, Hook hook, Arg* arg); - CleanupHook(Env env, Hook hook); - bool Remove(Env env); + CleanupHook(BasicEnv env, Hook hook, Arg* arg); + CleanupHook(BasicEnv env, Hook hook); + bool Remove(BasicEnv env); bool IsEmpty() const; private: @@ -371,6 +420,39 @@ class Env { #if NAPI_VERSION > 8 const char* GetModuleFileName() const; #endif // NAPI_VERSION > 8 + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + template + inline void PostFinalizer(FinalizerType finalizeCallback) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, T* data) const; + + template + inline void PostFinalizer(FinalizerType finalizeCallback, + T* data, + Hint* finalizeHint) const; +#endif // NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + + friend class Env; +}; + +class Env : public BasicEnv { + public: + Env(napi_env env); + + operator napi_env() const; + + Object Global() const; + Value Undefined() const; + Value Null() const; + + bool IsExceptionPending() const; + Error GetAndClearPendingException() const; + + MaybeOrValue RunScript(const char* utf8script) const; + MaybeOrValue RunScript(const std::string& utf8script) const; + MaybeOrValue RunScript(String script) const; }; /// A JavaScript value of unknown type. @@ -407,6 +489,8 @@ class Value { template static Value From(napi_env env, const T& value); + static void CheckCast(napi_env env, napi_value value); + /// Converts to a Node-API value primitive. /// /// If the instance is _empty_, this returns `nullptr`. @@ -461,6 +545,9 @@ class Value { bool IsDataView() const; ///< Tests if a value is a JavaScript data view. bool IsBuffer() const; ///< Tests if a value is a Node buffer. bool IsExternal() const; ///< Tests if a value is a pointer to external data. +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + bool IsSharedArrayBuffer() const; +#endif /// Casts to another type of `Napi::Value`, when the actual type is known or /// assumed. @@ -473,6 +560,10 @@ class Value { template T As() const; + // Unsafe Value::As(), should be avoided. + template + T UnsafeAs() const; + MaybeOrValue ToBoolean() const; ///< Coerces a value to a JavaScript boolean. MaybeOrValue ToNumber() @@ -596,6 +687,12 @@ class Date : public Value { double value ///< Number value ); + /// Creates a new Date value from a std::chrono::system_clock::time_point. + static Date New( + napi_env env, ///< Node-API environment + std::chrono::system_clock::time_point time_point ///< Time point value + ); + static void CheckCast(napi_env env, napi_value value); Date(); ///< Creates a new _empty_ Date instance. @@ -629,6 +726,11 @@ class String : public Name { const std::u16string& value ///< UTF-16 encoded C++ string ); + /// Creates a new String value from a UTF-8 encoded C++ string view. + static String New(napi_env env, ///< Node-API environment + std::string_view value ///< UTF-8 encoded C++ string view + ); + /// Creates a new String value from a UTF-8 encoded C string. static String New( napi_env env, ///< Node-API environment @@ -702,6 +804,13 @@ class Symbol : public Name { description ///< UTF-8 encoded C++ string describing the symbol ); + /// Creates a new Symbol value with a description. + static Symbol New( + napi_env env, ///< Node-API environment + std::string_view + description ///< UTF-8 encoded C++ string view describing the symbol + ); + /// Creates a new Symbol value with a description. static Symbol New(napi_env env, ///< Node-API environment String description ///< String value describing the symbol @@ -719,6 +828,9 @@ class Symbol : public Name { // Create a symbol in the global registry, UTF-8 Encoded cpp string static MaybeOrValue For(napi_env env, const std::string& description); + // Create a symbol in the global registry, UTF-8 encoded cpp string view + static MaybeOrValue For(napi_env env, std::string_view description); + // Create a symbol in the global registry, C style string (null terminated) static MaybeOrValue For(napi_env env, const char* description); @@ -774,6 +886,9 @@ class Object : public TypeTaggable { template PropertyLValue& operator=(ValueType value); + /// Converts an L-value to a value. For convenience. + Value AsValue() const; + private: PropertyLValue() = delete; PropertyLValue(Object object, Key key); @@ -1000,7 +1115,7 @@ class Object : public TypeTaggable { T* data, Hint* finalizeHint) const; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS class const_iterator; inline const_iterator begin() const; @@ -1012,7 +1127,7 @@ class Object : public TypeTaggable { inline iterator begin(); inline iterator end(); -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS #if NAPI_VERSION >= 8 /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into @@ -1026,6 +1141,12 @@ class Object : public TypeTaggable { /// https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof MaybeOrValue Seal() const; #endif // NAPI_VERSION >= 8 + + MaybeOrValue GetPrototype() const; + +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + MaybeOrValue SetPrototype(const Object& value) const; +#endif }; template @@ -1064,7 +1185,7 @@ class Array : public Object { uint32_t Length() const; }; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS class Object::const_iterator { private: enum class Type { BEGIN, END }; @@ -1111,7 +1232,22 @@ class Object::iterator { friend class Object; }; -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +class SharedArrayBuffer : public Object { + public: + SharedArrayBuffer(); + SharedArrayBuffer(napi_env env, napi_value value); + + static SharedArrayBuffer New(napi_env env, size_t byteLength); + + static void CheckCast(napi_env env, napi_value value); + + void* Data(); + size_t ByteLength(); +}; +#endif /// A JavaScript array buffer value. class ArrayBuffer : public Object { @@ -1203,7 +1339,21 @@ class TypedArray : public Object { napi_typedarray_type TypedArrayType() const; ///< Gets the type of this typed-array. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + + // Gets the backing `ArrayBuffer`. + // + // If this `TypedArray` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; uint8_t ElementSize() const; ///< Gets the size in bytes of one element in the array. @@ -1268,7 +1418,7 @@ class TypedArrayOf : public TypedArray { napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() #else - napi_typedarray_type type + napi_typedarray_type type #endif ///< Type of array, if different from the default array type for the ///< template parameter T. @@ -1291,12 +1441,38 @@ class TypedArrayOf : public TypedArray { napi_typedarray_type type = TypedArray::TypedArrayTypeForPrimitiveType() #else - napi_typedarray_type type + napi_typedarray_type type #endif ///< Type of array, if different from the default array type for the ///< template parameter T. ); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + /// Creates a new TypedArray instance over a provided SharedArrayBuffer. + /// + /// The array type parameter can normally be omitted (because it is inferred + /// from the template parameter T), except when creating a "clamped" array: + /// + /// Uint8Array::New(env, length, buffer, 0, napi_uint8_clamped_array) + static TypedArrayOf New( + napi_env env, ///< Node-API environment + size_t elementLength, ///< Length of the created array, as a number of + ///< elements + Napi::SharedArrayBuffer + arrayBuffer, ///< Backing shared array buffer instance to use + size_t bufferOffset, ///< Offset into the array buffer where the + ///< typed-array starts +#if defined(NAPI_HAS_CONSTEXPR) + napi_typedarray_type type = + TypedArray::TypedArrayTypeForPrimitiveType() +#else + napi_typedarray_type type +#endif + ///< Type of array, if different from the default array type for the + ///< template parameter T. + ); +#endif + static void CheckCast(napi_env env, napi_value value); TypedArrayOf(); ///< Creates a new _empty_ TypedArrayOf instance. @@ -1343,13 +1519,37 @@ class DataView : public Object { size_t byteOffset, size_t byteLength); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + static DataView New(napi_env env, Napi::SharedArrayBuffer arrayBuffer); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset); + static DataView New(napi_env env, + Napi::SharedArrayBuffer arrayBuffer, + size_t byteOffset, + size_t byteLength); +#endif + static void CheckCast(napi_env env, napi_value value); DataView(); ///< Creates a new _empty_ DataView instance. DataView(napi_env env, napi_value value); ///< Wraps a Node-API value primitive. - Napi::ArrayBuffer ArrayBuffer() const; ///< Gets the backing array buffer. + // Gets the backing `ArrayBuffer`. + // + // If this `DataView` is not backed by an `ArrayBuffer`, this method will + // terminate the process with a fatal error when using + // `NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS` or exhibit undefined behavior + // otherwise. Use `Buffer()` instead to get the backing buffer without + // assuming its type. + Napi::ArrayBuffer ArrayBuffer() const; + + // Gets the backing buffer (an `ArrayBuffer` or `SharedArrayBuffer`). + // + // Use `IsArrayBuffer()` or `IsSharedArrayBuffer()` to check the type of the + // backing buffer prior to casting with `As()`. + Napi::Value Buffer() const; size_t ByteOffset() const; ///< Gets the offset into the buffer where the array starts. size_t ByteLength() const; ///< Gets the length of the array in bytes. @@ -1381,8 +1581,8 @@ class DataView : public Object { template void WriteData(size_t byteOffset, T value) const; - void* _data; - size_t _length; + void* _data{}; + size_t _length{}; }; class Function : public Object { @@ -1485,7 +1685,18 @@ class Promise : public Object { static void CheckCast(napi_env env, napi_value value); + Promise(); Promise(napi_env env, napi_value value); + + MaybeOrValue Then(napi_value onFulfilled) const; + MaybeOrValue Then(napi_value onFulfilled, + napi_value onRejected) const; + MaybeOrValue Catch(napi_value onRejected) const; + + MaybeOrValue Then(const Function& onFulfilled) const; + MaybeOrValue Then(const Function& onFulfilled, + const Function& onRejected) const; + MaybeOrValue Catch(const Function& onRejected) const; }; template @@ -1615,7 +1826,7 @@ class ObjectReference : public Reference { MaybeOrValue Set(const std::string& utf8name, napi_value value) const; MaybeOrValue Set(const std::string& utf8name, Napi::Value value) const; MaybeOrValue Set(const std::string& utf8name, - std::string& utf8value) const; + const std::string& utf8value) const; MaybeOrValue Set(const std::string& utf8name, bool boolValue) const; MaybeOrValue Set(const std::string& utf8name, double numberValue) const; @@ -1715,7 +1926,7 @@ FunctionReference Persistent(Function value); /// /// Following C++ statements will not be executed. The exception will bubble /// up as a C++ exception of type `Napi::Error`, until it is either caught -/// while still in C++, or else automatically propataged as a JavaScript +/// while still in C++, or else automatically propagated as a JavaScript /// exception when the callback returns to JavaScript. /// /// #### Example 2A - Propagating a Node-API C++ exception: @@ -1743,14 +1954,15 @@ FunctionReference Persistent(Function value); /// /// ### Handling Errors Without C++ Exceptions /// -/// If C++ exceptions are disabled (by defining `NAPI_DISABLE_CPP_EXCEPTIONS`) -/// then this class does not extend `std::exception`, and APIs in the `Napi` -/// namespace do not throw C++ exceptions when they fail. Instead, they raise -/// _pending_ JavaScript exceptions and return _empty_ `Value`s. Calling code -/// should check `Value::IsEmpty()` before attempting to use a returned value, -/// and may use methods on the `Env` class to check for, get, and clear a -/// pending JavaScript exception. If the pending exception is not cleared, it -/// will be thrown when the native callback returns to JavaScript. +/// If C++ exceptions are disabled (by defining +/// `NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS`) then this class does not extend +/// `std::exception`, and APIs in the `Napi` namespace do not throw C++ +/// exceptions when they fail. Instead, they raise _pending_ JavaScript +/// exceptions and return _empty_ `Value`s. Calling code should check +/// `Value::IsEmpty()` before attempting to use a returned value, and may use +/// methods on the `Env` class to check for, get, and clear a pending JavaScript +/// exception. If the pending exception is not cleared, it will be thrown when +/// the native callback returns to JavaScript. /// /// #### Example 1B - Throwing a JS exception /// @@ -1785,10 +1997,10 @@ FunctionReference Persistent(Function value); /// Since the exception was cleared here, it will not be propagated as a /// JavaScript exception after the native callback returns. class Error : public ObjectReference -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS , public std::exception -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS { public: static Error New(napi_env env); @@ -1811,9 +2023,9 @@ class Error : public ObjectReference Object Value() const; -#ifdef NAPI_CPP_EXCEPTIONS +#ifdef NODE_ADDON_API_CPP_EXCEPTIONS const char* what() const NAPI_NOEXCEPT override; -#endif // NAPI_CPP_EXCEPTIONS +#endif // NODE_ADDON_API_CPP_EXCEPTIONS protected: /// !cond INTERNAL @@ -1888,7 +2100,7 @@ class CallbackInfo { napi_value _this; size_t _argc; napi_value* _argv; - napi_value _staticArgs[6]; + napi_value _staticArgs[6]{}; napi_value* _dynamicArgs; void* _data; }; @@ -2395,6 +2607,7 @@ class ObjectWrap : public InstanceWrap, public Reference { napi_property_attributes attributes = napi_default); static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& callbackInfo); virtual void Finalize(Napi::Env env); + virtual void Finalize(BasicEnv env); private: using This = ObjectWrap; @@ -2409,7 +2622,12 @@ class ObjectWrap : public InstanceWrap, public Reference { napi_callback_info info); static napi_value StaticSetterCallbackWrapper(napi_env env, napi_callback_info info); - static void FinalizeCallback(napi_env env, void* data, void* hint); + static void FinalizeCallback(node_addon_api_basic_env env, + void* data, + void* hint); + + static void PostFinalizeCallback(napi_env env, void* data, void* hint); + static Function DefineClass(Napi::Env env, const char* utf8name, const size_t props_count, @@ -2440,6 +2658,7 @@ class ObjectWrap : public InstanceWrap, public Reference { } bool _construction_failed = true; + bool _finalized = false; }; class HandleScope { @@ -3000,8 +3219,8 @@ class AsyncProgressWorkerBase : public AsyncWorker { AsyncProgressWorkerBase* asyncprogressworker() { return _asyncprogressworker; - }; - DataType* data() { return _data; }; + } + DataType* data() { return _data; } private: AsyncProgressWorkerBase* _asyncprogressworker; @@ -3160,14 +3379,14 @@ class AsyncProgressQueueWorker // Memory management. class MemoryManagement { public: - static int64_t AdjustExternalMemory(Env env, int64_t change_in_bytes); + static int64_t AdjustExternalMemory(BasicEnv env, int64_t change_in_bytes); }; // Version management class VersionManagement { public: - static uint32_t GetNapiVersion(Env env); - static const napi_node_version* GetNodeVersion(Env env); + static uint32_t GetNapiVersion(BasicEnv env); + static const napi_node_version* GetNodeVersion(BasicEnv env); }; #if NAPI_VERSION > 5 diff --git a/node_addon_api.gyp b/node_addon_api.gyp index 29905ed47..8c099262a 100644 --- a/node_addon_api.gyp +++ b/node_addon_api.gyp @@ -18,6 +18,16 @@ 'includes': ['except.gypi'], } }, + { + 'target_name': 'node_addon_api_except_all', + 'type': 'none', + 'sources': [ 'napi.h', 'napi-inl.h' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + 'includes': ['except.gypi'], + 'defines': [ 'NODE_ADDON_API_CPP_EXCEPTIONS_ALL' ] + } + }, { 'target_name': 'node_addon_api_maybe', 'type': 'none', diff --git a/noexcept.gypi b/noexcept.gypi index 404a05f30..83df4ddf0 100644 --- a/noexcept.gypi +++ b/noexcept.gypi @@ -1,5 +1,5 @@ { - 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], + 'defines': [ 'NODE_ADDON_API_DISABLE_CPP_EXCEPTIONS' ], 'cflags': [ '-fno-exceptions' ], 'cflags_cc': [ '-fno-exceptions' ], 'conditions': [ diff --git a/package.json b/package.json index 515c63584..f3d472a06 100644 --- a/package.json +++ b/package.json @@ -374,7 +374,6 @@ { "name": "Vladimir Morozov", "url": "https://github.com/vmoroz" - }, { "name": "WenheLI", @@ -407,6 +406,14 @@ { "name": "Caleb Hearon", "url": "https://github.com/chearon" + }, + { + "name": "Marx", + "url": "https://github.com/MarxJiao" + }, + { + "name": "Ömer AKGÜL", + "url": "https://github.com/tuhalf" } ], "description": "Node.js API (Node-API)", @@ -414,16 +421,11 @@ "benchmark": "^2.1.4", "bindings": "^1.5.0", "clang-format": "^1.4.0", - "eslint": "^7.32.0", - "eslint-config-semistandard": "^16.0.0", - "eslint-config-standard": "^16.0.3", - "eslint-plugin-import": "^2.24.2", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^5.1.0", + "eslint": "^9.13.0", "fs-extra": "^11.1.1", - "path": "^0.12.7", + "neostandard": "^0.12.0", "pre-commit": "^1.2.2", - "safe-buffer": "^5.1.1" + "semver": "^7.6.0" }, "directories": {}, "gypfile": false, @@ -455,6 +457,9 @@ "scripts": { "prebenchmark": "node-gyp rebuild -C benchmark", "benchmark": "node benchmark", + "create-coverage": "npm test --coverage", + "report-coverage-html": "rm -rf coverage-html && mkdir coverage-html && gcovr -e test --merge-mode-functions merge-use-line-max --html-nested ./coverage-html/index.html test", + "report-coverage-xml": "rm -rf coverage-xml && mkdir coverage-xml && gcovr -e test --merge-mode-functions merge-use-line-max --xml -o ./coverage-xml/coverage-cxx.xml test", "pretest": "node-gyp rebuild -C test", "test": "node test", "test:debug": "node-gyp rebuild -C test --debug && NODE_API_BUILD_CONFIG=Debug node ./test/index.js", @@ -463,13 +468,13 @@ "predev:incremental": "node-gyp configure build -C test --debug", "dev:incremental": "node test", "doc": "doxygen doc/Doxyfile", - "lint": "node tools/eslint-format && node tools/clang-format", - "lint:fix": "node tools/clang-format --fix && node tools/eslint-format --fix" + "lint": "eslint && node tools/clang-format", + "lint:fix": "eslint --fix && node tools/clang-format --fix" }, "pre-commit": "lint", - "version": "7.0.0", + "version": "8.9.0", "support": true, "engines": { - "node": "^16 || ^18 || >= 20" + "node": "^18 || ^20 || >= 21" } } diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 000000000..63ce659f2 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "node", + "pull-request-title-pattern": "chore: release v${version}", + "bootstrap-sha": "bc5acef9dd5298cbbcabd5c01c9590ada683951d", + "packages": { + ".": { + "include-component-in-tag": false, + "extra-files": [ + "README.md" + ], + "changelog-path": "CHANGELOG.md" + } + } +} diff --git a/test/addon_build/tpl/binding.gyp b/test/addon_build/tpl/binding.gyp index aa26f1acb..5b4f9f8ad 100644 --- a/test/addon_build/tpl/binding.gyp +++ b/test/addon_build/tpl/binding.gyp @@ -4,11 +4,12 @@ " { // Detach the ArrayBuffer in JavaScript. - // eslint-disable-next-line no-undef + const mem = new WebAssembly.Memory({ initial: 1 }); binding.arraybuffer.checkDetachUpdatesData(mem.buffer, () => mem.grow(1)); diff --git a/test/async_context.js b/test/async_context.js index 6a4bef662..6cf0418f6 100644 --- a/test/async_context.js +++ b/test/async_context.js @@ -73,7 +73,7 @@ async function makeCallbackWithResource (binding) { { eventName: 'init', type: 'async_context_test', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { foo: 'foo' } }, { eventName: 'before' }, @@ -95,7 +95,7 @@ async function makeCallbackWithoutResource (binding) { { eventName: 'init', type: 'async_context_no_res_test', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { } }, { eventName: 'before' }, diff --git a/test/async_progress_queue_worker.js b/test/async_progress_queue_worker.js index 3f031a415..b72a55c71 100644 --- a/test/async_progress_queue_worker.js +++ b/test/async_progress_queue_worker.js @@ -42,7 +42,7 @@ async function asyncProgressWorkerCallbackOverloads (bindingFunction) { { eventName: 'init', type: 'cbResources', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: {} }, { eventName: 'before' }, @@ -90,7 +90,7 @@ async function asyncProgressWorkerRecvOverloads (bindingFunction) { { eventName: 'init', type: asyncResName, - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: asyncResObject }, { eventName: 'before' }, @@ -131,7 +131,7 @@ async function asyncProgressWorkerNoCbOverloads (bindingFunction) { { eventName: 'init', type: asyncResName, - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: asyncResObject }, { eventName: 'before' }, diff --git a/test/async_progress_worker.js b/test/async_progress_worker.js index 78ba7b63d..d96ab64b8 100644 --- a/test/async_progress_worker.js +++ b/test/async_progress_worker.js @@ -44,7 +44,7 @@ async function asyncProgressWorkerCallbackOverloads (bindingFunction) { { eventName: 'init', type: 'cbResources', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: {} }, { eventName: 'before' }, @@ -92,7 +92,7 @@ async function asyncProgressWorkerRecvOverloads (bindingFunction) { { eventName: 'init', type: asyncResName, - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: asyncResObject }, { eventName: 'before' }, @@ -133,7 +133,7 @@ async function asyncProgressWorkerNoCbOverloads (bindingFunction) { { eventName: 'init', type: asyncResName, - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: asyncResObject }, { eventName: 'before' }, diff --git a/test/async_worker.cc b/test/async_worker.cc index 972b7b2eb..34044e9c8 100644 --- a/test/async_worker.cc +++ b/test/async_worker.cc @@ -20,14 +20,14 @@ class TestWorkerWithUserDefRecv : public AsyncWorker { static void DoWorkWithAsyncRes(const CallbackInfo& info) { Object recv = info[0].As(); Function cb = info[1].As(); - Object resource = info[2].As(); + Value resource = info[2]; TestWorkerWithUserDefRecv* worker = nullptr; if (resource == info.Env().Null()) { worker = new TestWorkerWithUserDefRecv(recv, cb, "TestResource"); } else { - worker = - new TestWorkerWithUserDefRecv(recv, cb, "TestResource", resource); + worker = new TestWorkerWithUserDefRecv( + recv, cb, "TestResource", resource.As()); } worker->Queue(); @@ -51,15 +51,17 @@ class TestWorkerWithUserDefRecv : public AsyncWorker { }; // Using default std::allocator impl, but assuming user can define their own -// alloc/dealloc methods +// allocate/deallocate methods class CustomAllocWorker : public AsyncWorker { + using Allocator = std::allocator; + public: CustomAllocWorker(Function& cb) : AsyncWorker(cb){}; static void DoWork(const CallbackInfo& info) { Function cb = info[0].As(); - std::allocator create_alloc; - CustomAllocWorker* newWorker = create_alloc.allocate(1); - create_alloc.construct(newWorker, cb); + Allocator allocator; + CustomAllocWorker* newWorker = allocator.allocate(1); + std::allocator_traits::construct(allocator, newWorker, cb); newWorker->Queue(); } @@ -67,9 +69,9 @@ class CustomAllocWorker : public AsyncWorker { void Execute() override {} void Destroy() override { assert(this->_secretVal == 24); - std::allocator deallocer; - deallocer.destroy(this); - deallocer.deallocate(this, 1); + Allocator allocator; + std::allocator_traits::destroy(allocator, this); + allocator.deallocate(this, 1); } private: @@ -108,7 +110,7 @@ class TestWorker : public AsyncWorker { : AsyncWorker(cb, resource_name, resource) {} TestWorker(Function cb, const char* resource_name) : AsyncWorker(cb, resource_name) {} - bool _succeed; + bool _succeed{}; }; class TestWorkerWithResult : public AsyncWorker { @@ -143,7 +145,7 @@ class TestWorkerWithResult : public AsyncWorker { const char* resource_name, const Object& resource) : AsyncWorker(cb, resource_name, resource) {} - bool _succeed; + bool _succeed{}; }; class TestWorkerNoCallback : public AsyncWorker { @@ -194,7 +196,7 @@ class TestWorkerNoCallback : public AsyncWorker { : AsyncWorker(env, resource_name, resource), _deferred(Napi::Promise::Deferred::New(env)) {} Promise::Deferred _deferred; - bool _succeed; + bool _succeed{}; }; class EchoWorker : public AsyncWorker { diff --git a/test/async_worker.js b/test/async_worker.js index 304a3247f..16e2a8e96 100644 --- a/test/async_worker.js +++ b/test/async_worker.js @@ -139,7 +139,7 @@ async function test (binding) { { eventName: 'init', type: 'TestResource', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { foo: 'fooBar' } }, { eventName: 'before' }, @@ -167,7 +167,7 @@ async function test (binding) { { eventName: 'init', type: 'TestResource', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { } }, { eventName: 'before' }, @@ -194,7 +194,7 @@ async function test (binding) { { eventName: 'init', type: 'TestResource', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { foo: 'foo' } }, { eventName: 'before' }, @@ -224,7 +224,7 @@ async function test (binding) { { eventName: 'init', type: 'TestResource', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { foo: 'foo' } }, { eventName: 'before' }, @@ -252,7 +252,7 @@ async function test (binding) { { eventName: 'init', type: 'TestResource', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: { foo: 'foo' } }, { eventName: 'before' }, diff --git a/test/basic_types/value.cc b/test/basic_types/value.cc index 0d19726bb..7ec3b7e04 100644 --- a/test/basic_types/value.cc +++ b/test/basic_types/value.cc @@ -128,6 +128,11 @@ static Value ToObject(const CallbackInfo& info) { return MaybeUnwrap(info[0].ToObject()); } +static Value AccessProp(const CallbackInfo& info) { + Object obj = MaybeUnwrap(info[0].ToObject()); + return obj[info[1]].AsValue(); +} + Object InitBasicTypesValue(Env env) { Object exports = Object::New(env); @@ -150,6 +155,7 @@ Object InitBasicTypesValue(Env env) { exports["toNumber"] = Function::New(env, ToNumber); exports["toString"] = Function::New(env, ToString); exports["toObject"] = Function::New(env, ToObject); + exports["accessProp"] = Function::New(env, AccessProp); exports["strictlyEquals"] = Function::New(env, StrictlyEquals); exports["strictlyEqualsOverload"] = Function::New(env, StrictEqualsOverload); diff --git a/test/basic_types/value.js b/test/basic_types/value.js index 173bcc40a..adf73d39d 100644 --- a/test/basic_types/value.js +++ b/test/basic_types/value.js @@ -117,6 +117,21 @@ function test (binding) { assert(value.assertNonEmptyReturnValOnCast()); } + function accessPropTest (value) { + const testObject = { key: '123' }; + const testSymbol = Symbol('123'); + const testNumber = 123; + const destObj = { + testObject, + testSymbol, + [testNumber]: testNumber + }; + assert.strictEqual(value.accessProp(destObj, 'testObject'), testObject); + assert.strictEqual(value.accessProp(destObj, 'testSymbol'), testSymbol); + assert.strictEqual(value.accessProp(destObj, testNumber), testNumber); + assert.strictEqual(value.accessProp(destObj, 'invalidKey'), undefined); + } + const value = binding.basic_types_value; assertValueStrictlyEqual(value); @@ -153,4 +168,6 @@ function test (binding) { assert.strictEqual(value.toString(null), 'null'); typeConverterTest(value.toObject, Object); + + accessPropTest(value); } diff --git a/test/binding.cc b/test/binding.cc index 0415c69bc..fa651cc13 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -64,6 +64,7 @@ Object InitTypedThreadSafeFunctionSum(Env env); Object InitTypedThreadSafeFunctionUnref(Env env); Object InitTypedThreadSafeFunction(Env env); #endif +Object InitSharedArrayBuffer(Env env); Object InitSymbol(Env env); Object InitTypedArray(Env env); Object InitGlobalObject(Env env); @@ -86,6 +87,7 @@ Object InitEnvMiscellaneous(Env env); #if defined(NODE_ADDON_API_ENABLE_MAYBE) Object InitMaybeCheck(Env env); #endif +Object InitFinalizerOrder(Env env); Object Init(Env env, Object exports) { #if (NAPI_VERSION > 5) @@ -139,6 +141,7 @@ Object Init(Env env, Object exports) { exports.Set("promise", InitPromise(env)); exports.Set("run_script", InitRunScript(env)); exports.Set("symbol", InitSymbol(env)); + exports.Set("sharedarraybuffer", InitSharedArrayBuffer(env)); #if (NAPI_VERSION > 3) exports.Set("threadsafe_function_ctx", InitThreadSafeFunctionCtx(env)); exports.Set("threadsafe_function_exception", @@ -186,6 +189,19 @@ Object Init(Env env, Object exports) { #if defined(NODE_ADDON_API_ENABLE_MAYBE) exports.Set("maybe_check", InitMaybeCheck(env)); #endif + + exports.Set("finalizer_order", InitFinalizerOrder(env)); + + exports.Set( + "isExperimental", + Napi::Boolean::New(env, NAPI_VERSION == NAPI_VERSION_EXPERIMENTAL)); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, true)); +#else + exports.Set("hasSharedArrayBuffer", Napi::Boolean::New(env, false)); +#endif + return exports; } diff --git a/test/binding.gyp b/test/binding.gyp index 08c51885b..9ff334b64 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -30,6 +30,7 @@ 'error.cc', 'error_handling_for_primitives.cc', 'external.cc', + 'finalizer_order.cc', 'function.cc', 'function_reference.cc', 'handlescope.cc', @@ -53,6 +54,7 @@ 'object/subscript_operator.cc', 'promise.cc', 'run_script.cc', + 'shared_array_buffer.cc', 'symbol.cc', 'threadsafe_function/threadsafe_function_ctx.cc', 'threadsafe_function/threadsafe_function_exception.cc', @@ -84,21 +86,42 @@ 'binding-swallowexcept.cc', 'error.cc', ], + 'build_sources_except_all': [ + 'except_all.cc', + ], 'build_sources_type_check': [ 'value_type_cast.cc' ], + 'want_coverage': '@(build_sources)'] + 'sources': ['>@(build_sources)'], + 'defines': ['NODE_ADDON_API_ENABLE_TYPE_CHECK_ON_AS'] + }, + { + 'target_name': 'binding_except_all', + 'dependencies': ['../node_addon_api.gyp:node_addon_api_except_all'], + 'sources': [ '>@(build_sources_except_all)'] }, { 'target_name': 'binding_noexcept', diff --git a/test/buffer.js b/test/buffer.js index 8b190b79f..3f49201a7 100644 --- a/test/buffer.js +++ b/test/buffer.js @@ -2,7 +2,6 @@ const assert = require('assert'); const testUtil = require('./testUtil'); -const safeBuffer = require('safe-buffer'); module.exports = require('./common').runTest(test); @@ -14,7 +13,7 @@ function test (binding) { binding.buffer.checkBuffer(test); assert.ok(test instanceof Buffer); - const test2 = safeBuffer.Buffer.alloc(test.length); + const test2 = Buffer.alloc(test.length); test.copy(test2); binding.buffer.checkBuffer(test2); }, diff --git a/test/common/index.js b/test/common/index.js index ae0350ec7..2469151ef 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -142,7 +142,7 @@ async function checkBuildType (buildType) { async function whichBuildType () { let buildType = 'Release'; - const envBuildType = process.env.NODE_API_BUILD_CONFIG; + const envBuildType = process.env.NODE_API_BUILD_CONFIG || (process.env.npm_config_debug === 'true' ? 'Debug' : 'Release'); if (envBuildType) { if (Object.values(buildTypes).includes(envBuildType)) { if (await checkBuildType(envBuildType)) { diff --git a/test/dataview/dataview.cc b/test/dataview/dataview.cc index f055d95f1..cbd5933e7 100644 --- a/test/dataview/dataview.cc +++ b/test/dataview/dataview.cc @@ -2,24 +2,51 @@ using namespace Napi; -static Value CreateDataView1(const CallbackInfo& info) { +static Value CreateDataView(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); return DataView::New(info.Env(), arrayBuffer); } -static Value CreateDataView2(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffset(const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset); } -static Value CreateDataView3(const CallbackInfo& info) { +static Value CreateDataViewWithByteOffsetAndByteLength( + const CallbackInfo& info) { ArrayBuffer arrayBuffer = info[0].As(); size_t byteOffset = info[1].As().Uint32Value(); size_t byteLength = info[2].As().Uint32Value(); return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +static Value CreateDataViewOnSharedArrayBuffer(const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + return DataView::New(info.Env(), arrayBuffer); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffset( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset); +} + +static Value CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength( + const CallbackInfo& info) { + SharedArrayBuffer arrayBuffer = info[0].As(); + size_t byteOffset = info[1].As().Uint32Value(); + size_t byteLength = info[2].As().Uint32Value(); + return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); +} +#endif + +static Value GetBuffer(const CallbackInfo& info) { + return info[0].As().Buffer(); +} + static Value GetArrayBuffer(const CallbackInfo& info) { return info[0].As().ArrayBuffer(); } @@ -37,10 +64,24 @@ static Value GetByteLength(const CallbackInfo& info) { Object InitDataView(Env env) { Object exports = Object::New(env); - exports["createDataView1"] = Function::New(env, CreateDataView1); - exports["createDataView2"] = Function::New(env, CreateDataView2); - exports["createDataView3"] = Function::New(env, CreateDataView3); + exports["createDataView"] = Function::New(env, CreateDataView); + exports["createDataViewWithByteOffset"] = + Function::New(env, CreateDataViewWithByteOffset); + exports["createDataViewWithByteOffsetAndByteLength"] = + Function::New(env, CreateDataViewWithByteOffsetAndByteLength); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createDataViewOnSharedArrayBuffer"] = + Function::New(env, CreateDataViewOnSharedArrayBuffer); + exports["createDataViewOnSharedArrayBufferWithByteOffset"] = + Function::New(env, CreateDataViewOnSharedArrayBufferWithByteOffset); + exports["createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength"] = + Function::New( + env, CreateDataViewOnSharedArrayBufferWithByteOffsetAndByteLength); +#endif + exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer); + exports["getBuffer"] = Function::New(env, GetBuffer); exports["getByteOffset"] = Function::New(env, GetByteOffset); exports["getByteLength"] = Function::New(env, GetByteLength); diff --git a/test/dataview/dataview.js b/test/dataview/dataview.js index 595612856..5916f6b90 100644 --- a/test/dataview/dataview.js +++ b/test/dataview/dataview.js @@ -3,12 +3,19 @@ const assert = require('assert'); module.exports = require('../common').runTest(test); +let runSharedArrayBufferTests = true; + function test (binding) { function testDataViewCreation (factory, arrayBuffer, offset, length) { const view = factory(arrayBuffer, offset, length); offset = offset || 0; - assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); - assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + if (arrayBuffer instanceof ArrayBuffer) { + assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); + assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); + } else { + assert.ok(dataview.getBuffer(view) instanceof SharedArrayBuffer); + assert.strictEqual(dataview.getBuffer(view), arrayBuffer); + } assert.strictEqual(dataview.getByteOffset(view), offset); assert.strictEqual(dataview.getByteLength(view), length || arrayBuffer.byteLength - offset); @@ -20,16 +27,48 @@ function test (binding) { }, RangeError); } - const dataview = binding.dataview; - const arrayBuffer = new ArrayBuffer(10); + const { hasSharedArrayBuffer, dataview } = binding; + + { + const arrayBuffer = new ArrayBuffer(10); + + testDataViewCreation(dataview.createDataView, arrayBuffer); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 2); + testDataViewCreation(dataview.createDataViewWithByteOffset, arrayBuffer, 10); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 2, 4); + testDataViewCreation(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 10, 0); + + testInvalidRange(dataview.createDataViewWithByteOffset, arrayBuffer, 11); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 11, 0); + testInvalidRange(dataview.createDataViewWithByteOffsetAndByteLength, arrayBuffer, 6, 5); + } + + if (hasSharedArrayBuffer && runSharedArrayBufferTests) { + const sab = new SharedArrayBuffer(10); - testDataViewCreation(dataview.createDataView1, arrayBuffer); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 2); - testDataViewCreation(dataview.createDataView2, arrayBuffer, 10); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4); - testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0); + try { + testDataViewCreation(dataview.createDataViewOnSharedArrayBuffer, sab); + } catch (ex) { + // The `napi_create_dataview` API does not have a valid `#define` + // preprocessor guard for SharedArrayBuffer support, so it is + // possible that the API is present but creating a DataView on + // SharedArrayBuffer is not supported in the current version of Node.js. + // In that case, we should skip the test instead of throwing. + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating DataViews on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } - testInvalidRange(dataview.createDataView2, arrayBuffer, 11); - testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0); - testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5); + throw ex; + } + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 2); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 10); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 2, 4); + testDataViewCreation(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 10, 0); + + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffset, sab, 11); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 11, 0); + testInvalidRange(dataview.createDataViewOnSharedArrayBufferWithByteOffsetAndByteLength, sab, 6, 5); + } } diff --git a/test/date.cc b/test/date.cc index efd433af4..a446bde22 100644 --- a/test/date.cc +++ b/test/date.cc @@ -11,6 +11,11 @@ Value CreateDate(const CallbackInfo& info) { return Date::New(info.Env(), input); } +Value CreateDateFromTimePoint(const CallbackInfo& info) { + auto input = std::chrono::system_clock::time_point{}; + return Date::New(info.Env(), input); +} + Value IsDate(const CallbackInfo& info) { Date input = info[0].As(); @@ -35,6 +40,8 @@ Value OperatorValue(const CallbackInfo& info) { Object InitDate(Env env) { Object exports = Object::New(env); exports["CreateDate"] = Function::New(env, CreateDate); + exports["CreateDateFromTimePoint"] = + Function::New(env, CreateDateFromTimePoint); exports["IsDate"] = Function::New(env, IsDate); exports["ValueOf"] = Function::New(env, ValueOf); exports["OperatorValue"] = Function::New(env, OperatorValue); diff --git a/test/date.js b/test/date.js index 86c8af6ac..588b741b1 100644 --- a/test/date.js +++ b/test/date.js @@ -9,9 +9,11 @@ function test (binding) { CreateDate, IsDate, ValueOf, - OperatorValue + OperatorValue, + CreateDateFromTimePoint } = binding.date; assert.deepStrictEqual(CreateDate(0), new Date(0)); + assert.deepStrictEqual(CreateDateFromTimePoint(), new Date(0)); assert.strictEqual(IsDate(new Date(0)), true); assert.strictEqual(ValueOf(new Date(42)), 42); assert.strictEqual(OperatorValue(new Date(42)), true); diff --git a/test/except_all.cc b/test/except_all.cc new file mode 100644 index 000000000..e2c230b21 --- /dev/null +++ b/test/except_all.cc @@ -0,0 +1,22 @@ +#include +#include "napi.h" + +using namespace Napi; + +void ThrowStdException(const CallbackInfo& info) { + std::string message = info[0].As().Utf8Value(); + throw std::runtime_error(message); +} + +void ThrowPrimitiveException(const CallbackInfo&) { + throw 0; +} + +Object Init(Env env, Object exports) { + exports.Set("throwStdException", Napi::Function::New(env, ThrowStdException)); + exports.Set("throwPrimitiveException", + Napi::Function::New(env, ThrowPrimitiveException)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/test/except_all.js b/test/except_all.js new file mode 100644 index 000000000..d650ece6f --- /dev/null +++ b/test/except_all.js @@ -0,0 +1,14 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTestWithBuildType(test); + +function test (buildType) { + const binding = require(`./build/${buildType}/binding_except_all.node`); + + const message = 'error message'; + assert.throws(binding.throwStdException.bind(undefined, message), { message }); + + assert.throws(binding.throwPrimitiveException.bind(undefined), { message: 'A native exception was thrown' }); +} diff --git a/test/exports.js b/test/exports.js new file mode 100644 index 000000000..1aa39281c --- /dev/null +++ b/test/exports.js @@ -0,0 +1,19 @@ +'use strict'; + +const { strictEqual } = require('assert'); +const { valid } = require('semver'); + +const nodeAddonApi = require('../'); + +module.exports = function test () { + strictEqual(nodeAddonApi.include.startsWith('"'), true); + strictEqual(nodeAddonApi.include.endsWith('"'), true); + strictEqual(nodeAddonApi.include.includes('node-addon-api'), true); + strictEqual(nodeAddonApi.include_dir, ''); + strictEqual(nodeAddonApi.gyp, 'node_api.gyp:nothing'); + strictEqual(nodeAddonApi.targets, 'node_addon_api.gyp'); + strictEqual(valid(nodeAddonApi.version), true); + strictEqual(nodeAddonApi.version, require('../package.json').version); + strictEqual(nodeAddonApi.isNodeApiBuiltin, true); + strictEqual(nodeAddonApi.needsFlag, false); +}; diff --git a/test/finalizer_order.cc b/test/finalizer_order.cc new file mode 100644 index 000000000..0767ced70 --- /dev/null +++ b/test/finalizer_order.cc @@ -0,0 +1,152 @@ +#include + +namespace { +class Test : public Napi::ObjectWrap { + public: + Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + basicFinalizerCalled = false; + finalizerCalled = false; + + if (info.Length() > 0) { + finalizeCb_ = Napi::Persistent(info[0].As()); + } + } + + static void Initialize(Napi::Env env, Napi::Object exports) { + exports.Set("Test", + DefineClass(env, + "Test", + { + StaticAccessor("isBasicFinalizerCalled", + &IsBasicFinalizerCalled, + nullptr, + napi_default), + StaticAccessor("isFinalizerCalled", + &IsFinalizerCalled, + nullptr, + napi_default), + })); + } + + void Finalize(Napi::BasicEnv /*env*/) { basicFinalizerCalled = true; } + + void Finalize(Napi::Env /*env*/) { + finalizerCalled = true; + if (!finalizeCb_.IsEmpty()) { + finalizeCb_.Call({}); + } + } + + static Napi::Value IsBasicFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), basicFinalizerCalled); + } + + static Napi::Value IsFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), finalizerCalled); + } + + private: + Napi::FunctionReference finalizeCb_; + + static bool basicFinalizerCalled; + static bool finalizerCalled; +}; + +bool Test::basicFinalizerCalled = false; +bool Test::finalizerCalled = false; + +bool externalBasicFinalizerCalled = false; +bool externalFinalizerCalled = false; + +Napi::Value CreateExternalBasicFinalizer(const Napi::CallbackInfo& info) { + externalBasicFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::BasicEnv /*env*/, int* data) { + externalBasicFinalizerCalled = true; + delete data; + }); +} + +Napi::Value CreateExternalFinalizer(const Napi::CallbackInfo& info) { + externalFinalizerCalled = false; + return Napi::External::New( + info.Env(), new int(1), [](Napi::Env /*env*/, int* data) { + externalFinalizerCalled = true; + delete data; + }); +} + +Napi::Value isExternalBasicFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalBasicFinalizerCalled); +} + +Napi::Value IsExternalFinalizerCalled(const Napi::CallbackInfo& info) { + return Napi::Boolean::New(info.Env(), externalFinalizerCalled); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER +Napi::Value PostFinalizer(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer([callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/) { callback.Call({}); }); + + return env.Undefined(); +} + +Napi::Value PostFinalizerWithData(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer( + [callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/, Napi::Reference* data) { + callback.Call({data->Value()}); + delete data; + }, + new Napi::Reference(Napi::Persistent(info[1]))); + + return env.Undefined(); +} + +Napi::Value PostFinalizerWithDataAndHint(const Napi::CallbackInfo& info) { + auto env = info.Env(); + + env.PostFinalizer( + [callback = Napi::Persistent(info[0].As())]( + Napi::Env /*env*/, + Napi::Reference* data, + Napi::Reference* hint) { + callback.Call({data->Value(), hint->Value()}); + delete data; + delete hint; + }, + new Napi::Reference(Napi::Persistent(info[1])), + new Napi::Reference(Napi::Persistent(info[2]))); + + return env.Undefined(); +} +#endif + +} // namespace + +Napi::Object InitFinalizerOrder(Napi::Env env) { + Napi::Object exports = Napi::Object::New(env); + Test::Initialize(env, exports); + exports["createExternalBasicFinalizer"] = + Napi::Function::New(env, CreateExternalBasicFinalizer); + exports["createExternalFinalizer"] = + Napi::Function::New(env, CreateExternalFinalizer); + exports["isExternalBasicFinalizerCalled"] = + Napi::Function::New(env, isExternalBasicFinalizerCalled); + exports["isExternalFinalizerCalled"] = + Napi::Function::New(env, IsExternalFinalizerCalled); + +#ifdef NODE_API_EXPERIMENTAL_HAS_POST_FINALIZER + exports["PostFinalizer"] = Napi::Function::New(env, PostFinalizer); + exports["PostFinalizerWithData"] = + Napi::Function::New(env, PostFinalizerWithData); + exports["PostFinalizerWithDataAndHint"] = + Napi::Function::New(env, PostFinalizerWithDataAndHint); +#endif + return exports; +} diff --git a/test/finalizer_order.js b/test/finalizer_order.js new file mode 100644 index 000000000..4b267a0d0 --- /dev/null +++ b/test/finalizer_order.js @@ -0,0 +1,98 @@ +'use strict'; + +/* eslint-disable no-unused-vars */ + +const assert = require('assert'); +const common = require('./common'); +const testUtil = require('./testUtil'); + +module.exports = require('./common').runTest(test); + +function test (binding) { + const { isExperimental } = binding; + + let isCallbackCalled = false; + + const tests = [ + 'Finalizer Order - ObjectWrap', + () => { + let test = new binding.finalizer_order.Test(() => { isCallbackCalled = true; }); + test = null; + + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, false, 'Expected basic finalizer to not be called [before ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, false, 'Expected (extended) finalizer to not be called [before ticking]'); + assert.strictEqual(isCallbackCalled, false, 'Expected callback to not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.Test.isBasicFinalizerCalled, true, 'Expected basic finalizer to be called [after ticking]'); + assert.strictEqual(binding.finalizer_order.Test.isFinalizerCalled, true, 'Expected (extended) finalizer to be called [after ticking]'); + assert.strictEqual(isCallbackCalled, true, 'Expected callback to be called [after ticking]'); + }, + + 'Finalizer Order - External with Basic Finalizer', + () => { + let ext = binding.finalizer_order.createExternalBasicFinalizer(); + ext = null; + global.gc(); + + if (isExperimental) { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [before ticking]'); + } else { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), false, 'Expected External basic finalizer to not be called [before ticking]'); + } + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalBasicFinalizerCalled(), true, 'Expected External basic finalizer to be called [after ticking]'); + }, + + 'Finalizer Order - External with Finalizer', + () => { + let ext = binding.finalizer_order.createExternalFinalizer(); + ext = null; + global.gc(); + assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), false, 'Expected External extended finalizer to not be called [before ticking]'); + }, + () => { + assert.strictEqual(binding.finalizer_order.isExternalFinalizerCalled(), true, 'Expected External extended finalizer to be called [after ticking]'); + } + ]; + + if (binding.isExperimental) { + tests.push(...[ + 'PostFinalizer', + () => { + binding.finalizer_order.PostFinalizer(common.mustCall()); + }, + + 'PostFinalizerWithData', + () => { + const data = {}; + const callback = (callbackData) => { + assert.strictEqual(callbackData, data); + }; + binding.finalizer_order.PostFinalizerWithData(common.mustCall(callback), data); + }, + + 'PostFinalizerWithDataAndHint', + () => { + const data = {}; + const hint = {}; + const callback = (callbackData, callbackHint) => { + assert.strictEqual(callbackData, data); + assert.strictEqual(callbackHint, hint); + }; + binding.finalizer_order.PostFinalizerWithDataAndHint(common.mustCall(callback), data, hint); + } + ]); + } + + return testUtil.runGCTests(tests); +} diff --git a/test/function.js b/test/function.js index 04b020394..c5514db36 100644 --- a/test/function.js +++ b/test/function.js @@ -18,7 +18,7 @@ function test (binding) { assert.deepStrictEqual(binding.valueCallback(), { foo: 'bar' }); - /* eslint-disable-next-line no-new, new-cap */ + /* eslint-disable-next-line new-cap */ assert.strictEqual(new binding.newTargetCallback(), binding.newTargetCallback); assert.strictEqual(binding.newTargetCallback(), undefined); @@ -89,7 +89,7 @@ function test (binding) { assert.deepStrictEqual(args, [7, 8, 9]); assert.throws(() => { - binding.callWithInvalidReceiver(); + binding.callWithInvalidReceiver(() => {}); }, /Invalid (pointer passed as )?argument/); obj = binding.callConstructorWithArgs(testConstructor, 5, 6, 7); diff --git a/test/function_reference.cc b/test/function_reference.cc index 99f560f27..b50eb46fd 100644 --- a/test/function_reference.cc +++ b/test/function_reference.cc @@ -30,17 +30,17 @@ class FuncRefObject : public Napi::ObjectWrap { namespace { Value ConstructRefFromExisitingRef(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; FunctionReference movedRef; ref.Reset(info[0].As()); movedRef = std::move(ref); - return MaybeUnwrap(movedRef({})); + return scope.Escape(MaybeUnwrap(movedRef({}))); } Value CallWithVectorArgs(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); std::vector newVec; FunctionReference ref; ref.Reset(info[0].As()); @@ -48,27 +48,28 @@ Value CallWithVectorArgs(const CallbackInfo& info) { for (int i = 1; i < (int)info.Length(); i++) { newVec.push_back(info[i]); } - return MaybeUnwrap(ref.Call(newVec)); + return scope.Escape(MaybeUnwrap(ref.Call(newVec))); } Value CallWithInitList(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrap(ref.Call({info[1], info[2], info[3]})); + return scope.Escape(MaybeUnwrap(ref.Call({info[1], info[2], info[3]}))); } Value CallWithRecvInitList(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrap(ref.Call(info[1], {info[2], info[3], info[4]})); + return scope.Escape( + MaybeUnwrap(ref.Call(info[1], {info[2], info[3], info[4]}))); } Value CallWithRecvVector(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; std::vector newVec; ref.Reset(info[0].As()); @@ -76,11 +77,11 @@ Value CallWithRecvVector(const CallbackInfo& info) { for (int i = 2; i < (int)info.Length(); i++) { newVec.push_back(info[i]); } - return MaybeUnwrap(ref.Call(info[1], newVec)); + return scope.Escape(MaybeUnwrap(ref.Call(info[1], newVec))); } Value CallWithRecvArgc(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; ref.Reset(info[0].As()); @@ -91,7 +92,7 @@ Value CallWithRecvArgc(const CallbackInfo& info) { args[i] = info[i + 2]; } - return MaybeUnwrap(ref.Call(info[1], argLength, args.get())); + return scope.Escape(MaybeUnwrap(ref.Call(info[1], argLength, args.get()))); } Value MakeAsyncCallbackWithInitList(const Napi::CallbackInfo& info) { @@ -163,19 +164,19 @@ Value CreateFunctionReferenceUsingNewVec(const Napi::CallbackInfo& info) { } Value Call(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrapOr(ref.Call({}), Value()); + return scope.Escape(MaybeUnwrapOr(ref.Call({}), Value())); } Value Construct(const CallbackInfo& info) { - HandleScope scope(info.Env()); + EscapableHandleScope scope(info.Env()); FunctionReference ref; ref.Reset(info[0].As()); - return MaybeUnwrapOr(ref.New({}), Object()); + return scope.Escape(MaybeUnwrapOr(ref.New({}), Object())); } } // namespace diff --git a/test/function_reference.js b/test/function_reference.js index 84263fce9..e69fc8e5b 100644 --- a/test/function_reference.js +++ b/test/function_reference.js @@ -95,7 +95,7 @@ async function canCallAsyncFunctionWithDifferentOverloads (binding) { { eventName: 'init', type: 'func_ref_resources', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: {} }, { eventName: 'before' }, @@ -113,7 +113,7 @@ async function canCallAsyncFunctionWithDifferentOverloads (binding) { { eventName: 'init', type: 'func_ref_resources', - triggerAsyncId: triggerAsyncId, + triggerAsyncId, resource: {} }, { eventName: 'before' }, diff --git a/test/globalObject/global_object_delete_property.cc b/test/globalObject/global_object_delete_property.cc index 295ed3f36..70738c2f4 100644 --- a/test/globalObject/global_object_delete_property.cc +++ b/test/globalObject/global_object_delete_property.cc @@ -5,27 +5,27 @@ using namespace Napi; Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); return Boolean::New( info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value().c_str()))); } Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value()))); } Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Number key = info[0].As(); + Number key = info[0].UnsafeAs(); return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Name key = info[0].As(); + Name key = info[0].UnsafeAs(); return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key))); } diff --git a/test/globalObject/global_object_get_property.cc b/test/globalObject/global_object_get_property.cc index dd112043c..81f727d91 100644 --- a/test/globalObject/global_object_get_property.cc +++ b/test/globalObject/global_object_get_property.cc @@ -5,25 +5,25 @@ using namespace Napi; Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Name key = info[0].As(); + Name key = info[0].UnsafeAs(); return MaybeUnwrap(globalObject.Get(key)); } Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Number key = info[0].As(); + Number key = info[0].UnsafeAs(); return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); } Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String cStrkey = info[0].As(); + String cStrkey = info[0].UnsafeAs(); return MaybeUnwrapOr(globalObject.Get(cStrkey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String cppStrKey = info[0].As(); + String cppStrKey = info[0].UnsafeAs(); return MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); } diff --git a/test/globalObject/global_object_has_own_property.cc b/test/globalObject/global_object_has_own_property.cc index 89c299913..388788d97 100644 --- a/test/globalObject/global_object_has_own_property.cc +++ b/test/globalObject/global_object_has_own_property.cc @@ -5,7 +5,7 @@ using namespace Napi; Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); return Boolean::New( info.Env(), MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()), @@ -14,7 +14,7 @@ Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); return Boolean::New( info.Env(), MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()), false)); @@ -22,7 +22,7 @@ Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Name key = info[0].As(); + Name key = info[0].UnsafeAs(); return Boolean::New(info.Env(), MaybeUnwrap(globalObject.HasOwnProperty(key))); } diff --git a/test/globalObject/global_object_set_property.cc b/test/globalObject/global_object_set_property.cc index 7065bee56..06da6315a 100644 --- a/test/globalObject/global_object_set_property.cc +++ b/test/globalObject/global_object_set_property.cc @@ -4,28 +4,28 @@ using namespace Napi; void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); Value value = info[1]; globalObject.Set(key.Utf8Value().c_str(), value); } void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - String key = info[0].As(); + String key = info[0].UnsafeAs(); Value value = info[1]; globalObject.Set(key.Utf8Value(), value); } void SetPropertyWithInt32AsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Number key = info[0].As(); + Number key = info[0].UnsafeAs(); Value value = info[1]; globalObject.Set(key.Uint32Value(), value); } void SetPropertyWithNapiValueAsKey(const CallbackInfo& info) { Object globalObject = info.Env().Global(); - Name key = info[0].As(); + Name key = info[0].UnsafeAs(); Value value = info[1]; globalObject.Set(key, value); -} \ No newline at end of file +} diff --git a/test/name.cc b/test/name.cc index d8556e154..d94a3937f 100644 --- a/test/name.cc +++ b/test/name.cc @@ -1,5 +1,7 @@ #include "napi.h" +#include + using namespace Napi; const char* testValueUtf8 = "123456789"; @@ -21,19 +23,21 @@ Value EchoString(const CallbackInfo& info) { Value CreateString(const CallbackInfo& info) { String encoding = info[0].As(); - Number length = info[1].As(); + Value length = info[1]; if (encoding.Utf8Value() == "utf8") { if (length.IsUndefined()) { return String::New(info.Env(), testValueUtf8); } else { - return String::New(info.Env(), testValueUtf8, length.Uint32Value()); + return String::New( + info.Env(), testValueUtf8, length.As().Uint32Value()); } } else if (encoding.Utf8Value() == "utf16") { if (length.IsUndefined()) { return String::New(info.Env(), testValueUtf16); } else { - return String::New(info.Env(), testValueUtf16, length.Uint32Value()); + return String::New( + info.Env(), testValueUtf16, length.As().Uint32Value()); } } else { Error::New(info.Env(), "Invalid encoding.").ThrowAsJavaScriptException(); @@ -41,15 +45,19 @@ Value CreateString(const CallbackInfo& info) { } } +Value CreateStringFromStringView(const CallbackInfo& info) { + return String::New(info.Env(), std::string_view("hello1")); +} + Value CheckString(const CallbackInfo& info) { String value = info[0].As(); String encoding = info[1].As(); - Number length = info[2].As(); + Value length = info[2]; if (encoding.Utf8Value() == "utf8") { std::string testValue = testValueUtf8; if (!length.IsUndefined()) { - testValue = testValue.substr(0, length.Uint32Value()); + testValue = testValue.substr(0, length.As().Uint32Value()); } std::string stringValue = value; @@ -57,7 +65,7 @@ Value CheckString(const CallbackInfo& info) { } else if (encoding.Utf8Value() == "utf16") { std::u16string testValue = testValueUtf16; if (!length.IsUndefined()) { - testValue = testValue.substr(0, length.Uint32Value()); + testValue = testValue.substr(0, length.As().Uint32Value()); } std::u16string stringValue = value; @@ -69,15 +77,19 @@ Value CheckString(const CallbackInfo& info) { } Value CreateSymbol(const CallbackInfo& info) { - String description = info[0].As(); + Value description = info[0]; if (!description.IsUndefined()) { - return Symbol::New(info.Env(), description); + return Symbol::New(info.Env(), description.As()); } else { return Symbol::New(info.Env()); } } +Value CreateSymbolFromStringView(const CallbackInfo& info) { + return Symbol::New(info.Env(), std::string_view("hello2")); +} + Value CheckSymbol(const CallbackInfo& info) { return Boolean::New(info.Env(), info[0].Type() == napi_symbol); } @@ -97,11 +109,15 @@ Object InitName(Env env) { exports["echoString"] = Function::New(env, EchoString); exports["createString"] = Function::New(env, CreateString); + exports["createStringFromStringView"] = + Function::New(env, CreateStringFromStringView); exports["nullStringShouldThrow"] = Function::New(env, NullStringShouldThrow); exports["nullString16ShouldThrow"] = Function::New(env, NullString16ShouldThrow); exports["checkString"] = Function::New(env, CheckString); exports["createSymbol"] = Function::New(env, CreateSymbol); + exports["createSymbolFromStringView"] = + Function::New(env, CreateSymbolFromStringView); exports["checkSymbol"] = Function::New(env, CheckSymbol); return exports; diff --git a/test/name.js b/test/name.js index 406c533e3..8113565c8 100644 --- a/test/name.js +++ b/test/name.js @@ -56,4 +56,9 @@ function test (binding) { assert.strictEqual(binding.name.echoString(str, 'utf8'), str); assert.strictEqual(binding.name.echoString(str, 'utf16'), str); } + + assert.strictEqual(binding.name.createStringFromStringView(), 'hello1'); + const symFromStringView = binding.name.createSymbolFromStringView(); + assert.strictEqual(typeof symFromStringView, 'symbol'); + assert.strictEqual(symFromStringView.description, 'hello2'); } diff --git a/test/object/delete_property.cc b/test/object/delete_property.cc index ca69e5387..b05af20cc 100644 --- a/test/object/delete_property.cc +++ b/test/object/delete_property.cc @@ -4,13 +4,13 @@ using namespace Napi; Value DeletePropertyWithUint32(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Number key = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrap(obj.Delete(key.Uint32Value()))); } Value DeletePropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New( info.Env(), @@ -18,20 +18,20 @@ Value DeletePropertyWithNapiValue(const CallbackInfo& info) { } Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(key), false)); } Value DeletePropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New( info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value().c_str()), false)); } Value DeletePropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value()), false)); diff --git a/test/object/get_property.cc b/test/object/get_property.cc index 523f99199..2791ad2aa 100644 --- a/test/object/get_property.cc +++ b/test/object/get_property.cc @@ -4,31 +4,31 @@ using namespace Napi; Value GetPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return MaybeUnwrapOr(obj.Get(static_cast(key)), Value()); } Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return MaybeUnwrapOr(obj.Get(key), Value()); } Value GetPropertyWithUint32(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Number key = info[1].As(); return MaybeUnwrap(obj.Get(key.Uint32Value())); } Value GetPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value().c_str()), Value()); } Value GetPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value()), Value()); } diff --git a/test/object/has_own_property.cc b/test/object/has_own_property.cc index d7fbde98b..b566fefbb 100644 --- a/test/object/has_own_property.cc +++ b/test/object/has_own_property.cc @@ -4,7 +4,7 @@ using namespace Napi; Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New( info.Env(), @@ -12,14 +12,14 @@ Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { } Value HasOwnPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(key), false)); } Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New( info.Env(), @@ -27,7 +27,7 @@ Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { } Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New( info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value()), false)); diff --git a/test/object/has_property.cc b/test/object/has_property.cc index fa410833f..46c13de30 100644 --- a/test/object/has_property.cc +++ b/test/object/has_property.cc @@ -4,34 +4,34 @@ using namespace Napi; Value HasPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New( info.Env(), MaybeUnwrapOr(obj.Has(static_cast(key)), false)); } Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(key), false)); } Value HasPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()), false)); } Value HasPropertyWithUint32(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Number jsKey = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(jsKey.Uint32Value()), false)); } Value HasPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(jsKey.Utf8Value()), false)); diff --git a/test/object/object.cc b/test/object/object.cc index f6f0bd98b..60aae768f 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -338,11 +338,24 @@ void Increment(const CallbackInfo& info) { #endif // NAPI_CPP_EXCEPTIONS Value InstanceOf(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Function constructor = info[1].As(); return Boolean::New(info.Env(), MaybeUnwrap(obj.InstanceOf(constructor))); } +Value GetPrototype(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + return MaybeUnwrap(obj.GetPrototype()); +} + +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE +Value SetPrototype(const CallbackInfo& info) { + Object obj = info[0].UnsafeAs(); + Object prototype = info[1].UnsafeAs(); + return Boolean::New(info.Env(), MaybeUnwrap(obj.SetPrototype(prototype))); +} +#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + Object InitObject(Env env) { Object exports = Object::New(env); @@ -426,5 +439,10 @@ Object InitObject(Env env) { Function::New(env, SubscriptSetWithCppStyleString); exports["subscriptSetAtIndex"] = Function::New(env, SubscriptSetAtIndex); + exports["getPrototype"] = Function::New(env, GetPrototype); +#ifdef NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + exports["setPrototype"] = Function::New(env, SetPrototype); +#endif // NODE_API_EXPERIMENTAL_HAS_SET_PROTOTYPE + return exports; } diff --git a/test/object/object.js b/test/object/object.js index 6cd9735e9..13488940c 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -215,4 +215,16 @@ function test (binding) { c: 3 }); } + + for (const prototype of [null, {}, Object.prototype]) { + const obj = Object.create(prototype); + assert.strictEqual(binding.object.getPrototype(obj), prototype); + } + + if ('setPrototype' in binding.object) { + const prototype = {}; + const obj = Object.create(null); + assert.strictEqual(binding.object.setPrototype(obj, prototype), true); + assert.strictEqual(Object.getPrototypeOf(obj), prototype); + } } diff --git a/test/object/set_property.cc b/test/object/set_property.cc index 8171568d6..da8c93bbd 100644 --- a/test/object/set_property.cc +++ b/test/object/set_property.cc @@ -4,7 +4,7 @@ using namespace Napi; Value SetPropertyWithNapiValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); Value value = info[2]; return Boolean::New( @@ -13,14 +13,14 @@ Value SetPropertyWithNapiValue(const CallbackInfo& info) { } Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Name key = info[1].As(); Value value = info[2]; return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Set(key, value), false)); } Value SetPropertyWithUint32(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); Number key = info[1].As(); Value value = info[2]; return Boolean::New(info.Env(), @@ -28,7 +28,7 @@ Value SetPropertyWithUint32(const CallbackInfo& info) { } Value SetPropertyWithCStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); Value value = info[2]; return Boolean::New( @@ -37,7 +37,7 @@ Value SetPropertyWithCStyleString(const CallbackInfo& info) { } Value SetPropertyWithCppStyleString(const CallbackInfo& info) { - Object obj = info[0].As(); + Object obj = info[0].UnsafeAs(); String jsKey = info[1].As(); Value value = info[2]; return Boolean::New(info.Env(), diff --git a/test/object/subscript_operator.cc b/test/object/subscript_operator.cc index 7f94b2dd3..15bb74620 100644 --- a/test/object/subscript_operator.cc +++ b/test/object/subscript_operator.cc @@ -8,7 +8,7 @@ Value SubscriptGetWithCStyleString(const CallbackInfo& info) { // make sure const case compiles const Object obj2 = info[0].As(); - MaybeUnwrap(obj2[jsKey.Utf8Value().c_str()]).As(); + MaybeUnwrap(obj2[jsKey.Utf8Value().c_str()]).As(); Object obj = info[0].As(); return obj[jsKey.Utf8Value().c_str()]; @@ -19,7 +19,7 @@ Value SubscriptGetWithCppStyleString(const CallbackInfo& info) { // make sure const case compiles const Object obj2 = info[0].As(); - MaybeUnwrap(obj2[jsKey.Utf8Value()]).As(); + MaybeUnwrap(obj2[jsKey.Utf8Value()]).As(); Object obj = info[0].As(); return obj[jsKey.Utf8Value()]; @@ -30,7 +30,7 @@ Value SubscriptGetAtIndex(const CallbackInfo& info) { // make sure const case compiles const Object obj2 = info[0].As(); - MaybeUnwrap(obj2[index]).As(); + MaybeUnwrap(obj2[index]).As(); Object obj = info[0].As(); return obj[index]; diff --git a/test/object_reference.cc b/test/object_reference.cc index 2d3fc4807..691b5f63d 100644 --- a/test/object_reference.cc +++ b/test/object_reference.cc @@ -28,6 +28,25 @@ ObjectReference casted_reference; enum VAL_TYPES { JS = 0, C_STR, CPP_STR, BOOL, INT, DOUBLE, JS_CAST }; +// Test that Set() with std::string key and value accepts temporaries (rvalues). +// This verifies that the parameter is `const std::string&` rather than +// `std::string&`. +void SetWithTempString(const Napi::CallbackInfo& info) { + Env env = info.Env(); + HandleScope scope(env); + + Napi::ObjectReference ref = Persistent(Object::New(env)); + ref.SuppressDestruct(); + + ref.Set(std::string("tempKey"), std::string("tempValue")); + ref.Set(std::string("anotherKey"), info[0].As().Utf8Value()); + + assert(MaybeUnwrap(ref.Get("tempKey")).As().Utf8Value() == + "tempValue"); + assert(MaybeUnwrap(ref.Get("anotherKey")).As().Utf8Value() == + info[0].As().Utf8Value()); +} + void MoveOperatorsTest(const Napi::CallbackInfo& info) { Napi::ObjectReference existingRef; Napi::ObjectReference existingRef2; @@ -224,13 +243,13 @@ void SetCastedObjects(const CallbackInfo& info) { Value GetFromValue(const CallbackInfo& info) { Env env = info.Env(); - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { if (weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { return weak.Value(); } - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { return persistent.Value(); } else { return reference.Value(); @@ -290,7 +309,7 @@ Value GetFromGetters(const CallbackInfo& info) { Value GetFromGetter(const CallbackInfo& info) { Env env = info.Env(); - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { if (weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { @@ -300,7 +319,7 @@ Value GetFromGetter(const CallbackInfo& info) { return MaybeUnwrap(weak.Get(info[1].As().Uint32Value())); } } - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { if (info[1].IsString()) { return MaybeUnwrap(persistent.Get(info[1].As().Utf8Value())); } else if (info[1].IsNumber()) { @@ -322,13 +341,13 @@ Value GetFromGetter(const CallbackInfo& info) { Value GetCastedFromValue(const CallbackInfo& info) { Env env = info.Env(); - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { if (casted_weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { return casted_weak.Value(); } - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { return casted_persistent.Value(); } else { return casted_reference.Value(); @@ -341,13 +360,13 @@ Value GetCastedFromValue(const CallbackInfo& info) { Value GetCastedFromGetter(const CallbackInfo& info) { Env env = info.Env(); - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { if (casted_weak.IsEmpty()) { return String::New(env, "No Referenced Value"); } else { return MaybeUnwrap(casted_weak.Get(info[1].As())); } - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { return MaybeUnwrap(casted_persistent.Get(info[1].As())); } else { return MaybeUnwrap(casted_reference.Get(info[1].As())); @@ -360,15 +379,15 @@ Number UnrefObjects(const CallbackInfo& info) { Env env = info.Env(); uint32_t num; - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { num = weak.Unref(); - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { num = persistent.Unref(); - } else if (info[0].As() == String::New(env, "references")) { + } else if (info[0] == String::New(env, "references")) { num = reference.Unref(); - } else if (info[0].As() == String::New(env, "casted weak")) { + } else if (info[0] == String::New(env, "casted weak")) { num = casted_weak.Unref(); - } else if (info[0].As() == String::New(env, "casted persistent")) { + } else if (info[0] == String::New(env, "casted persistent")) { num = casted_persistent.Unref(); } else { num = casted_reference.Unref(); @@ -383,15 +402,15 @@ Number RefObjects(const CallbackInfo& info) { Env env = info.Env(); uint32_t num; - if (info[0].As() == String::New(env, "weak")) { + if (info[0] == String::New(env, "weak")) { num = weak.Ref(); - } else if (info[0].As() == String::New(env, "persistent")) { + } else if (info[0] == String::New(env, "persistent")) { num = persistent.Ref(); - } else if (info[0].As() == String::New(env, "references")) { + } else if (info[0] == String::New(env, "references")) { num = reference.Ref(); - } else if (info[0].As() == String::New(env, "casted weak")) { + } else if (info[0] == String::New(env, "casted weak")) { num = casted_weak.Ref(); - } else if (info[0].As() == String::New(env, "casted persistent")) { + } else if (info[0] == String::New(env, "casted persistent")) { num = casted_persistent.Ref(); } else { num = casted_reference.Ref(); @@ -412,6 +431,7 @@ Object InitObjectReference(Env env) { exports["unrefObjects"] = Function::New(env, UnrefObjects); exports["refObjects"] = Function::New(env, RefObjects); exports["moveOpTest"] = Function::New(env, MoveOperatorsTest); + exports["setWithTempString"] = Function::New(env, SetWithTempString); return exports; } diff --git a/test/object_reference.js b/test/object_reference.js index 8039b3741..5b713dce8 100644 --- a/test/object_reference.js +++ b/test/object_reference.js @@ -47,6 +47,8 @@ const configObjects = [ function test (binding) { binding.objectreference.moveOpTest(); + binding.objectreference.setWithTempString('testValue'); + function testCastedEqual (testToCompare) { const compareTest = ['hello', 'world', '!']; if (testToCompare instanceof Array) { diff --git a/test/promise.cc b/test/promise.cc index f25600283..06ba22b86 100644 --- a/test/promise.cc +++ b/test/promise.cc @@ -1,4 +1,5 @@ #include "napi.h" +#include "test_helper.h" using namespace Napi; @@ -23,6 +24,74 @@ Value PromiseReturnsCorrectEnv(const CallbackInfo& info) { return Boolean::New(info.Env(), deferred.Env() == info.Env()); } +Value ThenMethodOnFulfilled(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Then(onFulfilled)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedResolve(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = + MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Resolve(Number::New(info.Env(), 42)); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value ThenMethodOnFulfilledOnRejectedReject(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onFulfilled = info[0].As(); + Function onRejected = info[1].As(); + + Promise resultPromise = + MaybeUnwrap(deferred.Promise().Then(onFulfilled, onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + +Value CatchMethod(const CallbackInfo& info) { + auto deferred = Promise::Deferred::New(info.Env()); + Function onRejected = info[0].As(); + + Promise resultPromise = MaybeUnwrap(deferred.Promise().Catch(onRejected)); + + bool isPromise = resultPromise.IsPromise(); + deferred.Reject(String::New(info.Env(), "Rejected")); + + Object result = Object::New(info.Env()); + result["isPromise"] = Boolean::New(info.Env(), isPromise); + result["promise"] = resultPromise; + + return result; +} + Object InitPromise(Env env) { Object exports = Object::New(env); @@ -31,6 +100,12 @@ Object InitPromise(Env env) { exports["rejectPromise"] = Function::New(env, RejectPromise); exports["promiseReturnsCorrectEnv"] = Function::New(env, PromiseReturnsCorrectEnv); + exports["thenMethodOnFulfilled"] = Function::New(env, ThenMethodOnFulfilled); + exports["thenMethodOnFulfilledOnRejectedResolve"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedResolve); + exports["thenMethodOnFulfilledOnRejectedReject"] = + Function::New(env, ThenMethodOnFulfilledOnRejectedReject); + exports["catchMethod"] = Function::New(env, CatchMethod); return exports; } diff --git a/test/promise.js b/test/promise.js index 63b20cef8..e61a783ce 100644 --- a/test/promise.js +++ b/test/promise.js @@ -17,4 +17,27 @@ async function test (binding) { rejecting.then(common.mustNotCall()).catch(common.mustCall()); assert(binding.promise.promiseReturnsCorrectEnv()); + + const onFulfilled = (value) => value * 2; + const onRejected = (reason) => reason + '!'; + + const thenOnFulfilled = binding.promise.thenMethodOnFulfilled(onFulfilled); + assert.strictEqual(thenOnFulfilled.isPromise, true); + const onFulfilledValue = await thenOnFulfilled.promise; + assert.strictEqual(onFulfilledValue, 84); + + const thenResolve = binding.promise.thenMethodOnFulfilledOnRejectedResolve(onFulfilled, onRejected); + assert.strictEqual(thenResolve.isPromise, true); + const thenResolveValue = await thenResolve.promise; + assert.strictEqual(thenResolveValue, 84); + + const thenRejected = binding.promise.thenMethodOnFulfilledOnRejectedReject(onFulfilled, onRejected); + assert.strictEqual(thenRejected.isPromise, true); + const rejectedValue = await thenRejected.promise; + assert.strictEqual(rejectedValue, 'Rejected!'); + + const catchMethod = binding.promise.catchMethod(onRejected); + assert.strictEqual(catchMethod.isPromise, true); + const catchValue = await catchMethod.promise; + assert.strictEqual(catchValue, 'Rejected!'); } diff --git a/test/require_basic_finalizers/index.js b/test/require_basic_finalizers/index.js new file mode 100644 index 000000000..31ee4f00b --- /dev/null +++ b/test/require_basic_finalizers/index.js @@ -0,0 +1,38 @@ +'use strict'; + +const { promisify } = require('util'); +const exec = promisify(require('child_process').exec); +const { copy, remove } = require('fs-extra'); +const path = require('path'); +const assert = require('assert'); + +async function test () { + const addon = 'require-basic-finalizers'; + const ADDON_FOLDER = path.join(__dirname, 'addons', addon); + + await remove(ADDON_FOLDER); + await copy(path.join(__dirname, 'tpl'), ADDON_FOLDER); + + console.log(' >Building addon'); + + // Fail when NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS is enabled + await assert.rejects(exec('npm --require-basic-finalizers install', { + cwd: ADDON_FOLDER + }), 'Addon unexpectedly compiled successfully'); + + // Succeed when NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS is not enabled + return assert.doesNotReject(exec('npm install', { + cwd: ADDON_FOLDER + })); +} + +module.exports = (function () { + // This test will only run under an experimental version test. + const isExperimental = Number(process.env.NAPI_VERSION) === 2147483647; + + if (isExperimental) { + return test(); + } else { + console.log(' >Skipped (non-experimental test run)'); + } +})(); diff --git a/test/require_basic_finalizers/tpl/.npmrc b/test/require_basic_finalizers/tpl/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/test/require_basic_finalizers/tpl/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/test/require_basic_finalizers/tpl/addon.cc b/test/require_basic_finalizers/tpl/addon.cc new file mode 100644 index 000000000..f4277ac74 --- /dev/null +++ b/test/require_basic_finalizers/tpl/addon.cc @@ -0,0 +1,12 @@ +#include + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set( + "external", + Napi::External::New( + env, new int(1), [](Napi::Env /*env*/, int* data) { delete data; })); + + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/require_basic_finalizers/tpl/binding.gyp b/test/require_basic_finalizers/tpl/binding.gyp new file mode 100644 index 000000000..caf99d21f --- /dev/null +++ b/test/require_basic_finalizers/tpl/binding.gyp @@ -0,0 +1,48 @@ +{ + 'target_defaults': { + 'include_dirs': [ + "()), Value()); + return MaybeUnwrapOr(env.RunScript(info[0].UnsafeAs()), Value()); } Value RunWithContext(const CallbackInfo& info) { diff --git a/test/shared_array_buffer.cc b/test/shared_array_buffer.cc new file mode 100644 index 000000000..57f66495a --- /dev/null +++ b/test/shared_array_buffer.cc @@ -0,0 +1,104 @@ +#include "napi.h" + +using namespace Napi; + +namespace { + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value TestIsSharedArrayBuffer(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } + + return Boolean::New(info.Env(), info[0].IsSharedArrayBuffer()); +} + +Value TestCreateSharedArrayBuffer(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsNumber()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a number as first argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().Uint32Value(); + if (byte_length == 0) { + Error::New(info.Env(), + "Invalid byte length. Expects a non-negative integer.") + .ThrowAsJavaScriptException(); + return Value(); + } + + return SharedArrayBuffer::New(info.Env(), byte_length); +} + +Value TestGetSharedArrayBufferInfo(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsSharedArrayBuffer()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a SharedArrayBuffer as first " + "argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().ByteLength(); + + return Number::New(info.Env(), byte_length); +} + +Value TestSharedArrayBufferData(const CallbackInfo& info) { + if (info.Length() < 1) { + Error::New(info.Env(), "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return Value(); + } else if (!info[0].IsSharedArrayBuffer()) { + Error::New(info.Env(), + "Wrong type of arguments. Expects a SharedArrayBuffer as first " + "argument.") + .ThrowAsJavaScriptException(); + return Value(); + } + + auto byte_length = info[0].As().ByteLength(); + void* data = info[0].As().Data(); + + if (byte_length > 0 && data != nullptr) { + uint8_t* bytes = static_cast(data); + for (size_t i = 0; i < byte_length; i++) { + bytes[i] = i % 256; + } + + return Boolean::New(info.Env(), true); + } + + return Boolean::New(info.Env(), false); +} +#endif +} // end anonymous namespace + +Object InitSharedArrayBuffer(Env env) { + Object exports = Object::New(env); + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["testIsSharedArrayBuffer"] = + Function::New(env, TestIsSharedArrayBuffer); + exports["testCreateSharedArrayBuffer"] = + Function::New(env, TestCreateSharedArrayBuffer); + exports["testGetSharedArrayBufferInfo"] = + Function::New(env, TestGetSharedArrayBufferInfo); + exports["testSharedArrayBufferData"] = + Function::New(env, TestSharedArrayBufferData); +#endif + + return exports; +} diff --git a/test/shared_array_buffer.js b/test/shared_array_buffer.js new file mode 100644 index 000000000..018021ace --- /dev/null +++ b/test/shared_array_buffer.js @@ -0,0 +1,55 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = require('./common').runTest(test); + +let skippedMessageShown = false; + +function test ({ hasSharedArrayBuffer, sharedarraybuffer }) { + if (!hasSharedArrayBuffer) { + if (!skippedMessageShown) { + console.log(' >Skipped (no SharedArrayBuffer support)'); + skippedMessageShown = true; + } + return; + } + + { + const sab = new SharedArrayBuffer(16); + const ab = new ArrayBuffer(16); + const obj = {}; + const arr = []; + + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(sab), true); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(ab), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(obj), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(arr), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(null), false); + assert.strictEqual(sharedarraybuffer.testIsSharedArrayBuffer(undefined), false); + } + + { + const sab = sharedarraybuffer.testCreateSharedArrayBuffer(16); + assert(sab instanceof SharedArrayBuffer); + assert.strictEqual(sab.byteLength, 16); + } + + { + const sab = new SharedArrayBuffer(32); + const byteLength = sharedarraybuffer.testGetSharedArrayBufferInfo(sab); + assert.strictEqual(byteLength, 32); + } + + { + const sab = new SharedArrayBuffer(8); + const result = sharedarraybuffer.testSharedArrayBufferData(sab); + assert.strictEqual(result, true); + + // Check if data was written correctly + const view = new Uint8Array(sab); + for (let i = 0; i < 8; i++) { + assert.strictEqual(view[i], i % 256); + } + } +} diff --git a/test/symbol.cc b/test/symbol.cc index 08ea80393..d978739ff 100644 --- a/test/symbol.cc +++ b/test/symbol.cc @@ -1,4 +1,7 @@ #include + +#include + #include "test_helper.h" using namespace Napi; @@ -37,6 +40,13 @@ Symbol FetchSymbolFromGlobalRegistryWithCppKey(const Napi::CallbackInfo& info) { return MaybeUnwrap(Napi::Symbol::For(info.Env(), cppStringKey.Utf8Value())); } +Symbol FetchSymbolFromGlobalRegistryWithStringViewKey( + const Napi::CallbackInfo& info) { + String cppStringKey = info[0].As(); + std::string key = cppStringKey.Utf8Value(); + return MaybeUnwrap(Napi::Symbol::For(info.Env(), std::string_view(key))); +} + Symbol FetchSymbolFromGlobalRegistryWithCKey(const Napi::CallbackInfo& info) { String cppStringKey = info[0].As(); return MaybeUnwrap( @@ -71,6 +81,8 @@ Object InitSymbol(Env env) { Function::New(env, FetchSymbolFromGlobalRegistryWithCKey); exports["getSymbolFromGlobalRegistryWithCppKey"] = Function::New(env, FetchSymbolFromGlobalRegistryWithCppKey); + exports["getSymbolFromGlobalRegistryWithStringViewKey"] = + Function::New(env, FetchSymbolFromGlobalRegistryWithStringViewKey); exports["testUndefinedSymbolCanBeCreated"] = Function::New(env, TestUndefinedSymbolsCanBeCreated); exports["testNullSymbolCanBeCreated"] = diff --git a/test/symbol.js b/test/symbol.js index bd2e3c83e..baf39c81b 100644 --- a/test/symbol.js +++ b/test/symbol.js @@ -54,6 +54,7 @@ function test (binding) { assertCanCreateOrFetchGlobalSymbols('data', binding.symbol.getSymbolFromGlobalRegistry); assertCanCreateOrFetchGlobalSymbols('CppKey', binding.symbol.getSymbolFromGlobalRegistryWithCppKey); + assertCanCreateOrFetchGlobalSymbols('StringViewKey', binding.symbol.getSymbolFromGlobalRegistryWithStringViewKey); assertCanCreateOrFetchGlobalSymbols('CKey', binding.symbol.getSymbolFromGlobalRegistryWithCKey); assert(binding.symbol.createNewSymbolWithNoArgs() === undefined); diff --git a/test/type_taggable.cc b/test/type_taggable.cc index 2320dd81c..ac58f9281 100644 --- a/test/type_taggable.cc +++ b/test/type_taggable.cc @@ -26,7 +26,7 @@ class TestTypeTaggable { static Value CheckTypeTag(const CallbackInfo& info) { uint32_t type_index = info[0].As().Int32Value(); - TypeTaggable instance = info[1].As(); + TypeTaggable instance = info[1].UnsafeAs(); return Boolean::New(info.Env(), instance.CheckTypeTag(&type_tags[type_index])); diff --git a/test/type_taggable.js b/test/type_taggable.js index 263befa9d..7bc843cf6 100644 --- a/test/type_taggable.js +++ b/test/type_taggable.js @@ -53,8 +53,7 @@ function testTypeTaggable ({ typeTaggedInstance, checkTypeTag }) { assert.strictEqual(checkTypeTag(4, obj3), false); } -// eslint-disable-next-line camelcase -function test ({ type_taggable }) { - testTypeTaggable(type_taggable.external); - testTypeTaggable(type_taggable.object); +function test (binding) { + testTypeTaggable(binding.type_taggable.external); + testTypeTaggable(binding.type_taggable.object); } diff --git a/test/typedarray.cc b/test/typedarray.cc index 3ee9f3f2a..795f2819e 100644 --- a/test/typedarray.cc +++ b/test/typedarray.cc @@ -21,7 +21,7 @@ namespace { Value CreateTypedArray(const CallbackInfo& info) { std::string arrayType = info[0].As(); size_t length = info[1].As().Uint32Value(); - ArrayBuffer buffer = info[2].As(); + Value buffer = info[2]; size_t bufferOffset = info[3].IsUndefined() ? 0 : info[3].As().Uint32Value(); @@ -32,7 +32,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_int8_array); } else if (arrayType == "uint8") { @@ -42,7 +42,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Uint8Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_uint8_array); } else if (arrayType == "uint8_clamped") { @@ -50,7 +50,7 @@ Value CreateTypedArray(const CallbackInfo& info) { ? Uint8Array::New(info.Env(), length, napi_uint8_clamped_array) : Uint8Array::New(info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_uint8_clamped_array); } else if (arrayType == "int16") { @@ -60,7 +60,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Int16Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_int16_array); } else if (arrayType == "uint16") { @@ -70,7 +70,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Uint16Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_uint16_array); } else if (arrayType == "int32") { @@ -80,7 +80,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Int32Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_int32_array); } else if (arrayType == "uint32") { @@ -90,7 +90,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Uint32Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_uint32_array); } else if (arrayType == "float32") { @@ -100,7 +100,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Float32Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_float32_array); } else if (arrayType == "float64") { @@ -110,7 +110,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(Float64Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_float64_array); #if (NAPI_VERSION > 5) @@ -121,7 +121,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(BigInt64Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_bigint64_array); } else if (arrayType == "biguint64") { @@ -131,7 +131,7 @@ Value CreateTypedArray(const CallbackInfo& info) { : NAPI_TYPEDARRAY_NEW_BUFFER(BigUint64Array, info.Env(), length, - buffer, + buffer.As(), bufferOffset, napi_biguint64_array); #endif @@ -208,8 +208,8 @@ Value CheckBufferContent(const CallbackInfo& info) { case napi_uint8_array: return Boolean::New( info.Env(), - TypedArrayDataIsEquivalent(info[0].As(), - info[1].As())); + TypedArrayDataIsEquivalent(info[0].As(), + info[1].As())); case napi_uint8_clamped_array: return Boolean::New( @@ -297,6 +297,11 @@ Value GetTypedArrayBuffer(const CallbackInfo& info) { return array.ArrayBuffer(); } +Value GetTypedArrayBufferValue(const CallbackInfo& info) { + TypedArray array = info[0].As(); + return array.Buffer(); +} + Value GetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); @@ -335,35 +340,39 @@ Value GetTypedArrayElement(const CallbackInfo& info) { void SetTypedArrayElement(const CallbackInfo& info) { TypedArray array = info[0].As(); size_t index = info[1].As().Uint32Value(); - Number value = info[2].As(); + Value value = info[2]; switch (array.TypedArrayType()) { case napi_int8_array: - array.As()[index] = static_cast(value.Int32Value()); + array.As()[index] = + static_cast(value.As().Int32Value()); break; case napi_uint8_array: - array.As()[index] = static_cast(value.Uint32Value()); + array.As()[index] = + static_cast(value.As().Uint32Value()); break; case napi_uint8_clamped_array: - array.As()[index] = static_cast(value.Uint32Value()); + array.As()[index] = + static_cast(value.As().Uint32Value()); break; case napi_int16_array: - array.As()[index] = static_cast(value.Int32Value()); + array.As()[index] = + static_cast(value.As().Int32Value()); break; case napi_uint16_array: array.As()[index] = - static_cast(value.Uint32Value()); + static_cast(value.As().Uint32Value()); break; case napi_int32_array: - array.As()[index] = value.Int32Value(); + array.As()[index] = value.As().Int32Value(); break; case napi_uint32_array: - array.As()[index] = value.Uint32Value(); + array.As()[index] = value.As().Uint32Value(); break; case napi_float32_array: - array.As()[index] = value.FloatValue(); + array.As()[index] = value.As().FloatValue(); break; case napi_float64_array: - array.As()[index] = value.DoubleValue(); + array.As()[index] = value.As().DoubleValue(); break; #if (NAPI_VERSION > 5) case napi_bigint64_array: { @@ -385,12 +394,30 @@ void SetTypedArrayElement(const CallbackInfo& info) { } } +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +Value CreateInt8TypedArrayFromSharedArrayBuffer(const CallbackInfo& info) { + auto buffer = info[0].As(); + size_t length = buffer.ByteLength(); + + return NAPI_TYPEDARRAY_NEW_BUFFER(Int8Array, + info.Env(), + length, + buffer.As(), + 0, + napi_int8_array); +} +#endif + } // end anonymous namespace Object InitTypedArray(Env env) { Object exports = Object::New(env); exports["createTypedArray"] = Function::New(env, CreateTypedArray); +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + exports["createInt8TypedArrayFromSharedArrayBuffer"] = + Function::New(env, CreateInt8TypedArrayFromSharedArrayBuffer); +#endif exports["createInvalidTypedArray"] = Function::New(env, CreateInvalidTypedArray); exports["getTypedArrayType"] = Function::New(env, GetTypedArrayType); @@ -401,6 +428,8 @@ Object InitTypedArray(Env env) { exports["getTypedArrayByteLength"] = Function::New(env, GetTypedArrayByteLength); exports["getTypedArrayBuffer"] = Function::New(env, GetTypedArrayBuffer); + exports["getTypedArrayBufferValue"] = + Function::New(env, GetTypedArrayBufferValue); exports["getTypedArrayElement"] = Function::New(env, GetTypedArrayElement); exports["setTypedArrayElement"] = Function::New(env, SetTypedArrayElement); exports["checkBufferContent"] = Function::New(env, CheckBufferContent); diff --git a/test/typedarray.js b/test/typedarray.js index f7224efb7..b6ae4a7f7 100644 --- a/test/typedarray.js +++ b/test/typedarray.js @@ -2,6 +2,8 @@ const assert = require('assert'); +let runSharedArrayBufferTests = true; + module.exports = require('./common').runTest(test); function test (binding) { @@ -61,6 +63,9 @@ function test (binding) { const b = binding.typedarray.getTypedArrayBuffer(t); assert.ok(b instanceof ArrayBuffer); + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof ArrayBuffer); + assert.strictEqual(b, bAsValue); } catch (e) { console.log(data); throw e; @@ -100,4 +105,35 @@ function test (binding) { assert.throws(() => { binding.typedarray.createInvalidTypedArray(); }, /Invalid (pointer passed as )?argument/); + + if (binding.hasSharedArrayBuffer && runSharedArrayBufferTests) { + const length = 4; + const sab = new SharedArrayBuffer(length); + /** @type {Int8Array} */ + let t; + + try { + t = binding.typedarray.createInt8TypedArrayFromSharedArrayBuffer(sab); + } catch (ex) { + if (ex.message === 'Invalid argument') { + console.warn(`The current version of Node.js (${process.version}) does not support creating TypedArrays on SharedArrayBuffers; skipping tests.`); + runSharedArrayBufferTests = false; + return; + } + + throw ex; + } + + assert.ok(t instanceof Int8Array); + assert.strictEqual(binding.typedarray.getTypedArrayType(t), 'int8'); + assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); + for (let i = 0; i < length; i++) { + const value = 2 ** (i + 1); + t[i] = value; + assert.strictEqual(binding.typedarray.getTypedArrayElement(t, i), value); + } + const bAsValue = binding.typedarray.getTypedArrayBufferValue(t); + assert.ok(bAsValue instanceof SharedArrayBuffer); + assert.strictEqual(bAsValue, sab); + } } diff --git a/test/value_type_cast.cc b/test/value_type_cast.cc index 9a140d281..dfc03b38b 100644 --- a/test/value_type_cast.cc +++ b/test/value_type_cast.cc @@ -27,6 +27,11 @@ namespace { #define V(Type) \ void TypeCast##Type(const CallbackInfo& info) { USE(info[0].As()); } TYPE_CAST_TYPES(V) + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER +V(SharedArrayBuffer) +#endif + #undef V void TypeCastBuffer(const CallbackInfo& info) { @@ -47,6 +52,11 @@ Object InitValueTypeCast(Env env, Object exports) { #define V(Type) exports["typeCast" #Type] = Function::New(env, TypeCast##Type); TYPE_CAST_TYPES(V) + +#ifdef NODE_API_EXPERIMENTAL_HAS_SHAREDARRAYBUFFER + V(SharedArrayBuffer) +#endif + #undef V exports["typeCastBuffer"] = Function::New(env, TypeCastBuffer); diff --git a/test/value_type_cast.js b/test/value_type_cast.js index cdebd4e03..274dd278b 100644 --- a/test/value_type_cast.js +++ b/test/value_type_cast.js @@ -42,7 +42,7 @@ function test (buildType) { }, typeCastArrayBuffer: { positiveValues: [new ArrayBuffer(0)], - negativeValues: [new Uint8Array(1), {}, [], null, undefined] + negativeValues: [new Uint8Array(1), new SharedArrayBuffer(0), {}, [], null, undefined] }, typeCastTypedArray: { positiveValues: [new Uint8Array(0)], @@ -77,6 +77,13 @@ function test (buildType) { } }; + if ('typeCastSharedArrayBuffer' in binding) { + testTable.typeCastSharedArrayBuffer = { + positiveValues: [new SharedArrayBuffer(0)], + negativeValues: [new Uint8Array(1), new ArrayBuffer(0), {}, [], null, undefined] + }; + } + if (process.argv[2] === 'child') { child(binding, testTable, process.argv[3], process.argv[4], parseInt(process.argv[5])); return; diff --git a/tools/conversion.js b/tools/conversion.js index f89245ac6..e92a03a26 100755 --- a/tools/conversion.js +++ b/tools/conversion.js @@ -12,7 +12,7 @@ if (!dir) { process.exit(1); } -const NodeApiVersion = require('../package.json').version; +const NodeApiVersion = require('../').version; const disable = args[1]; let ConfigFileOperations; diff --git a/tools/eslint-format.js b/tools/eslint-format.js deleted file mode 100644 index 6923ab7b6..000000000 --- a/tools/eslint-format.js +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env node - -const spawn = require('child_process').spawnSync; - -const filesToCheck = '*.js'; -const FORMAT_START = process.env.FORMAT_START || 'main'; -const IS_WIN = process.platform === 'win32'; -const ESLINT_PATH = IS_WIN ? 'node_modules\\.bin\\eslint.cmd' : 'node_modules/.bin/eslint'; - -function main (args) { - let fix = false; - while (args.length > 0) { - switch (args[0]) { - case '-f': - case '--fix': - fix = true; - break; - default: - } - args.shift(); - } - - // Check js files that change on unstaged file - const fileUnStaged = spawn( - 'git', - ['diff', '--name-only', '--diff-filter=d', FORMAT_START, filesToCheck], - { - encoding: 'utf-8' - } - ); - - // Check js files that change on staged file - const fileStaged = spawn( - 'git', - ['diff', '--name-only', '--cached', '--diff-filter=d', FORMAT_START, filesToCheck], - { - encoding: 'utf-8' - } - ); - - const options = [ - ...fileStaged.stdout.split('\n').filter((f) => f !== ''), - ...fileUnStaged.stdout.split('\n').filter((f) => f !== '') - ]; - - if (fix) { - options.push('--fix'); - } - - const result = spawn(ESLINT_PATH, [...options], { - encoding: 'utf-8' - }); - - if (result.error && result.error.errno === 'ENOENT') { - console.error('Eslint not found! Eslint is supposed to be found at ', ESLINT_PATH); - return 2; - } - - if (result.status === 1) { - console.error('Eslint error:', result.stdout); - const fixCmd = 'npm run lint:fix'; - console.error(`ERROR: please run "${fixCmd}" to format changes in your commit - Note that when running the command locally, please keep your local - main branch and working branch up to date with nodejs/node-addon-api - to exclude un-related complains. - Or you can run "env FORMAT_START=upstream/main ${fixCmd}". - Also fix JS files by yourself if necessary.`); - return 1; - } - - if (result.stderr) { - console.error('Error running eslint:', result.stderr); - return 2; - } -} - -if (require.main === module) { - process.exitCode = main(process.argv.slice(2)); -} diff --git a/unit-test/README.md b/unit-test/README.md index e10b1c448..2dfd5abfb 100644 --- a/unit-test/README.md +++ b/unit-test/README.md @@ -1,11 +1,17 @@ # Enable running tests with specific filter conditions: +The `--filter` option limits which test modules are executed by `node test`. +The default `pretest` step is still `node-gyp rebuild -C test`, so +`npm test --filter=...` still performs a full rebuild of the test addon +targets before the filtered tests run. + ### Example: - - compile and run only tests on objectwrap.cc and objectwrap.js + - perform the default test rebuild, then run only the `objectwrap` + test module ``` - npm run test --filter=objectwrap + npm test --filter=objectwrap ``` @@ -13,16 +19,20 @@ ### Example: - - compile and run all tests files ending with reference -> function_reference.cc object_reference.cc reference.cc + - perform the default test rebuild, then run all test modules ending + with `reference` + (`function_reference`, `object_reference`, and `reference`) ``` - npm run test --filter=*reference + npm test --filter=*reference ``` # Multiple filter conditions are also allowed ### Example: - - compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file + - perform the default test rebuild, then run all tests under + `threadsafe_function` and `typed_threadsafe_function`, and also the + `objectwrap` test module ``` - npm run test --filter='*function objectwrap' + npm test --filter='*function objectwrap' ```