├── .clang-format ├── .editorconfig ├── .eslintrc.js ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── ci-win.yml │ ├── ci.yml │ ├── codeql.yml │ ├── dependency-review.yml │ ├── linter.yml │ ├── scorecards.yml │ └── stale.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── appveyor.yml ├── benchmark ├── README.md ├── binding.gyp ├── function_args.cc ├── function_args.js ├── index.js ├── property_descriptor.cc └── property_descriptor.js ├── common.gypi ├── doc ├── .gitignore ├── addon.md ├── array.md ├── array_buffer.md ├── async_context.md ├── async_operations.md ├── async_worker.md ├── async_worker_variants.md ├── bigint.md ├── boolean.md ├── buffer.md ├── callback_scope.md ├── callbackinfo.md ├── checker-tool.md ├── class_property_descriptor.md ├── cmake-js.md ├── conversion-tool.md ├── creating_a_release.md ├── dataview.md ├── date.md ├── env.md ├── error.md ├── error_handling.md ├── escapable_handle_scope.md ├── external.md ├── external_buffer.md ├── function.md ├── function_reference.md ├── generator.md ├── handle_scope.md ├── hierarchy.md ├── instance_wrap.md ├── maybe.md ├── memory_management.md ├── name.md ├── node-gyp.md ├── number.md ├── object.md ├── object_lifetime_management.md ├── object_reference.md ├── object_wrap.md ├── prebuild_tools.md ├── promises.md ├── property_descriptor.md ├── propertylvalue.md ├── range_error.md ├── reference.md ├── setup.md ├── string.md ├── symbol.md ├── syntax_error.md ├── threadsafe.md ├── threadsafe_function.md ├── type_error.md ├── type_taggable.md ├── typed_array.md ├── typed_array_of.md ├── typed_threadsafe_function.md ├── value.md └── version_management.md ├── except.gypi ├── index.js ├── napi-inl.deprecated.h ├── napi-inl.h ├── napi.h ├── node_api.gyp ├── noexcept.gypi ├── nothing.c ├── package-support.json ├── package.json ├── test ├── .gitignore ├── README.md ├── addon.cc ├── addon.js ├── addon_build │ ├── index.js │ └── tpl │ │ ├── .npmrc │ │ ├── addon.cc │ │ ├── binding.gyp │ │ ├── index.js │ │ └── package.json ├── addon_data.cc ├── addon_data.js ├── array_buffer.cc ├── array_buffer.js ├── async_context.cc ├── async_context.js ├── async_progress_queue_worker.cc ├── async_progress_queue_worker.js ├── async_progress_worker.cc ├── async_progress_worker.js ├── async_worker.cc ├── async_worker.js ├── async_worker_nocallback.js ├── async_worker_persistent.cc ├── async_worker_persistent.js ├── basic_types │ ├── array.cc │ ├── array.js │ ├── boolean.cc │ ├── boolean.js │ ├── number.cc │ ├── number.js │ ├── value.cc │ └── value.js ├── bigint.cc ├── bigint.js ├── binding-swallowexcept.cc ├── binding.cc ├── binding.gyp ├── buffer.cc ├── buffer.h ├── buffer.js ├── buffer_new_or_copy-inl.h ├── buffer_no_external.cc ├── callbackInfo.cc ├── callbackInfo.js ├── callbackscope.cc ├── callbackscope.js ├── child_processes │ ├── addon.js │ ├── addon_data.js │ ├── objectwrap_function.js │ ├── threadsafe_function_exception.js │ └── typed_threadsafe_function_exception.js ├── common │ ├── index.js │ └── test_helper.h ├── dataview │ ├── dataview.cc │ ├── dataview.js │ ├── dataview_read_write.cc │ └── dataview_read_write.js ├── date.cc ├── date.js ├── env_cleanup.cc ├── env_cleanup.js ├── env_misc.cc ├── env_misc.js ├── error.cc ├── error.js ├── error_handling_for_primitives.cc ├── error_handling_for_primitives.js ├── error_terminating_environment.js ├── external.cc ├── external.js ├── function.cc ├── function.js ├── function_reference.cc ├── function_reference.js ├── globalObject │ ├── global_object.cc │ ├── global_object_delete_property.cc │ ├── global_object_delete_property.js │ ├── global_object_get_property.cc │ ├── global_object_get_property.js │ ├── global_object_has_own_property.cc │ ├── global_object_has_own_property.js │ ├── global_object_set_property.cc │ └── global_object_set_property.js ├── handlescope.cc ├── handlescope.js ├── index.js ├── maybe │ ├── check.cc │ └── index.js ├── memory_management.cc ├── memory_management.js ├── movable_callbacks.cc ├── movable_callbacks.js ├── name.cc ├── name.js ├── napi_child.js ├── object │ ├── delete_property.cc │ ├── delete_property.js │ ├── finalizer.cc │ ├── finalizer.js │ ├── get_property.cc │ ├── get_property.js │ ├── has_own_property.cc │ ├── has_own_property.js │ ├── has_property.cc │ ├── has_property.js │ ├── object.cc │ ├── object.js │ ├── object_deprecated.cc │ ├── object_deprecated.js │ ├── object_freeze_seal.cc │ ├── object_freeze_seal.js │ ├── set_property.cc │ ├── set_property.js │ ├── subscript_operator.cc │ └── subscript_operator.js ├── object_reference.cc ├── object_reference.js ├── objectwrap.cc ├── objectwrap.js ├── objectwrap_constructor_exception.cc ├── objectwrap_constructor_exception.js ├── objectwrap_function.cc ├── objectwrap_function.js ├── objectwrap_multiple_inheritance.cc ├── objectwrap_multiple_inheritance.js ├── objectwrap_removewrap.cc ├── objectwrap_removewrap.js ├── objectwrap_worker_thread.js ├── promise.cc ├── promise.js ├── reference.cc ├── reference.js ├── run_script.cc ├── run_script.js ├── symbol.cc ├── symbol.js ├── testUtil.js ├── threadsafe_function │ ├── threadsafe_function.cc │ ├── threadsafe_function.js │ ├── threadsafe_function_ctx.cc │ ├── threadsafe_function_ctx.js │ ├── threadsafe_function_exception.cc │ ├── threadsafe_function_exception.js │ ├── threadsafe_function_existing_tsfn.cc │ ├── threadsafe_function_existing_tsfn.js │ ├── threadsafe_function_ptr.cc │ ├── threadsafe_function_ptr.js │ ├── threadsafe_function_sum.cc │ ├── threadsafe_function_sum.js │ ├── threadsafe_function_unref.cc │ └── threadsafe_function_unref.js ├── thunking_manual.cc ├── thunking_manual.js ├── type_taggable.cc ├── type_taggable.js ├── typed_threadsafe_function │ ├── typed_threadsafe_function.cc │ ├── typed_threadsafe_function.js │ ├── typed_threadsafe_function_ctx.cc │ ├── typed_threadsafe_function_ctx.js │ ├── typed_threadsafe_function_exception.cc │ ├── typed_threadsafe_function_exception.js │ ├── typed_threadsafe_function_existing_tsfn.cc │ ├── typed_threadsafe_function_existing_tsfn.js │ ├── typed_threadsafe_function_ptr.cc │ ├── typed_threadsafe_function_ptr.js │ ├── typed_threadsafe_function_sum.cc │ ├── typed_threadsafe_function_sum.js │ ├── typed_threadsafe_function_unref.cc │ └── typed_threadsafe_function_unref.js ├── typedarray-bigint.js ├── typedarray.cc ├── typedarray.js ├── value_type_cast.cc ├── value_type_cast.js ├── version_management.cc └── version_management.js ├── tools ├── README.md ├── check-napi.js ├── clang-format.js ├── conversion.js └── eslint-format.js └── unit-test ├── .gitignore ├── README.md ├── binding-file-template.js ├── binding.gyp ├── exceptions.js ├── generate-binding-cc.js ├── injectTestParams.js ├── listOfTestModules.js ├── matchModules.js ├── setup.js ├── spawnTask.js └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | sourceType: 'script' 4 | }, 5 | extends: ['semistandard'] 6 | }; 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: daily 12 | 13 | - package-ecosystem: npm 14 | directory: /test/addon_build/tpl 15 | schedule: 16 | interval: daily 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-win.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI Windows Platform 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test: 10 | timeout-minutes: 30 11 | strategy: 12 | matrix: 13 | node-version: [ 16.x, 18.x, 20.x ] 14 | architecture: [x64, x86] 15 | os: 16 | - windows-2019 17 | - windows-2022 18 | runs-on: ${{ matrix.os }} 19 | steps: 20 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | architecture: ${{ matrix.architecture }} 26 | - name: Check Node.js installation 27 | run: | 28 | node --version 29 | npm --version 30 | - name: Install dependencies 31 | run: | 32 | npm install 33 | - name: npm test 34 | run: | 35 | npm run pretest -- --verbose 36 | node test 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI Unix Platform 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test: 10 | timeout-minutes: 30 11 | strategy: 12 | matrix: 13 | node-version: [ 16.x, 18.x, 20.x ] 14 | os: 15 | - macos-latest 16 | - ubuntu-latest 17 | compiler: 18 | - clang 19 | - gcc 20 | exclude: 21 | - os: macos-latest 22 | compiler: gcc # GCC is an alias for clang on the MacOS image. 23 | runs-on: ${{ matrix.os }} 24 | steps: 25 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 26 | - name: Use Node.js ${{ matrix.node-version }} 27 | uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 28 | with: 29 | node-version: ${{ matrix.node-version }} 30 | - name: Check Node.js installation 31 | run: | 32 | node --version 33 | npm --version 34 | - name: Install dependencies 35 | run: | 36 | npm install 37 | - name: npm test 38 | run: | 39 | if [ "${{ matrix.compiler }}" = "gcc" ]; then 40 | export CC="gcc" CXX="g++" 41 | fi 42 | if [ "${{ matrix.compiler }}" = "clang" ]; then 43 | export CC="clang" CXX="clang++" 44 | fi 45 | echo "CC=\"$CC\" CXX=\"$CXX\"" 46 | echo "$CC --version" 47 | $CC --version 48 | echo "$CXX --version" 49 | $CXX --version 50 | export CFLAGS="$CFLAGS -O3 --coverage" LDFLAGS="$LDFLAGS --coverage" 51 | echo "CFLAGS=\"$CFLAGS\" LDFLAGS=\"$LDFLAGS\"" 52 | npm run pretest -- --verbose 53 | node test 54 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, 4 | # surfacing known-vulnerable versions of the packages declared or updated in the PR. 5 | # Once installed, if the workflow run is marked as required, 6 | # PRs introducing known-vulnerable packages will be blocked from merging. 7 | # 8 | # Source repository: https://github.com/actions/dependency-review-action 9 | name: 'Dependency Review' 10 | on: [pull_request] 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | dependency-review: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Harden Runner 20 | uses: step-security/harden-runner@cba0d00b1fc9a034e1e642ea0f1103c282990604 # v2.5.0 21 | with: 22 | egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs 23 | 24 | - name: 'Checkout Repository' 25 | uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 26 | - name: 'Dependency Review' 27 | uses: actions/dependency-review-action@1360a344ccb0ab6e9475edef90ad2f46bf8003b1 # v3.0.6 28 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Style Checks 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | lint: 10 | if: github.repository == 'nodejs/node-addon-api' 11 | strategy: 12 | matrix: 13 | node-version: [16.x] 14 | os: [ubuntu-latest] 15 | 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 19 | with: 20 | fetch-depth: 0 21 | - run: git branch -a 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: npm install 27 | - run: FORMAT_START=refs/remotes/origin/main npm run lint 28 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | stale: 11 | permissions: 12 | issues: write # for actions/stale to close stale issues 13 | pull-requests: write # for actions/stale to close stale PRs 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 17 | with: 18 | repo-token: ${{ secrets.GITHUB_TOKEN }} 19 | 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.' 20 | stale-issue-label: 'stale' 21 | exempt-issue-labels: 'never-stale' 22 | days-before-stale: 90 23 | days-before-close: 30 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /benchmark/build 4 | /benchmark/src 5 | /test/addon_build/addons 6 | /.vscode 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The Node.js Code of Conduct, which applies to this project, can be found at 4 | https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md. 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright (c) 2017 Node.js API collaborators 5 | ----------------------------------- 6 | 7 | *Node.js API collaborators listed at * 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | # https://github.com/jasongin/nvs/blob/HEAD/doc/CI.md 3 | NVS_VERSION: 1.4.2 4 | matrix: 5 | - NODEJS_VERSION: node/10 6 | - NODEJS_VERSION: node/12 7 | - NODEJS_VERSION: node/14 8 | - NODEJS_VERSION: nightly 9 | 10 | os: Visual Studio 2017 11 | platform: 12 | - x86 13 | - x64 14 | 15 | install: 16 | # nvs 17 | - git clone --branch v%NVS_VERSION% --depth 1 https://github.com/jasongin/nvs %LOCALAPPDATA%\nvs 18 | - set PATH=%LOCALAPPDATA%\nvs;%PATH% 19 | - nvs --version 20 | # node.js 21 | - nvs add %NODEJS_VERSION%/%PLATFORM% 22 | - nvs use %NODEJS_VERSION%/%PLATFORM% 23 | - node --version 24 | - node -p process.arch 25 | - npm --version 26 | # app 27 | - npm install 28 | 29 | test_script: 30 | - npm test 31 | 32 | build: off 33 | 34 | version: "{build}" 35 | 36 | cache: 37 | - node_modules 38 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## Running the benchmarks 4 | 5 | From the parent directory, run 6 | 7 | ```bash 8 | npm run-script benchmark 9 | ``` 10 | 11 | The above script supports the following arguments: 12 | 13 | * `--benchmarks=...`: A semicolon-separated list of benchmark names. These names 14 | will be mapped to file names in this directory by appending `.js`. 15 | 16 | ## Adding benchmarks 17 | 18 | The steps below should be followed when adding new benchmarks. 19 | 20 | 0. Decide on a name for the benchmark. This name will be used in several places. 21 | This example will use the name `new_benchmark`. 22 | 23 | 0. Create files `new_benchmark.cc` and `new_benchmark.js` in this directory. 24 | 25 | 0. Copy an existing benchmark in `binding.gyp` and change the target name prefix 26 | and the source file name to `new_benchmark`. This should result in two new 27 | targets which look like this: 28 | 29 | ```gyp 30 | { 31 | 'target_name': 'new_benchmark', 32 | 'sources': [ 'new_benchmark.cc' ], 33 | 'includes': [ '../except.gypi' ], 34 | }, 35 | { 36 | 'target_name': 'new_benchmark_noexcept', 37 | 'sources': [ 'new_benchmark.cc' ], 38 | 'includes': [ '../noexcept.gypi' ], 39 | }, 40 | ``` 41 | 42 | There should always be a pair of targets: one bearing the name of the 43 | benchmark and configured with C++ exceptions enabled, and one bearing the 44 | same name followed by the suffix `_noexcept` and configured with C++ 45 | exceptions disabled. This will ensure that the benchmark can be written to 46 | cover both the case where C++ exceptions are enabled and the case where they 47 | are disabled. 48 | -------------------------------------------------------------------------------- /benchmark/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'target_defaults': { 'includes': ['../common.gypi'] }, 3 | 'targets': [ 4 | { 5 | 'target_name': 'function_args', 6 | 'sources': [ 'function_args.cc' ], 7 | 'includes': [ '../except.gypi' ], 8 | }, 9 | { 10 | 'target_name': 'function_args_noexcept', 11 | 'sources': [ 'function_args.cc' ], 12 | 'includes': [ '../noexcept.gypi' ], 13 | }, 14 | { 15 | 'target_name': 'property_descriptor', 16 | 'sources': [ 'property_descriptor.cc' ], 17 | 'includes': [ '../except.gypi' ], 18 | }, 19 | { 20 | 'target_name': 'property_descriptor_noexcept', 21 | 'sources': [ 'property_descriptor.cc' ], 22 | 'includes': [ '../noexcept.gypi' ], 23 | }, 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /benchmark/function_args.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Benchmark = require('benchmark'); 3 | const addonName = path.basename(__filename, '.js'); 4 | 5 | [addonName, addonName + '_noexcept'] 6 | .forEach((addonName) => { 7 | const rootAddon = require('bindings')({ 8 | bindings: addonName, 9 | module_root: __dirname 10 | }); 11 | delete rootAddon.path; 12 | const implems = Object.keys(rootAddon); 13 | const maxNameLength = 14 | implems.reduce((soFar, value) => Math.max(soFar, value.length), 0); 15 | const anObject = {}; 16 | 17 | console.log(`\n${addonName}: `); 18 | 19 | console.log('no arguments:'); 20 | implems.reduce((suite, implem) => { 21 | const fn = rootAddon[implem].noArgFunction; 22 | return suite.add(implem.padStart(maxNameLength, ' '), () => fn()); 23 | }, new Benchmark.Suite()) 24 | .on('cycle', (event) => console.log(String(event.target))) 25 | .run(); 26 | 27 | console.log('one argument:'); 28 | implems.reduce((suite, implem) => { 29 | const fn = rootAddon[implem].oneArgFunction; 30 | return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x')); 31 | }, new Benchmark.Suite()) 32 | .on('cycle', (event) => console.log(String(event.target))) 33 | .run(); 34 | 35 | console.log('two arguments:'); 36 | implems.reduce((suite, implem) => { 37 | const fn = rootAddon[implem].twoArgFunction; 38 | return suite.add(implem.padStart(maxNameLength, ' '), () => fn('x', 12)); 39 | }, new Benchmark.Suite()) 40 | .on('cycle', (event) => console.log(String(event.target))) 41 | .run(); 42 | 43 | console.log('three arguments:'); 44 | implems.reduce((suite, implem) => { 45 | const fn = rootAddon[implem].threeArgFunction; 46 | return suite.add(implem.padStart(maxNameLength, ' '), 47 | () => fn('x', 12, true)); 48 | }, new Benchmark.Suite()) 49 | .on('cycle', (event) => console.log(String(event.target))) 50 | .run(); 51 | 52 | console.log('four arguments:'); 53 | implems.reduce((suite, implem) => { 54 | const fn = rootAddon[implem].fourArgFunction; 55 | return suite.add(implem.padStart(maxNameLength, ' '), 56 | () => fn('x', 12, true, anObject)); 57 | }, new Benchmark.Suite()) 58 | .on('cycle', (event) => console.log(String(event.target))) 59 | .run(); 60 | }); 61 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { readdirSync } = require('fs'); 4 | const { spawnSync } = require('child_process'); 5 | const path = require('path'); 6 | 7 | let benchmarks = []; 8 | 9 | if (process.env.npm_config_benchmarks) { 10 | benchmarks = process.env.npm_config_benchmarks 11 | .split(';') 12 | .map((item) => (item + '.js')); 13 | } 14 | 15 | // Run each file in this directory or the list given on the command line except 16 | // index.js as a Node.js process. 17 | (benchmarks.length > 0 ? benchmarks : readdirSync(__dirname)) 18 | .filter((item) => (item !== 'index.js' && item.match(/\.js$/))) 19 | .map((item) => path.join(__dirname, item)) 20 | .forEach((item) => { 21 | const child = spawnSync(process.execPath, [ 22 | '--expose-gc', 23 | item 24 | ], { stdio: 'inherit' }); 25 | if (child.signal) { 26 | console.error(`Tests aborted with ${child.signal}`); 27 | process.exitCode = 1; 28 | } else { 29 | process.exitCode = child.status; 30 | } 31 | if (child.status !== 0) { 32 | process.exit(process.exitCode); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /benchmark/property_descriptor.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Benchmark = require('benchmark'); 3 | const addonName = path.basename(__filename, '.js'); 4 | 5 | [addonName, addonName + '_noexcept'] 6 | .forEach((addonName) => { 7 | const rootAddon = require('bindings')({ 8 | bindings: addonName, 9 | module_root: __dirname 10 | }); 11 | delete rootAddon.path; 12 | const getters = new Benchmark.Suite(); 13 | const setters = new Benchmark.Suite(); 14 | const maxNameLength = Object.keys(rootAddon) 15 | .reduce((soFar, value) => Math.max(soFar, value.length), 0); 16 | 17 | console.log(`\n${addonName}: `); 18 | 19 | Object.keys(rootAddon).forEach((key) => { 20 | getters.add(`${key} getter`.padStart(maxNameLength + 7), () => { 21 | // eslint-disable-next-line no-unused-vars 22 | const x = rootAddon[key]; 23 | }); 24 | setters.add(`${key} setter`.padStart(maxNameLength + 7), () => { 25 | rootAddon[key] = 5; 26 | }); 27 | }); 28 | 29 | getters 30 | .on('cycle', (event) => console.log(String(event.target))) 31 | .run(); 32 | 33 | console.log(''); 34 | 35 | setters 36 | .on('cycle', (event) => console.log(String(event.target))) 37 | .run(); 38 | }); 39 | -------------------------------------------------------------------------------- /common.gypi: -------------------------------------------------------------------------------- 1 | { 2 | 'variables': { 3 | 'NAPI_VERSION%': "().ValueOf(); 68 | ``` 69 | -------------------------------------------------------------------------------- /doc/external_buffer.md: -------------------------------------------------------------------------------- 1 | # External Buffer 2 | 3 | **Some runtimes other than Node.js have dropped support for external buffers**. 4 | On runtimes other than Node.js, node-api methods may return 5 | `napi_no_external_buffers_allowed` to indicate that external 6 | buffers are not supported. One such runtime is Electron as 7 | described in this issue 8 | [electron/issues/35801](https://github.com/electron/electron/issues/35801). 9 | 10 | In order to maintain broadest compatibility with all runtimes, 11 | you may define `NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED` in your addon before 12 | includes for the node-api and node-addon-api headers. Doing so will hide the 13 | functions that create external buffers. This will ensure a compilation error 14 | occurs if you accidentally use one of these methods. 15 | 16 | In node-addon-api, the `Napi::Buffer::NewOrCopy` provides a convenient way to 17 | create an external buffer, or allocate a new buffer and copy the data when the 18 | external buffer is not supported. 19 | -------------------------------------------------------------------------------- /doc/generator.md: -------------------------------------------------------------------------------- 1 | # Generator 2 | 3 | ## What is generator 4 | 5 | **[generator-napi-module](https://www.npmjs.com/package/generator-napi-module)** is a module to quickly generate a skeleton module using 6 | **Node-API**, the new API for Native addons. This module automatically sets up your 7 | **gyp file** to use **node-addon-api**, the C++ wrappers for Node-API and generates 8 | a wrapper JS module. Optionally, it can even configure the generated project to 9 | use **TypeScript** instead. 10 | 11 | ## **generator-napi-module** reference 12 | 13 | - [Installation and usage](https://www.npmjs.com/package/generator-napi-module#installation) 14 | -------------------------------------------------------------------------------- /doc/handle_scope.md: -------------------------------------------------------------------------------- 1 | # HandleScope 2 | 3 | The HandleScope class is used to manage the lifetime of object handles 4 | which are created through the use of node-addon-api. These handles 5 | keep an object alive in the heap in order to ensure that the objects 6 | are not collected while native code is using them. 7 | A handle may be created when any new node-addon-api Value or one 8 | of its subclasses is created or returned. For more details refer to 9 | the section titled [Object lifetime management](object_lifetime_management.md). 10 | 11 | ## Methods 12 | 13 | ### Constructor 14 | 15 | Creates a new handle scope on the stack. 16 | 17 | ```cpp 18 | Napi::HandleScope::HandleScope(Napi::Env env); 19 | ``` 20 | 21 | - `[in] env`: The environment in which to construct the `Napi::HandleScope` object. 22 | 23 | Returns a new `Napi::HandleScope` 24 | 25 | ### Constructor 26 | 27 | Creates a new handle scope on the stack. 28 | 29 | ```cpp 30 | Napi::HandleScope::HandleScope(Napi::Env env, Napi::HandleScope scope); 31 | ``` 32 | 33 | - `[in] env`: `Napi::Env` in which the scope passed in was created. 34 | - `[in] scope`: pre-existing `Napi::HandleScope`. 35 | 36 | Returns a new `Napi::HandleScope` instance which wraps the `napi_handle_scope` 37 | handle passed in. This can be used to mix usage of the C Node-API 38 | and node-addon-api. 39 | 40 | ```cpp 41 | operator Napi::HandleScope::napi_handle_scope() const 42 | ``` 43 | 44 | Returns the Node-API `napi_handle_scope` wrapped by the `Napi::EscapableHandleScope` object. 45 | This can be used to mix usage of the C Node-API and node-addon-api by allowing 46 | the class to be used be converted to a `napi_handle_scope`. 47 | 48 | ### Destructor 49 | ```cpp 50 | Napi::HandleScope::~HandleScope(); 51 | ``` 52 | 53 | Deletes the `Napi::HandleScope` instance and allows any objects/handles created 54 | in the scope to be collected by the garbage collector. There is no 55 | guarantee as to when the garbage collector will do this. 56 | 57 | ### Env 58 | 59 | ```cpp 60 | Napi::Env Napi::HandleScope::Env() const; 61 | ``` 62 | 63 | Returns the `Napi::Env` associated with the `Napi::HandleScope`. 64 | 65 | ## Example 66 | 67 | ```cpp 68 | for (int i = 0; i < LOOP_MAX; i++) { 69 | Napi::HandleScope scope(info.Env()); 70 | std::string name = std::string("inner-scope") + std::to_string(i); 71 | Napi::Value newValue = Napi::String::New(info.Env(), name.c_str()); 72 | // do something with newValue 73 | }; 74 | ``` 75 | 76 | For more details refer to the section titled [Object lifetime 77 | management](object_lifetime_management.md). 78 | -------------------------------------------------------------------------------- /doc/maybe.md: -------------------------------------------------------------------------------- 1 | # Maybe (template) 2 | 3 | Class `Napi::Maybe` represents a value that may be empty: every `Maybe` is 4 | either `Just` and contains a value, or `Nothing`, and does not. `Maybe` types 5 | are very common in node-addon-api code, as they represent that the function may 6 | throw a JavaScript exception and cause the program to be unable to evaluate any 7 | JavaScript code until the exception has been handled. 8 | 9 | Typically, the value wrapped in `Napi::Maybe` is [`Napi::Value`] and its 10 | subclasses. 11 | 12 | ## Methods 13 | 14 | ### IsNothing 15 | 16 | ```cpp 17 | template 18 | bool Napi::Maybe::IsNothing() const; 19 | ``` 20 | 21 | Returns `true` if the `Maybe` is `Nothing` and does not contain a value, and 22 | `false` otherwise. 23 | 24 | ### IsJust 25 | 26 | ```cpp 27 | template 28 | bool Napi::Maybe::IsJust() const; 29 | ``` 30 | 31 | Returns `true` if the `Maybe` is `Just` and contains a value, and `false` 32 | otherwise. 33 | 34 | ### Check 35 | 36 | ```cpp 37 | template 38 | void Napi::Maybe::Check() const; 39 | ``` 40 | 41 | Short-hand for `Maybe::Unwrap()`, which doesn't return a value. Could be used 42 | where the actual value of the Maybe is not needed like `Object::Set`. 43 | If this Maybe is nothing (empty), node-addon-api will crash the 44 | process. 45 | 46 | ### Unwrap 47 | 48 | ```cpp 49 | template 50 | T Napi::Maybe::Unwrap() const; 51 | ``` 52 | 53 | Return the value of type `T` contained in the Maybe. If this Maybe is 54 | nothing (empty), node-addon-api will crash the process. 55 | 56 | ### UnwrapOr 57 | 58 | ```cpp 59 | template 60 | T Napi::Maybe::UnwrapOr(const T& default_value) const; 61 | ``` 62 | 63 | Return the value of type T contained in the Maybe, or use a default 64 | value if this Maybe is nothing (empty). 65 | 66 | ### UnwrapTo 67 | 68 | ```cpp 69 | template 70 | bool Napi::Maybe::UnwrapTo(T* result) const; 71 | ``` 72 | 73 | Converts this Maybe to a value of type `T` in the `out`. If this Maybe is 74 | nothing (empty), `false` is returned and `out` is left untouched. 75 | 76 | [`Napi::Value`]: ./value.md 77 | -------------------------------------------------------------------------------- /doc/memory_management.md: -------------------------------------------------------------------------------- 1 | # MemoryManagement 2 | 3 | The `Napi::MemoryManagement` class contains functions that give the JavaScript engine 4 | an indication of the amount of externally allocated memory that is kept alive by 5 | JavaScript objects. 6 | 7 | ## Methods 8 | 9 | ### AdjustExternalMemory 10 | 11 | The function `AdjustExternalMemory` adjusts the amount of registered external 12 | memory used to give the JavaScript engine an indication of the amount of externally 13 | allocated memory that is kept alive by JavaScript objects. 14 | The JavaScript engine uses this to decide when to perform global garbage collections. 15 | Registering externally allocated memory will trigger global garbage collections 16 | more often than it would otherwise in an attempt to garbage collect the JavaScript 17 | objects that keep the externally allocated memory alive. 18 | 19 | ```cpp 20 | static int64_t Napi::MemoryManagement::AdjustExternalMemory(Napi::Env env, int64_t change_in_bytes); 21 | ``` 22 | 23 | - `[in] env`: The environment in which the API is invoked under. 24 | - `[in] change_in_bytes`: The change in externally allocated memory that is kept 25 | alive by JavaScript objects expressed in bytes. 26 | 27 | Returns the adjusted memory value. 28 | -------------------------------------------------------------------------------- /doc/name.md: -------------------------------------------------------------------------------- 1 | # Name 2 | 3 | Class `Napi::Name` inherits from class [`Napi::Value`][]. 4 | 5 | Names are JavaScript values that can be used as a property name. There are two 6 | specialized types of names supported in Node.js Addon API [`Napi::String`](string.md) 7 | and [`Napi::Symbol`](symbol.md). 8 | 9 | ## Methods 10 | 11 | ### Constructor 12 | ```cpp 13 | Napi::Name::Name(); 14 | ``` 15 | 16 | Returns an empty `Napi::Name`. 17 | 18 | ```cpp 19 | Napi::Name::Name(napi_env env, napi_value value); 20 | ``` 21 | - `[in] env` - The environment in which to create the array. 22 | - `[in] value` - The primitive to wrap. 23 | 24 | Returns a `Napi::Name` created from the JavaScript primitive. 25 | 26 | Note: 27 | The value is not coerced to a string. 28 | 29 | [`Napi::Value`]: ./value.md 30 | -------------------------------------------------------------------------------- /doc/prebuild_tools.md: -------------------------------------------------------------------------------- 1 | # Prebuild tools 2 | 3 | The distribution of a native add-on is just as important as its implementation. 4 | In order to install a native add-on it's important to have all the necessary 5 | dependencies installed and well configured (see the [setup](setup.md) section). 6 | The end-user will need to compile the add-on when they will do an `npm install` 7 | and in some cases this could create problems. To avoid the compilation process it's 8 | possible to distribute the native add-on in pre-built form for different platform 9 | and architectures. The prebuild tools help to create and distribute the pre-built 10 | form of a native add-on. 11 | 12 | The following list report known tools that are compatible with **Node-API**: 13 | 14 | - **[node-pre-gyp](https://www.npmjs.com/package/node-pre-gyp)** 15 | - **[prebuild](https://www.npmjs.com/package/prebuild)** 16 | - **[prebuildify](https://www.npmjs.com/package/prebuildify)** 17 | -------------------------------------------------------------------------------- /doc/promises.md: -------------------------------------------------------------------------------- 1 | # Promise 2 | 3 | Class `Napi::Promise` inherits from class [`Napi::Object`][]. 4 | 5 | The `Napi::Promise` class, along with its `Napi::Promise::Deferred` class, implement the ability to create, resolve, and reject Promise objects. 6 | 7 | The basic approach is to create a `Napi::Promise::Deferred` object and return to your caller the value returned by the `Napi::Promise::Deferred::Promise` method. For example: 8 | 9 | ```cpp 10 | Napi::Value YourFunction(const Napi::CallbackInfo& info) { 11 | // your code goes here... 12 | Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(info.Env()); 13 | // deferred needs to survive this call... 14 | return deferred.Promise(); 15 | } 16 | ``` 17 | 18 | Later, when the asynchronous process completes, call either the `Resolve` or `Reject` method on the `Napi::Promise::Deferred` object created earlier: 19 | 20 | ```cpp 21 | deferred.Resolve(String::New(info.Env(), "OK")); 22 | ``` 23 | 24 | ## Promise::Deferred Methods 25 | 26 | ### Factory Method 27 | 28 | ```cpp 29 | static Napi::Promise::Deferred Napi::Promise::Deferred::New(napi_env env); 30 | ``` 31 | 32 | * `[in] env`: The `napi_env` environment in which to create the `Napi::Promise::Deferred` object. 33 | 34 | ### Constructor 35 | 36 | ```cpp 37 | Napi::Promise::Deferred(napi_env env); 38 | ``` 39 | 40 | * `[in] env`: The `napi_env` environment in which to construct the `Napi::Promise::Deferred` object. 41 | 42 | ### Env 43 | 44 | ```cpp 45 | Napi::Env Napi::Promise::Deferred::Env() const; 46 | ``` 47 | 48 | Returns the Env environment this `Napi::Promise::Deferred` object is associated with. 49 | 50 | ### Promise 51 | 52 | ```cpp 53 | Napi::Promise Napi::Promise::Deferred::Promise() const; 54 | ``` 55 | 56 | Returns the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. 57 | 58 | ### Resolve 59 | 60 | ```cpp 61 | void Napi::Promise::Deferred::Resolve(napi_value value) const; 62 | ``` 63 | 64 | Resolves the `Napi::Promise` object held by the `Napi::Promise::Deferred` object. 65 | 66 | * `[in] value`: The Node-API primitive value with which to resolve the `Napi::Promise`. 67 | 68 | ### Reject 69 | 70 | ```cpp 71 | void Napi::Promise::Deferred::Reject(napi_value value) const; 72 | ``` 73 | 74 | Rejects the Promise object held by the `Napi::Promise::Deferred` object. 75 | 76 | * `[in] value`: The Node-API primitive value with which to reject the `Napi::Promise`. 77 | 78 | 79 | [`Napi::Object`]: ./object.md 80 | -------------------------------------------------------------------------------- /doc/propertylvalue.md: -------------------------------------------------------------------------------- 1 | # PropertyLValue 2 | 3 | The `Napi::Object::PropertyLValue` class is a helper class provided by 4 | `Napi::Object` to allow more intuitive assignment of properties. 5 | 6 | ## Example 7 | ```cpp 8 | #include 9 | 10 | using namespace Napi; 11 | 12 | Void Init(Env env) { 13 | // Create a new instance 14 | Object obj = Object::New(env); 15 | 16 | // Assign a value to a property. 17 | obj["hello"] = "world"; 18 | } 19 | ``` 20 | 21 | In the above example, `obj["hello"]` returns a `Napi::Object::PropertyLValue` 22 | whose `operator=()` method accepts a string which will become the value of the 23 | "hello" property of the newly created object. 24 | 25 | In general, `obj[key] = value` is the equivalent of `obj.Set(key, value)`, where 26 | the types of `key` and `value` are all those supported by 27 | [`Napi::Object::Set()`](object.md#set). 28 | 29 | ## Methods 30 | 31 | ### operator Value() 32 | 33 | ```cpp 34 | operator Value() const; 35 | ``` 36 | 37 | Implicitly casts this `Napi::Object::PropertyLValue` to a `Napi::Value`. 38 | 39 | ### operator =() 40 | 41 | ```cpp 42 | template 43 | PropertyLValue& operator =(ValueType value); 44 | ``` 45 | 46 | * `[in] value` a value to assign to the property referred to by the 47 | `Napi::Object::PropertyLValue`. The type of the value is one of the types 48 | supported by the second parameter of [`Napi::Object::Set()`](object.md#set). 49 | 50 | Returns a self-reference. 51 | -------------------------------------------------------------------------------- /doc/range_error.md: -------------------------------------------------------------------------------- 1 | # RangeError 2 | 3 | The `Napi::RangeError` class is a representation of the JavaScript `RangeError` that is 4 | thrown when trying to pass a value as an argument to a function that does not allow 5 | a range that includes the value. 6 | 7 | The `Napi::RangeError` class inherits its behaviors from the `Napi::Error` class (for 8 | more info see: [`Napi::Error`](error.md)). 9 | 10 | For more details about error handling refer to the section titled [Error handling](error_handling.md). 11 | 12 | ## Methods 13 | 14 | ### New 15 | 16 | Creates a new instance of a `Napi::RangeError` object. 17 | 18 | ```cpp 19 | Napi::RangeError::New(Napi::Env env, const char* message); 20 | ``` 21 | 22 | - `[in] Env`: The environment in which to construct the `Napi::RangeError` object. 23 | - `[in] message`: Null-terminated string to be used as the message for the `Napi::RangeError`. 24 | 25 | Returns an instance of a `Napi::RangeError` object. 26 | 27 | ### New 28 | 29 | Creates a new instance of a `Napi::RangeError` object. 30 | 31 | ```cpp 32 | Napi::RangeError::New(Napi::Env env, const std::string& message); 33 | ``` 34 | 35 | - `[in] Env`: The environment in which to construct the `Napi::RangeError` object. 36 | - `[in] message`: Reference string to be used as the message for the `Napi::RangeError`. 37 | 38 | Returns an instance of a `Napi::RangeError` object. 39 | 40 | ### Constructor 41 | 42 | Creates a new empty instance of a `Napi::RangeError`. 43 | 44 | ```cpp 45 | Napi::RangeError::RangeError(); 46 | ``` 47 | 48 | ### Constructor 49 | 50 | Initializes a `Napi::RangeError` instance from an existing Javascript error object. 51 | 52 | ```cpp 53 | Napi::RangeError::RangeError(napi_env env, napi_value value); 54 | ``` 55 | 56 | - `[in] Env`: The environment in which to construct the `Napi::RangeError` object. 57 | - `[in] value`: The `Napi::Error` reference to wrap. 58 | 59 | Returns an instance of a `Napi::RangeError` object. 60 | -------------------------------------------------------------------------------- /doc/symbol.md: -------------------------------------------------------------------------------- 1 | # Symbol 2 | 3 | Class `Napi::Symbol` inherits from class [`Napi::Name`][]. 4 | 5 | ## Methods 6 | 7 | ### Constructor 8 | 9 | Instantiates a new `Napi::Symbol` value. 10 | 11 | ```cpp 12 | Napi::Symbol::Symbol(); 13 | ``` 14 | 15 | Returns a new empty `Napi::Symbol`. 16 | 17 | ### New 18 | ```cpp 19 | Napi::Symbol::New(napi_env env, const std::string& description); 20 | Napi::Symbol::New(napi_env env, const char* description); 21 | Napi::Symbol::New(napi_env env, Napi::String description); 22 | Napi::Symbol::New(napi_env env, napi_value description); 23 | ``` 24 | 25 | - `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. 26 | - `[in] value`: The C++ primitive which represents the description hint for the `Napi::Symbol`. 27 | `description` may be any of: 28 | - `std::string&` - UTF8 string description. 29 | - `const char*` - represents a UTF8 string description. 30 | - `String` - Node addon API String description. 31 | - `napi_value` - Node-API `napi_value` description. 32 | 33 | If an error occurs, a `Napi::Error` will get thrown. If C++ exceptions are not 34 | being used, callers should check the result of `Napi::Env::IsExceptionPending` before 35 | attempting to use the returned value. 36 | 37 | ### WellKnown 38 | ```cpp 39 | static Napi::Symbol Napi::Symbol::WellKnown(napi_env env, const std::string& name); 40 | ``` 41 | 42 | - `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. 43 | - `[in] name`: The C++ string representing the `Napi::Symbol` to retrieve. 44 | 45 | Returns a `Napi::Symbol` representing a well-known `Symbol` from the 46 | `Symbol` registry. 47 | 48 | ### For 49 | ```cpp 50 | static Napi::Symbol Napi::Symbol::For(napi_env env, const std::string& description); 51 | static Napi::Symbol Napi::Symbol::For(napi_env env, const char* description); 52 | static Napi::Symbol Napi::Symbol::For(napi_env env, String description); 53 | static Napi::Symbol Napi::Symbol::For(napi_env env, napi_value description); 54 | ``` 55 | 56 | - `[in] env`: The `napi_env` environment in which to construct the `Napi::Symbol` object. 57 | - `[in] description`: The C++ string representing the `Napi::Symbol` in the global registry to retrieve. 58 | 59 | Searches in the global registry for existing symbol with the given name. If the symbol already exist it will be returned, otherwise a new symbol will be created in the registry. It's equivalent to Symbol.for() called from JavaScript. 60 | 61 | [`Napi::Name`]: ./name.md -------------------------------------------------------------------------------- /doc/syntax_error.md: -------------------------------------------------------------------------------- 1 | # SyntaxError 2 | 3 | The `Napi::SyntaxError` class is a representation of the JavaScript 4 | `SyntaxError` that is thrown when the engine encounters tokens or token order 5 | that does not conform to the syntax of the language when parsing code. 6 | 7 | The `Napi::SyntaxError` class inherits its behaviors from the `Napi::Error` 8 | class (for more info see: [`Napi::Error`](error.md)). 9 | 10 | For more details about error handling refer to the section titled [Error 11 | handling](error_handling.md). 12 | 13 | ## Methods 14 | 15 | ### New 16 | 17 | Creates a new instance of a `Napi::SyntaxError` object. 18 | 19 | ```cpp 20 | Napi::SyntaxError::New(Napi::Env env, const char* message); 21 | ``` 22 | 23 | - `[in] Env`: The environment in which to construct the `Napi::SyntaxError` 24 | object. 25 | - `[in] message`: Null-terminated string to be used as the message for the 26 | `Napi::SyntaxError`. 27 | 28 | Returns an instance of a `Napi::SyntaxError` object. 29 | 30 | ### New 31 | 32 | Creates a new instance of a `Napi::SyntaxError` object. 33 | 34 | ```cpp 35 | Napi::SyntaxError::New(Napi::Env env, const std::string& message); 36 | ``` 37 | 38 | - `[in] Env`: The environment in which to construct the `Napi::SyntaxError` 39 | object. 40 | - `[in] message`: Reference string to be used as the message for the 41 | `Napi::SyntaxError`. 42 | 43 | Returns an instance of a `Napi::SyntaxError` object. 44 | 45 | ### Constructor 46 | 47 | Creates a new empty instance of a `Napi::SyntaxError`. 48 | 49 | ```cpp 50 | Napi::SyntaxError::SyntaxError(); 51 | ``` 52 | 53 | ### Constructor 54 | 55 | Initializes a `Napi::SyntaxError` instance from an existing Javascript error 56 | object. 57 | 58 | ```cpp 59 | Napi::SyntaxError::SyntaxError(napi_env env, napi_value value); 60 | ``` 61 | 62 | - `[in] Env`: The environment in which to construct the `Napi::SyntaxError` 63 | object. 64 | - `[in] value`: The `Napi::Error` reference to wrap. 65 | 66 | Returns an instance of a `Napi::SyntaxError` object. 67 | -------------------------------------------------------------------------------- /doc/type_error.md: -------------------------------------------------------------------------------- 1 | # TypeError 2 | 3 | The `Napi::TypeError` class is a representation of the JavaScript `TypeError` that is 4 | thrown when an operand or argument passed to a function is incompatible with the 5 | type expected by the operator or function. 6 | 7 | The `Napi::TypeError` class inherits its behaviors from the `Napi::Error` class (for more info 8 | see: [`Napi::Error`](error.md)). 9 | 10 | For more details about error handling refer to the section titled [Error handling](error_handling.md). 11 | 12 | ## Methods 13 | 14 | ### New 15 | 16 | Creates a new instance of the `Napi::TypeError` object. 17 | 18 | ```cpp 19 | Napi::TypeError::New(Napi::Env env, const char* message); 20 | ``` 21 | 22 | - `[in] Env`: The environment in which to construct the `Napi::TypeError` object. 23 | - `[in] message`: Null-terminated string to be used as the message for the `Napi::TypeError`. 24 | 25 | Returns an instance of a `Napi::TypeError` object. 26 | 27 | ### New 28 | 29 | Creates a new instance of a `Napi::TypeError` object. 30 | 31 | ```cpp 32 | Napi::TypeError::New(Napi::Env env, const std::string& message); 33 | ``` 34 | 35 | - `[in] Env`: The environment in which to construct the `Napi::TypeError` object. 36 | - `[in] message`: Reference string to be used as the message for the `Napi::TypeError`. 37 | 38 | Returns an instance of a `Napi::TypeError` object. 39 | 40 | ### Constructor 41 | 42 | Creates a new empty instance of a `Napi::TypeError`. 43 | 44 | ```cpp 45 | Napi::TypeError::TypeError(); 46 | ``` 47 | 48 | ### Constructor 49 | 50 | Initializes a `Napi::TypeError` instance from an existing JavaScript error object. 51 | 52 | ```cpp 53 | Napi::TypeError::TypeError(napi_env env, napi_value value); 54 | ``` 55 | 56 | - `[in] Env`: The environment in which to construct the `Napi::TypeError` object. 57 | - `[in] value`: The `Napi::Error` reference to wrap. 58 | 59 | Returns an instance of a `Napi::TypeError` object. 60 | -------------------------------------------------------------------------------- /doc/type_taggable.md: -------------------------------------------------------------------------------- 1 | # TypeTaggable 2 | 3 | Class `Napi::TypeTaggable` inherits from class [`Napi::Value`][]. 4 | 5 | The `Napi::TypeTaggable` class is the base class for [`Napi::Object`][] and 6 | [`Napi::External`][]. It adds type-tagging capabilities to both. It is an 7 | abstract-only base class. 8 | 9 | ### TypeTag() 10 | 11 | ```cpp 12 | void Napi::TypeTaggable::TypeTag(const napi_type_tag* type_tag) const; 13 | ``` 14 | 15 | - `[in] type_tag`: The tag with which this object or external is to be marked. 16 | 17 | The `Napi::TypeTaggable::TypeTag()` method associates the value of the 18 | `type_tag` pointer with this JavaScript object or external. 19 | `Napi::TypeTaggable::CheckTypeTag()` can then be used to compare the tag that 20 | was attached with one owned by the add-on to ensure that this object or external 21 | has the right type. 22 | 23 | ### CheckTypeTag() 24 | 25 | ```cpp 26 | bool Napi::TypeTaggable::CheckTypeTag(const napi_type_tag* type_tag) const; 27 | ``` 28 | 29 | - `[in] type_tag`: The tag with which to compare any tag found on this object or 30 | external. 31 | 32 | The `Napi::TypeTaggable::CheckTypeTag()` method compares the pointer given as 33 | `type_tag` with any that can be found on this JavaScript object or external. If 34 | no tag is found or if a tag is found but it does not match `type_tag`, then the 35 | return value is `false`. If a tag is found and it matches `type_tag`, then the 36 | return value is `true`. 37 | 38 | [`Napi::Value`]: ./value.md 39 | [`Napi::Object`]: ./object.md 40 | [`Napi::External`]: ./external.md 41 | -------------------------------------------------------------------------------- /doc/typed_array.md: -------------------------------------------------------------------------------- 1 | # TypedArray 2 | 3 | Class `Napi::TypedArray` inherits from class [`Napi::Object`][]. 4 | 5 | The `Napi::TypedArray` class corresponds to the 6 | [JavaScript `TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) 7 | class. 8 | 9 | ## Methods 10 | 11 | ### Constructor 12 | 13 | Initializes an empty instance of the `Napi::TypedArray` class. 14 | 15 | ```cpp 16 | Napi::TypedArray::TypedArray(); 17 | ``` 18 | 19 | ### Constructor 20 | 21 | Initializes a wrapper instance of an existing `Napi::TypedArray` instance. 22 | 23 | ```cpp 24 | Napi::TypedArray::TypedArray(napi_env env, napi_value value); 25 | ``` 26 | 27 | - `[in] env`: The environment in which to create the `Napi::TypedArray` instance. 28 | - `[in] value`: The `Napi::TypedArray` reference to wrap. 29 | 30 | ### TypedArrayType 31 | 32 | ```cpp 33 | napi_typedarray_type Napi::TypedArray::TypedArrayType() const; 34 | ``` 35 | 36 | Returns the type of this instance. 37 | 38 | ### ArrayBuffer 39 | 40 | ```cpp 41 | Napi::ArrayBuffer Napi::TypedArray::ArrayBuffer() const; 42 | ``` 43 | 44 | Returns the backing array buffer. 45 | 46 | ### ElementSize 47 | 48 | ```cpp 49 | uint8_t Napi::TypedArray::ElementSize() const; 50 | ``` 51 | 52 | Returns the size of one element, in bytes. 53 | 54 | ### ElementLength 55 | 56 | ```cpp 57 | size_t Napi::TypedArray::ElementLength() const; 58 | ``` 59 | 60 | Returns the number of elements. 61 | 62 | ### ByteOffset 63 | 64 | ```cpp 65 | size_t Napi::TypedArray::ByteOffset() const; 66 | ``` 67 | 68 | Returns the offset into the `Napi::ArrayBuffer` where the array starts, in bytes. 69 | 70 | ### ByteLength 71 | 72 | ```cpp 73 | size_t Napi::TypedArray::ByteLength() const; 74 | ``` 75 | 76 | Returns the length of the array, in bytes. 77 | 78 | [`Napi::Object`]: ./object.md 79 | -------------------------------------------------------------------------------- /doc/version_management.md: -------------------------------------------------------------------------------- 1 | # VersionManagement 2 | 3 | The `Napi::VersionManagement` class contains methods that allow information 4 | to be retrieved about the version of Node-API and Node.js. In some cases it is 5 | important to make decisions based on different versions of the system. 6 | 7 | ## Methods 8 | 9 | ### GetNapiVersion 10 | 11 | Retrieves the highest Node-API version supported by Node.js runtime. 12 | 13 | ```cpp 14 | static uint32_t Napi::VersionManagement::GetNapiVersion(Env env); 15 | ``` 16 | 17 | - `[in] env`: The environment in which the API is invoked under. 18 | 19 | Returns the highest Node-API version supported by Node.js runtime. 20 | 21 | ### GetNodeVersion 22 | 23 | Retrieves information about Node.js version present on the system. All the 24 | information is stored in the `napi_node_version` structure that is defined as 25 | shown below: 26 | 27 | ```cpp 28 | typedef struct { 29 | uint32_t major; 30 | uint32_t minor; 31 | uint32_t patch; 32 | const char* release; 33 | } napi_node_version; 34 | ```` 35 | 36 | ```cpp 37 | static const napi_node_version* Napi::VersionManagement::GetNodeVersion(Env env); 38 | ``` 39 | 40 | - `[in] env`: The environment in which the API is invoked under. 41 | 42 | Returns the structure a pointer to the structure `napi_node_version` populated by 43 | the version information of Node.js runtime. 44 | -------------------------------------------------------------------------------- /except.gypi: -------------------------------------------------------------------------------- 1 | { 2 | 'defines': [ 'NAPI_CPP_EXCEPTIONS' ], 3 | 'cflags!': [ '-fno-exceptions' ], 4 | 'cflags_cc!': [ '-fno-exceptions' ], 5 | 'conditions': [ 6 | ["OS=='win'", { 7 | "defines": [ 8 | "_HAS_EXCEPTIONS=1" 9 | ], 10 | "msvs_settings": { 11 | "VCCLCompilerTool": { 12 | "ExceptionHandling": 1, 13 | 'EnablePREfast': 'true', 14 | }, 15 | }, 16 | }], 17 | ["OS=='mac'", { 18 | 'xcode_settings': { 19 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 20 | 'CLANG_CXX_LIBRARY': 'libc++', 21 | 'MACOSX_DEPLOYMENT_TARGET': '10.7', 22 | }, 23 | }], 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const includeDir = path.relative('.', __dirname); 4 | 5 | module.exports = { 6 | include: `"${__dirname}"`, // deprecated, can be removed as part of 4.0.0 7 | include_dir: includeDir, 8 | gyp: path.join(includeDir, 'node_api.gyp:nothing'), 9 | isNodeApiBuiltin: true, 10 | needsFlag: false 11 | }; 12 | -------------------------------------------------------------------------------- /node_api.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'nothing', 5 | 'type': 'static_library', 6 | 'sources': [ 'nothing.c' ] 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /noexcept.gypi: -------------------------------------------------------------------------------- 1 | { 2 | 'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ], 3 | 'cflags': [ '-fno-exceptions' ], 4 | 'cflags_cc': [ '-fno-exceptions' ], 5 | 'conditions': [ 6 | ["OS=='win'", { 7 | # _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi 8 | #"defines": [ 9 | # "_HAS_EXCEPTIONS=0" 10 | #], 11 | "msvs_settings": { 12 | "VCCLCompilerTool": { 13 | 'ExceptionHandling': 0, 14 | 'EnablePREfast': 'true', 15 | }, 16 | }, 17 | }], 18 | ["OS=='mac'", { 19 | 'xcode_settings': { 20 | 'CLANG_CXX_LIBRARY': 'libc++', 21 | 'MACOSX_DEPLOYMENT_TARGET': '10.7', 22 | 'GCC_ENABLE_CPP_EXCEPTIONS': 'NO', 23 | }, 24 | }], 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /nothing.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnfrench3/node-addon-api-git/655c783f545a44479ef30d311583bd01252eb34e/nothing.c -------------------------------------------------------------------------------- /package-support.json: -------------------------------------------------------------------------------- 1 | { 2 | "versions": [ 3 | { 4 | "version": "*", 5 | "target": { 6 | "node": "active" 7 | }, 8 | "response": { 9 | "type": "time-permitting", 10 | "paid": false, 11 | "contact": { 12 | "name": "node-addon-api team", 13 | "url": "https://github.com/nodejs/node-addon-api/issues" 14 | } 15 | }, 16 | "backing": [ { "project": "https://github.com/nodejs" }, 17 | { "foundation": "https://openjsf.org/" } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | src/ 3 | -------------------------------------------------------------------------------- /test/addon.cc: -------------------------------------------------------------------------------- 1 | #if (NAPI_VERSION > 5) 2 | #include 3 | #include "napi.h" 4 | 5 | namespace { 6 | 7 | class TestAddon : public Napi::Addon { 8 | public: 9 | inline TestAddon(Napi::Env env, Napi::Object exports) { 10 | DefineAddon( 11 | exports, 12 | {InstanceMethod("increment", &TestAddon::Increment), 13 | InstanceValue( 14 | "subObject", 15 | DefineProperties( 16 | Napi::Object::New(env), 17 | {InstanceMethod("decrement", &TestAddon::Decrement)}))}); 18 | } 19 | 20 | ~TestAddon() { fprintf(stderr, "TestAddon::~TestAddon\n"); } 21 | 22 | private: 23 | Napi::Value Increment(const Napi::CallbackInfo& info) { 24 | return Napi::Number::New(info.Env(), ++value); 25 | } 26 | 27 | Napi::Value Decrement(const Napi::CallbackInfo& info) { 28 | return Napi::Number::New(info.Env(), --value); 29 | } 30 | 31 | uint32_t value = 42; 32 | }; 33 | 34 | Napi::Value CreateAddon(const Napi::CallbackInfo& info) { 35 | return TestAddon::Init(info.Env(), Napi::Object::New(info.Env())); 36 | } 37 | 38 | } // end of anonymous namespace 39 | 40 | Napi::Object InitAddon(Napi::Env env) { 41 | return Napi::Function::New(env, "CreateAddon"); 42 | } 43 | 44 | #endif // (NAPI_VERSION > 5) 45 | -------------------------------------------------------------------------------- /test/addon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./common').runTestInChildProcess({ 4 | suite: 'addon', 5 | testName: 'workingCode', 6 | expectedStderr: ['TestAddon::~TestAddon'] 7 | }); 8 | -------------------------------------------------------------------------------- /test/addon_build/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { promisify } = require('util'); 4 | const exec = promisify(require('child_process').exec); 5 | const { copy, remove } = require('fs-extra'); 6 | const path = require('path'); 7 | const assert = require('assert'); 8 | 9 | const ADDONS_FOLDER = path.join(__dirname, 'addons'); 10 | 11 | const addons = [ 12 | 'echo addon', 13 | 'echo-addon' 14 | ]; 15 | 16 | async function beforeAll (addons) { 17 | console.log(' >Preparing native addons to build'); 18 | for (const addon of addons) { 19 | await remove(path.join(ADDONS_FOLDER, addon)); 20 | await copy(path.join(__dirname, 'tpl'), path.join(ADDONS_FOLDER, addon)); 21 | } 22 | } 23 | 24 | async function test (addon) { 25 | console.log(` >Building addon: '${addon}'`); 26 | await exec('npm install', { 27 | cwd: path.join(ADDONS_FOLDER, addon) 28 | }); 29 | console.log(` >Running test for: '${addon}'`); 30 | // Disabled the checks on stderr and stdout because of this issue on npm: 31 | // Stop using process.umask(): https://github.com/npm/cli/issues/1103 32 | // We should enable the following checks again after the resolution of 33 | // the reported issue. 34 | // assert.strictEqual(stderr, ''); 35 | // assert.ok(stderr.length === 0); 36 | // assert.ok(stdout.length > 0); 37 | const binding = require(`${ADDONS_FOLDER}/${addon}`); 38 | assert.strictEqual(binding.except.echo('except'), 'except'); 39 | assert.strictEqual(binding.except.echo(101), 101); 40 | assert.strictEqual(binding.noexcept.echo('noexcept'), 'noexcept'); 41 | assert.strictEqual(binding.noexcept.echo(103), 103); 42 | } 43 | 44 | module.exports = (async function () { 45 | await beforeAll(addons); 46 | for (const addon of addons) { 47 | await test(addon); 48 | } 49 | })(); 50 | -------------------------------------------------------------------------------- /test/addon_build/tpl/.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /test/addon_build/tpl/addon.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::Value Echo(const Napi::CallbackInfo& info) { 4 | Napi::Env env = info.Env(); 5 | if (info.Length() != 1) { 6 | Napi::TypeError::New(env, 7 | "Wrong number of arguments. One argument expected.") 8 | .ThrowAsJavaScriptException(); 9 | } 10 | return info[0].As(); 11 | } 12 | 13 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 14 | exports.Set(Napi::String::New(env, "echo"), Napi::Function::New(env, Echo)); 15 | return exports; 16 | } 17 | 18 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) 19 | -------------------------------------------------------------------------------- /test/addon_build/tpl/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'target_defaults': { 3 | 'include_dirs': [ 4 | " { 12 | const test = binding.arraybuffer.createBuffer(); 13 | binding.arraybuffer.checkBuffer(test); 14 | assert.ok(test instanceof ArrayBuffer); 15 | 16 | const test2 = test.slice(0); 17 | binding.arraybuffer.checkBuffer(test2); 18 | }, 19 | 20 | 'External ArrayBuffer', 21 | () => { 22 | const test = binding.arraybuffer.createExternalBuffer(); 23 | binding.arraybuffer.checkBuffer(test); 24 | assert.ok(test instanceof ArrayBuffer); 25 | assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); 26 | }, 27 | 28 | () => assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()), 29 | 30 | 'External ArrayBuffer with finalizer', 31 | () => { 32 | const test = binding.arraybuffer.createExternalBufferWithFinalize(); 33 | binding.arraybuffer.checkBuffer(test); 34 | assert.ok(test instanceof ArrayBuffer); 35 | assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); 36 | }, 37 | 38 | () => assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()), 39 | 40 | 'External ArrayBuffer with finalizer hint', 41 | () => { 42 | const test = binding.arraybuffer.createExternalBufferWithFinalizeHint(); 43 | binding.arraybuffer.checkBuffer(test); 44 | assert.ok(test instanceof ArrayBuffer); 45 | assert.strictEqual(0, binding.arraybuffer.getFinalizeCount()); 46 | }, 47 | 48 | () => assert.strictEqual(1, binding.arraybuffer.getFinalizeCount()), 49 | 50 | 'ArrayBuffer with constructor', 51 | () => { 52 | assert.strictEqual(true, binding.arraybuffer.checkEmptyBuffer()); 53 | const test = binding.arraybuffer.createBufferWithConstructor(); 54 | binding.arraybuffer.checkBuffer(test); 55 | assert.ok(test instanceof ArrayBuffer); 56 | }, 57 | 58 | 'ArrayBuffer updates data pointer and length when detached', 59 | () => { 60 | // Detach the ArrayBuffer in JavaScript. 61 | // eslint-disable-next-line no-undef 62 | const mem = new WebAssembly.Memory({ initial: 1 }); 63 | binding.arraybuffer.checkDetachUpdatesData(mem.buffer, () => mem.grow(1)); 64 | 65 | // Let C++ detach the ArrayBuffer. 66 | const extBuffer = binding.arraybuffer.createExternalBuffer(); 67 | binding.arraybuffer.checkDetachUpdatesData(extBuffer); 68 | } 69 | ]); 70 | } 71 | -------------------------------------------------------------------------------- /test/async_context.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | namespace { 6 | 7 | static void MakeCallback(const CallbackInfo& info) { 8 | Function callback = info[0].As(); 9 | Object resource = info[1].As(); 10 | AsyncContext context(info.Env(), "async_context_test", resource); 11 | callback.MakeCallback( 12 | Object::New(info.Env()), std::initializer_list{}, context); 13 | } 14 | 15 | static void MakeCallbackNoResource(const CallbackInfo& info) { 16 | Function callback = info[0].As(); 17 | AsyncContext context(info.Env(), "async_context_no_res_test"); 18 | callback.MakeCallback( 19 | Object::New(info.Env()), std::initializer_list{}, context); 20 | } 21 | 22 | static Boolean AssertAsyncContextReturnCorrectEnv(const CallbackInfo& info) { 23 | AsyncContext context(info.Env(), "empty_context_test"); 24 | return Boolean::New(info.Env(), context.Env() == info.Env()); 25 | } 26 | } // end anonymous namespace 27 | 28 | Object InitAsyncContext(Env env) { 29 | Object exports = Object::New(env); 30 | exports["makeCallback"] = Function::New(env, MakeCallback); 31 | exports["makeCallbackNoResource"] = 32 | Function::New(env, MakeCallbackNoResource); 33 | exports["asyncCxtReturnCorrectEnv"] = 34 | Function::New(env, AssertAsyncContextReturnCorrectEnv); 35 | return exports; 36 | } 37 | -------------------------------------------------------------------------------- /test/async_worker_nocallback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('./common'); 4 | 5 | module.exports = common.runTest(test); 6 | 7 | async function test (binding) { 8 | await binding.asyncworker.doWorkAsyncResNoCallback(true, {}) 9 | .then(common.mustCall()).catch(common.mustNotCall()); 10 | 11 | await binding.asyncworker.doWorkAsyncResNoCallback(false, {}) 12 | .then(common.mustNotCall()).catch(common.mustCall()); 13 | 14 | await binding.asyncworker.doWorkNoCallback(false) 15 | .then(common.mustNotCall()).catch(common.mustCall()); 16 | 17 | await binding.asyncworker.doWorkNoCallback(true) 18 | .then(common.mustNotCall()).catch(common.mustCall()); 19 | } 20 | -------------------------------------------------------------------------------- /test/async_worker_persistent.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | // A variant of TestWorker wherein destruction is suppressed. That is, instances 4 | // are not destroyed during the `OnOK` callback. They must be explicitly 5 | // destroyed. 6 | 7 | using namespace Napi; 8 | 9 | namespace { 10 | 11 | class PersistentTestWorker : public AsyncWorker { 12 | public: 13 | static PersistentTestWorker* current_worker; 14 | static void DoWork(const CallbackInfo& info) { 15 | bool succeed = info[0].As(); 16 | Function cb = info[1].As(); 17 | 18 | PersistentTestWorker* worker = new PersistentTestWorker(cb, "TestResource"); 19 | current_worker = worker; 20 | 21 | worker->SuppressDestruct(); 22 | worker->_succeed = succeed; 23 | worker->Queue(); 24 | } 25 | 26 | static Value GetWorkerGone(const CallbackInfo& info) { 27 | return Boolean::New(info.Env(), current_worker == nullptr); 28 | } 29 | 30 | static void DeleteWorker(const CallbackInfo& info) { 31 | (void)info; 32 | delete current_worker; 33 | } 34 | 35 | ~PersistentTestWorker() { current_worker = nullptr; } 36 | 37 | protected: 38 | void Execute() override { 39 | if (!_succeed) { 40 | SetError("test error"); 41 | } 42 | } 43 | 44 | private: 45 | PersistentTestWorker(Function cb, const char* resource_name) 46 | : AsyncWorker(cb, resource_name) {} 47 | 48 | bool _succeed; 49 | }; 50 | 51 | PersistentTestWorker* PersistentTestWorker::current_worker = nullptr; 52 | 53 | } // end of anonymous namespace 54 | 55 | Object InitPersistentAsyncWorker(Env env) { 56 | Object exports = Object::New(env); 57 | exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork); 58 | exports.DefineProperty(PropertyDescriptor::Accessor( 59 | env, exports, "workerGone", PersistentTestWorker::GetWorkerGone)); 60 | exports["deleteWorker"] = 61 | Function::New(env, PersistentTestWorker::DeleteWorker); 62 | return exports; 63 | } 64 | -------------------------------------------------------------------------------- /test/async_worker_persistent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | function test (binding, succeed) { 6 | return new Promise((resolve) => 7 | // Can't pass an arrow function to doWork because that results in an 8 | // undefined context inside its body when the function gets called. 9 | binding.doWork(succeed, function (e) { 10 | setImmediate(() => { 11 | // If the work is supposed to fail, make sure there's an error. 12 | assert.strictEqual(succeed || e.message === 'test error', true); 13 | assert.strictEqual(binding.workerGone, false); 14 | binding.deleteWorker(); 15 | assert.strictEqual(binding.workerGone, true); 16 | resolve(); 17 | }); 18 | })); 19 | } 20 | 21 | module.exports = require('./common').runTest(async binding => { 22 | await test(binding.persistentasyncworker, false); 23 | await test(binding.persistentasyncworker, true); 24 | }); 25 | -------------------------------------------------------------------------------- /test/basic_types/array.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value CreateArray(const CallbackInfo& info) { 6 | if (info.Length() > 0) { 7 | size_t length = info[0].As().Uint32Value(); 8 | return Array::New(info.Env(), length); 9 | } else { 10 | return Array::New(info.Env()); 11 | } 12 | } 13 | 14 | Value GetLength(const CallbackInfo& info) { 15 | Array array = info[0].As(); 16 | return Number::New(info.Env(), static_cast(array.Length())); 17 | } 18 | 19 | Value GetElement(const CallbackInfo& info) { 20 | Array array = info[0].As(); 21 | size_t index = info[1].As().Uint32Value(); 22 | return array[index]; 23 | } 24 | 25 | void SetElement(const CallbackInfo& info) { 26 | Array array = info[0].As(); 27 | size_t index = info[1].As().Uint32Value(); 28 | array[index] = info[2].As(); 29 | } 30 | 31 | Object InitBasicTypesArray(Env env) { 32 | Object exports = Object::New(env); 33 | 34 | exports["createArray"] = Function::New(env, CreateArray); 35 | exports["getLength"] = Function::New(env, GetLength); 36 | exports["get"] = Function::New(env, GetElement); 37 | exports["set"] = Function::New(env, SetElement); 38 | 39 | return exports; 40 | } 41 | -------------------------------------------------------------------------------- /test/basic_types/array.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | module.exports = require('../common').runTest(test); 5 | 6 | function test (binding) { 7 | // create empty array 8 | const array = binding.basic_types_array.createArray(); 9 | assert.strictEqual(binding.basic_types_array.getLength(array), 0); 10 | 11 | // create array with length 12 | const arrayWithLength = binding.basic_types_array.createArray(10); 13 | assert.strictEqual(binding.basic_types_array.getLength(arrayWithLength), 10); 14 | 15 | // set function test 16 | binding.basic_types_array.set(array, 0, 10); 17 | binding.basic_types_array.set(array, 1, 'test'); 18 | binding.basic_types_array.set(array, 2, 3.0); 19 | 20 | // check length after set data 21 | assert.strictEqual(binding.basic_types_array.getLength(array), 3); 22 | 23 | // get function test 24 | assert.strictEqual(binding.basic_types_array.get(array, 0), 10); 25 | assert.strictEqual(binding.basic_types_array.get(array, 1), 'test'); 26 | assert.strictEqual(binding.basic_types_array.get(array, 2), 3.0); 27 | 28 | // overwrite test 29 | binding.basic_types_array.set(array, 0, 5); 30 | assert.strictEqual(binding.basic_types_array.get(array, 0), 5); 31 | 32 | // out of index test 33 | assert.strictEqual(binding.basic_types_array.get(array, 5), undefined); 34 | } 35 | -------------------------------------------------------------------------------- /test/basic_types/boolean.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value CreateBoolean(const CallbackInfo& info) { 6 | return Boolean::New(info.Env(), info[0].As().Value()); 7 | } 8 | 9 | Value CreateEmptyBoolean(const CallbackInfo& info) { 10 | Boolean* boolean = new Boolean(); 11 | return Boolean::New(info.Env(), boolean->IsEmpty()); 12 | } 13 | 14 | Value CreateBooleanFromExistingValue(const CallbackInfo& info) { 15 | Boolean boolean(info.Env(), info[0].As()); 16 | return Boolean::New(info.Env(), boolean.Value()); 17 | } 18 | 19 | Value CreateBooleanFromPrimitive(const CallbackInfo& info) { 20 | bool boolean = info[0].As(); 21 | return Boolean::New(info.Env(), boolean); 22 | } 23 | 24 | Value OperatorBool(const CallbackInfo& info) { 25 | Boolean boolean(info.Env(), info[0].As()); 26 | return Boolean::New(info.Env(), static_cast(boolean)); 27 | } 28 | 29 | Object InitBasicTypesBoolean(Env env) { 30 | Object exports = Object::New(env); 31 | 32 | exports["createBoolean"] = Function::New(env, CreateBoolean); 33 | exports["createEmptyBoolean"] = Function::New(env, CreateEmptyBoolean); 34 | exports["createBooleanFromExistingValue"] = 35 | Function::New(env, CreateBooleanFromExistingValue); 36 | exports["createBooleanFromPrimitive"] = 37 | Function::New(env, CreateBooleanFromPrimitive); 38 | exports["operatorBool"] = Function::New(env, OperatorBool); 39 | return exports; 40 | } 41 | -------------------------------------------------------------------------------- /test/basic_types/boolean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | const bool1 = binding.basic_types_boolean.createBoolean(true); 9 | assert.strictEqual(bool1, true); 10 | 11 | const bool2 = binding.basic_types_boolean.createBoolean(false); 12 | assert.strictEqual(bool2, false); 13 | 14 | const emptyBoolean = binding.basic_types_boolean.createEmptyBoolean(); 15 | assert.strictEqual(emptyBoolean, true); 16 | 17 | const bool3 = binding.basic_types_boolean.createBooleanFromExistingValue(true); 18 | assert.strictEqual(bool3, true); 19 | 20 | const bool4 = binding.basic_types_boolean.createBooleanFromExistingValue(false); 21 | assert.strictEqual(bool4, false); 22 | 23 | const bool5 = binding.basic_types_boolean.createBooleanFromPrimitive(true); 24 | assert.strictEqual(bool5, true); 25 | 26 | const bool6 = binding.basic_types_boolean.createBooleanFromPrimitive(false); 27 | assert.strictEqual(bool6, false); 28 | 29 | const bool7 = binding.basic_types_boolean.operatorBool(true); 30 | assert.strictEqual(bool7, true); 31 | 32 | const bool8 = binding.basic_types_boolean.operatorBool(false); 33 | assert.strictEqual(bool8, false); 34 | } 35 | -------------------------------------------------------------------------------- /test/bigint.cc: -------------------------------------------------------------------------------- 1 | #if (NAPI_VERSION > 5) 2 | 3 | #define NAPI_EXPERIMENTAL 4 | #include "napi.h" 5 | 6 | #include "test_helper.h" 7 | 8 | using namespace Napi; 9 | 10 | namespace { 11 | 12 | Value IsLossless(const CallbackInfo& info) { 13 | Env env = info.Env(); 14 | 15 | BigInt big = info[0].As(); 16 | bool is_signed = MaybeUnwrap(info[1].ToBoolean()).Value(); 17 | 18 | bool lossless; 19 | if (is_signed) { 20 | big.Int64Value(&lossless); 21 | } else { 22 | big.Uint64Value(&lossless); 23 | } 24 | 25 | return Boolean::New(env, lossless); 26 | } 27 | 28 | Value IsBigInt(const CallbackInfo& info) { 29 | Env env = info.Env(); 30 | 31 | BigInt big = info[0].As(); 32 | 33 | return Boolean::New(env, big.IsBigInt()); 34 | } 35 | 36 | Value TestInt64(const CallbackInfo& info) { 37 | bool lossless; 38 | int64_t input = info[0].As().Int64Value(&lossless); 39 | 40 | return BigInt::New(info.Env(), input); 41 | } 42 | 43 | Value TestUint64(const CallbackInfo& info) { 44 | bool lossless; 45 | uint64_t input = info[0].As().Uint64Value(&lossless); 46 | 47 | return BigInt::New(info.Env(), input); 48 | } 49 | 50 | Value TestWords(const CallbackInfo& info) { 51 | BigInt big = info[0].As(); 52 | 53 | size_t expected_word_count = big.WordCount(); 54 | 55 | int sign_bit; 56 | size_t word_count = 10; 57 | uint64_t words[10] = {0}; 58 | 59 | big.ToWords(&sign_bit, &word_count, words); 60 | 61 | if (word_count != expected_word_count) { 62 | Error::New(info.Env(), "word count did not match") 63 | .ThrowAsJavaScriptException(); 64 | return BigInt(); 65 | } 66 | 67 | return BigInt::New(info.Env(), sign_bit, word_count, words); 68 | } 69 | 70 | Value TestTooBigBigInt(const CallbackInfo& info) { 71 | int sign_bit = 0; 72 | size_t word_count = SIZE_MAX; 73 | uint64_t words[10] = {0}; 74 | 75 | return BigInt::New(info.Env(), sign_bit, word_count, words); 76 | } 77 | 78 | } // anonymous namespace 79 | 80 | Object InitBigInt(Env env) { 81 | Object exports = Object::New(env); 82 | exports["IsLossless"] = Function::New(env, IsLossless); 83 | exports["IsBigInt"] = Function::New(env, IsBigInt); 84 | exports["TestInt64"] = Function::New(env, TestInt64); 85 | exports["TestUint64"] = Function::New(env, TestUint64); 86 | exports["TestWords"] = Function::New(env, TestWords); 87 | exports["TestTooBigBigInt"] = Function::New(env, TestTooBigBigInt); 88 | 89 | return exports; 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /test/bigint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | const { 9 | TestInt64, 10 | TestUint64, 11 | TestWords, 12 | IsLossless, 13 | IsBigInt, 14 | TestTooBigBigInt 15 | } = binding.bigint; 16 | 17 | [ 18 | 0n, 19 | -0n, 20 | 1n, 21 | -1n, 22 | 100n, 23 | 2121n, 24 | -1233n, 25 | 986583n, 26 | -976675n, 27 | 98765432213456789876546896323445679887645323232436587988766545658n, 28 | -4350987086545760976737453646576078997096876957864353245245769809n 29 | ].forEach((num) => { 30 | if (num > -(2n ** 63n) && num < 2n ** 63n) { 31 | assert.strictEqual(TestInt64(num), num); 32 | assert.strictEqual(IsLossless(num, true), true); 33 | } else { 34 | assert.strictEqual(IsLossless(num, true), false); 35 | } 36 | 37 | if (num >= 0 && num < 2n ** 64n) { 38 | assert.strictEqual(TestUint64(num), num); 39 | assert.strictEqual(IsLossless(num, false), true); 40 | } else { 41 | assert.strictEqual(IsLossless(num, false), false); 42 | } 43 | 44 | assert.strictEqual(IsBigInt(num), true); 45 | 46 | assert.strictEqual(num, TestWords(num)); 47 | }); 48 | 49 | assert.throws(TestTooBigBigInt, { 50 | name: /^(RangeError|Error)$/, 51 | message: /^(Maximum BigInt size exceeded|Invalid argument)$/ 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/binding-swallowexcept.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Object InitError(Env env); 6 | 7 | Object Init(Env env, Object exports) { 8 | exports.Set("error", InitError(env)); 9 | return exports; 10 | } 11 | 12 | NODE_API_MODULE(addon, Init) 13 | -------------------------------------------------------------------------------- /test/buffer.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace test_buffer { 5 | 6 | const size_t testLength = 4; 7 | extern uint16_t testData[testLength]; 8 | extern int finalizeCount; 9 | 10 | template 11 | void InitData(T* data, size_t length) { 12 | for (size_t i = 0; i < length; i++) { 13 | data[i] = static_cast(i); 14 | } 15 | } 16 | 17 | template 18 | bool VerifyData(T* data, size_t length) { 19 | for (size_t i = 0; i < length; i++) { 20 | if (data[i] != static_cast(i)) { 21 | return false; 22 | } 23 | } 24 | return true; 25 | } 26 | } // namespace test_buffer 27 | -------------------------------------------------------------------------------- /test/buffer_new_or_copy-inl.h: -------------------------------------------------------------------------------- 1 | // Same tests on when NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED is defined or not 2 | // defined. 3 | 4 | Value CreateOrCopyExternalBuffer(const CallbackInfo& info) { 5 | finalizeCount = 0; 6 | 7 | InitData(testData, testLength); 8 | Buffer buffer = 9 | Buffer::NewOrCopy(info.Env(), testData, testLength); 10 | 11 | if (buffer.Length() != testLength) { 12 | Error::New(info.Env(), "Incorrect buffer length.") 13 | .ThrowAsJavaScriptException(); 14 | return Value(); 15 | } 16 | 17 | VerifyData(buffer.Data(), testLength); 18 | return buffer; 19 | } 20 | 21 | Value CreateOrCopyExternalBufferWithFinalize(const CallbackInfo& info) { 22 | finalizeCount = 0; 23 | 24 | uint16_t* data = new uint16_t[testLength]; 25 | InitData(data, testLength); 26 | 27 | Buffer buffer = Buffer::NewOrCopy( 28 | info.Env(), data, testLength, [](Env /*env*/, uint16_t* finalizeData) { 29 | delete[] finalizeData; 30 | finalizeCount++; 31 | }); 32 | 33 | if (buffer.Length() != testLength) { 34 | Error::New(info.Env(), "Incorrect buffer length.") 35 | .ThrowAsJavaScriptException(); 36 | return Value(); 37 | } 38 | 39 | VerifyData(buffer.Data(), testLength); 40 | return buffer; 41 | } 42 | 43 | Value CreateOrCopyExternalBufferWithFinalizeHint(const CallbackInfo& info) { 44 | finalizeCount = 0; 45 | 46 | uint16_t* data = new uint16_t[testLength]; 47 | InitData(data, testLength); 48 | 49 | char* hint = nullptr; 50 | Buffer buffer = Buffer::NewOrCopy( 51 | info.Env(), 52 | data, 53 | testLength, 54 | [](Env /*env*/, uint16_t* finalizeData, char* /*finalizeHint*/) { 55 | delete[] finalizeData; 56 | finalizeCount++; 57 | }, 58 | hint); 59 | 60 | if (buffer.Length() != testLength) { 61 | Error::New(info.Env(), "Incorrect buffer length.") 62 | .ThrowAsJavaScriptException(); 63 | return Value(); 64 | } 65 | 66 | VerifyData(buffer.Data(), testLength); 67 | return buffer; 68 | } 69 | -------------------------------------------------------------------------------- /test/buffer_no_external.cc: -------------------------------------------------------------------------------- 1 | #define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED 2 | // Should compile without errors 3 | #include "buffer.h" 4 | #include "napi.h" 5 | 6 | using namespace Napi; 7 | using namespace test_buffer; 8 | 9 | namespace { 10 | #include "buffer_new_or_copy-inl.h" 11 | } 12 | 13 | Object InitBufferNoExternal(Env env) { 14 | Object exports = Object::New(env); 15 | 16 | exports["createOrCopyExternalBuffer"] = 17 | Function::New(env, CreateOrCopyExternalBuffer); 18 | exports["createOrCopyExternalBufferWithFinalize"] = 19 | Function::New(env, CreateOrCopyExternalBufferWithFinalize); 20 | exports["createOrCopyExternalBufferWithFinalizeHint"] = 21 | Function::New(env, CreateOrCopyExternalBufferWithFinalizeHint); 22 | 23 | return exports; 24 | } 25 | -------------------------------------------------------------------------------- /test/callbackInfo.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "napi.h" 3 | using namespace Napi; 4 | 5 | struct TestCBInfoSetData { 6 | static void Test(napi_env env, napi_callback_info info) { 7 | Napi::CallbackInfo cbInfo(env, info); 8 | int valuePointer = 1220202; 9 | cbInfo.SetData(&valuePointer); 10 | 11 | int* placeHolder = static_cast(cbInfo.Data()); 12 | assert(*(placeHolder) == valuePointer); 13 | assert(placeHolder == &valuePointer); 14 | } 15 | }; 16 | 17 | void TestCallbackInfoSetData(const Napi::CallbackInfo& info) { 18 | napi_callback_info cb_info = static_cast(info); 19 | TestCBInfoSetData::Test(info.Env(), cb_info); 20 | } 21 | 22 | Object InitCallbackInfo(Env env) { 23 | Object exports = Object::New(env); 24 | 25 | exports["testCbSetData"] = Function::New(env, TestCallbackInfoSetData); 26 | return exports; 27 | } 28 | -------------------------------------------------------------------------------- /test/callbackInfo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('./common'); 4 | 5 | module.exports = common.runTest(test); 6 | 7 | async function test (binding) { 8 | binding.callbackInfo.testCbSetData(); 9 | } 10 | -------------------------------------------------------------------------------- /test/callbackscope.cc: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "napi.h" 3 | using namespace Napi; 4 | 5 | #if (NAPI_VERSION > 2) 6 | 7 | namespace { 8 | 9 | static void RunInCallbackScope(const CallbackInfo& info) { 10 | Function callback = info[0].As(); 11 | AsyncContext context(info.Env(), "callback_scope_test"); 12 | CallbackScope scope(info.Env(), context); 13 | callback.Call({}); 14 | } 15 | 16 | static void RunInCallbackScopeFromExisting(const CallbackInfo& info) { 17 | Function callback = info[0].As(); 18 | Env env = info.Env(); 19 | 20 | AsyncContext ctx(env, "existing_callback_scope_test"); 21 | napi_callback_scope scope; 22 | napi_open_callback_scope(env, Object::New(env), ctx, &scope); 23 | 24 | CallbackScope existingScope(env, scope); 25 | assert(existingScope.Env() == env); 26 | 27 | callback.Call({}); 28 | } 29 | 30 | } // namespace 31 | 32 | Object InitCallbackScope(Env env) { 33 | Object exports = Object::New(env); 34 | exports["runInCallbackScope"] = Function::New(env, RunInCallbackScope); 35 | exports["runInPreExistingCbScope"] = 36 | Function::New(env, RunInCallbackScopeFromExisting); 37 | return exports; 38 | } 39 | #endif 40 | -------------------------------------------------------------------------------- /test/callbackscope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | // we only check async hooks on 8.x an higher were 5 | // they are closer to working properly 6 | const nodeVersion = process.versions.node.split('.')[0]; 7 | let asyncHooks; 8 | function checkAsyncHooks () { 9 | if (nodeVersion >= 8) { 10 | if (asyncHooks === undefined) { 11 | asyncHooks = require('async_hooks'); 12 | } 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | module.exports = require('./common').runTest(test); 19 | 20 | function test (binding) { 21 | if (!checkAsyncHooks()) { return; } 22 | 23 | let id; 24 | let insideHook = false; 25 | const hook = asyncHooks.createHook({ 26 | init (asyncId, type, triggerAsyncId, resource) { 27 | if (id === undefined && (type === 'callback_scope_test' || type === 'existing_callback_scope_test')) { 28 | id = asyncId; 29 | } 30 | }, 31 | before (asyncId) { 32 | if (asyncId === id) { insideHook = true; } 33 | }, 34 | after (asyncId) { 35 | if (asyncId === id) { insideHook = false; } 36 | } 37 | }).enable(); 38 | 39 | return new Promise(resolve => { 40 | binding.callbackscope.runInCallbackScope(function () { 41 | assert(insideHook); 42 | binding.callbackscope.runInPreExistingCbScope(function () { 43 | assert(insideHook); 44 | hook.disable(); 45 | resolve(); 46 | }); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /test/child_processes/addon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | module.exports = { 5 | workingCode: binding => { 6 | const addon = binding.addon(); 7 | assert.strictEqual(addon.increment(), 43); 8 | assert.strictEqual(addon.increment(), 44); 9 | assert.strictEqual(addon.subObject.decrement(), 43); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /test/child_processes/addon_data.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | // Make sure the instance data finalizer is called at process exit. If the hint 6 | // is non-zero, it will be printed out by the child process. 7 | const cleanupTest = (binding, hint) => { 8 | binding.addon_data(hint).verbose = true; 9 | }; 10 | 11 | module.exports = { 12 | workingCode: binding => { 13 | const addonData = binding.addon_data(0); 14 | 15 | // Make sure it is possible to get/set instance data. 16 | assert.strictEqual(addonData.verbose.verbose, false); 17 | addonData.verbose = true; 18 | assert.strictEqual(addonData.verbose.verbose, true); 19 | addonData.verbose = false; 20 | assert.strictEqual(addonData.verbose.verbose, false); 21 | }, 22 | cleanupWithHint: binding => cleanupTest(binding, 42), 23 | cleanupWithoutHint: binding => cleanupTest(binding, 0) 24 | }; 25 | -------------------------------------------------------------------------------- /test/child_processes/objectwrap_function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const testUtil = require('../testUtil'); 5 | 6 | module.exports = { 7 | runTest: function (binding) { 8 | return testUtil.runGCTests([ 9 | 'objectwrap function', 10 | () => { 11 | const { FunctionTest } = binding.objectwrap_function(); 12 | const newConstructed = new FunctionTest(); 13 | const functionConstructed = FunctionTest(); 14 | assert(newConstructed instanceof FunctionTest); 15 | assert(functionConstructed instanceof FunctionTest); 16 | assert.throws(() => (FunctionTest(true)), /an exception/); 17 | }, 18 | // Do one gc before returning. 19 | () => {} 20 | ]); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /test/child_processes/threadsafe_function_exception.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const common = require('../common'); 5 | 6 | module.exports = { 7 | testCall: async binding => { 8 | const { testCall } = binding.threadsafe_function_exception; 9 | 10 | await new Promise(resolve => { 11 | process.once('uncaughtException', common.mustCall(err => { 12 | assert.strictEqual(err.message, 'test'); 13 | resolve(); 14 | }, 1)); 15 | 16 | testCall(common.mustCall(() => { 17 | throw new Error('test'); 18 | }, 1)); 19 | }); 20 | }, 21 | testCallWithNativeCallback: async binding => { 22 | const { testCallWithNativeCallback } = binding.threadsafe_function_exception; 23 | 24 | await new Promise(resolve => { 25 | process.once('uncaughtException', common.mustCall(err => { 26 | assert.strictEqual(err.message, 'test-from-native'); 27 | resolve(); 28 | }, 1)); 29 | 30 | testCallWithNativeCallback(); 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/child_processes/typed_threadsafe_function_exception.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const common = require('../common'); 5 | 6 | module.exports = { 7 | testCall: async binding => { 8 | const { testCall } = binding.typed_threadsafe_function_exception; 9 | 10 | await new Promise(resolve => { 11 | process.once('uncaughtException', common.mustCall(err => { 12 | assert.strictEqual(err.message, 'test-from-native'); 13 | resolve(); 14 | }, 1)); 15 | 16 | testCall(); 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /test/common/test_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "napi.h" 3 | 4 | namespace Napi { 5 | 6 | // Needs this here since the MaybeUnwrap() functions need to be in the 7 | // same namespace as their arguments for C++ argument-dependent lookup 8 | #ifdef NAPI_CPP_CUSTOM_NAMESPACE 9 | namespace NAPI_CPP_CUSTOM_NAMESPACE { 10 | #endif 11 | 12 | // Use this when a variable or parameter is unused in order to explicitly 13 | // silence a compiler warning about that. 14 | template 15 | inline void USE(T&&) {} 16 | 17 | /** 18 | * A test helper that converts MaybeOrValue to T by checking that 19 | * MaybeOrValue is NOT an empty Maybe when NODE_ADDON_API_ENABLE_MAYBE is 20 | * defined. 21 | * 22 | * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. 23 | */ 24 | template 25 | inline T MaybeUnwrap(MaybeOrValue maybe) { 26 | #if defined(NODE_ADDON_API_ENABLE_MAYBE) 27 | return maybe.Unwrap(); 28 | #else 29 | return maybe; 30 | #endif 31 | } 32 | 33 | /** 34 | * A test helper that converts MaybeOrValue to T by getting the value that 35 | * wrapped by the Maybe or return the default_value if the Maybe is empty when 36 | * NODE_ADDON_API_ENABLE_MAYBE is defined. 37 | * 38 | * Do nothing when NODE_ADDON_API_ENABLE_MAYBE is not defined. 39 | */ 40 | template 41 | inline T MaybeUnwrapOr(MaybeOrValue maybe, const T& default_value) { 42 | #if defined(NODE_ADDON_API_ENABLE_MAYBE) 43 | return maybe.UnwrapOr(default_value); 44 | #else 45 | USE(default_value); 46 | return maybe; 47 | #endif 48 | } 49 | 50 | /** 51 | * A test helper that converts MaybeOrValue to T by getting the value that 52 | * wrapped by the Maybe or return false if the Maybe is empty when 53 | * NODE_ADDON_API_ENABLE_MAYBE is defined. 54 | * 55 | * Copying the value to out when NODE_ADDON_API_ENABLE_MAYBE is not defined. 56 | */ 57 | template 58 | inline bool MaybeUnwrapTo(MaybeOrValue maybe, T* out) { 59 | #if defined(NODE_ADDON_API_ENABLE_MAYBE) 60 | return maybe.UnwrapTo(out); 61 | #else 62 | *out = maybe; 63 | return true; 64 | #endif 65 | } 66 | 67 | #ifdef NAPI_CPP_CUSTOM_NAMESPACE 68 | } // namespace NAPI_CPP_CUSTOM_NAMESPACE 69 | #endif 70 | 71 | } // namespace Napi 72 | -------------------------------------------------------------------------------- /test/dataview/dataview.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | static Value CreateDataView1(const CallbackInfo& info) { 6 | ArrayBuffer arrayBuffer = info[0].As(); 7 | return DataView::New(info.Env(), arrayBuffer); 8 | } 9 | 10 | static Value CreateDataView2(const CallbackInfo& info) { 11 | ArrayBuffer arrayBuffer = info[0].As(); 12 | size_t byteOffset = info[1].As().Uint32Value(); 13 | return DataView::New(info.Env(), arrayBuffer, byteOffset); 14 | } 15 | 16 | static Value CreateDataView3(const CallbackInfo& info) { 17 | ArrayBuffer arrayBuffer = info[0].As(); 18 | size_t byteOffset = info[1].As().Uint32Value(); 19 | size_t byteLength = info[2].As().Uint32Value(); 20 | return DataView::New(info.Env(), arrayBuffer, byteOffset, byteLength); 21 | } 22 | 23 | static Value GetArrayBuffer(const CallbackInfo& info) { 24 | return info[0].As().ArrayBuffer(); 25 | } 26 | 27 | static Value GetByteOffset(const CallbackInfo& info) { 28 | return Number::New(info.Env(), 29 | static_cast(info[0].As().ByteOffset())); 30 | } 31 | 32 | static Value GetByteLength(const CallbackInfo& info) { 33 | return Number::New(info.Env(), 34 | static_cast(info[0].As().ByteLength())); 35 | } 36 | 37 | Object InitDataView(Env env) { 38 | Object exports = Object::New(env); 39 | 40 | exports["createDataView1"] = Function::New(env, CreateDataView1); 41 | exports["createDataView2"] = Function::New(env, CreateDataView2); 42 | exports["createDataView3"] = Function::New(env, CreateDataView3); 43 | exports["getArrayBuffer"] = Function::New(env, GetArrayBuffer); 44 | exports["getByteOffset"] = Function::New(env, GetByteOffset); 45 | exports["getByteLength"] = Function::New(env, GetByteLength); 46 | 47 | return exports; 48 | } 49 | -------------------------------------------------------------------------------- /test/dataview/dataview.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | module.exports = require('../common').runTest(test); 5 | 6 | function test (binding) { 7 | function testDataViewCreation (factory, arrayBuffer, offset, length) { 8 | const view = factory(arrayBuffer, offset, length); 9 | offset = offset || 0; 10 | assert.ok(dataview.getArrayBuffer(view) instanceof ArrayBuffer); 11 | assert.strictEqual(dataview.getArrayBuffer(view), arrayBuffer); 12 | assert.strictEqual(dataview.getByteOffset(view), offset); 13 | assert.strictEqual(dataview.getByteLength(view), 14 | length || arrayBuffer.byteLength - offset); 15 | } 16 | 17 | function testInvalidRange (factory, arrayBuffer, offset, length) { 18 | assert.throws(() => { 19 | factory(arrayBuffer, offset, length); 20 | }, RangeError); 21 | } 22 | 23 | const dataview = binding.dataview; 24 | const arrayBuffer = new ArrayBuffer(10); 25 | 26 | testDataViewCreation(dataview.createDataView1, arrayBuffer); 27 | testDataViewCreation(dataview.createDataView2, arrayBuffer, 2); 28 | testDataViewCreation(dataview.createDataView2, arrayBuffer, 10); 29 | testDataViewCreation(dataview.createDataView3, arrayBuffer, 2, 4); 30 | testDataViewCreation(dataview.createDataView3, arrayBuffer, 10, 0); 31 | 32 | testInvalidRange(dataview.createDataView2, arrayBuffer, 11); 33 | testInvalidRange(dataview.createDataView3, arrayBuffer, 11, 0); 34 | testInvalidRange(dataview.createDataView3, arrayBuffer, 6, 5); 35 | } 36 | -------------------------------------------------------------------------------- /test/date.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | #if (NAPI_VERSION > 4) 6 | namespace { 7 | 8 | Value CreateDate(const CallbackInfo& info) { 9 | double input = info[0].As().DoubleValue(); 10 | 11 | return Date::New(info.Env(), input); 12 | } 13 | 14 | Value IsDate(const CallbackInfo& info) { 15 | Date input = info[0].As(); 16 | 17 | return Boolean::New(info.Env(), input.IsDate()); 18 | } 19 | 20 | Value ValueOf(const CallbackInfo& info) { 21 | Date input = info[0].As(); 22 | 23 | return Number::New(info.Env(), input.ValueOf()); 24 | } 25 | 26 | Value OperatorValue(const CallbackInfo& info) { 27 | Date input = info[0].As(); 28 | 29 | return Boolean::New(info.Env(), 30 | input.ValueOf() == static_cast(input)); 31 | } 32 | 33 | } // anonymous namespace 34 | 35 | Object InitDate(Env env) { 36 | Object exports = Object::New(env); 37 | exports["CreateDate"] = Function::New(env, CreateDate); 38 | exports["IsDate"] = Function::New(env, IsDate); 39 | exports["ValueOf"] = Function::New(env, ValueOf); 40 | exports["OperatorValue"] = Function::New(env, OperatorValue); 41 | 42 | return exports; 43 | } 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /test/date.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | const { 9 | CreateDate, 10 | IsDate, 11 | ValueOf, 12 | OperatorValue 13 | } = binding.date; 14 | assert.deepStrictEqual(CreateDate(0), new Date(0)); 15 | assert.strictEqual(IsDate(new Date(0)), true); 16 | assert.strictEqual(ValueOf(new Date(42)), 42); 17 | assert.strictEqual(OperatorValue(new Date(42)), true); 18 | } 19 | -------------------------------------------------------------------------------- /test/env_cleanup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | if (process.argv[2] === 'runInChildProcess') { 6 | const bindingPath = process.argv[3]; 7 | const removeHooks = process.argv[4] === 'true'; 8 | 9 | const binding = require(bindingPath); 10 | const actualAdded = binding.env_cleanup.addHooks(removeHooks); 11 | const expectedAdded = removeHooks === true ? 0 : 8; 12 | assert(actualAdded === expectedAdded, 'Incorrect number of hooks added'); 13 | } else { 14 | module.exports = require('./common').runTestWithBindingPath(test); 15 | } 16 | 17 | function test (bindingPath) { 18 | for (const removeHooks of [false, true]) { 19 | const { status, output } = require('./napi_child').spawnSync( 20 | process.execPath, 21 | [ 22 | __filename, 23 | 'runInChildProcess', 24 | bindingPath, 25 | removeHooks 26 | ], 27 | { encoding: 'utf8' } 28 | ); 29 | 30 | const stdout = output[1].trim(); 31 | /** 32 | * There is no need to sort the lines, as per Node-API documentation: 33 | * > The hooks will be called in reverse order, i.e. the most recently 34 | * > added one will be called first. 35 | */ 36 | const lines = stdout.split(/[\r\n]+/); 37 | 38 | assert(status === 0, `Process aborted with status ${status}`); 39 | 40 | if (removeHooks) { 41 | assert.deepStrictEqual(lines, [''], 'Child process had console output when none expected'); 42 | } else { 43 | assert.deepStrictEqual(lines, [ 44 | 'lambda cleanup()', 45 | 'lambda cleanup(void)', 46 | 'lambda cleanup(42)', 47 | 'static cleanup()', 48 | 'static cleanup()', 49 | 'static cleanup(43)', 50 | 'static cleanup(42)', 51 | 'static cleanup(42)' 52 | ], 'Child process console output mismisatch'); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/env_misc.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | #if (NAPI_VERSION > 8) 5 | 6 | using namespace Napi; 7 | 8 | namespace { 9 | 10 | Value GetModuleFileName(const CallbackInfo& info) { 11 | Env env = info.Env(); 12 | return String::New(env, env.GetModuleFileName()); 13 | } 14 | 15 | } // end anonymous namespace 16 | 17 | Object InitEnvMiscellaneous(Env env) { 18 | Object exports = Object::New(env); 19 | 20 | exports["get_module_file_name"] = Function::New(env, GetModuleFileName); 21 | 22 | return exports; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /test/env_misc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const { pathToFileURL } = require('url'); 5 | 6 | module.exports = require('./common').runTest(test); 7 | 8 | function test (binding, { bindingPath } = {}) { 9 | const path = binding.env_misc.get_module_file_name(); 10 | const bindingFileUrl = pathToFileURL(bindingPath).toString(); 11 | assert(bindingFileUrl === path); 12 | } 13 | -------------------------------------------------------------------------------- /test/error_handling_for_primitives.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace { 4 | void Test(const Napi::CallbackInfo& info) { 5 | info[0].As().Call({}); 6 | } 7 | 8 | } // namespace 9 | Napi::Object InitErrorHandlingPrim(Napi::Env env) { 10 | Napi::Object exports = Napi::Object::New(env); 11 | exports.Set("errorHandlingPrim", Napi::Function::New(env)); 12 | return exports; 13 | } 14 | -------------------------------------------------------------------------------- /test/error_handling_for_primitives.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest((binding) => { 6 | test(binding.errorHandlingPrim); 7 | }); 8 | 9 | function canThrow (binding, errorMessage, errorType) { 10 | try { 11 | binding.errorHandlingPrim(() => { 12 | throw errorMessage; 13 | }); 14 | } catch (e) { 15 | // eslint-disable-next-line valid-typeof 16 | assert(typeof e === errorType); 17 | assert(e === errorMessage); 18 | } 19 | } 20 | 21 | function test (binding) { 22 | canThrow(binding, '404 server not found!', 'string'); 23 | canThrow(binding, 42, 'number'); 24 | canThrow(binding, Symbol.for('newSym'), 'symbol'); 25 | canThrow(binding, false, 'boolean'); 26 | canThrow(binding, BigInt(123), 'bigint'); 27 | canThrow(binding, () => { console.log('Logger shutdown incorrectly'); }, 'function'); 28 | canThrow(binding, { status: 403, errorMsg: 'Not authenticated' }, 'object'); 29 | } 30 | -------------------------------------------------------------------------------- /test/globalObject/global_object_delete_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value DeletePropertyWithCStyleStringAsKey(const CallbackInfo& info) { 7 | Object globalObject = info.Env().Global(); 8 | String key = info[0].As(); 9 | return Boolean::New( 10 | info.Env(), MaybeUnwrap(globalObject.Delete(key.Utf8Value().c_str()))); 11 | } 12 | 13 | Value DeletePropertyWithCppStyleStringAsKey(const CallbackInfo& info) { 14 | Object globalObject = info.Env().Global(); 15 | String key = info[0].As(); 16 | return Boolean::New(info.Env(), 17 | MaybeUnwrap(globalObject.Delete(key.Utf8Value()))); 18 | } 19 | 20 | Value DeletePropertyWithInt32AsKey(const CallbackInfo& info) { 21 | Object globalObject = info.Env().Global(); 22 | Number key = info[0].As(); 23 | return Boolean::New(info.Env(), 24 | MaybeUnwrap(globalObject.Delete(key.Uint32Value()))); 25 | } 26 | 27 | Value DeletePropertyWithNapiValueAsKey(const CallbackInfo& info) { 28 | Object globalObject = info.Env().Global(); 29 | Name key = info[0].As(); 30 | return Boolean::New(info.Env(), MaybeUnwrap(globalObject.Delete(key))); 31 | } 32 | -------------------------------------------------------------------------------- /test/globalObject/global_object_delete_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | const KEY_TYPE = { 9 | C_STR: 'KEY_AS_C_STRING', 10 | CPP_STR: 'KEY_AS_CPP_STRING', 11 | NAPI: 'KEY_AS_NAPI_VALUES', 12 | INT_32: 'KEY_AS_INT_32_NUM' 13 | }; 14 | 15 | function assertNotGlobalObjectHasNoProperty (key, keyType) { 16 | switch (keyType) { 17 | case KEY_TYPE.NAPI: 18 | assert.notStrictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); 19 | break; 20 | 21 | case KEY_TYPE.C_STR: 22 | assert.notStrictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); 23 | break; 24 | 25 | case KEY_TYPE.CPP_STR: 26 | assert.notStrictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); 27 | break; 28 | 29 | case KEY_TYPE.INT_32: 30 | assert.notStrictEqual(binding.globalObject.hasPropertyWithInt32(key), true); 31 | break; 32 | } 33 | } 34 | 35 | function assertErrMessageIsThrown (propertyCheckExistenceFunction, errMsg) { 36 | assert.throws(() => { 37 | propertyCheckExistenceFunction(undefined); 38 | }, errMsg); 39 | } 40 | 41 | binding.globalObject.createMockTestObject(); 42 | 43 | binding.globalObject.deletePropertyWithCStyleString('c_str_key'); 44 | binding.globalObject.deletePropertyWithCppStyleString('cpp_string_key'); 45 | binding.globalObject.deletePropertyWithCppStyleString('circular'); 46 | binding.globalObject.deletePropertyWithInt32(15); 47 | binding.globalObject.deletePropertyWithNapiValue('2'); 48 | 49 | assertNotGlobalObjectHasNoProperty('c_str_key', KEY_TYPE.C_STR); 50 | assertNotGlobalObjectHasNoProperty('cpp_string_key', KEY_TYPE.CPP_STR); 51 | assertNotGlobalObjectHasNoProperty('circular', KEY_TYPE.CPP_STR); 52 | assertNotGlobalObjectHasNoProperty(15, true); 53 | assertNotGlobalObjectHasNoProperty('2', KEY_TYPE.NAPI); 54 | 55 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); 56 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); 57 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); 58 | } 59 | -------------------------------------------------------------------------------- /test/globalObject/global_object_get_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value GetPropertyWithNapiValueAsKey(const CallbackInfo& info) { 7 | Object globalObject = info.Env().Global(); 8 | Name key = info[0].As(); 9 | return MaybeUnwrap(globalObject.Get(key)); 10 | } 11 | 12 | Value GetPropertyWithInt32AsKey(const CallbackInfo& info) { 13 | Object globalObject = info.Env().Global(); 14 | Number key = info[0].As(); 15 | return MaybeUnwrapOr(globalObject.Get(key.Uint32Value()), Value()); 16 | } 17 | 18 | Value GetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { 19 | Object globalObject = info.Env().Global(); 20 | String cStrkey = info[0].As(); 21 | return MaybeUnwrapOr(globalObject.Get(cStrkey.Utf8Value().c_str()), Value()); 22 | } 23 | 24 | Value GetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { 25 | Object globalObject = info.Env().Global(); 26 | String cppStrKey = info[0].As(); 27 | return MaybeUnwrapOr(globalObject.Get(cppStrKey.Utf8Value()), Value()); 28 | } 29 | 30 | void CreateMockTestObject(const CallbackInfo& info) { 31 | Object globalObject = info.Env().Global(); 32 | Number napi_key = Number::New(info.Env(), 2); 33 | const char* CStringKey = "c_str_key"; 34 | 35 | globalObject.Set(napi_key, "napi_attribute"); 36 | globalObject[CStringKey] = "c_string_attribute"; 37 | globalObject[std::string("cpp_string_key")] = "cpp_string_attribute"; 38 | globalObject[std::string("circular")] = globalObject; 39 | globalObject[(uint32_t)15] = 15; 40 | } 41 | -------------------------------------------------------------------------------- /test/globalObject/global_object_get_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | const KEY_TYPE = { 9 | C_STR: 'KEY_AS_C_STRING', 10 | CPP_STR: 'KEY_AS_CPP_STRING', 11 | NAPI: 'KEY_AS_NAPI_VALUES', 12 | INT_32: 'KEY_AS_INT_32_NUM' 13 | }; 14 | 15 | binding.globalObject.createMockTestObject(); 16 | function assertGlobalObjectPropertyIs (key, attribute, keyType) { 17 | let napiObjectAttr; 18 | switch (keyType) { 19 | case KEY_TYPE.NAPI: 20 | napiObjectAttr = binding.globalObject.getPropertyWithNapiValue(key); 21 | assert.deepStrictEqual(attribute, napiObjectAttr); 22 | break; 23 | 24 | case KEY_TYPE.C_STR: 25 | napiObjectAttr = binding.globalObject.getPropertyWithCString(key); 26 | assert.deepStrictEqual(attribute, napiObjectAttr); 27 | break; 28 | 29 | case KEY_TYPE.CPP_STR: 30 | napiObjectAttr = binding.globalObject.getPropertyWithCppString(key); 31 | assert.deepStrictEqual(attribute, napiObjectAttr); 32 | break; 33 | 34 | case KEY_TYPE.INT_32: 35 | napiObjectAttr = binding.globalObject.getPropertyWithInt32(key); 36 | assert.deepStrictEqual(attribute, napiObjectAttr); 37 | break; 38 | } 39 | } 40 | 41 | function assertErrMessageIsThrown (propertyFetchFunction, errMsg) { 42 | assert.throws(() => { 43 | propertyFetchFunction(undefined); 44 | }, errMsg); 45 | } 46 | 47 | assertGlobalObjectPropertyIs('2', global['2'], KEY_TYPE.NAPI); 48 | assertGlobalObjectPropertyIs('c_str_key', global.c_str_key, KEY_TYPE.C_STR); 49 | assertGlobalObjectPropertyIs('cpp_string_key', global.cpp_string_key, KEY_TYPE.CPP_STR); 50 | assertGlobalObjectPropertyIs('circular', global.circular, KEY_TYPE.CPP_STR); 51 | assertGlobalObjectPropertyIs(15, global['15'], KEY_TYPE.INT_32); 52 | 53 | assertErrMessageIsThrown(binding.globalObject.getPropertyWithCString, 'Error: A string was expected'); 54 | assertErrMessageIsThrown(binding.globalObject.getPropertyWithCppString, 'Error: A string was expected'); 55 | assertErrMessageIsThrown(binding.globalObject.getPropertyWithInt32, 'Error: A number was expected'); 56 | } 57 | -------------------------------------------------------------------------------- /test/globalObject/global_object_has_own_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value HasPropertyWithCStyleStringAsKey(const CallbackInfo& info) { 7 | Object globalObject = info.Env().Global(); 8 | String key = info[0].As(); 9 | return Boolean::New( 10 | info.Env(), 11 | MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value().c_str()), 12 | false)); 13 | } 14 | 15 | Value HasPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { 16 | Object globalObject = info.Env().Global(); 17 | String key = info[0].As(); 18 | return Boolean::New( 19 | info.Env(), 20 | MaybeUnwrapOr(globalObject.HasOwnProperty(key.Utf8Value()), false)); 21 | } 22 | 23 | Value HasPropertyWithNapiValueAsKey(const CallbackInfo& info) { 24 | Object globalObject = info.Env().Global(); 25 | Name key = info[0].As(); 26 | return Boolean::New(info.Env(), 27 | MaybeUnwrap(globalObject.HasOwnProperty(key))); 28 | } 29 | -------------------------------------------------------------------------------- /test/globalObject/global_object_has_own_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | const KEY_TYPE = { 9 | C_STR: 'KEY_AS_C_STRING', 10 | CPP_STR: 'KEY_AS_CPP_STRING', 11 | NAPI: 'KEY_AS_NAPI_VALUES', 12 | INT_32: 'KEY_AS_INT_32_NUM' 13 | }; 14 | 15 | function assertGlobalObjectHasProperty (key, keyType) { 16 | switch (keyType) { 17 | case KEY_TYPE.NAPI: 18 | assert.strictEqual(binding.globalObject.hasPropertyWithNapiValue(key), true); 19 | break; 20 | 21 | case KEY_TYPE.C_STR: 22 | assert.strictEqual(binding.globalObject.hasPropertyWithCStyleString(key), true); 23 | break; 24 | 25 | case KEY_TYPE.CPP_STR: 26 | assert.strictEqual(binding.globalObject.hasPropertyWithCppStyleString(key), true); 27 | break; 28 | } 29 | } 30 | 31 | function assertErrMessageIsThrown (propertyCheckExistenceFunction, errMsg) { 32 | assert.throws(() => { 33 | propertyCheckExistenceFunction(undefined); 34 | }, errMsg); 35 | } 36 | 37 | binding.globalObject.createMockTestObject(); 38 | assertGlobalObjectHasProperty('c_str_key', KEY_TYPE.C_STR); 39 | assertGlobalObjectHasProperty('cpp_string_key', KEY_TYPE.CPP_STR); 40 | assertGlobalObjectHasProperty('circular', KEY_TYPE.CPP_STR); 41 | assertGlobalObjectHasProperty('2', KEY_TYPE.NAPI); 42 | 43 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCppStyleString, 'Error: A string was expected'); 44 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithCStyleString, 'Error: A string was expected'); 45 | assertErrMessageIsThrown(binding.globalObject.hasPropertyWithInt32, 'Error: A number was expected'); 46 | } 47 | -------------------------------------------------------------------------------- /test/globalObject/global_object_set_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | void SetPropertyWithCStyleStringAsKey(const CallbackInfo& info) { 6 | Object globalObject = info.Env().Global(); 7 | String key = info[0].As(); 8 | Value value = info[1]; 9 | globalObject.Set(key.Utf8Value().c_str(), value); 10 | } 11 | 12 | void SetPropertyWithCppStyleStringAsKey(const CallbackInfo& info) { 13 | Object globalObject = info.Env().Global(); 14 | String key = info[0].As(); 15 | Value value = info[1]; 16 | globalObject.Set(key.Utf8Value(), value); 17 | } 18 | 19 | void SetPropertyWithInt32AsKey(const CallbackInfo& info) { 20 | Object globalObject = info.Env().Global(); 21 | Number key = info[0].As(); 22 | Value value = info[1]; 23 | globalObject.Set(key.Uint32Value(), value); 24 | } 25 | 26 | void SetPropertyWithNapiValueAsKey(const CallbackInfo& info) { 27 | Object globalObject = info.Env().Global(); 28 | Name key = info[0].As(); 29 | Value value = info[1]; 30 | globalObject.Set(key, value); 31 | } -------------------------------------------------------------------------------- /test/globalObject/global_object_set_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | const KEY_TYPE = { 9 | C_STR: 'KEY_AS_C_STRING', 10 | CPP_STR: 'KEY_AS_CPP_STRING', 11 | NAPI: 'KEY_AS_NAPI_VALUES', 12 | INT_32: 'KEY_AS_INT_32_NUM' 13 | }; 14 | 15 | function setGlobalObjectKeyValue (key, value, keyType) { 16 | switch (keyType) { 17 | case KEY_TYPE.CPP_STR: 18 | binding.globalObject.setPropertyWithCppStyleString(key, value); 19 | break; 20 | 21 | case KEY_TYPE.C_STR: 22 | binding.globalObject.setPropertyWithCStyleString(key, value); 23 | break; 24 | 25 | case KEY_TYPE.INT_32: 26 | binding.globalObject.setPropertyWithInt32(key, value); 27 | break; 28 | 29 | case KEY_TYPE.NAPI: 30 | binding.globalObject.setPropertyWithNapiValue(key, value); 31 | break; 32 | } 33 | } 34 | 35 | function assertErrMessageIsThrown (nativeObjectSetFunction, errMsg) { 36 | assert.throws(() => { 37 | nativeObjectSetFunction(undefined, 1); 38 | }, errMsg); 39 | } 40 | 41 | setGlobalObjectKeyValue('cKey', 'cValue', KEY_TYPE.CPP_STR); 42 | setGlobalObjectKeyValue(1, 10, KEY_TYPE.INT_32); 43 | setGlobalObjectKeyValue('napi_key', 'napi_value', KEY_TYPE.NAPI); 44 | setGlobalObjectKeyValue('cppKey', 'cppValue', KEY_TYPE.CPP_STR); 45 | setGlobalObjectKeyValue('circular', global, KEY_TYPE.NAPI); 46 | 47 | assert.deepStrictEqual(global.circular, global); 48 | assert.deepStrictEqual(global.cppKey, 'cppValue'); 49 | assert.deepStrictEqual(global.napi_key, 'napi_value'); 50 | assert.deepStrictEqual(global[1], 10); 51 | assert.deepStrictEqual(global.cKey, 'cValue'); 52 | 53 | assertErrMessageIsThrown(binding.globalObject.setPropertyWithCppStyleString, 'Error: A string was expected'); 54 | assertErrMessageIsThrown(binding.globalObject.setPropertyWithCStyleString, 'Error: A string was expected'); 55 | assertErrMessageIsThrown(binding.globalObject.setPropertyWithInt32, 'Error: A number was expected'); 56 | } 57 | -------------------------------------------------------------------------------- /test/handlescope.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | assert.strictEqual(binding.handlescope.createScope(), 'scope'); 9 | assert.strictEqual(binding.handlescope.createScopeFromExisting(), 'existing_scope'); 10 | assert.strictEqual(binding.handlescope.escapeFromScope(), 'inner-scope'); 11 | assert.strictEqual(binding.handlescope.escapeFromExistingScope(), 'inner-existing-scope'); 12 | assert.strictEqual(binding.handlescope.stressEscapeFromScope(), 'inner-scope999999'); 13 | assert.throws(() => binding.handlescope.doubleEscapeFromScope(), 14 | Error, 15 | ' napi_escape_handle already called on scope'); 16 | } 17 | -------------------------------------------------------------------------------- /test/maybe/check.cc: -------------------------------------------------------------------------------- 1 | #include "assert.h" 2 | #include "napi.h" 3 | #if defined(NODE_ADDON_API_ENABLE_MAYBE) 4 | 5 | using namespace Napi; 6 | 7 | namespace { 8 | 9 | void VoidCallback(const CallbackInfo& info) { 10 | Napi::Function fn = info[0].As(); 11 | Maybe ret = fn.Call({}); 12 | 13 | assert(ret.IsNothing() == true); 14 | assert(ret.IsJust() == false); 15 | 16 | Napi::Value placeHolder = Napi::Number::New(info.Env(), 12345); 17 | Napi::Value unwrappedValue = ret.UnwrapOr(placeHolder); 18 | 19 | assert(unwrappedValue.As().Uint32Value() == 12345); 20 | 21 | assert(ret.UnwrapTo(&placeHolder) == false); 22 | assert(placeHolder.As().Uint32Value() == 12345); 23 | 24 | ret.Check(); 25 | } 26 | 27 | void TestMaybeOperatorOverload(const CallbackInfo& info) { 28 | Napi::Function fn_a = info[0].As(); 29 | Napi::Function fn_b = info[1].As(); 30 | 31 | assert(fn_a.Call({}) == fn_a.Call({})); 32 | assert(fn_a.Call({}) != fn_b.Call({})); 33 | } 34 | 35 | void NormalJsCallback(const CallbackInfo& info) { 36 | Napi::Function fn = info[0].As(); 37 | uint32_t magic_number = info[1].As().Uint32Value(); 38 | 39 | Maybe ret = fn.Call({}); 40 | 41 | assert(ret.IsNothing() == false); 42 | assert(ret.IsJust() == true); 43 | 44 | Napi::Value unwrappedValue = ret.Unwrap(); 45 | assert(unwrappedValue.IsNumber() == true); 46 | 47 | assert(unwrappedValue.As().Uint32Value() == magic_number); 48 | 49 | unwrappedValue = 50 | ret.UnwrapOr(Napi::Number::New(info.Env(), magic_number - 1)); 51 | assert(unwrappedValue.As().Uint32Value() == magic_number); 52 | 53 | Napi::Value placeHolder = Napi::Number::New(info.Env(), magic_number - 1); 54 | assert(ret.UnwrapTo(&placeHolder) == true); 55 | assert(placeHolder.As().Uint32Value() == magic_number); 56 | } 57 | 58 | } // end anonymous namespace 59 | 60 | Object InitMaybeCheck(Env env) { 61 | Object exports = Object::New(env); 62 | exports.Set("voidCallback", Function::New(env, VoidCallback)); 63 | exports.Set("normalJsCallback", Function::New(env, NormalJsCallback)); 64 | exports.Set("testMaybeOverloadOp", 65 | Function::New(env, TestMaybeOperatorOverload)); 66 | return exports; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /test/maybe/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const { whichBuildType } = require('../common'); 5 | 6 | const napiChild = require('../napi_child'); 7 | 8 | module.exports = async function wrapTest () { 9 | const buildType = await whichBuildType(); 10 | test(require(`../build/${buildType}/binding_noexcept_maybe.node`).maybe_check); 11 | }; 12 | 13 | function test (binding) { 14 | if (process.argv.includes('child')) { 15 | child(binding); 16 | return; 17 | } 18 | const cp = napiChild.spawn(process.execPath, [__filename, 'child'], { 19 | stdio: ['ignore', 'inherit', 'pipe'] 20 | }); 21 | cp.stderr.setEncoding('utf8'); 22 | let stderr = ''; 23 | cp.stderr.on('data', chunk => { 24 | stderr += chunk; 25 | }); 26 | cp.on('exit', (code, signal) => { 27 | if (process.platform === 'win32') { 28 | assert.strictEqual(code, 128 + 6 /* SIGABRT */); 29 | } else { 30 | assert.strictEqual(signal, 'SIGABRT'); 31 | } 32 | assert.ok(stderr.match(/FATAL ERROR: Napi::Maybe::Check Maybe value is Nothing./)); 33 | }); 34 | } 35 | 36 | function child (binding) { 37 | const MAGIC_NUMBER = 12459062; 38 | binding.normalJsCallback(() => { 39 | return MAGIC_NUMBER; 40 | }, MAGIC_NUMBER); 41 | 42 | binding.testMaybeOverloadOp( 43 | () => { return MAGIC_NUMBER; }, 44 | () => { throw Error('Foobar'); } 45 | ); 46 | 47 | binding.voidCallback(() => { 48 | throw new Error('foobar'); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /test/memory_management.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value externalAllocatedMemory(const CallbackInfo& info) { 6 | int64_t kSize = 1024 * 1024; 7 | int64_t baseline = MemoryManagement::AdjustExternalMemory(info.Env(), 0); 8 | int64_t tmp = MemoryManagement::AdjustExternalMemory(info.Env(), kSize); 9 | tmp = MemoryManagement::AdjustExternalMemory(info.Env(), -kSize); 10 | return Boolean::New(info.Env(), tmp == baseline); 11 | } 12 | 13 | Object InitMemoryManagement(Env env) { 14 | Object exports = Object::New(env); 15 | exports["externalAllocatedMemory"] = 16 | Function::New(env, externalAllocatedMemory); 17 | return exports; 18 | } 19 | -------------------------------------------------------------------------------- /test/memory_management.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | assert.strictEqual(binding.memory_management.externalAllocatedMemory(), true); 9 | } 10 | -------------------------------------------------------------------------------- /test/movable_callbacks.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value createExternal(const CallbackInfo& info) { 6 | FunctionReference ref = Reference::New(info[0].As(), 1); 7 | auto ret = External::New( 8 | info.Env(), 9 | nullptr, 10 | [ref = std::move(ref)](Napi::Env /*env*/, char* /*data*/) { 11 | ref.Call({}); 12 | }); 13 | 14 | return ret; 15 | } 16 | 17 | Object InitMovableCallbacks(Env env) { 18 | Object exports = Object::New(env); 19 | 20 | exports["createExternal"] = Function::New(env, createExternal); 21 | 22 | return exports; 23 | } 24 | -------------------------------------------------------------------------------- /test/movable_callbacks.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('./common'); 4 | const testUtil = require('./testUtil'); 5 | 6 | module.exports = require('./common').runTest(binding => test(binding.movable_callbacks)); 7 | 8 | async function test (binding) { 9 | await testUtil.runGCTests([ 10 | 'External', 11 | () => { 12 | const fn = common.mustCall(() => { 13 | // noop 14 | }, 1); 15 | binding.createExternal(fn); 16 | }, 17 | () => { 18 | // noop, wait for gc 19 | } 20 | ]); 21 | } 22 | -------------------------------------------------------------------------------- /test/name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | const expected = '123456789'; 9 | 10 | assert.throws(binding.name.nullStringShouldThrow, { 11 | name: 'Error', 12 | message: 'Error in native callback' 13 | }); 14 | assert.ok(binding.name.checkString(expected, 'utf8')); 15 | assert.ok(binding.name.checkString(expected, 'utf16')); 16 | assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf8', 3)); 17 | assert.ok(binding.name.checkString(expected.substr(0, 3), 'utf16', 3)); 18 | 19 | const str1 = binding.name.createString('utf8'); 20 | assert.strictEqual(str1, expected); 21 | assert.ok(binding.name.checkString(str1, 'utf8')); 22 | assert.ok(binding.name.checkString(str1, 'utf16')); 23 | 24 | const substr1 = binding.name.createString('utf8', 3); 25 | assert.strictEqual(substr1, expected.substr(0, 3)); 26 | assert.ok(binding.name.checkString(substr1, 'utf8', 3)); 27 | assert.ok(binding.name.checkString(substr1, 'utf16', 3)); 28 | 29 | const str2 = binding.name.createString('utf16'); 30 | assert.strictEqual(str1, expected); 31 | assert.ok(binding.name.checkString(str2, 'utf8')); 32 | assert.ok(binding.name.checkString(str2, 'utf16')); 33 | 34 | const substr2 = binding.name.createString('utf16', 3); 35 | assert.strictEqual(substr1, expected.substr(0, 3)); 36 | assert.ok(binding.name.checkString(substr2, 'utf8', 3)); 37 | assert.ok(binding.name.checkString(substr2, 'utf16', 3)); 38 | 39 | // eslint-disable-next-line symbol-description 40 | assert.ok(binding.name.checkSymbol(Symbol())); 41 | assert.ok(binding.name.checkSymbol(Symbol('test'))); 42 | 43 | const sym1 = binding.name.createSymbol(); 44 | assert.strictEqual(typeof sym1, 'symbol'); 45 | assert.ok(binding.name.checkSymbol(sym1)); 46 | 47 | const sym2 = binding.name.createSymbol('test'); 48 | assert.strictEqual(typeof sym2, 'symbol'); 49 | assert.ok(binding.name.checkSymbol(sym1)); 50 | 51 | // Check for off-by-one errors which might only appear for strings of certain sizes, 52 | // due to how std::string increments its capacity in chunks. 53 | const longString = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; 54 | for (let i = 10; i <= longString.length; i++) { 55 | const str = longString.substr(0, i); 56 | assert.strictEqual(binding.name.echoString(str, 'utf8'), str); 57 | assert.strictEqual(binding.name.echoString(str, 'utf16'), str); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/napi_child.js: -------------------------------------------------------------------------------- 1 | // Makes sure that child processes are spawned appropriately. 2 | exports.spawnSync = function (command, args, options) { 3 | if (require('../index').needsFlag) { 4 | args.splice(0, 0, '--napi-modules'); 5 | } 6 | return require('child_process').spawnSync(command, args, options); 7 | }; 8 | 9 | exports.spawn = function (command, args, options) { 10 | if (require('../index').needsFlag) { 11 | args.splice(0, 0, '--napi-modules'); 12 | } 13 | return require('child_process').spawn(command, args, options); 14 | }; 15 | -------------------------------------------------------------------------------- /test/object/delete_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value DeletePropertyWithUint32(const CallbackInfo& info) { 7 | Object obj = info[0].As(); 8 | Number key = info[1].As(); 9 | return Boolean::New(info.Env(), MaybeUnwrap(obj.Delete(key.Uint32Value()))); 10 | } 11 | 12 | Value DeletePropertyWithNapiValue(const CallbackInfo& info) { 13 | Object obj = info[0].As(); 14 | Name key = info[1].As(); 15 | return Boolean::New( 16 | info.Env(), 17 | MaybeUnwrapOr(obj.Delete(static_cast(key)), false)); 18 | } 19 | 20 | Value DeletePropertyWithNapiWrapperValue(const CallbackInfo& info) { 21 | Object obj = info[0].As(); 22 | Name key = info[1].As(); 23 | return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Delete(key), false)); 24 | } 25 | 26 | Value DeletePropertyWithCStyleString(const CallbackInfo& info) { 27 | Object obj = info[0].As(); 28 | String jsKey = info[1].As(); 29 | return Boolean::New( 30 | info.Env(), MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value().c_str()), false)); 31 | } 32 | 33 | Value DeletePropertyWithCppStyleString(const CallbackInfo& info) { 34 | Object obj = info[0].As(); 35 | String jsKey = info[1].As(); 36 | return Boolean::New(info.Env(), 37 | MaybeUnwrapOr(obj.Delete(jsKey.Utf8Value()), false)); 38 | } 39 | -------------------------------------------------------------------------------- /test/object/delete_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testDeleteProperty (nativeDeleteProperty) { 9 | const obj = { one: 1, two: 2 }; 10 | Object.defineProperty(obj, 'three', { configurable: false, value: 3 }); 11 | assert.strictEqual(nativeDeleteProperty(obj, 'one'), true); 12 | assert.strictEqual(nativeDeleteProperty(obj, 'missing'), true); 13 | 14 | /* Returns true for all cases except when the property is an own non- 15 | configurable property, in which case, false is returned in non-strict mode. */ 16 | assert.strictEqual(nativeDeleteProperty(obj, 'three'), false); 17 | assert.deepStrictEqual(obj, { two: 2 }); 18 | } 19 | 20 | function testShouldThrowErrorIfKeyIsInvalid (nativeDeleteProperty) { 21 | assert.throws(() => { 22 | nativeDeleteProperty(undefined, 'test'); 23 | }, /Cannot convert undefined or null to object/); 24 | } 25 | 26 | const testObj = { 15: 42, three: 3 }; 27 | 28 | binding.object.deletePropertyWithUint32(testObj, 15); 29 | 30 | assert.strictEqual(Object.prototype.hasOwnProperty.call(testObj, 15), false); 31 | 32 | testDeleteProperty(binding.object.deletePropertyWithNapiValue); 33 | testDeleteProperty(binding.object.deletePropertyWithNapiWrapperValue); 34 | testDeleteProperty(binding.object.deletePropertyWithCStyleString); 35 | testDeleteProperty(binding.object.deletePropertyWithCppStyleString); 36 | 37 | testShouldThrowErrorIfKeyIsInvalid(binding.object.deletePropertyWithNapiValue); 38 | testShouldThrowErrorIfKeyIsInvalid(binding.object.deletePropertyWithNapiWrapperValue); 39 | testShouldThrowErrorIfKeyIsInvalid(binding.object.deletePropertyWithCStyleString); 40 | testShouldThrowErrorIfKeyIsInvalid(binding.object.deletePropertyWithCppStyleString); 41 | } 42 | -------------------------------------------------------------------------------- /test/object/finalizer.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | static int dummy; 6 | 7 | Value AddFinalizer(const CallbackInfo& info) { 8 | ObjectReference* ref = new ObjectReference; 9 | *ref = Persistent(Object::New(info.Env())); 10 | info[0].As().AddFinalizer( 11 | [](Napi::Env /*env*/, ObjectReference* ref) { 12 | ref->Set("finalizerCalled", true); 13 | delete ref; 14 | }, 15 | ref); 16 | return ref->Value(); 17 | } 18 | 19 | Value AddFinalizerWithHint(const CallbackInfo& info) { 20 | ObjectReference* ref = new ObjectReference; 21 | *ref = Persistent(Object::New(info.Env())); 22 | info[0].As().AddFinalizer( 23 | [](Napi::Env /*env*/, ObjectReference* ref, int* dummy_p) { 24 | ref->Set("finalizerCalledWithCorrectHint", dummy_p == &dummy); 25 | delete ref; 26 | }, 27 | ref, 28 | &dummy); 29 | return ref->Value(); 30 | } 31 | -------------------------------------------------------------------------------- /test/object/finalizer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const testUtil = require('../testUtil'); 5 | 6 | module.exports = require('../common').runTest(test); 7 | 8 | function createWeakRef (binding, bindingToTest) { 9 | return binding.object[bindingToTest]({}); 10 | } 11 | 12 | function test (binding) { 13 | let obj1; 14 | let obj2; 15 | return testUtil.runGCTests([ 16 | 'addFinalizer', 17 | () => { 18 | obj1 = createWeakRef(binding, 'addFinalizer'); 19 | }, 20 | () => assert.deepStrictEqual(obj1, { finalizerCalled: true }), 21 | 22 | 'addFinalizerWithHint', 23 | () => { 24 | obj2 = createWeakRef(binding, 'addFinalizerWithHint'); 25 | }, 26 | () => assert.deepStrictEqual(obj2, { finalizerCalledWithCorrectHint: true }) 27 | ]); 28 | } 29 | -------------------------------------------------------------------------------- /test/object/get_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value GetPropertyWithNapiValue(const CallbackInfo& info) { 7 | Object obj = info[0].As(); 8 | Name key = info[1].As(); 9 | return MaybeUnwrapOr(obj.Get(static_cast(key)), Value()); 10 | } 11 | 12 | Value GetPropertyWithNapiWrapperValue(const CallbackInfo& info) { 13 | Object obj = info[0].As(); 14 | Name key = info[1].As(); 15 | return MaybeUnwrapOr(obj.Get(key), Value()); 16 | } 17 | 18 | Value GetPropertyWithUint32(const CallbackInfo& info) { 19 | Object obj = info[0].As(); 20 | Number key = info[1].As(); 21 | return MaybeUnwrap(obj.Get(key.Uint32Value())); 22 | } 23 | 24 | Value GetPropertyWithCStyleString(const CallbackInfo& info) { 25 | Object obj = info[0].As(); 26 | String jsKey = info[1].As(); 27 | return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value().c_str()), Value()); 28 | } 29 | 30 | Value GetPropertyWithCppStyleString(const CallbackInfo& info) { 31 | Object obj = info[0].As(); 32 | String jsKey = info[1].As(); 33 | return MaybeUnwrapOr(obj.Get(jsKey.Utf8Value()), Value()); 34 | } 35 | -------------------------------------------------------------------------------- /test/object/get_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testGetProperty (nativeGetProperty) { 9 | const obj = { test: 1 }; 10 | assert.strictEqual(nativeGetProperty(obj, 'test'), 1); 11 | } 12 | 13 | function testShouldReturnUndefinedIfKeyIsNotPresent (nativeGetProperty) { 14 | const obj = { }; 15 | assert.strictEqual(nativeGetProperty(obj, 'test'), undefined); 16 | } 17 | 18 | function testShouldThrowErrorIfKeyIsInvalid (nativeGetProperty) { 19 | assert.throws(() => { 20 | nativeGetProperty(undefined, 'test'); 21 | }, /Cannot convert undefined or null to object/); 22 | } 23 | 24 | const testObject = { 42: 100 }; 25 | const property = binding.object.getPropertyWithUint32(testObject, 42); 26 | assert.strictEqual(property, 100); 27 | 28 | const nativeFunctions = [ 29 | binding.object.getPropertyWithNapiValue, 30 | binding.object.getPropertyWithNapiWrapperValue, 31 | binding.object.getPropertyWithCStyleString, 32 | binding.object.getPropertyWithCppStyleString 33 | ]; 34 | 35 | nativeFunctions.forEach((nativeFunction) => { 36 | testGetProperty(nativeFunction); 37 | testShouldReturnUndefinedIfKeyIsNotPresent(nativeFunction); 38 | testShouldThrowErrorIfKeyIsInvalid(nativeFunction); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /test/object/has_own_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value HasOwnPropertyWithNapiValue(const CallbackInfo& info) { 7 | Object obj = info[0].As(); 8 | Name key = info[1].As(); 9 | return Boolean::New( 10 | info.Env(), 11 | MaybeUnwrapOr(obj.HasOwnProperty(static_cast(key)), false)); 12 | } 13 | 14 | Value HasOwnPropertyWithNapiWrapperValue(const CallbackInfo& info) { 15 | Object obj = info[0].As(); 16 | Name key = info[1].As(); 17 | return Boolean::New(info.Env(), 18 | MaybeUnwrapOr(obj.HasOwnProperty(key), false)); 19 | } 20 | 21 | Value HasOwnPropertyWithCStyleString(const CallbackInfo& info) { 22 | Object obj = info[0].As(); 23 | String jsKey = info[1].As(); 24 | return Boolean::New( 25 | info.Env(), 26 | MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value().c_str()), false)); 27 | } 28 | 29 | Value HasOwnPropertyWithCppStyleString(const CallbackInfo& info) { 30 | Object obj = info[0].As(); 31 | String jsKey = info[1].As(); 32 | return Boolean::New( 33 | info.Env(), MaybeUnwrapOr(obj.HasOwnProperty(jsKey.Utf8Value()), false)); 34 | } 35 | -------------------------------------------------------------------------------- /test/object/has_own_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testHasOwnProperty (nativeHasOwnProperty) { 9 | const obj = { one: 1 }; 10 | 11 | Object.defineProperty(obj, 'two', { value: 2 }); 12 | 13 | assert.strictEqual(nativeHasOwnProperty(obj, 'one'), true); 14 | assert.strictEqual(nativeHasOwnProperty(obj, 'two'), true); 15 | assert.strictEqual('toString' in obj, true); 16 | assert.strictEqual(nativeHasOwnProperty(obj, 'toString'), false); 17 | } 18 | 19 | function testShouldThrowErrorIfKeyIsInvalid (nativeHasOwnProperty) { 20 | assert.throws(() => { 21 | nativeHasOwnProperty(undefined, 'test'); 22 | }, /Cannot convert undefined or null to object/); 23 | } 24 | 25 | testHasOwnProperty(binding.object.hasOwnPropertyWithNapiValue); 26 | testHasOwnProperty(binding.object.hasOwnPropertyWithNapiWrapperValue); 27 | testHasOwnProperty(binding.object.hasOwnPropertyWithCStyleString); 28 | testHasOwnProperty(binding.object.hasOwnPropertyWithCppStyleString); 29 | 30 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithNapiValue); 31 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithNapiWrapperValue); 32 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithCStyleString); 33 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasOwnPropertyWithCppStyleString); 34 | } 35 | -------------------------------------------------------------------------------- /test/object/has_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value HasPropertyWithNapiValue(const CallbackInfo& info) { 7 | Object obj = info[0].As(); 8 | Name key = info[1].As(); 9 | return Boolean::New( 10 | info.Env(), MaybeUnwrapOr(obj.Has(static_cast(key)), false)); 11 | } 12 | 13 | Value HasPropertyWithNapiWrapperValue(const CallbackInfo& info) { 14 | Object obj = info[0].As(); 15 | Name key = info[1].As(); 16 | return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Has(key), false)); 17 | } 18 | 19 | Value HasPropertyWithCStyleString(const CallbackInfo& info) { 20 | Object obj = info[0].As(); 21 | String jsKey = info[1].As(); 22 | return Boolean::New(info.Env(), 23 | MaybeUnwrapOr(obj.Has(jsKey.Utf8Value().c_str()), false)); 24 | } 25 | 26 | Value HasPropertyWithUint32(const CallbackInfo& info) { 27 | Object obj = info[0].As(); 28 | Number jsKey = info[1].As(); 29 | return Boolean::New(info.Env(), 30 | MaybeUnwrapOr(obj.Has(jsKey.Uint32Value()), false)); 31 | } 32 | 33 | Value HasPropertyWithCppStyleString(const CallbackInfo& info) { 34 | Object obj = info[0].As(); 35 | String jsKey = info[1].As(); 36 | return Boolean::New(info.Env(), 37 | MaybeUnwrapOr(obj.Has(jsKey.Utf8Value()), false)); 38 | } 39 | -------------------------------------------------------------------------------- /test/object/has_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testHasProperty (nativeHasProperty) { 9 | const obj = { one: 1 }; 10 | 11 | Object.defineProperty(obj, 'two', { value: 2 }); 12 | 13 | assert.strictEqual(nativeHasProperty(obj, 'one'), true); 14 | assert.strictEqual(nativeHasProperty(obj, 'two'), true); 15 | assert.strictEqual('toString' in obj, true); 16 | assert.strictEqual(nativeHasProperty(obj, 'toString'), true); 17 | } 18 | 19 | function testShouldThrowErrorIfKeyIsInvalid (nativeHasProperty) { 20 | assert.throws(() => { 21 | nativeHasProperty(undefined, 'test'); 22 | }, /Cannot convert undefined or null to object/); 23 | } 24 | 25 | const objectWithInt32Key = { 12: 101 }; 26 | assert.strictEqual(binding.object.hasPropertyWithUint32(objectWithInt32Key, 12), true); 27 | 28 | testHasProperty(binding.object.hasPropertyWithNapiValue); 29 | testHasProperty(binding.object.hasPropertyWithNapiWrapperValue); 30 | testHasProperty(binding.object.hasPropertyWithCStyleString); 31 | testHasProperty(binding.object.hasPropertyWithCppStyleString); 32 | 33 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithNapiValue); 34 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithNapiWrapperValue); 35 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithCStyleString); 36 | testShouldThrowErrorIfKeyIsInvalid(binding.object.hasPropertyWithCppStyleString); 37 | } 38 | -------------------------------------------------------------------------------- /test/object/object_deprecated.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | static bool testValue = true; 6 | 7 | namespace { 8 | 9 | Value TestGetter(const CallbackInfo& info) { 10 | return Boolean::New(info.Env(), testValue); 11 | } 12 | 13 | void TestSetter(const CallbackInfo& info) { 14 | testValue = info[0].As(); 15 | } 16 | 17 | Value TestFunction(const CallbackInfo& info) { 18 | return Boolean::New(info.Env(), true); 19 | } 20 | 21 | void DefineProperties(const CallbackInfo& info) { 22 | Object obj = info[0].As(); 23 | String nameType = info[1].As(); 24 | Env env = info.Env(); 25 | 26 | if (nameType.Utf8Value() == "literal") { 27 | obj.DefineProperties({ 28 | PropertyDescriptor::Accessor("readonlyAccessor", TestGetter), 29 | PropertyDescriptor::Accessor( 30 | "readwriteAccessor", TestGetter, TestSetter), 31 | PropertyDescriptor::Function("function", TestFunction), 32 | }); 33 | } else if (nameType.Utf8Value() == "string") { 34 | // VS2013 has lifetime issues when passing temporary objects into the 35 | // constructor of another object. It generates code to destruct the object 36 | // as soon as the constructor call returns. Since this isn't a common case 37 | // for using std::string objects, I'm refactoring the test to work around 38 | // the issue. 39 | std::string str1("readonlyAccessor"); 40 | std::string str2("readwriteAccessor"); 41 | std::string str7("function"); 42 | 43 | obj.DefineProperties({ 44 | PropertyDescriptor::Accessor(str1, TestGetter), 45 | PropertyDescriptor::Accessor(str2, TestGetter, TestSetter), 46 | PropertyDescriptor::Function(str7, TestFunction), 47 | }); 48 | } else if (nameType.Utf8Value() == "value") { 49 | obj.DefineProperties({ 50 | PropertyDescriptor::Accessor(Napi::String::New(env, "readonlyAccessor"), 51 | TestGetter), 52 | PropertyDescriptor::Accessor( 53 | Napi::String::New(env, "readwriteAccessor"), 54 | TestGetter, 55 | TestSetter), 56 | PropertyDescriptor::Function(Napi::String::New(env, "function"), 57 | TestFunction), 58 | }); 59 | } 60 | } 61 | 62 | } // end of anonymous namespace 63 | 64 | Object InitObjectDeprecated(Env env) { 65 | Object exports = Object::New(env); 66 | 67 | exports["defineProperties"] = Function::New(env, DefineProperties); 68 | 69 | return exports; 70 | } 71 | -------------------------------------------------------------------------------- /test/object/object_deprecated.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | if (!('object_deprecated' in binding)) { 9 | return; 10 | } 11 | 12 | function assertPropertyIsNot (obj, key, attribute) { 13 | const propDesc = Object.getOwnPropertyDescriptor(obj, key); 14 | assert.ok(propDesc); 15 | assert.ok(!propDesc[attribute]); 16 | } 17 | 18 | function testDefineProperties (nameType) { 19 | const obj = {}; 20 | binding.object.defineProperties(obj, nameType); 21 | 22 | assertPropertyIsNot(obj, 'readonlyAccessor', 'enumerable'); 23 | assertPropertyIsNot(obj, 'readonlyAccessor', 'configurable'); 24 | assert.strictEqual(obj.readonlyAccessor, true); 25 | 26 | assertPropertyIsNot(obj, 'readwriteAccessor', 'enumerable'); 27 | assertPropertyIsNot(obj, 'readwriteAccessor', 'configurable'); 28 | obj.readwriteAccessor = false; 29 | assert.strictEqual(obj.readwriteAccessor, false); 30 | obj.readwriteAccessor = true; 31 | assert.strictEqual(obj.readwriteAccessor, true); 32 | 33 | assertPropertyIsNot(obj, 'function', 'writable'); 34 | assertPropertyIsNot(obj, 'function', 'enumerable'); 35 | assertPropertyIsNot(obj, 'function', 'configurable'); 36 | assert.strictEqual(obj.function(), true); 37 | } 38 | 39 | testDefineProperties('literal'); 40 | testDefineProperties('string'); 41 | testDefineProperties('value'); 42 | } 43 | -------------------------------------------------------------------------------- /test/object/object_freeze_seal.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | #if (NAPI_VERSION > 7) 5 | 6 | using namespace Napi; 7 | 8 | Value Freeze(const CallbackInfo& info) { 9 | Object obj = info[0].As(); 10 | return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Freeze(), false)); 11 | } 12 | 13 | Value Seal(const CallbackInfo& info) { 14 | Object obj = info[0].As(); 15 | return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Seal(), false)); 16 | } 17 | 18 | Object InitObjectFreezeSeal(Env env) { 19 | Object exports = Object::New(env); 20 | exports["freeze"] = Function::New(env, Freeze); 21 | exports["seal"] = Function::New(env, Seal); 22 | return exports; 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /test/object/object_freeze_seal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | { 9 | const obj = { x: 'a', y: 'b', z: 'c' }; 10 | assert.strictEqual(binding.object_freeze_seal.freeze(obj), true); 11 | assert.strictEqual(Object.isFrozen(obj), true); 12 | assert.throws(() => { 13 | obj.x = 10; 14 | }, /Cannot assign to read only property 'x' of object '#/); 15 | assert.throws(() => { 16 | obj.w = 15; 17 | }, /Cannot add property w, object is not extensible/); 18 | assert.throws(() => { 19 | delete obj.x; 20 | }, /Cannot delete property 'x' of #/); 21 | } 22 | 23 | { 24 | const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { 25 | preventExtensions () { 26 | throw new Error('foo'); 27 | } 28 | }); 29 | 30 | assert.throws(() => { 31 | binding.object_freeze_seal.freeze(obj); 32 | }, /foo/); 33 | } 34 | 35 | { 36 | const obj = { x: 'a', y: 'b', z: 'c' }; 37 | assert.strictEqual(binding.object_freeze_seal.seal(obj), true); 38 | assert.strictEqual(Object.isSealed(obj), true); 39 | assert.throws(() => { 40 | obj.w = 'd'; 41 | }, /Cannot add property w, object is not extensible/); 42 | assert.throws(() => { 43 | delete obj.x; 44 | }, /Cannot delete property 'x' of #/); 45 | // Sealed objects allow updating existing properties, 46 | // so this should not throw. 47 | obj.x = 'd'; 48 | } 49 | 50 | { 51 | const obj = new Proxy({ x: 'a', y: 'b', z: 'c' }, { 52 | preventExtensions () { 53 | throw new Error('foo'); 54 | } 55 | }); 56 | 57 | assert.throws(() => { 58 | binding.object_freeze_seal.seal(obj); 59 | }, /foo/); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/object/set_property.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value SetPropertyWithNapiValue(const CallbackInfo& info) { 7 | Object obj = info[0].As(); 8 | Name key = info[1].As(); 9 | Value value = info[2]; 10 | return Boolean::New( 11 | info.Env(), 12 | MaybeUnwrapOr(obj.Set(static_cast(key), value), false)); 13 | } 14 | 15 | Value SetPropertyWithNapiWrapperValue(const CallbackInfo& info) { 16 | Object obj = info[0].As(); 17 | Name key = info[1].As(); 18 | Value value = info[2]; 19 | return Boolean::New(info.Env(), MaybeUnwrapOr(obj.Set(key, value), false)); 20 | } 21 | 22 | Value SetPropertyWithUint32(const CallbackInfo& info) { 23 | Object obj = info[0].As(); 24 | Number key = info[1].As(); 25 | Value value = info[2]; 26 | return Boolean::New(info.Env(), 27 | MaybeUnwrapOr(obj.Set(key.Uint32Value(), value), false)); 28 | } 29 | 30 | Value SetPropertyWithCStyleString(const CallbackInfo& info) { 31 | Object obj = info[0].As(); 32 | String jsKey = info[1].As(); 33 | Value value = info[2]; 34 | return Boolean::New( 35 | info.Env(), 36 | MaybeUnwrapOr(obj.Set(jsKey.Utf8Value().c_str(), value), false)); 37 | } 38 | 39 | Value SetPropertyWithCppStyleString(const CallbackInfo& info) { 40 | Object obj = info[0].As(); 41 | String jsKey = info[1].As(); 42 | Value value = info[2]; 43 | return Boolean::New(info.Env(), 44 | MaybeUnwrapOr(obj.Set(jsKey.Utf8Value(), value), false)); 45 | } 46 | -------------------------------------------------------------------------------- /test/object/set_property.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testSetProperty (nativeSetProperty, key = 'test') { 9 | const obj = {}; 10 | assert.strictEqual(nativeSetProperty(obj, key, 1), true); 11 | assert.strictEqual(obj[key], 1); 12 | } 13 | 14 | function testShouldThrowErrorIfKeyIsInvalid (nativeSetProperty) { 15 | assert.throws(() => { 16 | nativeSetProperty(undefined, 'test', 1); 17 | }, /Cannot convert undefined or null to object/); 18 | } 19 | 20 | testSetProperty(binding.object.setPropertyWithNapiValue); 21 | testSetProperty(binding.object.setPropertyWithNapiWrapperValue); 22 | testSetProperty(binding.object.setPropertyWithCStyleString); 23 | testSetProperty(binding.object.setPropertyWithCppStyleString); 24 | testSetProperty(binding.object.setPropertyWithUint32, 12); 25 | 26 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiValue); 27 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithNapiWrapperValue); 28 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithCStyleString); 29 | testShouldThrowErrorIfKeyIsInvalid(binding.object.setPropertyWithCppStyleString); 30 | } 31 | -------------------------------------------------------------------------------- /test/object/subscript_operator.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | Value SubscriptGetWithCStyleString(const CallbackInfo& info) { 7 | String jsKey = info[1].As(); 8 | 9 | // make sure const case compiles 10 | const Object obj2 = info[0].As(); 11 | MaybeUnwrap(obj2[jsKey.Utf8Value().c_str()]).As(); 12 | 13 | Object obj = info[0].As(); 14 | return obj[jsKey.Utf8Value().c_str()]; 15 | } 16 | 17 | Value SubscriptGetWithCppStyleString(const CallbackInfo& info) { 18 | String jsKey = info[1].As(); 19 | 20 | // make sure const case compiles 21 | const Object obj2 = info[0].As(); 22 | MaybeUnwrap(obj2[jsKey.Utf8Value()]).As(); 23 | 24 | Object obj = info[0].As(); 25 | return obj[jsKey.Utf8Value()]; 26 | } 27 | 28 | Value SubscriptGetAtIndex(const CallbackInfo& info) { 29 | uint32_t index = info[1].As(); 30 | 31 | // make sure const case compiles 32 | const Object obj2 = info[0].As(); 33 | MaybeUnwrap(obj2[index]).As(); 34 | 35 | Object obj = info[0].As(); 36 | return obj[index]; 37 | } 38 | 39 | void SubscriptSetWithCStyleString(const CallbackInfo& info) { 40 | Object obj = info[0].As(); 41 | String jsKey = info[1].As(); 42 | Value value = info[2]; 43 | obj[jsKey.Utf8Value().c_str()] = value; 44 | } 45 | 46 | void SubscriptSetWithCppStyleString(const CallbackInfo& info) { 47 | Object obj = info[0].As(); 48 | String jsKey = info[1].As(); 49 | Value value = info[2]; 50 | obj[jsKey.Utf8Value()] = value; 51 | } 52 | 53 | void SubscriptSetAtIndex(const CallbackInfo& info) { 54 | Object obj = info[0].As(); 55 | uint32_t index = info[1].As(); 56 | Value value = info[2]; 57 | obj[index] = value; 58 | } 59 | -------------------------------------------------------------------------------- /test/object/subscript_operator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | function test (binding) { 8 | function testProperty (obj, key, value, nativeGetProperty, nativeSetProperty) { 9 | nativeSetProperty(obj, key, value); 10 | assert.strictEqual(nativeGetProperty(obj, key), value); 11 | } 12 | 13 | testProperty({}, 'key', 'value', binding.object.subscriptGetWithCStyleString, binding.object.subscriptSetWithCStyleString); 14 | testProperty({ key: 'override me' }, 'key', 'value', binding.object.subscriptGetWithCppStyleString, binding.object.subscriptSetWithCppStyleString); 15 | testProperty({}, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex); 16 | testProperty({ key: 'override me' }, 0, 'value', binding.object.subscriptGetAtIndex, binding.object.subscriptSetAtIndex); 17 | } 18 | -------------------------------------------------------------------------------- /test/objectwrap_constructor_exception.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class ConstructorExceptionTest 4 | : public Napi::ObjectWrap { 5 | public: 6 | ConstructorExceptionTest(const Napi::CallbackInfo& info) 7 | : Napi::ObjectWrap(info) { 8 | Napi::Error error = Napi::Error::New(info.Env(), "an exception"); 9 | #ifdef NAPI_DISABLE_CPP_EXCEPTIONS 10 | error.ThrowAsJavaScriptException(); 11 | #else 12 | throw error; 13 | #endif // NAPI_DISABLE_CPP_EXCEPTIONS 14 | } 15 | 16 | static void Initialize(Napi::Env env, Napi::Object exports) { 17 | const char* name = "ConstructorExceptionTest"; 18 | exports.Set(name, DefineClass(env, name, {})); 19 | } 20 | }; 21 | 22 | Napi::Object InitObjectWrapConstructorException(Napi::Env env) { 23 | Napi::Object exports = Napi::Object::New(env); 24 | ConstructorExceptionTest::Initialize(env, exports); 25 | return exports; 26 | } 27 | -------------------------------------------------------------------------------- /test/objectwrap_constructor_exception.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const testUtil = require('./testUtil'); 5 | 6 | function test (binding) { 7 | return testUtil.runGCTests([ 8 | 'objectwrap constructor exception', 9 | () => { 10 | const { ConstructorExceptionTest } = binding.objectwrapConstructorException; 11 | assert.throws(() => (new ConstructorExceptionTest()), /an exception/); 12 | }, 13 | // Do on gc before returning. 14 | () => {} 15 | ]); 16 | } 17 | 18 | module.exports = require('./common').runTest(test); 19 | -------------------------------------------------------------------------------- /test/objectwrap_function.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "test_helper.h" 4 | 5 | class FunctionTest : public Napi::ObjectWrap { 6 | public: 7 | FunctionTest(const Napi::CallbackInfo& info) 8 | : Napi::ObjectWrap(info) {} 9 | 10 | static Napi::Value OnCalledAsFunction(const Napi::CallbackInfo& info) { 11 | // If called with a "true" argument, throw an exeption to test the handling. 12 | if (!info[0].IsUndefined() && MaybeUnwrap(info[0].ToBoolean())) { 13 | NAPI_THROW(Napi::Error::New(info.Env(), "an exception"), Napi::Value()); 14 | } 15 | // Otherwise, act as a factory. 16 | std::vector args; 17 | for (size_t i = 0; i < info.Length(); i++) args.push_back(info[i]); 18 | return MaybeUnwrap(GetConstructor(info.Env()).New(args)); 19 | } 20 | 21 | static void Initialize(Napi::Env env, Napi::Object exports) { 22 | const char* name = "FunctionTest"; 23 | Napi::Function func = DefineClass(env, name, {}); 24 | Napi::FunctionReference* ctor = new Napi::FunctionReference(); 25 | *ctor = Napi::Persistent(func); 26 | env.SetInstanceData(ctor); 27 | exports.Set(name, func); 28 | } 29 | 30 | static Napi::Function GetConstructor(Napi::Env env) { 31 | return env.GetInstanceData()->Value(); 32 | } 33 | }; 34 | 35 | Napi::Value ObjectWrapFunctionFactory(const Napi::CallbackInfo& info) { 36 | Napi::Object exports = Napi::Object::New(info.Env()); 37 | FunctionTest::Initialize(info.Env(), exports); 38 | return exports; 39 | } 40 | 41 | Napi::Object InitObjectWrapFunction(Napi::Env env) { 42 | return Napi::Function::New(env, "FunctionFactory"); 43 | } 44 | -------------------------------------------------------------------------------- /test/objectwrap_function.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./common').runTestInChildProcess({ 4 | suite: 'objectwrap_function', 5 | testName: 'runTest' 6 | }); 7 | -------------------------------------------------------------------------------- /test/objectwrap_multiple_inheritance.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class TestMIBase { 4 | public: 5 | TestMIBase() : test(0) {} 6 | virtual void dummy() {} 7 | uint32_t test; 8 | }; 9 | 10 | class TestMI : public TestMIBase, public Napi::ObjectWrap { 11 | public: 12 | TestMI(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} 13 | 14 | Napi::Value GetTest(const Napi::CallbackInfo& info) { 15 | return Napi::Number::New(info.Env(), test); 16 | } 17 | 18 | static void Initialize(Napi::Env env, Napi::Object exports) { 19 | exports.Set( 20 | "TestMI", 21 | DefineClass( 22 | env, "TestMI", {InstanceAccessor<&TestMI::GetTest>("test")})); 23 | } 24 | }; 25 | 26 | Napi::Object InitObjectWrapMultipleInheritance(Napi::Env env) { 27 | Napi::Object exports = Napi::Object::New(env); 28 | TestMI::Initialize(env, exports); 29 | return exports; 30 | } 31 | -------------------------------------------------------------------------------- /test/objectwrap_multiple_inheritance.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | const test = bindingName => { 6 | const binding = require(bindingName); 7 | const TestMI = binding.objectwrap_multiple_inheritance.TestMI; 8 | const testmi = new TestMI(); 9 | 10 | assert.strictEqual(testmi.test, 0); 11 | }; 12 | 13 | module.exports = require('./common').runTestWithBindingPath(test); 14 | -------------------------------------------------------------------------------- /test/objectwrap_removewrap.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace { 5 | 6 | static int dtor_called = 0; 7 | 8 | class DtorCounter { 9 | public: 10 | ~DtorCounter() { 11 | assert(dtor_called == 0); 12 | dtor_called++; 13 | } 14 | }; 15 | 16 | Napi::Value GetDtorCalled(const Napi::CallbackInfo& info) { 17 | return Napi::Number::New(info.Env(), dtor_called); 18 | } 19 | 20 | class Test : public Napi::ObjectWrap { 21 | public: 22 | Test(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { 23 | #ifdef NAPI_CPP_EXCEPTIONS 24 | throw Napi::Error::New(Env(), "Some error"); 25 | #else 26 | Napi::Error::New(Env(), "Some error").ThrowAsJavaScriptException(); 27 | #endif 28 | } 29 | 30 | static void Initialize(Napi::Env env, Napi::Object exports) { 31 | exports.Set("Test", DefineClass(env, "Test", {})); 32 | exports.Set("getDtorCalled", Napi::Function::New(env, GetDtorCalled)); 33 | } 34 | 35 | private: 36 | DtorCounter dtor_counter_; 37 | }; 38 | 39 | } // anonymous namespace 40 | 41 | Napi::Object InitObjectWrapRemoveWrap(Napi::Env env) { 42 | Napi::Object exports = Napi::Object::New(env); 43 | Test::Initialize(env, exports); 44 | return exports; 45 | } 46 | -------------------------------------------------------------------------------- /test/objectwrap_removewrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (process.argv[2] === 'child') { 4 | // Create a single wrapped instance then exit. 5 | // eslint-disable-next-line no-new 6 | new (require(process.argv[3]).objectwrap.Test)(); 7 | } else { 8 | const assert = require('assert'); 9 | const testUtil = require('./testUtil'); 10 | 11 | module.exports = require('./common').runTestWithBindingPath(test); 12 | 13 | function test (bindingName) { 14 | return testUtil.runGCTests([ 15 | 'objectwrap removewrap test', 16 | () => { 17 | const binding = require(bindingName); 18 | const Test = binding.objectwrap_removewrap.Test; 19 | const getDtorCalled = binding.objectwrap_removewrap.getDtorCalled; 20 | 21 | assert.strictEqual(getDtorCalled(), 0); 22 | assert.throws(() => { 23 | // eslint-disable-next-line no-new 24 | new Test(); 25 | }); 26 | assert.strictEqual(getDtorCalled(), 1); 27 | }, 28 | // Test that gc does not crash. 29 | () => {} 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/objectwrap_worker_thread.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const { Worker, isMainThread } = require('worker_threads'); 4 | const { runTestWithBuildType, whichBuildType } = require('./common'); 5 | 6 | module.exports = runTestWithBuildType(test); 7 | 8 | async function test () { 9 | if (isMainThread) { 10 | const buildType = await whichBuildType(); 11 | const worker = new Worker(__filename, { workerData: buildType }); 12 | return new Promise((resolve, reject) => { 13 | worker.on('exit', () => { 14 | resolve(); 15 | }); 16 | }, () => {}); 17 | } else { 18 | await require(path.join(__dirname, 'objectwrap.js')); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/promise.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value IsPromise(const CallbackInfo& info) { 6 | return Boolean::New(info.Env(), info[0].IsPromise()); 7 | } 8 | 9 | Value ResolvePromise(const CallbackInfo& info) { 10 | auto deferred = Promise::Deferred::New(info.Env()); 11 | deferred.Resolve(info[0]); 12 | return deferred.Promise(); 13 | } 14 | 15 | Value RejectPromise(const CallbackInfo& info) { 16 | auto deferred = Promise::Deferred::New(info.Env()); 17 | deferred.Reject(info[0]); 18 | return deferred.Promise(); 19 | } 20 | 21 | Value PromiseReturnsCorrectEnv(const CallbackInfo& info) { 22 | auto deferred = Promise::Deferred::New(info.Env()); 23 | return Boolean::New(info.Env(), deferred.Env() == info.Env()); 24 | } 25 | 26 | Object InitPromise(Env env) { 27 | Object exports = Object::New(env); 28 | 29 | exports["isPromise"] = Function::New(env, IsPromise); 30 | exports["resolvePromise"] = Function::New(env, ResolvePromise); 31 | exports["rejectPromise"] = Function::New(env, RejectPromise); 32 | exports["promiseReturnsCorrectEnv"] = 33 | Function::New(env, PromiseReturnsCorrectEnv); 34 | 35 | return exports; 36 | } 37 | -------------------------------------------------------------------------------- /test/promise.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const common = require('./common'); 5 | 6 | module.exports = common.runTest(test); 7 | 8 | async function test (binding) { 9 | assert.strictEqual(binding.promise.isPromise({}), false); 10 | 11 | const resolving = binding.promise.resolvePromise('resolved'); 12 | await assert.strictEqual(binding.promise.isPromise(resolving), true); 13 | resolving.then(common.mustCall()).catch(common.mustNotCall()); 14 | 15 | const rejecting = binding.promise.rejectPromise('error'); 16 | await assert.strictEqual(binding.promise.isPromise(rejecting), true); 17 | rejecting.then(common.mustNotCall()).catch(common.mustCall()); 18 | 19 | assert(binding.promise.promiseReturnsCorrectEnv()); 20 | } 21 | -------------------------------------------------------------------------------- /test/reference.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const testUtil = require('./testUtil'); 5 | 6 | module.exports = require('./common').runTest(test); 7 | 8 | function test (binding) { 9 | return testUtil.runGCTests([ 10 | 'test reference', 11 | () => binding.reference.createWeakArray(), 12 | () => assert.strictEqual(true, binding.reference.accessWeakArrayEmpty()), 13 | 'test reference move op', 14 | () => binding.reference.refMoveAssignTest(), 15 | 'test reference ref', 16 | () => binding.reference.referenceRefTest(), 17 | 'test reference reset', 18 | () => binding.reference.refResetTest() 19 | ]); 20 | } 21 | -------------------------------------------------------------------------------- /test/run_script.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | using namespace Napi; 5 | 6 | namespace { 7 | 8 | Value RunPlainString(const CallbackInfo& info) { 9 | Env env = info.Env(); 10 | return MaybeUnwrap(env.RunScript("1 + 2 + 3")); 11 | } 12 | 13 | Value RunStdString(const CallbackInfo& info) { 14 | Env env = info.Env(); 15 | std::string str = "1 + 2 + 3"; 16 | return MaybeUnwrap(env.RunScript(str)); 17 | } 18 | 19 | Value RunJsString(const CallbackInfo& info) { 20 | Env env = info.Env(); 21 | return MaybeUnwrapOr(env.RunScript(info[0].As()), Value()); 22 | } 23 | 24 | Value RunWithContext(const CallbackInfo& info) { 25 | Env env = info.Env(); 26 | 27 | Array keys = MaybeUnwrap(info[1].As().GetPropertyNames()); 28 | std::string code = "("; 29 | for (unsigned int i = 0; i < keys.Length(); i++) { 30 | if (i != 0) code += ","; 31 | code += MaybeUnwrap(keys.Get(i)).As().Utf8Value(); 32 | } 33 | code += ") => " + info[0].As().Utf8Value(); 34 | 35 | Value ret = MaybeUnwrap(env.RunScript(code)); 36 | Function fn = ret.As(); 37 | std::vector args; 38 | for (unsigned int i = 0; i < keys.Length(); i++) { 39 | Value key = MaybeUnwrap(keys.Get(i)); 40 | args.push_back(MaybeUnwrap(info[1].As().Get(key))); 41 | } 42 | return MaybeUnwrap(fn.Call(args)); 43 | } 44 | 45 | } // end anonymous namespace 46 | 47 | Object InitRunScript(Env env) { 48 | Object exports = Object::New(env); 49 | 50 | exports["plainString"] = Function::New(env, RunPlainString); 51 | exports["stdString"] = Function::New(env, RunStdString); 52 | exports["jsString"] = Function::New(env, RunJsString); 53 | exports["withContext"] = Function::New(env, RunWithContext); 54 | 55 | return exports; 56 | } 57 | -------------------------------------------------------------------------------- /test/run_script.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | const testUtil = require('./testUtil'); 5 | 6 | module.exports = require('./common').runTest(test); 7 | 8 | function test (binding) { 9 | return testUtil.runGCTests([ 10 | 'Plain C string', 11 | () => { 12 | const sum = binding.run_script.plainString(); 13 | assert.strictEqual(sum, 1 + 2 + 3); 14 | }, 15 | 16 | 'std::string', 17 | () => { 18 | const sum = binding.run_script.stdString(); 19 | assert.strictEqual(sum, 1 + 2 + 3); 20 | }, 21 | 22 | 'JavaScript string', 23 | () => { 24 | const sum = binding.run_script.jsString('1 + 2 + 3'); 25 | assert.strictEqual(sum, 1 + 2 + 3); 26 | }, 27 | 28 | 'JavaScript, but not a string', 29 | () => { 30 | assert.throws(() => { 31 | binding.run_script.jsString(true); 32 | }, { 33 | name: 'TypeError', 34 | message: 'A string was expected' 35 | }); 36 | }, 37 | 38 | 'With context', 39 | () => { 40 | const a = 1; const b = 2; const c = 3; 41 | const sum = binding.run_script.withContext('a + b + c', { a, b, c }); 42 | assert.strictEqual(sum, a + b + c); 43 | } 44 | ]); 45 | } 46 | -------------------------------------------------------------------------------- /test/testUtil.js: -------------------------------------------------------------------------------- 1 | // Run each test function in sequence, 2 | // with an async delay and GC call between each. 3 | 4 | function tick (x) { 5 | return new Promise((resolve) => { 6 | setImmediate(function ontick () { 7 | if (--x === 0) { 8 | resolve(); 9 | } else { 10 | setImmediate(ontick); 11 | } 12 | }); 13 | }); 14 | } 15 | 16 | async function runGCTests (tests) { 17 | // Break up test list into a list of lists of the form 18 | // [ [ 'test name', function() {}, ... ], ..., ]. 19 | const testList = []; 20 | let currentTest; 21 | for (const item of tests) { 22 | if (typeof item === 'string') { 23 | currentTest = []; 24 | testList.push(currentTest); 25 | } 26 | currentTest.push(item); 27 | } 28 | 29 | for (const test of testList) { 30 | await (async function (test) { 31 | let title; 32 | for (let i = 0; i < test.length; i++) { 33 | if (i === 0) { 34 | title = test[i]; 35 | } else { 36 | try { 37 | test[i](); 38 | } catch (e) { 39 | console.error('Test failed: ' + title); 40 | throw e; 41 | } 42 | if (i < tests.length - 1) { 43 | global.gc(); 44 | await tick(10); 45 | } 46 | } 47 | } 48 | })(test); 49 | } 50 | } 51 | 52 | module.exports = { 53 | runGCTests 54 | }; 55 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_ctx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | async function test (binding) { 8 | const ctx = { }; 9 | const tsfn = new binding.threadsafe_function_ctx.TSFNWrap(ctx); 10 | assert(tsfn.getContext() === ctx); 11 | await tsfn.release(); 12 | binding.threadsafe_function_ctx.AssertFnReturnCorrectCxt(); 13 | } 14 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_exception.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "napi.h" 3 | #include "test_helper.h" 4 | 5 | #if (NAPI_VERSION > 3) 6 | 7 | using namespace Napi; 8 | 9 | namespace { 10 | 11 | void CallJS(napi_env env, napi_value /* callback */, void* /*data*/) { 12 | Napi::Error error = Napi::Error::New(env, "test-from-native"); 13 | NAPI_THROW_VOID(error); 14 | } 15 | 16 | void TestCall(const CallbackInfo& info) { 17 | Napi::Env env = info.Env(); 18 | 19 | ThreadSafeFunction wrapped = 20 | ThreadSafeFunction::New(env, 21 | info[0].As(), 22 | Object::New(env), 23 | String::New(env, "Test"), 24 | 0, 25 | 1); 26 | wrapped.BlockingCall(static_cast(nullptr)); 27 | wrapped.Release(); 28 | } 29 | 30 | void TestCallWithNativeCallback(const CallbackInfo& info) { 31 | Napi::Env env = info.Env(); 32 | 33 | ThreadSafeFunction wrapped = ThreadSafeFunction::New( 34 | env, Napi::Function(), Object::New(env), String::New(env, "Test"), 0, 1); 35 | wrapped.BlockingCall(static_cast(nullptr), CallJS); 36 | wrapped.Release(); 37 | } 38 | 39 | } // namespace 40 | 41 | Object InitThreadSafeFunctionException(Env env) { 42 | Object exports = Object::New(env); 43 | exports["testCall"] = Function::New(env, TestCall); 44 | exports["testCallWithNativeCallback"] = 45 | Function::New(env, TestCallWithNativeCallback); 46 | 47 | return exports; 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_exception.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('../common'); 4 | 5 | module.exports = common.runTest(test); 6 | 7 | const execArgv = ['--force-node-api-uncaught-exceptions-policy=true']; 8 | async function test () { 9 | await common.runTestInChildProcess({ 10 | suite: 'threadsafe_function_exception', 11 | testName: 'testCall', 12 | execArgv 13 | }); 14 | 15 | await common.runTestInChildProcess({ 16 | suite: 'threadsafe_function_exception', 17 | testName: 'testCallWithNativeCallback', 18 | execArgv 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_existing_tsfn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | async function test (binding) { 8 | const testCall = binding.threadsafe_function_existing_tsfn.testCall; 9 | 10 | assert.strictEqual(typeof await testCall({ blocking: true, data: true }), 'number'); 11 | assert.strictEqual(typeof await testCall({ blocking: true, data: false }), 'undefined'); 12 | assert.strictEqual(typeof await testCall({ blocking: false, data: true }), 'number'); 13 | assert.strictEqual(typeof await testCall({ blocking: false, data: false }), 'undefined'); 14 | } 15 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_ptr.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | #if (NAPI_VERSION > 3) 4 | 5 | using namespace Napi; 6 | 7 | namespace { 8 | 9 | static Value Test(const CallbackInfo& info) { 10 | Object resource = info[0].As(); 11 | Function cb = info[1].As(); 12 | ThreadSafeFunction tsfn = 13 | ThreadSafeFunction::New(info.Env(), cb, resource, "Test", 1, 1); 14 | tsfn.Release(); 15 | return info.Env().Undefined(); 16 | } 17 | 18 | } // namespace 19 | 20 | Object InitThreadSafeFunctionPtr(Env env) { 21 | Object exports = Object::New(env); 22 | exports["test"] = Function::New(env, Test); 23 | 24 | return exports; 25 | } 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_ptr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('../common').runTest(test); 4 | 5 | function test (binding) { 6 | binding.threadsafe_function_ptr.test({}, () => {}); 7 | } 8 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_sum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | /** 5 | * 6 | * ThreadSafeFunction Tests: Thread Id Sums 7 | * 8 | * Every native C++ function that utilizes the TSFN will call the registered 9 | * callback with the thread id. Passing Array.prototype.push with a bound array 10 | * will push the thread id to the array. Therefore, starting `N` threads, we 11 | * will expect the sum of all elements in the array to be `(N-1) * (N) / 2` (as 12 | * thread IDs are 0-based) 13 | * 14 | * We check different methods of passing a ThreadSafeFunction around multiple 15 | * threads: 16 | * - `testWithTSFN`: The main thread creates the TSFN. Then, it creates 17 | * threads, passing the TSFN at thread construction. The number of threads is 18 | * static (known at TSFN creation). 19 | * - `testDelayedTSFN`: The main thread creates threads, passing a promise to a 20 | * TSFN at construction. Then, it creates the TSFN, and resolves each 21 | * threads' promise. The number of threads is static. 22 | * - `testAcquire`: The native binding returns a function to start a new. A 23 | * call to this function will return `false` once `N` calls have been made. 24 | * Each thread will acquire its own use of the TSFN, call it, and then 25 | * release. 26 | */ 27 | 28 | const THREAD_COUNT = 5; 29 | const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; 30 | 31 | module.exports = require('../common').runTest(test); 32 | 33 | /** @param {number[]} N */ 34 | const sum = (N) => N.reduce((sum, n) => sum + n, 0); 35 | 36 | function test (binding) { 37 | async function check (bindingFunction) { 38 | const calls = []; 39 | const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); 40 | assert.ok(result); 41 | assert.equal(sum(calls), EXPECTED_SUM); 42 | } 43 | 44 | async function checkAcquire () { 45 | const calls = []; 46 | const { promise, createThread, stopThreads } = binding.threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); 47 | for (let i = 0; i < THREAD_COUNT; i++) { 48 | createThread(); 49 | } 50 | stopThreads(); 51 | const result = await promise; 52 | assert.ok(result); 53 | assert.equal(sum(calls), EXPECTED_SUM); 54 | } 55 | 56 | return check(binding.threadsafe_function_sum.testDelayedTSFN) 57 | .then(() => check(binding.threadsafe_function_sum.testWithTSFN)) 58 | .then(() => checkAcquire()); 59 | } 60 | -------------------------------------------------------------------------------- /test/threadsafe_function/threadsafe_function_unref.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | #if (NAPI_VERSION > 3) 5 | 6 | using namespace Napi; 7 | 8 | namespace { 9 | 10 | static Value TestUnref(const CallbackInfo& info) { 11 | Napi::Env env = info.Env(); 12 | Object global = env.Global(); 13 | Object resource = info[0].As(); 14 | Function cb = info[1].As(); 15 | Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); 16 | ThreadSafeFunction* tsfn = new ThreadSafeFunction; 17 | 18 | *tsfn = ThreadSafeFunction::New( 19 | info.Env(), cb, resource, "Test", 1, 1, [tsfn](Napi::Env /* env */) { 20 | delete tsfn; 21 | }); 22 | 23 | tsfn->BlockingCall(); 24 | 25 | setTimeout.Call( 26 | global, 27 | {Function::New( 28 | env, [tsfn](const CallbackInfo& info) { tsfn->Unref(info.Env()); }), 29 | Number::New(env, 100)}); 30 | 31 | return info.Env().Undefined(); 32 | } 33 | 34 | static Value TestRef(const CallbackInfo& info) { 35 | Function cb = info[1].As(); 36 | 37 | auto tsfn = ThreadSafeFunction::New(info.Env(), cb, "testRes", 1, 1); 38 | 39 | tsfn.BlockingCall(); 40 | tsfn.Unref(info.Env()); 41 | tsfn.Ref(info.Env()); 42 | 43 | return info.Env().Undefined(); 44 | } 45 | 46 | } // namespace 47 | 48 | Object InitThreadSafeFunctionUnref(Env env) { 49 | Object exports = Object::New(env); 50 | exports["testUnref"] = Function::New(env, TestUnref); 51 | exports["testRef"] = Function::New(env, TestRef); 52 | return exports; 53 | } 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /test/thunking_manual.js: -------------------------------------------------------------------------------- 1 | // Flags: --expose-gc 2 | 'use strict'; 3 | 4 | module.exports = require('./common').runTest(test); 5 | 6 | function test (binding) { 7 | console.log('Thunking: Performing initial GC'); 8 | global.gc(); 9 | console.log('Thunking: Creating test object'); 10 | let object = binding.thunking_manual.createTestObject(); 11 | // eslint-disable-next-line no-unused-vars 12 | object = null; 13 | console.log('Thunking: About to GC\n--------'); 14 | global.gc(); 15 | console.log('--------\nThunking: GC complete'); 16 | } 17 | -------------------------------------------------------------------------------- /test/type_taggable.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | #if (NAPI_VERSION > 7) 4 | 5 | using namespace Napi; 6 | 7 | static const napi_type_tag type_tags[5] = { 8 | {0xdaf987b3cc62481a, 0xb745b0497f299531}, 9 | {0xbb7936c374084d9b, 0xa9548d0762eeedb9}, 10 | {0xa5ed9ce2e4c00c38, 0}, 11 | {0, 0}, 12 | {0xa5ed9ce2e4c00c38, 0xdaf987b3cc62481a}, 13 | }; 14 | 15 | template 16 | class TestTypeTaggable { 17 | public: 18 | static Value TypeTaggedInstance(const CallbackInfo& info) { 19 | TypeTaggable instance = Factory(info.Env()); 20 | uint32_t type_index = info[0].As().Int32Value(); 21 | 22 | instance.TypeTag(&type_tags[type_index]); 23 | 24 | return instance; 25 | } 26 | 27 | static Value CheckTypeTag(const CallbackInfo& info) { 28 | uint32_t type_index = info[0].As().Int32Value(); 29 | TypeTaggable instance = info[1].As(); 30 | 31 | return Boolean::New(info.Env(), 32 | instance.CheckTypeTag(&type_tags[type_index])); 33 | } 34 | }; 35 | 36 | TypeTaggable ObjectFactory(Env env) { 37 | return Object::New(env); 38 | } 39 | 40 | TypeTaggable ExternalFactory(Env env) { 41 | // External does not accept a nullptr for its data. 42 | return External::New(env, reinterpret_cast(0x1)); 43 | } 44 | 45 | using TestObject = TestTypeTaggable; 46 | using TestExternal = TestTypeTaggable>; 47 | 48 | Object InitTypeTaggable(Env env) { 49 | Object exports = Object::New(env); 50 | 51 | Object external = Object::New(env); 52 | exports["external"] = external; 53 | external["checkTypeTag"] = Function::New(env, &TestExternal::CheckTypeTag); 54 | external["typeTaggedInstance"] = 55 | Function::New(env, &TestExternal::TypeTaggedInstance); 56 | 57 | Object object = Object::New(env); 58 | exports["object"] = object; 59 | object["checkTypeTag"] = Function::New(env, &TestObject::CheckTypeTag); 60 | object["typeTaggedInstance"] = 61 | Function::New(env, &TestObject::TypeTaggedInstance); 62 | 63 | return exports; 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /test/type_taggable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function testTypeTaggable ({ typeTaggedInstance, checkTypeTag }) { 8 | const obj1 = typeTaggedInstance(0); 9 | const obj2 = typeTaggedInstance(1); 10 | 11 | // Verify that type tags are correctly accepted. 12 | assert.strictEqual(checkTypeTag(0, obj1), true); 13 | assert.strictEqual(checkTypeTag(1, obj2), true); 14 | 15 | // Verify that wrongly tagged objects are rejected. 16 | assert.strictEqual(checkTypeTag(0, obj2), false); 17 | assert.strictEqual(checkTypeTag(1, obj1), false); 18 | 19 | // Verify that untagged objects are rejected. 20 | assert.strictEqual(checkTypeTag(0, {}), false); 21 | assert.strictEqual(checkTypeTag(1, {}), false); 22 | 23 | // Node v14 and v16 have an issue checking type tags if the `upper` in 24 | // `napi_type_tag` is 0, so these tests can only be performed on Node version 25 | // >=18. See: 26 | // - https://github.com/nodejs/node/issues/43786 27 | // - https://github.com/nodejs/node/pull/43788 28 | const nodeVersion = parseInt(process.versions.node.split('.')[0]); 29 | if (nodeVersion < 18) { 30 | return; 31 | } 32 | 33 | const obj3 = typeTaggedInstance(2); 34 | const obj4 = typeTaggedInstance(3); 35 | 36 | // Verify that untagged objects are rejected. 37 | assert.strictEqual(checkTypeTag(0, {}), false); 38 | assert.strictEqual(checkTypeTag(1, {}), false); 39 | 40 | // Verify that type tags are correctly accepted. 41 | assert.strictEqual(checkTypeTag(0, obj1), true); 42 | assert.strictEqual(checkTypeTag(1, obj2), true); 43 | assert.strictEqual(checkTypeTag(2, obj3), true); 44 | assert.strictEqual(checkTypeTag(3, obj4), true); 45 | 46 | // Verify that wrongly tagged objects are rejected. 47 | assert.strictEqual(checkTypeTag(0, obj2), false); 48 | assert.strictEqual(checkTypeTag(1, obj1), false); 49 | assert.strictEqual(checkTypeTag(0, obj3), false); 50 | assert.strictEqual(checkTypeTag(1, obj4), false); 51 | assert.strictEqual(checkTypeTag(2, obj4), false); 52 | assert.strictEqual(checkTypeTag(3, obj3), false); 53 | assert.strictEqual(checkTypeTag(4, obj3), false); 54 | } 55 | 56 | // eslint-disable-next-line camelcase 57 | function test ({ type_taggable }) { 58 | testTypeTaggable(type_taggable.external); 59 | testTypeTaggable(type_taggable.object); 60 | } 61 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_ctx.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | async function test (binding) { 8 | const ctx = { }; 9 | const tsfn = new binding.typed_threadsafe_function_ctx.TSFNWrap(ctx); 10 | assert(tsfn.getContext() === ctx); 11 | await tsfn.release(); 12 | 13 | binding.typed_threadsafe_function_ctx.AssertTSFNReturnCorrectCxt(); 14 | } 15 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_exception.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "napi.h" 3 | #include "test_helper.h" 4 | 5 | #if (NAPI_VERSION > 3) 6 | 7 | using namespace Napi; 8 | 9 | namespace { 10 | 11 | void CallJS(Napi::Env env, 12 | Napi::Function /* callback */, 13 | std::nullptr_t* /* context */, 14 | void* /*data*/) { 15 | Napi::Error error = Napi::Error::New(env, "test-from-native"); 16 | NAPI_THROW_VOID(error); 17 | } 18 | 19 | using TSFN = TypedThreadSafeFunction; 20 | 21 | void TestCall(const CallbackInfo& info) { 22 | Napi::Env env = info.Env(); 23 | 24 | TSFN wrapped = TSFN::New( 25 | env, Napi::Function(), Object::New(env), String::New(env, "Test"), 0, 1); 26 | wrapped.BlockingCall(static_cast(nullptr)); 27 | wrapped.Release(); 28 | } 29 | 30 | } // namespace 31 | 32 | Object InitTypedThreadSafeFunctionException(Env env) { 33 | Object exports = Object::New(env); 34 | exports["testCall"] = Function::New(env, TestCall); 35 | 36 | return exports; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_exception.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const common = require('../common'); 4 | 5 | module.exports = common.runTest(test); 6 | 7 | async function test () { 8 | await common.runTestInChildProcess({ 9 | suite: 'typed_threadsafe_function_exception', 10 | testName: 'testCall', 11 | execArgv: ['--force-node-api-uncaught-exceptions-policy=true'] 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_existing_tsfn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('../common').runTest(test); 6 | 7 | async function test (binding) { 8 | const testCall = binding.typed_threadsafe_function_existing_tsfn.testCall; 9 | 10 | assert.strictEqual(typeof await testCall({ blocking: true, data: true }), 'number'); 11 | assert.strictEqual(typeof await testCall({ blocking: true, data: false }), 'undefined'); 12 | assert.strictEqual(typeof await testCall({ blocking: false, data: true }), 'number'); 13 | assert.strictEqual(typeof await testCall({ blocking: false, data: false }), 'undefined'); 14 | } 15 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_ptr.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | #if (NAPI_VERSION > 3) 4 | 5 | using namespace Napi; 6 | 7 | namespace { 8 | 9 | using TSFN = TypedThreadSafeFunction<>; 10 | 11 | static Value Test(const CallbackInfo& info) { 12 | Object resource = info[0].As(); 13 | Function cb = info[1].As(); 14 | TSFN tsfn = TSFN::New(info.Env(), cb, resource, "Test", 1, 1); 15 | tsfn.Release(); 16 | return info.Env().Undefined(); 17 | } 18 | 19 | static Value ExtractEnvNullValue(const CallbackInfo& info) { 20 | return info.Env().Null(); 21 | } 22 | 23 | } // namespace 24 | 25 | Object InitTypedThreadSafeFunctionPtr(Env env) { 26 | Object exports = Object::New(env); 27 | exports["test"] = Function::New(env, Test); 28 | exports["null"] = Function::New(env, ExtractEnvNullValue); 29 | return exports; 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_ptr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | module.exports = require('../common').runTest(test); 4 | 5 | function test (binding) { 6 | assert(binding.typed_threadsafe_function_ptr.test({}, () => {}) === undefined); 7 | assert(binding.typed_threadsafe_function_ptr.null() === null); 8 | } 9 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_sum.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const assert = require('assert'); 3 | 4 | /** 5 | * 6 | * ThreadSafeFunction Tests: Thread Id Sums 7 | * 8 | * Every native C++ function that utilizes the TSFN will call the registered 9 | * callback with the thread id. Passing Array.prototype.push with a bound array 10 | * will push the thread id to the array. Therefore, starting `N` threads, we 11 | * will expect the sum of all elements in the array to be `(N-1) * (N) / 2` (as 12 | * thread IDs are 0-based) 13 | * 14 | * We check different methods of passing a ThreadSafeFunction around multiple 15 | * threads: 16 | * - `testWithTSFN`: The main thread creates the TSFN. Then, it creates 17 | * threads, passing the TSFN at thread construction. The number of threads is 18 | * static (known at TSFN creation). 19 | * - `testDelayedTSFN`: The main thread creates threads, passing a promise to a 20 | * TSFN at construction. Then, it creates the TSFN, and resolves each 21 | * threads' promise. The number of threads is static. 22 | * - `testAcquire`: The native binding returns a function to start a new. A 23 | * call to this function will return `false` once `N` calls have been made. 24 | * Each thread will acquire its own use of the TSFN, call it, and then 25 | * release. 26 | */ 27 | 28 | const THREAD_COUNT = 5; 29 | const EXPECTED_SUM = (THREAD_COUNT - 1) * (THREAD_COUNT) / 2; 30 | 31 | module.exports = require('../common').runTest(test); 32 | 33 | /** @param {number[]} N */ 34 | const sum = (N) => N.reduce((sum, n) => sum + n, 0); 35 | 36 | function test (binding) { 37 | async function check (bindingFunction) { 38 | const calls = []; 39 | const result = await bindingFunction(THREAD_COUNT, Array.prototype.push.bind(calls)); 40 | assert.ok(result); 41 | assert.equal(sum(calls), EXPECTED_SUM); 42 | } 43 | 44 | async function checkAcquire () { 45 | const calls = []; 46 | const { promise, createThread, stopThreads } = binding.typed_threadsafe_function_sum.testAcquire(Array.prototype.push.bind(calls)); 47 | for (let i = 0; i < THREAD_COUNT; i++) { 48 | createThread(); 49 | } 50 | stopThreads(); 51 | const result = await promise; 52 | assert.ok(result); 53 | assert.equal(sum(calls), EXPECTED_SUM); 54 | } 55 | 56 | return check(binding.typed_threadsafe_function_sum.testDelayedTSFN) 57 | .then(() => check(binding.typed_threadsafe_function_sum.testWithTSFN)) 58 | .then(() => checkAcquire()); 59 | } 60 | -------------------------------------------------------------------------------- /test/typed_threadsafe_function/typed_threadsafe_function_unref.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | #include "test_helper.h" 3 | 4 | #if (NAPI_VERSION > 3) 5 | 6 | using namespace Napi; 7 | 8 | namespace { 9 | 10 | using TSFN = TypedThreadSafeFunction<>; 11 | using ContextType = std::nullptr_t; 12 | using FinalizerDataType = void; 13 | static Value TestUnref(const CallbackInfo& info) { 14 | Napi::Env env = info.Env(); 15 | Object global = env.Global(); 16 | Object resource = info[0].As(); 17 | Function cb = info[1].As(); 18 | Function setTimeout = MaybeUnwrap(global.Get("setTimeout")).As(); 19 | TSFN* tsfn = new TSFN; 20 | 21 | *tsfn = TSFN::New( 22 | info.Env(), 23 | cb, 24 | resource, 25 | "Test", 26 | 1, 27 | 1, 28 | nullptr, 29 | [tsfn](Napi::Env /* env */, FinalizerDataType*, ContextType*) { 30 | delete tsfn; 31 | }, 32 | static_cast(nullptr)); 33 | 34 | tsfn->BlockingCall(); 35 | tsfn->Ref(info.Env()); 36 | 37 | setTimeout.Call( 38 | global, 39 | {Function::New( 40 | env, [tsfn](const CallbackInfo& info) { tsfn->Unref(info.Env()); }), 41 | Number::New(env, 100)}); 42 | 43 | return info.Env().Undefined(); 44 | } 45 | 46 | static Value TestRef(const CallbackInfo& info) { 47 | Function cb = info[1].As(); 48 | 49 | auto tsfn = TSFN::New(info.Env(), cb, "testRes", 1, 1, nullptr); 50 | 51 | tsfn.BlockingCall(); 52 | tsfn.Unref(info.Env()); 53 | tsfn.Ref(info.Env()); 54 | 55 | return info.Env().Undefined(); 56 | } 57 | 58 | } // namespace 59 | 60 | Object InitTypedThreadSafeFunctionUnref(Env env) { 61 | Object exports = Object::New(env); 62 | exports["testUnref"] = Function::New(env, TestUnref); 63 | exports["testRef"] = Function::New(env, TestRef); 64 | return exports; 65 | } 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /test/typedarray-bigint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function test (binding) { 8 | [ 9 | ['bigint64', BigInt64Array], 10 | ['biguint64', BigUint64Array] 11 | ].forEach(([type, Constructor]) => { 12 | try { 13 | const length = 4; 14 | const t = binding.typedarray.createTypedArray(type, length); 15 | assert.ok(t instanceof Constructor); 16 | assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); 17 | assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); 18 | 19 | t[3] = 11n; 20 | assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); 21 | binding.typedarray.setTypedArrayElement(t, 3, 22n); 22 | assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); 23 | assert.strictEqual(t[3], 22n); 24 | 25 | const b = binding.typedarray.getTypedArrayBuffer(t); 26 | assert.ok(b instanceof ArrayBuffer); 27 | } catch (e) { 28 | console.log(type, Constructor); 29 | throw e; 30 | } 31 | 32 | try { 33 | const length = 4; 34 | const offset = 8; 35 | const b = new ArrayBuffer(offset + 64 * 4); 36 | 37 | const t = binding.typedarray.createTypedArray(type, length, b, offset); 38 | assert.ok(t instanceof Constructor); 39 | assert.strictEqual(binding.typedarray.getTypedArrayType(t), type); 40 | assert.strictEqual(binding.typedarray.getTypedArrayLength(t), length); 41 | 42 | t[3] = 11n; 43 | assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 11n); 44 | binding.typedarray.setTypedArrayElement(t, 3, 22n); 45 | assert.strictEqual(binding.typedarray.getTypedArrayElement(t, 3), 22n); 46 | assert.strictEqual(t[3], 22n); 47 | 48 | assert.strictEqual(binding.typedarray.getTypedArrayBuffer(t), b); 49 | } catch (e) { 50 | console.log(type, Constructor); 51 | throw e; 52 | } 53 | }); 54 | 55 | assert.throws(() => { 56 | binding.typedarray.createInvalidTypedArray(); 57 | }, /Invalid (pointer passed as )?argument/); 58 | } 59 | -------------------------------------------------------------------------------- /test/value_type_cast.cc: -------------------------------------------------------------------------------- 1 | #include "common/test_helper.h" 2 | #include "napi.h" 3 | 4 | using namespace Napi; 5 | 6 | #define TYPE_CAST_TYPES(V) \ 7 | V(Boolean) \ 8 | V(Number) \ 9 | V(BigInt) \ 10 | V(Date) \ 11 | V(String) \ 12 | V(Symbol) \ 13 | V(Object) \ 14 | V(Array) \ 15 | V(ArrayBuffer) \ 16 | V(TypedArray) \ 17 | V(DataView) \ 18 | V(Function) \ 19 | V(Promise) 20 | 21 | // The following types are tested individually. 22 | // External 23 | // TypedArrayOf 24 | // Buffer 25 | 26 | namespace { 27 | #define V(Type) \ 28 | void TypeCast##Type(const CallbackInfo& info) { USE(info[0].As()); } 29 | TYPE_CAST_TYPES(V) 30 | #undef V 31 | 32 | void TypeCastBuffer(const CallbackInfo& info) { 33 | USE(info[0].As>()); 34 | } 35 | 36 | void TypeCastExternal(const CallbackInfo& info) { 37 | USE(info[0].As>()); 38 | } 39 | 40 | void TypeCastTypeArrayOfUint8(const CallbackInfo& info) { 41 | USE(info[0].As>()); 42 | } 43 | } // namespace 44 | 45 | Object InitValueTypeCast(Env env, Object exports) { 46 | exports["external"] = External::New(env, nullptr); 47 | 48 | #define V(Type) exports["typeCast" #Type] = Function::New(env, TypeCast##Type); 49 | TYPE_CAST_TYPES(V) 50 | #undef V 51 | 52 | exports["typeCastBuffer"] = Function::New(env, TypeCastBuffer); 53 | exports["typeCastExternal"] = Function::New(env, TypeCastExternal); 54 | exports["typeCastTypeArrayOfUint8"] = 55 | Function::New(env, TypeCastTypeArrayOfUint8); 56 | 57 | return exports; 58 | } 59 | 60 | NODE_API_MODULE(addon, InitValueTypeCast) 61 | -------------------------------------------------------------------------------- /test/version_management.cc: -------------------------------------------------------------------------------- 1 | #include "napi.h" 2 | 3 | using namespace Napi; 4 | 5 | Value getNapiVersion(const CallbackInfo& info) { 6 | Napi::Env env = info.Env(); 7 | uint32_t napi_version = VersionManagement::GetNapiVersion(env); 8 | return Number::New(env, napi_version); 9 | } 10 | 11 | Value getNodeVersion(const CallbackInfo& info) { 12 | Napi::Env env = info.Env(); 13 | const napi_node_version* node_version = 14 | VersionManagement::GetNodeVersion(env); 15 | Object version = Object::New(env); 16 | version.Set("major", Number::New(env, node_version->major)); 17 | version.Set("minor", Number::New(env, node_version->minor)); 18 | version.Set("patch", Number::New(env, node_version->patch)); 19 | version.Set("release", String::New(env, node_version->release)); 20 | return version; 21 | } 22 | 23 | Object InitVersionManagement(Env env) { 24 | Object exports = Object::New(env); 25 | exports["getNapiVersion"] = Function::New(env, getNapiVersion); 26 | exports["getNodeVersion"] = Function::New(env, getNodeVersion); 27 | return exports; 28 | } 29 | -------------------------------------------------------------------------------- /test/version_management.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const assert = require('assert'); 4 | 5 | module.exports = require('./common').runTest(test); 6 | 7 | function parseVersion () { 8 | const expected = {}; 9 | expected.napi = parseInt(process.versions.napi); 10 | expected.release = process.release.name; 11 | const nodeVersion = process.versions.node.split('.'); 12 | expected.major = parseInt(nodeVersion[0]); 13 | expected.minor = parseInt(nodeVersion[1]); 14 | expected.patch = parseInt(nodeVersion[2]); 15 | return expected; 16 | } 17 | 18 | function test (binding) { 19 | const expected = parseVersion(); 20 | 21 | const napiVersion = binding.version_management.getNapiVersion(); 22 | assert.strictEqual(napiVersion, expected.napi); 23 | 24 | const nodeVersion = binding.version_management.getNodeVersion(); 25 | assert.strictEqual(nodeVersion.major, expected.major); 26 | assert.strictEqual(nodeVersion.minor, expected.minor); 27 | assert.strictEqual(nodeVersion.patch, expected.patch); 28 | assert.strictEqual(nodeVersion.release, expected.release); 29 | } 30 | -------------------------------------------------------------------------------- /tools/clang-format.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const spawn = require('child_process').spawnSync; 4 | const path = require('path'); 5 | 6 | const filesToCheck = ['*.h', '*.cc']; 7 | const FORMAT_START = process.env.FORMAT_START || 'main'; 8 | 9 | function main (args) { 10 | let fix = false; 11 | while (args.length > 0) { 12 | switch (args[0]) { 13 | case '-f': 14 | case '--fix': 15 | fix = true; 16 | break; 17 | default: 18 | } 19 | args.shift(); 20 | } 21 | 22 | const clangFormatPath = path.dirname(require.resolve('clang-format')); 23 | const binary = process.platform === 'win32' 24 | ? 'node_modules\\.bin\\clang-format.cmd' 25 | : 'node_modules/.bin/clang-format'; 26 | const options = ['--binary=' + binary, '--style=file']; 27 | if (fix) { 28 | options.push(FORMAT_START); 29 | } else { 30 | options.push('--diff', FORMAT_START); 31 | } 32 | 33 | const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format'); 34 | const result = spawn( 35 | 'python', 36 | [gitClangFormatPath, ...options, '--', ...filesToCheck], 37 | { encoding: 'utf-8' } 38 | ); 39 | 40 | if (result.stderr) { 41 | console.error('Error running git-clang-format:', result.stderr); 42 | return 2; 43 | } 44 | 45 | const clangFormatOutput = result.stdout.trim(); 46 | // Bail fast if in fix mode. 47 | if (fix) { 48 | console.log(clangFormatOutput); 49 | return 0; 50 | } 51 | // Detect if there is any complains from clang-format 52 | if ( 53 | clangFormatOutput !== '' && 54 | clangFormatOutput !== 'no modified files to format' && 55 | clangFormatOutput !== 'clang-format did not modify any files' 56 | ) { 57 | console.error(clangFormatOutput); 58 | const fixCmd = 'npm run lint:fix'; 59 | console.error(` 60 | ERROR: please run "${fixCmd}" to format changes in your commit 61 | Note that when running the command locally, please keep your local 62 | main branch and working branch up to date with nodejs/node-addon-api 63 | to exclude un-related complains. 64 | Or you can run "env FORMAT_START=upstream/main ${fixCmd}".`); 65 | return 1; 66 | } 67 | } 68 | 69 | if (require.main === module) { 70 | process.exitCode = main(process.argv.slice(2)); 71 | } 72 | -------------------------------------------------------------------------------- /tools/eslint-format.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const spawn = require('child_process').spawnSync; 4 | 5 | const filesToCheck = '*.js'; 6 | const FORMAT_START = process.env.FORMAT_START || 'main'; 7 | const IS_WIN = process.platform === 'win32'; 8 | const ESLINT_PATH = IS_WIN ? 'node_modules\\.bin\\eslint.cmd' : 'node_modules/.bin/eslint'; 9 | 10 | function main (args) { 11 | let fix = false; 12 | while (args.length > 0) { 13 | switch (args[0]) { 14 | case '-f': 15 | case '--fix': 16 | fix = true; 17 | break; 18 | default: 19 | } 20 | args.shift(); 21 | } 22 | 23 | // Check js files that change on unstaged file 24 | const fileUnStaged = spawn( 25 | 'git', 26 | ['diff', '--name-only', '--diff-filter=d', FORMAT_START, filesToCheck], 27 | { 28 | encoding: 'utf-8' 29 | } 30 | ); 31 | 32 | // Check js files that change on staged file 33 | const fileStaged = spawn( 34 | 'git', 35 | ['diff', '--name-only', '--cached', '--diff-filter=d', FORMAT_START, filesToCheck], 36 | { 37 | encoding: 'utf-8' 38 | } 39 | ); 40 | 41 | const options = [ 42 | ...fileStaged.stdout.split('\n').filter((f) => f !== ''), 43 | ...fileUnStaged.stdout.split('\n').filter((f) => f !== '') 44 | ]; 45 | 46 | if (fix) { 47 | options.push('--fix'); 48 | } 49 | 50 | const result = spawn(ESLINT_PATH, [...options], { 51 | encoding: 'utf-8' 52 | }); 53 | 54 | if (result.error && result.error.errno === 'ENOENT') { 55 | console.error('Eslint not found! Eslint is supposed to be found at ', ESLINT_PATH); 56 | return 2; 57 | } 58 | 59 | if (result.status === 1) { 60 | console.error('Eslint error:', result.stdout); 61 | const fixCmd = 'npm run lint:fix'; 62 | console.error(`ERROR: please run "${fixCmd}" to format changes in your commit 63 | Note that when running the command locally, please keep your local 64 | main branch and working branch up to date with nodejs/node-addon-api 65 | to exclude un-related complains. 66 | Or you can run "env FORMAT_START=upstream/main ${fixCmd}". 67 | Also fix JS files by yourself if necessary.`); 68 | return 1; 69 | } 70 | 71 | if (result.stderr) { 72 | console.error('Error running eslint:', result.stderr); 73 | return 2; 74 | } 75 | } 76 | 77 | if (require.main === module) { 78 | process.exitCode = main(process.argv.slice(2)); 79 | } 80 | -------------------------------------------------------------------------------- /unit-test/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | /generated 4 | -------------------------------------------------------------------------------- /unit-test/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Enable running tests with specific filter conditions: 3 | 4 | ### Example: 5 | 6 | - compile and run only tests on objectwrap.cc and objectwrap.js 7 | ``` 8 | npm run test --filter=objectwrap 9 | ``` 10 | 11 | 12 | # Wildcards are also possible: 13 | 14 | ### Example: 15 | 16 | - compile and run all tests files ending with reference -> function_reference.cc object_reference.cc reference.cc 17 | ``` 18 | npm run test --filter=*reference 19 | ``` 20 | 21 | # Multiple filter conditions are also allowed 22 | 23 | ### Example: 24 | 25 | - compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file 26 | ``` 27 | npm run test --filter='*function objectwrap' 28 | ``` 29 | -------------------------------------------------------------------------------- /unit-test/binding-file-template.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | /** 5 | * @param bindingConfigurations 6 | * This method acts as a template to generate the content of binding.cc file 7 | */ 8 | module.exports.generateFileContent = function (bindingConfigurations) { 9 | const content = []; 10 | const inits = []; 11 | const exports = []; 12 | 13 | for (const config of bindingConfigurations) { 14 | inits.push(`Object Init${config.objectName}(Env env);`); 15 | exports.push(`exports.Set("${config.propertyName}", Init${config.objectName}(env));`); 16 | } 17 | 18 | content.push('#include "napi.h"'); 19 | content.push('using namespace Napi;'); 20 | 21 | inits.forEach(init => content.push(init)); 22 | 23 | content.push('Object Init(Env env, Object exports) {'); 24 | 25 | exports.forEach(exp => content.push(exp)); 26 | 27 | content.push('return exports;'); 28 | content.push('}'); 29 | content.push('NODE_API_MODULE(addon, Init);'); 30 | 31 | return Promise.resolve(content.join('\r\n')); 32 | }; 33 | 34 | module.exports.writeToBindingFile = function writeToBindingFile (content) { 35 | const generatedFilePath = path.join(__dirname, 'generated', 'binding.cc'); 36 | fs.writeFileSync(generatedFilePath, ''); 37 | fs.writeFileSync(generatedFilePath, content, { flag: 'a' }); 38 | console.log('generated binding file ', generatedFilePath, new Date()); 39 | }; 40 | -------------------------------------------------------------------------------- /unit-test/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'target_defaults': { 3 | 'includes': ['../common.gypi'], 4 | 'include_dirs': ['../test/common', "./generated"], 5 | 'variables': { 6 | 'setup': ["@(build_sources)'], 36 | 'dependencies': [ 'generateBindingCC' ] 37 | }, 38 | { 39 | 'target_name': 'binding_noexcept', 40 | 'includes': ['../noexcept.gypi'], 41 | 'sources': ['>@(build_sources)'], 42 | 'dependencies': [ 'generateBindingCC' ] 43 | }, 44 | { 45 | 'target_name': 'binding_noexcept_maybe', 46 | 'includes': ['../noexcept.gypi'], 47 | 'sources': ['>@(build_sources)'], 48 | 'defines': ['NODE_ADDON_API_ENABLE_MAYBE'] 49 | }, 50 | { 51 | 'target_name': 'binding_swallowexcept', 52 | 'includes': ['../except.gypi'], 53 | 'sources': ['>@(build_sources)'], 54 | 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], 55 | 'dependencies': [ 'generateBindingCC' ] 56 | }, 57 | { 58 | 'target_name': 'binding_swallowexcept_noexcept', 59 | 'includes': ['../noexcept.gypi'], 60 | 'sources': ['>@(build_sources)'], 61 | 'defines': ['NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'], 62 | 'dependencies': [ 'generateBindingCC' ] 63 | }, 64 | { 65 | 'target_name': 'binding_custom_namespace', 66 | 'includes': ['../noexcept.gypi'], 67 | 'sources': ['>@(build_sources)'], 68 | 'defines': ['NAPI_CPP_CUSTOM_NAMESPACE=cstm'], 69 | 'dependencies': [ 'generateBindingCC' ] 70 | }, 71 | ], 72 | } 73 | -------------------------------------------------------------------------------- /unit-test/exceptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file points out anomalies/exceptions in test files when generating the binding.cc file 3 | * 4 | * nouns: words in file names that are misspelled 5 | * *NOTE: a 'constructor' property is explicitly added to override javascript object constructor 6 | * 7 | * exportNames: anomalies in init function names 8 | * 9 | * propertyNames: anomalies in exported property name of init functions 10 | * 11 | * skipBinding: skip including this file in binding.cc 12 | */ 13 | module.exports = { 14 | nouns: { 15 | constructor: 'constructor', 16 | threadsafe: 'threadSafe', 17 | objectwrap: 'objectWrap' 18 | }, 19 | exportNames: { 20 | AsyncWorkerPersistent: 'PersistentAsyncWorker' 21 | }, 22 | propertyNames: { 23 | async_worker_persistent: 'persistentasyncworker', 24 | objectwrap_constructor_exception: 'objectwrapConstructorException' 25 | }, 26 | skipBinding: [ 27 | 'global_object_delete_property', 28 | 'global_object_get_property', 29 | 'global_object_has_own_property', 30 | 'global_object_set_property' 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /unit-test/generate-binding-cc.js: -------------------------------------------------------------------------------- 1 | const listOfTestModules = require('./listOfTestModules'); 2 | const exceptions = require('./exceptions'); 3 | const { generateFileContent, writeToBindingFile } = require('./binding-file-template'); 4 | 5 | const buildDirs = listOfTestModules.dirs; 6 | const buildFiles = listOfTestModules.files; 7 | 8 | /** 9 | * @param none 10 | * @requires list of files to bind as command-line argument 11 | * @returns list of binding configurations 12 | */ 13 | function generateBindingConfigurations () { 14 | const testFilesToBind = process.argv.slice(2); 15 | console.log('test modules to bind: ', testFilesToBind); 16 | 17 | const configs = []; 18 | 19 | testFilesToBind.forEach((file) => { 20 | const configName = file.split('.cc')[0]; 21 | 22 | if (buildDirs[configName]) { 23 | for (const file of buildDirs[configName]) { 24 | if (exceptions.skipBinding.includes(file)) continue; 25 | configs.push(buildFiles[file]); 26 | } 27 | } else if (buildFiles[configName]) { 28 | configs.push(buildFiles[configName]); 29 | } else { 30 | console.log('not found', file, configName); 31 | } 32 | }); 33 | 34 | return Promise.resolve(configs); 35 | } 36 | 37 | generateBindingConfigurations().then(generateFileContent).then(writeToBindingFile); 38 | 39 | /** 40 | * Test cases 41 | * @fires only when run directly from terminal with TEST=true 42 | * eg: TEST=true node generate-binding-cc 43 | */ 44 | if (require.main === module && process.env.TEST === 'true') { 45 | const assert = require('assert'); 46 | 47 | const setArgsAndCall = (fn, filterCondition) => { process.argv = [null, null, ...filterCondition.split(' ')]; return fn(); }; 48 | const assertPromise = (promise, expectedVal) => promise.then((val) => assert.deepEqual(val, expectedVal)).catch(console.log); 49 | 50 | const expectedVal = [{ 51 | dir: '', 52 | objectName: 'AsyncProgressWorker', 53 | propertyName: 'async_progress_worker' 54 | }, 55 | { 56 | dir: '', 57 | objectName: 'PersistentAsyncWorker', 58 | propertyName: 'persistentasyncworker' 59 | }]; 60 | assertPromise(setArgsAndCall(generateBindingConfigurations, 'async_progress_worker async_worker_persistent'), expectedVal); 61 | } 62 | -------------------------------------------------------------------------------- /unit-test/setup.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { generateFileContent, writeToBindingFile } = require('./binding-file-template'); 3 | 4 | /** 5 | * @summary setup script to execute before node-gyp begins target actions 6 | */ 7 | if (!fs.existsSync('./generated')) { 8 | // create generated folder 9 | fs.mkdirSync('./generated'); 10 | // create empty binding.cc file 11 | generateFileContent([]).then(writeToBindingFile); 12 | // FIX: Its necessary to have an empty bindng.cc file, otherwise build fails first time 13 | } 14 | -------------------------------------------------------------------------------- /unit-test/spawnTask.js: -------------------------------------------------------------------------------- 1 | const { spawn } = require('child_process'); 2 | 3 | /** 4 | * spawns a child process to run a given node.js script 5 | */ 6 | module.exports.runChildProcess = function (scriptName, options) { 7 | const childProcess = spawn('node', [scriptName], options); 8 | 9 | childProcess.stdout.on('data', data => { 10 | console.log(`${data}`); 11 | }); 12 | childProcess.stderr.on('data', data => { 13 | console.log(`error: ${data}`); 14 | }); 15 | 16 | return new Promise((resolve, reject) => { 17 | childProcess.on('error', (error) => { 18 | console.log(`error: ${error.message}`); 19 | reject(error); 20 | }); 21 | childProcess.on('close', code => { 22 | console.log(`child process exited with code ${code}`); 23 | resolve(code); 24 | }); 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /unit-test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | const runChildProcess = require('./spawnTask').runChildProcess; 4 | 5 | /* 6 | * Execute tests with given filter conditions as a child process 7 | */ 8 | const executeTests = async function () { 9 | try { 10 | const workingDir = path.join(__dirname, '../'); 11 | const relativeBuildPath = path.join('../', 'unit-test'); 12 | const buildPath = path.join(__dirname, './unit-test'); 13 | const envVars = { ...process.env, REL_BUILD_PATH: relativeBuildPath, BUILD_PATH: buildPath }; 14 | 15 | console.log('Starting to run tests in ', buildPath, new Date()); 16 | 17 | const code = await runChildProcess('test', { cwd: workingDir, env: envVars }); 18 | 19 | if (code !== '0') { 20 | process.exitCode = code; 21 | process.exit(process.exitCode); 22 | } 23 | 24 | console.log('Completed running tests', new Date()); 25 | } catch (e) { 26 | console.log('Error occured running tests', new Date()); 27 | } 28 | }; 29 | 30 | executeTests(); 31 | --------------------------------------------------------------------------------