diff --git a/.clang-format b/.clang-format index 195c9c1..12002c4 100644 --- a/.clang-format +++ b/.clang-format @@ -33,7 +33,7 @@ BraceWrapping: BeforeElse: true IndentBraces: false BreakBeforeBinaryOperators: None -BreakBeforeBraces: Attach +BreakBeforeBraces: Custom BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false ColumnLimit: 0 @@ -72,7 +72,7 @@ PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true -SortIncludes: true +SortIncludes: false SpaceAfterCStyleCast: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements diff --git a/.travis.yml b/.travis.yml index 8447789..d26a75b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,10 @@ language: node_js sudo: false -# enable c++11/14 builds addons: apt: sources: [ 'ubuntu-toolchain-r-test' ] - packages: [ 'libstdc++-4.9-dev' ] + packages: [ 'libstdc++-5-dev' ] install: - node -v @@ -35,7 +34,7 @@ matrix: ## ** Builds that are published ** - # linux cfi build node v6/release + # linux cfi build node v10/release - os: linux env: BUILDTYPE=release TOOLSET=cfi CXXFLAGS="-fsanitize=cfi -fvisibility=hidden" LDFLAGS="-fsanitize=cfi" node_js: 10 @@ -47,43 +46,33 @@ matrix: - os: linux env: BUILDTYPE=debug node_js: 10 - # linux publishable node v8/release + # linux publishable node v12/release - os: linux env: BUILDTYPE=release - node_js: 8 - # linux publishable node v8/debug + node_js: 12 + # linux publishable node v12/debug - os: linux env: BUILDTYPE=debug - node_js: 8 - # linux publishable node v6/release + node_js: 12 + # linux publishable node v13/release - os: linux env: BUILDTYPE=release - node_js: 6 - # linux publishable node v6/debug + node_js: 13 + # linux publishable node v13/debug - os: linux env: BUILDTYPE=debug - node_js: 6 + node_js: 13 # osx publishable node v10/release - os: osx osx_image: xcode9.3 env: BUILDTYPE=release node_js: 10 - # osx publishable node v8/release + # osx publishable node v12/release - os: osx osx_image: xcode9.3 env: BUILDTYPE=release - node_js: 8 - # osx publishable node v6/release - - os: osx - osx_image: xcode9.3 - env: BUILDTYPE=release - node_js: 6 - # osx publishable node v6/debug - - os: osx - osx_image: xcode9.3 - env: BUILDTYPE=debug - node_js: 6 - # linux sanitizer build node v6/debug + node_js: 12 + # linux sanitizer build node v10/debug - os: linux env: BUILDTYPE=debug TOOLSET=asan node_js: 10 @@ -93,7 +82,8 @@ matrix: - make sanitize # Overrides `before_script` (tests are already run in `make sanitize`) before_script: - # osx sanitizer build node v6/debug + - true + # osx sanitizer build node v10/debug - os: osx env: BUILDTYPE=debug TOOLSET=asan node_js: 10 @@ -103,7 +93,7 @@ matrix: - make sanitize # Overrides `before_script` (tests are already run in `make sanitize`) before_script: - + - true ## ** Builds that do not get published ** # g++ build (default builds all use clang++) @@ -122,6 +112,7 @@ matrix: - make ${BUILDTYPE} # Overrides `script` to disable publishing script: + - true # Coverage build - os: linux env: BUILDTYPE=debug CXXFLAGS="--coverage" LDFLAGS="--coverage" @@ -132,6 +123,8 @@ matrix: - which llvm-cov - curl -S -f https://codecov.io/bash -o codecov - chmod +x codecov + # Workaround until we can avoid problem after https://github.com/travis-ci/travis-build/pull/1263 lands + - PATH=$(echo "$PATH" | sed 's/.\/node_modules\/.bin://') - ./codecov -x "llvm-cov gcov" -Z # Clang format build - os: linux @@ -146,8 +139,10 @@ matrix: - make format # Overrides `before_script`, no need to run tests before_script: + - true # Overrides `script` to disable publishing script: + - true # Clang tidy build - os: linux env: CLANG_TIDY @@ -161,5 +156,7 @@ matrix: - make tidy # Overrides `before_script`, no need to run tests before_script: + - true # Overrides `script` to disable publishing script: + - true diff --git a/Makefile b/Makefile index ba7bd3b..7876bbb 100644 --- a/Makefile +++ b/Makefile @@ -24,10 +24,10 @@ export WERROR ?= true # just typing `make` will call `make release` default: release -node_modules/nan: +node_modules/install: npm install --ignore-scripts -mason_packages/headers: node_modules/nan +mason_packages/headers: node_modules/install node_modules/.bin/mason-js install mason_packages/.link/include: mason_packages/headers diff --git a/binding.gyp b/binding.gyp index 7c3dbe5..7460e14 100644 --- a/binding.gyp +++ b/binding.gyp @@ -15,7 +15,7 @@ # It's a variable to make easy to pass to # cflags (linux) and xcode (mac) 'system_includes': [ - "-isystem <(module_root_dir)/= 1.4 < 2" } }, "extsprintf": { @@ -316,7 +316,7 @@ "integrity": "sha1-LEBFC5NI6X8oEyJZO6lnBLmr1NQ=", "dev": true, "requires": { - "is-function": "1.0.1" + "is-function": "~1.0.0" } }, "foreach": { @@ -335,9 +335,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", + "asynckit": "^0.4.0", "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "mime-types": "^2.1.12" } }, "fs-extra": { @@ -345,9 +345,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } }, "fs-minipass": { @@ -355,7 +355,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "fs.realpath": { @@ -374,14 +374,14 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "requires": { - "aproba": "1.2.0", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" } }, "getpass": { @@ -389,7 +389,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "glob": { @@ -397,12 +397,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "graceful-fs": { @@ -420,8 +420,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has": { @@ -430,7 +430,7 @@ "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.0.2" } }, "has-unicode": { @@ -443,9 +443,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -453,7 +453,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", "requires": { - "safer-buffer": "2.1.2" + "safer-buffer": ">= 2.1.2 < 3" } }, "ieee754": { @@ -467,7 +467,7 @@ "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "requires": { - "minimatch": "3.0.4" + "minimatch": "^3.0.4" } }, "inflight": { @@ -475,8 +475,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -506,7 +506,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-function": { @@ -521,7 +521,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "^1.0.1" } }, "is-symbol": { @@ -577,7 +577,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsprim": { @@ -592,9 +592,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, "mason-js-sdk": { @@ -602,15 +602,15 @@ "resolved": "https://registry.npmjs.org/mason-js-sdk/-/mason-js-sdk-0.1.4.tgz", "integrity": "sha1-vrC0YA4VBCT68uuUcEXl1YtweSE=", "requires": { - "d3-queue": "3.0.7", + "d3-queue": "^3.0.7", "extfs": "0.0.7", - "fs-extra": "4.0.3", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "needle": "2.2.1", - "npmlog": "4.1.2", - "request": "2.87.0", - "tar": "4.4.4" + "fs-extra": "^4.0.2", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "npmlog": "^4.1.2", + "request": "^2.83.0", + "tar": "^4.0.2" } }, "mime-db": { @@ -623,7 +623,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.33.0" } }, "minimatch": { @@ -631,21 +631,21 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.11" + "brace-expansion": "^1.1.7" } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.3.tgz", "integrity": "sha512-/jAn9/tEX4gnpyRATxgHEOV6xbcyxgT7iUnxo9Y3+OB0zX00TgKIv/2FZCf5brBbICcwbLqVv2ImjvWWrQMSYw==", "requires": { - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" }, "dependencies": { "safe-buffer": { @@ -660,7 +660,7 @@ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz", "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "requires": { - "minipass": "2.3.3" + "minipass": "^2.2.1" } }, "mkdirp": { @@ -683,36 +683,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" - }, "needle": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.1.tgz", "integrity": "sha512-t/ZswCM9JTWjAdXS9VpvqhI2Ct2sL2MdY4fUXqGJaGBk13ge99ObqRksRTbBE56K+wxUXwwfZYOuZHifFW9q+Q==", "requires": { - "debug": "2.6.9", - "iconv-lite": "0.4.23", - "sax": "1.2.4" + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" } }, + "node-addon-api": { + "version": "github:nodejs/node-addon-api#2c3d5df4634dec4e8c41cf251e15cd87d3aabaf8", + "from": "github:nodejs/node-addon-api" + }, "node-pre-gyp": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz", "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "requires": { - "detect-libc": "1.0.3", - "mkdirp": "0.5.1", - "needle": "2.2.1", - "nopt": "4.0.1", - "npm-packlist": "1.1.10", - "npmlog": "4.1.2", - "rc": "1.2.8", - "rimraf": "2.6.2", - "semver": "5.5.0", - "tar": "4.4.4" + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" } }, "nopt": { @@ -720,8 +719,8 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "requires": { - "abbrev": "1.1.1", - "osenv": "0.1.5" + "abbrev": "1", + "osenv": "^0.1.4" } }, "npm-bundled": { @@ -734,8 +733,8 @@ "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.10.tgz", "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "requires": { - "ignore-walk": "3.0.1", - "npm-bundled": "1.0.3" + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" } }, "npmlog": { @@ -743,10 +742,10 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" } }, "number-is-nan": { @@ -781,7 +780,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "os-homedir": { @@ -799,8 +798,8 @@ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" } }, "path-is-absolute": { @@ -845,10 +844,10 @@ "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { - "deep-extend": "0.6.0", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" }, "dependencies": { "deep-extend": { @@ -863,13 +862,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "2.0.0", - "safe-buffer": "5.1.2", - "string_decoder": "1.1.1", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" }, "dependencies": { "string_decoder": { @@ -877,7 +876,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "~5.1.0" } } } @@ -887,26 +886,26 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.7.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.2", - "safe-buffer": "5.1.2", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" } }, "resolve": { @@ -915,7 +914,7 @@ "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", "dev": true, "requires": { - "path-parse": "1.0.5" + "path-parse": "^1.0.5" } }, "resumer": { @@ -924,7 +923,7 @@ "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", "dev": true, "requires": { - "through": "2.3.8" + "through": "~2.3.4" } }, "rimraf": { @@ -932,7 +931,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, "safe-buffer": { @@ -970,14 +969,14 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" } }, "string-width": { @@ -985,9 +984,9 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string.prototype.trim": { @@ -996,9 +995,9 @@ "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.11.0", - "function-bind": "1.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.0", + "function-bind": "^1.0.2" } }, "strip-ansi": { @@ -1006,7 +1005,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-json-comments": { @@ -1020,19 +1019,19 @@ "integrity": "sha512-j0jO9BiScfqtPBb9QmPLL0qvxXMz98xjkMb7x8lKipFlJZwNJkqkWPou+NU4V6T9RnVh1kuSthLE8gLrN8bBfw==", "dev": true, "requires": { - "deep-equal": "1.0.1", - "defined": "1.0.0", - "for-each": "0.3.2", - "function-bind": "1.1.1", - "glob": "7.1.2", - "has": "1.0.1", - "inherits": "2.0.3", - "minimist": "1.2.0", - "object-inspect": "1.5.0", - "resolve": "1.5.0", - "resumer": "0.0.0", - "string.prototype.trim": "1.1.2", - "through": "2.3.8" + "deep-equal": "~1.0.1", + "defined": "~1.0.0", + "for-each": "~0.3.2", + "function-bind": "~1.1.1", + "glob": "~7.1.2", + "has": "~1.0.1", + "inherits": "~2.0.3", + "minimist": "~1.2.0", + "object-inspect": "~1.5.0", + "resolve": "~1.5.0", + "resumer": "~0.0.0", + "string.prototype.trim": "~1.1.2", + "through": "~2.3.8" } }, "tar": { @@ -1040,13 +1039,13 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.4.tgz", "integrity": "sha512-mq9ixIYfNF9SK0IS/h2HKMu8Q2iaCuhDDsZhdEag/FHv8fOaYld4vN7ouMgcSSt5WKZzPs8atclTcJm36OTh4w==", "requires": { - "chownr": "1.0.1", - "fs-minipass": "1.2.5", - "minipass": "2.3.3", - "minizlib": "1.1.0", - "mkdirp": "0.5.1", - "safe-buffer": "5.1.2", - "yallist": "3.0.2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.3", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" }, "dependencies": { "safe-buffer": { @@ -1067,7 +1066,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" } }, "tunnel-agent": { @@ -1075,7 +1074,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -1127,9 +1126,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "wide-align": { @@ -1137,7 +1136,7 @@ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "requires": { - "string-width": "1.0.2" + "string-width": "^1.0.2" } }, "wrappy": { @@ -1151,8 +1150,8 @@ "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "dev": true, "requires": { - "sax": "1.2.4", - "xmlbuilder": "4.2.1" + "sax": ">=0.6.0", + "xmlbuilder": "^4.1.0" } }, "xmlbuilder": { @@ -1161,7 +1160,7 @@ "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", "dev": true, "requires": { - "lodash": "4.17.10" + "lodash": "^4.0.0" } }, "yallist": { diff --git a/package.json b/package.json index 77469f7..014ec40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@mapbox/node-cpp-skel", "version": "0.1.0", - "description": "Skeleton for bindings to C++ libraries for Node.js using NAN", + "description": "Skeleton for bindings to C++ libraries for Node.js using N-API (node-addon-api)", "url": "http://github.com/mapbox/node-cpp-skel", "main": "./lib/index.js", "repository": { @@ -17,15 +17,15 @@ "license": "ISC", "dependencies": { "mason-js-sdk": "~0.1.4", - "nan": "~2.10.0", + "node-addon-api": "nodejs/node-addon-api", "node-pre-gyp": "~0.10.0" }, "devDependencies": { "aws-sdk": "^2.4.7", - "tape": "^4.5.1", + "bytes": "^2.4.0", "d3-queue": "^3.0.1", - "minimist": "~1.2.0", - "bytes": "^2.4.0" + "minimist": "^1.2.5", + "tape": "^4.5.1" }, "binary": { "module_name": "module", diff --git a/scripts/sanitize.sh b/scripts/sanitize.sh index 4168cca..c9b8543 100755 --- a/scripts/sanitize.sh +++ b/scripts/sanitize.sh @@ -21,10 +21,8 @@ fi export MASON_LLVM_RT_PRELOAD=$(pwd)/$(ls mason_packages/.link/lib/clang/*/lib/*/libclang_rt.asan*${SHARED_LIB_EXT}) SUPPRESSION_FILE="/tmp/leak_suppressions.txt" -echo "leak:__strdup" > ${SUPPRESSION_FILE} -echo "leak:v8::internal" >> ${SUPPRESSION_FILE} -echo "leak:node::CreateEnvironment" >> ${SUPPRESSION_FILE} -echo "leak:node::Init" >> ${SUPPRESSION_FILE} +# Suppress leak related to https://github.com/libuv/libuv/pull/2480 +echo "leak:uv__set_process_title_platform_init" >> ${SUPPRESSION_FILE} export ASAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer export MSAN_SYMBOLIZER_PATH=$(pwd)/mason_packages/.link/bin/llvm-symbolizer export UBSAN_OPTIONS=print_stacktrace=1 diff --git a/src/cpu_intensive_task.hpp b/src/cpu_intensive_task.hpp new file mode 100644 index 0000000..2723f5f --- /dev/null +++ b/src/cpu_intensive_task.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace detail { + +// simulate CPU intensive task +inline std::unique_ptr> do_expensive_work(std::string const& name, bool louder) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::string str = "...threads are busy async bees...hello " + name; + std::unique_ptr> result = std::make_unique>(str.begin(), str.end()); + if (louder) + { + std::string extra{"!!!!"}; + std::copy(extra.c_str(), extra.c_str() + extra.length(), back_inserter(*result)); + } + return result; +} + +} // namespace detail diff --git a/src/module.cpp b/src/module.cpp index 12af606..f01d540 100644 --- a/src/module.cpp +++ b/src/module.cpp @@ -2,26 +2,24 @@ #include "object_sync/hello.hpp" #include "standalone/hello.hpp" #include "standalone_async/hello_async.hpp" -#include +#include // #include "your_code.hpp" -// "target" is a magic var that NAN_MODULE_INIT passes into a module's scope. -// When you write things to target, they become available to call from -// Javascript world. -NAN_MODULE_INIT(init) { - +Napi::Object init(Napi::Env env, Napi::Object exports) +{ // expose hello method - Nan::SetMethod(target, "hello", standalone::hello); + exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, standalone::hello)); // expose helloAsync method - Nan::SetMethod(target, "helloAsync", standalone_async::helloAsync); + exports.Set(Napi::String::New(env, "helloAsync"), Napi::Function::New(env, standalone_async::helloAsync)); // expose HelloObject class - object_sync::HelloObject::Init(target); + object_sync::HelloObject::Init(env, exports); // expose HelloObjectAsync class - object_async::HelloObjectAsync::Init(target); + object_async::HelloObjectAsync::Init(env, exports); + return exports; /** * You may have noticed there are multiple "hello" functions as part of this * module. @@ -36,13 +34,8 @@ NAN_MODULE_INIT(init) { // Include your .hpp file at the top of this file. } -// Here we initialize the module (we only do this once) -// by attaching the init function to the module. This invokes -// a variety of magic from inside nodejs core that we don't need to -// worry about, but if you care the details are at https://github.com/nodejs/node/blob/34d1b1144e1af8382dad71c28c8d956ebf709801/src/node.h#L431-L518 -// We mark this NOLINT to avoid the clang-tidy checks -// warning about code inside nodejs that we don't control and can't -// directly change to avoid the warning. +// Initialize the module (we only do this once) +// Mark this NOLINT to avoid the clang-tidy checks // NODE_GYP_MODULE_NAME is the name of our module as defined in 'target_name' // variable in the 'binding.gyp', which is passed along as a compiler define -NODE_MODULE(NODE_GYP_MODULE_NAME, init) // NOLINT +NODE_API_MODULE(NODE_GYP_MODULE_NAME, init) // NOLINT diff --git a/src/module_utils.hpp b/src/module_utils.hpp index ea6992f..6266441 100644 --- a/src/module_utils.hpp +++ b/src/module_utils.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include namespace utils { @@ -10,35 +10,24 @@ namespace utils { * throwing errors. * Usage: * -* v8::Local callback; -* return CallbackError("error message", callback); // "return" is important to -* prevent duplicate callbacks from being fired! -* -* -* "inline" is important here as well. See for more contex: -* - https://github.com/mapbox/cpp/blob/master/glossary.md#inline-keyword -* - https://github.com/mapbox/node-cpp-skel/pull/52#discussion_r126847394 for -* context +* Napi::CallbackInfo info; +* return CallbackError("error message", info); * */ -inline void CallbackError(std::string message, v8::Local func) { - Nan::Callback cb(func); - v8::Local argv[1] = {Nan::Error(message.c_str())}; - Nan::Call(cb, 1, argv); +inline Napi::Value CallbackError(std::string const& message, Napi::CallbackInfo const& info) +{ + Napi::Object obj = Napi::Object::New(info.Env()); + obj.Set("message", message); + auto func = info[info.Length() - 1].As(); + // ^^^ here we assume that info has a valid callback function + // TODO: consider changing either method signature or adding internal checks + return func.Call({obj}); } +} // namespace utils -inline Nan::MaybeLocal NewBufferFrom(std::unique_ptr&& ptr) { - Nan::MaybeLocal res = Nan::NewBuffer( - &(*ptr)[0], - ptr->size(), - [](char*, void* hint) { - delete static_cast(hint); - }, - ptr.get()); - if (!res.IsEmpty()) { - ptr.release(); // NOLINT ignore bugprone-unused-return-value - } - return res; -} +namespace gsl { +template +using owner = T; +} // namespace gsl -} // namespace utils +// ^^^ type alias required for clang-tidy (cppcoreguidelines-owning-memory) diff --git a/src/object_async/hello_async.cpp b/src/object_async/hello_async.cpp index 34233ba..a36ef63 100644 --- a/src/object_async/hello_async.cpp +++ b/src/object_async/hello_async.cpp @@ -1,11 +1,12 @@ #include "hello_async.hpp" +#include "../cpu_intensive_task.hpp" #include "../module_utils.hpp" #include -#include #include #include #include +#include /** * Asynchronous class, called HelloObjectAsync @@ -35,305 +36,220 @@ */ // If this was not defined within a namespace, it would be in the global scope. -namespace object_async { - -// Custom constructor, assigns custom name arg passed in from Javascript world. -// This constructor uses member init list via the colon, aka "direct -// initialization", which is more efficient than using assignment operators. -// This constructor is using move semantics to literally "move" the value of -// name to a new place in memory (to the "name_" variable). -// This avoids copying the value and duplicating memory allocation, which can -// negatively affect performance. -HelloObjectAsync::HelloObjectAsync(std::string&& name) - : name_(name) {} - -// Triggered from Javascript world when calling "new HelloObjectAsync(name)" -NAN_METHOD(HelloObjectAsync::New) { - if (info.IsConstructCall()) { - try { - if (info.Length() >= 1) { - if (info[0]->IsString()) { - // Don't want to risk passing a null string around, which might create unpredictable behavior. - Nan::Utf8String utf8_value(info[0]); - int len = utf8_value.length(); - if (len <= 0) { - return Nan::ThrowTypeError("arg must be a non-empty string"); - } - - /** - * This line converts a V8 string to a C++ std::string. - * In the background, it triggers memory allocation (stack allocating, but std:string is also dynamically allocating memory in the heap) - * We want to avoid heap allocation to ensure more performant code. - * See https://github.com/mapbox/cpp/blob/master/glossary.md#stack-allocation - * and https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap/80113#80113 - * Also, providing the length allows the std::string constructor to avoid calculating the length internally - * and should be faster since it skips an operation. - */ - std::string name(*utf8_value, static_cast(len)); - - /** - * This line is where HelloObjectAsync takes ownership of "name" with the use of move semantics. - * Then all later usage of "name" are passed by reference (const&), but the actual home or address in memory - * will always be owned by this instance of HelloObjectAsync. Generally important to know what has ownership of an object. - * When a object/value is a member of a class (like "name"), we know the class (HelloObjectAsync) has full control of the scope of the object/value. - * This avoids the scenario of "name" being destroyed or becoming out of scope. - * - * Also, we're using "new" here to create a custom C++ class, based on node::ObjectWrap since this is a node addon. - * In this case, "new" allocates a C++ object (dynamically on the heap) and then passes ownership (control of when it gets deleted) - * to V8, the javascript engine which decides when to clean up the object based on how its’ garbage collector works. - * In other words, the memory of HelloObjectAsync is expliclty deleted via node::ObjectWrap when it's gone out of scope - * (the object needs to stay alive until the V8 garbage collector has decided it's done): - * https://github.com/nodejs/node/blob/7ec28a0a506efe9d1c03240fd028bea4a3d350da/src/node_object_wrap.h#L124 - **/ - auto self = std::make_unique(std::move(name)); // Using unique pointer to adhere to cpp core guideline: https://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-owning-memory.html - self->Wrap(info.This()); // Connects C++ object to Javascript object (this) - self.release(); // NOLINT Release the ownership of self so it can be managed by wrapper - } else { - return Nan::ThrowTypeError( - "arg must be a string"); - } - } else { - return Nan::ThrowTypeError( - "must provide string arg"); - } - } catch (const std::exception& ex) { - return Nan::ThrowTypeError(ex.what()); - } - - info.GetReturnValue().Set(info.This()); - } else { - return Nan::ThrowTypeError( - "Cannot call constructor as function, you need to use 'new' keyword"); - } -} - -// This function performs expensive allocation of std::map, querying, and string -// comparison, therefore threads are nice & busy. -// Also, notice that name is passed by reference (std::string const& name) -std::unique_ptr do_expensive_work(bool louder, std::string const& name) { - - std::map container; - std::size_t work_to_do = 100000; - - for (std::size_t i = 0; i < work_to_do; ++i) { - container.emplace(i, std::to_string(i)); - } - for (std::size_t i = 0; i < work_to_do; ++i) { - std::string const& item = container[i]; - - if (item != std::to_string(i)) { - - // AsyncHelloWorker's Execute function will take care of this error - // and return it to js-world via callback - // Marked NOLINT to avoid clang-tidy cert-err60-cpp error which we cannot - // avoid on some linux distros where std::runtime_error is not properly - // marked noexcept. Details at https://www.securecoding.cert.org/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible - throw std::runtime_error("Uh oh, this should never happen"); // NOLINT - } - } - - std::unique_ptr result = std::make_unique("...threads are busy async bees...hello " + name); - - if (louder) { - *result += "!!!!"; - } - - return result; -} +namespace object_async { -// This is the worker running asynchronously and calling a user-provided -// callback when done. -// Consider storing all C++ objects you need by value or by shared_ptr to keep -// them alive until done. -// Nan AsyncWorker docs: -// https://github.com/nodejs/nan/blob/master/doc/asyncworker.md -struct AsyncHelloWorker : Nan::AsyncWorker // NOLINT to disable cppcoreguidelines-special-member-functions +/* +struct AsyncHelloWorker : Napi::AsyncWorker { - - using Base = Nan::AsyncWorker; - // We explicitly delete the copy constructor and assignment operator below (even though Nan::Asyncworker) - // already does this in the base class. This allows us to have the `const std::string* name` - // pointer member without the silly g++ warning of "error: ‘struct object_async::AsyncHelloWorker’ has pointer data members [-Werror=effc++]" - AsyncHelloWorker(AsyncHelloWorker const&) = delete; - AsyncHelloWorker& operator=(AsyncHelloWorker const&) = delete; + using Base = Napi::AsyncWorker; + // ctor AsyncHelloWorker(bool louder, bool buffer, - const std::string* name, - Nan::Callback* cb) - : Base(cb, "skel:object-async-worker"), + std::string name, + Napi::Function const& cb) + : Base(cb), louder_(louder), buffer_(buffer), - name_(name) {} + name_(std::move(name)) {} // The Execute() function is getting called when the worker starts to run. // - You only have access to member variables stored in this worker. // - You do not have access to Javascript v8 objects here. - void Execute() override { - try { - result_ = do_expensive_work(louder_, *name_); - } catch (const std::exception& e) { - SetErrorMessage(e.what()); + void Execute() override + { + try + { + result_ = detail::do_expensive_work(name_, louder_); + } + catch (std::exception const& e) + { + SetError(e.what()); } } - // The HandleOKCallback() is getting called when Execute() successfully + // The OnOK() is getting called when Execute() successfully // completed. // - In case Execute() invoked SetErrorMessage("") this function is not // getting called. // - You have access to Javascript v8 objects again // - You have to translate from C++ member variables to Javascript v8 objects // - Finally, you call the user's callback with your results - void HandleOKCallback() override { - Nan::HandleScope scope; + void OnOK() override + { + Napi::HandleScope scope(Env()); + if (!Callback().IsEmpty()) + { + if (buffer_) + { + char * data = result_->data(); + std::size_t size = result_->size(); + auto buffer = Napi::Buffer::New(Env(), + data, + size, + [](Napi::Env, char*, gsl::owner*> v) { + delete v; + }, + result_.release()); + Callback().Call({Env().Null(), buffer}); + } + else + { + Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())}); + } + } + } - if (buffer_) { - const auto argc = 2u; - v8::Local argv[argc] = { - Nan::Null(), utils::NewBufferFrom(std::move(result_)).ToLocalChecked()}; + std::unique_ptr> result_ = nullptr; + bool const louder_; + bool const buffer_; + std::string const name_; +}; +*/ - // Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy - callback->Call(argc, static_cast*>(argv), async_resource); +// This V2 worker is overriding `GetResult` to return the arguments +// passed to the Callback invoked by the default OnOK() implementation. +// Above is alternative implementation with OnOK() method calling +// Callback with appropriate args. Both implementations use default OnError(). +struct AsyncHelloWorker_v2 : Napi::AsyncWorker +{ + using Base = Napi::AsyncWorker; + // ctor + AsyncHelloWorker_v2(bool louder, + bool buffer, + std::string name, + Napi::Function const& cb) + : Base(cb), + louder_(louder), + buffer_(buffer), + name_(std::move(name)) {} - } else { - const auto argc = 2u; - v8::Local argv[argc] = { - Nan::Null(), Nan::New(*result_).ToLocalChecked()}; + // The Execute() function is getting called when the worker starts to run. + // - You only have access to member variables stored in this worker. + // - You do not have access to Javascript v8 objects here. + void Execute() override + { + try + { + result_ = detail::do_expensive_work(name_, louder_); + } + catch (std::exception const& e) + { + SetError(e.what()); + } + } - // Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy - callback->Call(argc, static_cast*>(argv), async_resource); + std::vector GetResult(Napi::Env env) override + { + if (result_) + { + if (buffer_) + { + char* data = result_->data(); + std::size_t size = result_->size(); + auto buffer = Napi::Buffer::New(env, + data, + size, + [](Napi::Env, char*, gsl::owner*> v) { + delete v; + }, + result_.release()); + return {env.Null(), buffer}; + } + return {env.Null(), Napi::String::New(env, result_->data(), result_->size())}; } + return Base::GetResult(env); // returns an empty vector (default) } - std::unique_ptr result_ = std::make_unique(); - const bool louder_; - const bool buffer_; - // We use a pointer here to avoid copying the string data. - // This works because we know that the original string we are - // pointing to will be kept in scope/alive for the time while AsyncHelloWorker - // is using it. If we could not guarantee this then we would need to either - // copy the string or pass a shared_ptr. - const std::string* name_; + std::unique_ptr> result_ = nullptr; + bool const louder_; + bool const buffer_; + std::string const name_; }; -NAN_METHOD(HelloObjectAsync::helloAsync) { - // "info" comes from the NAN_METHOD macro, which returns differently according - // to the Node version - // "What is node::ObjectWrap???" The short version is that node::ObjectWrap - // and wrapping/unwrapping objects - // is the somewhat clumsy way it is possible to bind Node and C++. The main - // points to remember: - // - To access a class instance inside a C++ static method, you must unwrap - // the object. - // - The C++ methods must be static to make them available at startup across - // the language boundary (JS <-> C++). - auto* h = - Nan::ObjectWrap::Unwrap(info.Holder()); +Napi::FunctionReference HelloObjectAsync::constructor; // NOLINT + +HelloObjectAsync::HelloObjectAsync(Napi::CallbackInfo const& info) + : Napi::ObjectWrap(info) +{ + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + + std::size_t length = info.Length(); + if (length != 1 || !info[0].IsString()) + { + Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); + return; + } + name_ = info[0].As().Utf8Value(); + if (name_.empty()) + { + Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException(); + } +} +Napi::Value HelloObjectAsync::helloAsync(Napi::CallbackInfo const& info) +{ bool louder = false; bool buffer = false; - // Check second argument, should be a 'callback' function. - // This allows us to set the callback so we can use it to return errors - // instead of throwing. - // Also, "info" comes from the NAN_METHOD macro, which returns differently - // according to the version of node - if (!info[1]->IsFunction()) { - return Nan::ThrowTypeError("second arg 'callback' must be a function"); + Napi::Env env = info.Env(); + if (!(info.Length() == 2 && info[1].IsFunction())) + { + Napi::TypeError::New(env, "second arg 'callback' must be a function").ThrowAsJavaScriptException(); + return env.Null(); } - v8::Local callback = info[1].As(); + + Napi::Function callback = info[1].As(); // Check first argument, should be an 'options' object - if (!info[0]->IsObject()) { - return utils::CallbackError("first arg 'options' must be an object", - callback); + if (!info[0].IsObject()) + { + return utils::CallbackError("first arg 'options' must be an object", info); } - v8::Local options = info[0].As(); + Napi::Object options = info[0].As(); // Check options object for the "louder" property, which should be a boolean // value - if (options->Has(Nan::New("louder").ToLocalChecked())) { - v8::Local louder_val = - options->Get(Nan::New("louder").ToLocalChecked()); - if (!louder_val->IsBoolean()) { - return utils::CallbackError("option 'louder' must be a boolean", - callback); - } - Nan::Maybe maybe_louder = Nan::To(louder_val); - if (maybe_louder.IsNothing()) { - return utils::CallbackError("option 'louder' must be a boolean", callback); + if (options.Has(Napi::String::New(env, "louder"))) + { + Napi::Value louder_val = options.Get(Napi::String::New(env, "louder")); + if (!louder_val.IsBoolean()) + { + return utils::CallbackError("option 'louder' must be a boolean", info); } - louder = maybe_louder.FromJust(); + louder = louder_val.As().Value(); } // Check options object for the "buffer" property, which should be a boolean // value - if (options->Has(Nan::New("buffer").ToLocalChecked())) { - v8::Local buffer_val = - options->Get(Nan::New("buffer").ToLocalChecked()); - if (!buffer_val->IsBoolean()) { - return utils::CallbackError("option 'buffer' must be a boolean", - callback); + if (options.Has(Napi::String::New(env, "buffer"))) + { + Napi::Value buffer_val = options.Get(Napi::String::New(env, "buffer")); + if (!buffer_val.IsBoolean()) + { + return utils::CallbackError("option 'buffer' must be a boolean", info); } - Nan::Maybe maybe_buffer = Nan::To(buffer_val); - if (maybe_buffer.IsNothing()) { - return utils::CallbackError("option 'buffer' must be a boolean", callback); - } - buffer = maybe_buffer.FromJust(); + buffer = buffer_val.As().Value(); } - // Create a worker instance and queues it to run asynchronously invoking the - // callback when done. - // - Nan::AsyncWorker takes a pointer to a Nan::Callback and deletes the - // pointer automatically. - // - Nan::AsyncQueueWorker takes a pointer to a Nan::AsyncWorker and deletes - // the pointer automatically. - auto cb = std::make_unique(callback); - auto worker = std::make_unique(louder, buffer, &h->name_, cb.release()); - Nan::AsyncQueueWorker(worker.release()); + auto* worker = new AsyncHelloWorker_v2{louder, buffer, name_, callback}; // NOLINT + worker->Queue(); + return info.Env().Undefined(); // NOLINT } -// Singleton -Nan::Persistent& HelloObjectAsync::create_once() { - static Nan::Persistent init; - return init; +Napi::Object HelloObjectAsync::Init(Napi::Env env, Napi::Object exports) +{ + Napi::Function func = DefineClass(env, "HelloObjectAsync", {InstanceMethod("helloAsync", &HelloObjectAsync::helloAsync)}); + // Create a peristent reference to the class constructor. This will allow + // a function called on a class prototype and a function + // called on instance of a class to be distinguished from each other. + constructor = Napi::Persistent(func); + // Call the SuppressDestruct() method on the static data prevent the calling + // to this destructor to reset the reference when the environment is no longer + // available. + constructor.SuppressDestruct(); + exports.Set("HelloObjectAsync", func); + return exports; } -void HelloObjectAsync::Init(v8::Local target) { - // A handlescope is needed so that v8 objects created in the local memory - // space (this function in this case) - // are cleaned up when the function is done running (and the handlescope is - // destroyed) - // Fun trivia: forgetting a handlescope is one of the most common causes of - // memory leaks in node.js core - // https://www.joyent.com/blog/walmart-node-js-memory-leak - Nan::HandleScope scope; - - // This is saying: - // "Node, please allocate a new Javascript string object - // inside the V8 local memory space, with the value 'HelloObjectAsync' " - v8::Local whoami = Nan::New("HelloObjectAsync").ToLocalChecked(); - - // Create the HelloObject - auto fnTp = Nan::New( - HelloObjectAsync::New, v8::Local()); // Passing the HelloObject::New method above - fnTp->InstanceTemplate()->SetInternalFieldCount(1); // It's 1 when holding the ObjectWrap itself and nothing else - fnTp->SetClassName(whoami); // Passing the Javascript string object above - - // Add custom methods here. - // This is how helloAsync() is exposed as part of HelloObjectAsync. - // This line is attaching the "helloAsync" method to a JavaScript function - // prototype. - // "helloAsync" is therefore like a property of the fnTp object - // ex: console.log(HelloObjectAsync.helloAsync) --> [Function: helloAsync] - SetPrototypeMethod(fnTp, "helloAsync", helloAsync); - - // Create an unique instance of the HelloObject function template, - // then set this unique instance to the target - const auto fn = Nan::GetFunction(fnTp).ToLocalChecked(); - create_once().Reset(fn); // calls the static &HelloObjectAsync::create_once - // method above. This ensures the instructions in - // this Init function are retained in memory even - // after this code block ends. - Nan::Set(target, whoami, fn); -} } // namespace object_async diff --git a/src/object_async/hello_async.hpp b/src/object_async/hello_async.hpp index 3daffa1..7ef2900 100644 --- a/src/object_async/hello_async.hpp +++ b/src/object_async/hello_async.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include namespace object_async { @@ -10,27 +10,18 @@ namespace object_async { * Also, this class adheres to the rule of Zero because we define no custom * destructor or copy constructor */ -class HelloObjectAsync : public Nan::ObjectWrap { - +class HelloObjectAsync : public Napi::ObjectWrap +{ public: // initializer - static void Init(v8::Local target); - - // methods required for the V8 constructor - static NAN_METHOD(New); - static Nan::Persistent& create_once(); - - // helloAsync, custom async method tied to Init of this class - // method's logic lives in ./hello.cpp - static NAN_METHOD(helloAsync); - - // C++ Constructor - // Passing the arg by rvalue reference (&&) - HelloObjectAsync(std::string&& name); + static Napi::Object Init(Napi::Env env, Napi::Object exports); + explicit HelloObjectAsync(Napi::CallbackInfo const& info); + Napi::Value helloAsync(Napi::CallbackInfo const& info); private: // member variable // specific to each instance of the class - std::string name_; + static Napi::FunctionReference constructor; + std::string name_ = ""; }; } // namespace object_async diff --git a/src/object_sync/hello.cpp b/src/object_sync/hello.cpp index b2b7142..e2a8344 100644 --- a/src/object_sync/hello.cpp +++ b/src/object_sync/hello.cpp @@ -1,5 +1,4 @@ #include "hello.hpp" - #include /** @@ -28,143 +27,47 @@ // clearly organize your application. namespace object_sync { -// Custom constructor, assigns custom name passed in from Javascript world. -// This constructor uses member init list via the semicolon, aka "direct initialization" -// which is more efficient than using assignment operators. -HelloObject::HelloObject(std::string&& name) : name_(name) {} +Napi::FunctionReference HelloObject::constructor; // NOLINT // Triggered from Javascript world when calling "new HelloObject(name)" -NAN_METHOD(HelloObject::New) { - if (info.IsConstructCall()) { - try { - if (info.Length() >= 1) { - if (info[0]->IsString()) { - // Don't want to risk passing a null string around, which might create unpredictable behavior. - Nan::Utf8String utf8_value(info[0]); - int len = utf8_value.length(); - if (len <= 0) { - return Nan::ThrowTypeError("arg must be a non-empty string"); - } - - /** - * This line converts a V8 string to a C++ std::string. - * In the background, it triggers memory allocation (stack allocating, but std:string is also dynamically allocating memory in the heap) - * We want to avoid heap allocation to ensure more performant code. - * See https://github.com/mapbox/cpp/blob/master/glossary.md#stack-allocation - * and https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap/80113#80113 - * Also, providing the length allows the std::string constructor to avoid calculating the length internally - * and should be faster since it skips an operation. - */ - std::string name(*utf8_value, static_cast(len)); - - /** - * This line is where HelloObject takes ownership of "name" with the use of move semantics. - * Then all later usage of "name" are passed by reference (const&), but the actual home or address in memory - * will always be owned by this instance of HelloObjectAsync. Generally important to know what has ownership of an object. - * When a object/value is a member of a class (like "name"), we know the class (HelloObjectAsync) has full control of the scope of the object/value. - * This avoids the scenario of "name" being destroyed or becoming out of scope. - * - * Also, we're using "new" here to create a custom C++ class, based on node::ObjectWrap since this is a node addon. - * In this case, "new" allocates a C++ object (dynamically on the heap) and then passes ownership (control of when it gets deleted) - * to V8, the javascript engine which decides when to clean up the object based on how its’ garbage collector works. - * In other words, the memory of HelloObjectAsync is expliclty deleted via node::ObjectWrap when it's gone out of scope - * (the object needs to stay alive until the V8 garbage collector has decided it's done): - * https://github.com/nodejs/node/blob/7ec28a0a506efe9d1c03240fd028bea4a3d350da/src/node_object_wrap.h#L124 - */ - auto self = std::make_unique(std::move(name)); // Using unique pointer to adhere to cpp core guideline: https://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-owning-memory.html - self->Wrap(info.This()); // Connects C++ object to Javascript object (this) - self.release(); // NOLINT Release the ownership of self so it can be managed by wrapper - } else { - return Nan::ThrowTypeError( - "arg must be a string"); - } - } else { - return Nan::ThrowTypeError( - "must provide string arg"); - } - } catch (const std::exception& ex) { - return Nan::ThrowTypeError(ex.what()); - } - - info.GetReturnValue().Set(info.This()); - } else { - return Nan::ThrowTypeError( - "Cannot call constructor as function, you need to use 'new' keyword"); +HelloObject::HelloObject(Napi::CallbackInfo const& info) + : Napi::ObjectWrap(info) +{ + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + std::size_t length = info.Length(); + if (length != 1 || !info[0].IsString()) + { + Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); + return; + } + name_ = info[0].As().Utf8Value(); + if (name_.empty()) + { + Napi::TypeError::New(env, "arg must be a non-empty string").ThrowAsJavaScriptException(); } } -// NAN_METHOD is applicable to methods you want to expose to JS world -NAN_METHOD(HelloObject::hello) { - /** - * Note: a HandleScope is automatically included inside NAN_METHOD. See the - * docs at NAN that say: - * 'Note that an implicit HandleScope is created for you on - * JavaScript-accessible methods so you do not need to insert one yourself.' - * at - * https://github.com/nodejs/nan/blob/2dfc5c2d19c8066903a19ced6a72c06d2c825dec/doc/scopes.md#nanhandlescope - - * "What is node::ObjectWrap???" The short version is that node::ObjectWrap - * and wrapping/unwrapping objects - * is the somewhat clumsy way it is possible to bind Node and C++. The main - * points to remember: - * - To access a class instance inside a C++ static method, you must unwrap - * the object. - * - The C++ methods must be static to make them available at startup across - * the language boundary (JS <-> C++). - */ - auto* h = Nan::ObjectWrap::Unwrap(info.Holder()); - - // "info" comes from the NAN_METHOD macro, which returns differently - // according to the version of node - info.GetReturnValue().Set( - Nan::New("...initialized an object...hello " + h->name_) - .ToLocalChecked()); -} - -// This is a Singleton, which is a general programming design concept for -// creating an instance once within a process. -Nan::Persistent& HelloObject::create_once() { - static Nan::Persistent init; - return init; +Napi::Value HelloObject::hello(Napi::CallbackInfo const& info) +{ + Napi::Env env = info.Env(); + return Napi::String::New(env, name_); } -void HelloObject::Init(v8::Local target) { - // A handlescope is needed so that v8 objects created in the local memory - // space (this function in this case) - // are cleaned up when the function is done running (and the handlescope is - // destroyed) - // Fun trivia: forgetting a handlescope is one of the most common causes of - // memory leaks in node.js core - // https://www.joyent.com/blog/walmart-node-js-memory-leak - Nan::HandleScope scope; - - // This is saying: - // "Node, please allocate a new Javascript string object - // inside the V8 local memory space, with the value 'HelloObject' " - v8::Local whoami = Nan::New("HelloObject").ToLocalChecked(); - - // Officially create the HelloObject - auto fnTp = Nan::New( - HelloObject::New, v8::Local()); // Passing the HelloObject::New method above - fnTp->InstanceTemplate()->SetInternalFieldCount(1); // It's 1 when holding the ObjectWrap itself and nothing else - fnTp->SetClassName(whoami); // Passing the Javascript string object above - - // Add custom methods here. - // This is how hello() is exposed as part of HelloObject. - // This line is attaching the "hello" method to a JavaScript function - // prototype. - // "hello" is therefore like a property of the fnTp object - // ex: console.log(HelloObject.hello) --> [Function: hello] - SetPrototypeMethod(fnTp, "helloMethod", hello); - - // Create an unique instance of the HelloObject function template, - // then set this unique instance to the target - const auto fn = Nan::GetFunction(fnTp).ToLocalChecked(); - create_once().Reset(fn); // calls the static &HelloObject::create_once method - // above. This ensures the instructions in this Init - // function are retained in memory even after this - // code block ends. - Nan::Set(target, whoami, fn); +Napi::Object HelloObject::Init(Napi::Env env, Napi::Object exports) +{ + + Napi::Function func = DefineClass(env, "HelloObject", {InstanceMethod("helloMethod", &HelloObject::hello)}); + // Create a peristent reference to the class constructor. This will allow + // a function called on a class prototype and a function + // called on instance of a class to be distinguished from each other. + constructor = Napi::Persistent(func); + // Call the SuppressDestruct() method on the static data prevent the calling + // to this destructor to reset the reference when the environment is no longer + // available. + constructor.SuppressDestruct(); + exports.Set("HelloObject", func); + return exports; } } // namespace object_sync diff --git a/src/object_sync/hello.hpp b/src/object_sync/hello.hpp index a556cfc..255992b 100644 --- a/src/object_sync/hello.hpp +++ b/src/object_sync/hello.hpp @@ -1,35 +1,24 @@ #pragma once -#include +#include namespace object_sync { /** - * HelloObject class - * This is in a header file so we can access it across other .cpp files if necessary - * Also, this class adheres to the rule of Zero because we define no custom destructor or copy constructor - */ -class HelloObject : public Nan::ObjectWrap { - + * HelloObject class + * This is in a header file so we can access it across other .cpp files if necessary + * Also, this class adheres to the rule of Zero because we define no custom destructor or copy constructor + */ +class HelloObject : public Napi::ObjectWrap +{ public: - // initializer - static void Init(v8::Local target); - - // methods required for the V8 constructor (?) - static NAN_METHOD(New); - static Nan::Persistent& create_once(); - - // hello, custom sync method tied to Init of this class - // method's logic lives in ./hello.cpp - static NAN_METHOD(hello); - - // C++ Constructor - // Passing the arg by rvalue reference (&&) - HelloObject(std::string&& name); + // initializers + static Napi::Object Init(Napi::Env env, Napi::Object exports); + explicit HelloObject(Napi::CallbackInfo const& info); + Napi::Value hello(Napi::CallbackInfo const& info); private: - // member variable - // specific to each instance of the class - std::string name_; + static Napi::FunctionReference constructor; + std::string name_ = ""; }; } // namespace object_sync diff --git a/src/standalone/hello.cpp b/src/standalone/hello.cpp index 2a21c0f..ce4be7a 100644 --- a/src/standalone/hello.cpp +++ b/src/standalone/hello.cpp @@ -10,20 +10,11 @@ * console.log(check); // => "hello world" */ namespace standalone { -// If this was not defined within a namespace, it would be in the global scope. -// Namespaces are used because C++ has no notion of scoped modules, so all of -// the code you write in any file could conflict with other code. -// Namespaces are generally a great idea in C++ because it helps scale and -// clearly organize your application. -// hello is a "standalone function" because it's not a class. -// If this function was not defined within a namespace, it would be in the -// global scope. -NAN_METHOD(hello) { - - // "info" comes from the NAN_METHOD macro, which returns differently - // according to the version of node - info.GetReturnValue().Set( - Nan::New("hello world").ToLocalChecked()); +Napi::Value hello(Napi::CallbackInfo const& info) +{ + Napi::Env env = info.Env(); + return Napi::String::New(env, "hello world"); } -} // namespace standalone \ No newline at end of file + +} // namespace standalone diff --git a/src/standalone/hello.hpp b/src/standalone/hello.hpp index d445cae..08d7498 100644 --- a/src/standalone/hello.hpp +++ b/src/standalone/hello.hpp @@ -1,36 +1,9 @@ #pragma once -#include -// specify #include with carrots, ex: --> look for header in global -// specify #include with quotes, ex: "hello.hpp" --> look for header in location -// relative to this file +#include -/* - - Namespace is an organizational method that helps to clearly show where a - method is coming from. - Namespaces are generally a great idea in C++ because they help us scale - things. C++ has no notion of scoped modules, - so all of the code you write in any file could potentially conflict with other - classes/functions/etc. - Namespaces help to differentiate pieces of your code. - - The convention In this skeleton is to name the namespace to match the name of - the subdirectory where it lives. - So in this case, the namespace is called "standalone" because this method - lives within the "standalone" subdirectory. - If there is another "hello" function used for another example, the compiler - will know the difference between the two: - - standalone::hello - - VS - - potato::hello - -*/ namespace standalone { -// hello, custom sync method tied to module.cpp -// method's logic lives in hello.cpp -NAN_METHOD(hello); -} // namespace standalone \ No newline at end of file +// hello, custom sync method +Napi::Value hello(Napi::CallbackInfo const& info); + +} // namespace standalone diff --git a/src/standalone_async/hello_async.cpp b/src/standalone_async/hello_async.cpp index adb317a..e2726a6 100644 --- a/src/standalone_async/hello_async.cpp +++ b/src/standalone_async/hello_async.cpp @@ -1,4 +1,5 @@ #include "hello_async.hpp" +#include "../cpu_intensive_task.hpp" #include "../module_utils.hpp" #include @@ -24,97 +25,73 @@ * }); */ -// If this was not defined within a namespace, it would be in the global scope. -// Namespaces are used because C++ has no notion of scoped modules, so all of -// the code you write in any file could conflict with other code. -// Namespaces are generally a great idea in C++ because it helps scale and -// clearly organize your application. namespace standalone_async { -// Expensive allocation of std::map, querying, and string comparison, -// therefore threads are busy -std::unique_ptr do_expensive_work(bool louder) { - - std::map container; - std::size_t work_to_do = 100000; - - for (std::size_t i = 0; i < work_to_do; ++i) { - container.emplace(i, std::to_string(i)); - } - - for (std::size_t i = 0; i < work_to_do; ++i) { - std::string const& item = container[i]; - if (item != std::to_string(i)) { - - // AsyncHelloWorker's Execute function will take care of this error - // and return it to js-world via callback - // Marked NOLINT to avoid clang-tidy cert-err60-cpp error which we cannot - // avoid on some linux distros where std::runtime_error is not properly - // marked noexcept. Details at https://www.securecoding.cert.org/confluence/display/cplusplus/ERR60-CPP.+Exception+objects+must+be+nothrow+copy+constructible - throw std::runtime_error("Uh oh, this should never happen"); // NOLINT - } - } - - std::unique_ptr result = std::make_unique("...threads are busy async bees...hello world"); - - if (louder) { - *result += "!!!!"; - } - - return result; -} - // This is the worker running asynchronously and calling a user-provided // callback when done. // Consider storing all C++ objects you need by value or by shared_ptr to keep // them alive until done. -// Nan AsyncWorker docs: -// https://github.com/nodejs/nan/blob/master/doc/asyncworker.md -struct AsyncHelloWorker : Nan::AsyncWorker { - using Base = Nan::AsyncWorker; - - AsyncHelloWorker(bool louder, bool buffer, Nan::Callback* cb) - : Base(cb, "skel:standalone-async-worker"), louder_(louder), buffer_(buffer) {} +// Napi::AsyncWorker docs: +// https://github.com/nodejs/node-addon-api/blob/master/doc/async_worker.md +struct AsyncHelloWorker : Napi::AsyncWorker +{ + using Base = Napi::AsyncWorker; + // ctor + AsyncHelloWorker(bool louder, bool buffer, Napi::Function const& cb) + : Base(cb), + louder_(louder), + buffer_(buffer) {} // The Execute() function is getting called when the worker starts to run. // - You only have access to member variables stored in this worker. // - You do not have access to Javascript v8 objects here. - void Execute() override { + void Execute() override + { // The try/catch is critical here: if code was added that could throw an // unhandled error INSIDE the threadpool, it would be disasterous - try { - result_ = do_expensive_work(louder_); - } catch (const std::exception& e) { - SetErrorMessage(e.what()); + try + { + result_ = detail::do_expensive_work("world", louder_); + } + catch (std::exception const& e) + { + SetError(e.what()); } } - // The HandleOKCallback() is getting called when Execute() successfully + // The OnOK() is getting called when Execute() successfully // completed. // - In case Execute() invoked SetErrorMessage("") this function is not // getting called. // - You have access to Javascript v8 objects again // - You have to translate from C++ member variables to Javascript v8 objects // - Finally, you call the user's callback with your results - void HandleOKCallback() override { - Nan::HandleScope scope; - - if (buffer_) { - const auto argc = 2u; - v8::Local argv[argc] = { - Nan::Null(), utils::NewBufferFrom(std::move(result_)).ToLocalChecked()}; - // Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy - callback->Call(argc, static_cast*>(argv), async_resource); - } else { - const auto argc = 2u; - v8::Local argv[argc] = { - Nan::Null(), Nan::New(*result_).ToLocalChecked()}; - // Static cast done here to avoid 'cppcoreguidelines-pro-bounds-array-to-pointer-decay' warning with clang-tidy - callback->Call(argc, static_cast*>(argv), async_resource); + void OnOK() final + { + Napi::HandleScope scope(Env()); + if (!Callback().IsEmpty() && result_) + { + if (buffer_) + { + char* data = result_->data(); + std::size_t size = result_->size(); + auto buffer = Napi::Buffer::New(Env(), + data, + size, + [](Napi::Env, char*, gsl::owner*> v) { + delete v; + }, + result_.release()); + Callback().Call({Env().Null(), buffer}); + } + else + { + Callback().Call({Env().Null(), Napi::String::New(Env(), result_->data(), result_->size())}); + } } } - std::unique_ptr result_ = std::make_unique(); + std::unique_ptr> result_ = nullptr; const bool louder_; const bool buffer_; }; @@ -122,68 +99,58 @@ struct AsyncHelloWorker : Nan::AsyncWorker { // helloAsync is a "standalone function" because it's not a class. // If this function was not defined within a namespace ("standalone_async" // specified above), it would be in the global scope. -NAN_METHOD(helloAsync) { - +Napi::Value helloAsync(Napi::CallbackInfo const& info) +{ bool louder = false; bool buffer = false; // Check second argument, should be a 'callback' function. - // This allows us to set the callback so we can use it to return errors - // instead of throwing. - // Also, "info" comes from the NAN_METHOD macro, which returns differently - // according to the version of node - if (!info[1]->IsFunction()) { - return Nan::ThrowTypeError("second arg 'callback' must be a function"); + if (!info[1].IsFunction()) + { + Napi::TypeError::New(info.Env(), "second arg 'callback' must be a function").ThrowAsJavaScriptException(); + return info.Env().Null(); } - v8::Local callback = info[1].As(); + + Napi::Function callback = info[1].As(); // Check first argument, should be an 'options' object - if (!info[0]->IsObject()) { - return utils::CallbackError("first arg 'options' must be an object", - callback); + if (!info[0].IsObject()) + { + return utils::CallbackError("first arg 'options' must be an object", info); } - v8::Local options = info[0].As(); + Napi::Object options = info[0].As(); // Check options object for the "louder" property, which should be a boolean // value - if (options->Has(Nan::New("louder").ToLocalChecked())) { - v8::Local louder_val = - options->Get(Nan::New("louder").ToLocalChecked()); - if (!louder_val->IsBoolean()) { - return utils::CallbackError("option 'louder' must be a boolean", - callback); - } - Nan::Maybe maybe_louder = Nan::To(louder_val); - if (maybe_louder.IsNothing()) { - return utils::CallbackError("option 'louder' must be a boolean", callback); + if (options.Has(Napi::String::New(info.Env(), "louder"))) + { + Napi::Value louder_val = options.Get(Napi::String::New(info.Env(), "louder")); + if (!louder_val.IsBoolean()) + { + return utils::CallbackError("option 'louder' must be a boolean", info); } - louder = maybe_louder.FromJust(); + louder = louder_val.As().Value(); } - // Check options object for the "buffer" property, which should be a boolean - // value - if (options->Has(Nan::New("buffer").ToLocalChecked())) { - v8::Local buffer_val = - options->Get(Nan::New("buffer").ToLocalChecked()); - if (!buffer_val->IsBoolean()) { - return utils::CallbackError("option 'buffer' must be a boolean", - callback); - } - Nan::Maybe maybe_buffer = Nan::To(buffer_val); - if (maybe_buffer.IsNothing()) { - return utils::CallbackError("option 'buffer' must be a boolean", callback); + // Check options object for the "buffer" property, which should be a boolean value + if (options.Has(Napi::String::New(info.Env(), "buffer"))) + { + Napi::Value buffer_val = options.Get(Napi::String::New(info.Env(), "buffer")); + if (!buffer_val.IsBoolean()) + { + return utils::CallbackError("option 'buffer' must be a boolean", info); } - buffer = maybe_buffer.FromJust(); + buffer = buffer_val.As().Value(); } // Creates a worker instance and queues it to run asynchronously, invoking the // callback when done. - // - Nan::AsyncWorker takes a pointer to a Nan::Callback and deletes the + // - Napi::AsyncWorker takes a pointer to a Napi::FunctionReference and deletes the // pointer automatically. - // - Nan::AsyncQueueWorker takes a pointer to a Nan::AsyncWorker and deletes + // - Napi::AsyncQueueWorker takes a pointer to a Napi::AsyncWorker and deletes // the pointer automatically. - auto cb = std::make_unique(callback); - auto worker = std::make_unique(louder, buffer, cb.release()); - Nan::AsyncQueueWorker(worker.release()); + auto* worker = new AsyncHelloWorker{louder, buffer, callback}; // NOLINT + worker->Queue(); + return info.Env().Undefined(); // NOLINT } } // namespace standalone_async diff --git a/src/standalone_async/hello_async.hpp b/src/standalone_async/hello_async.hpp index e846c42..ab21844 100644 --- a/src/standalone_async/hello_async.hpp +++ b/src/standalone_async/hello_async.hpp @@ -1,35 +1,10 @@ #pragma once -#include -// carrots, ex: --> look for header in global -// quotes, ex: "hello.hpp" --> look for header in location relative to this file +#include -/* - - Namespace is an organizational method that helps to clearly show where a - method is coming from. - Namespaces are generally a great idea in C++ because they help us scale - things. C++ has no notion of scoped modules, - so all of the code you write in any file could potentially conflict with other - classes/functions/etc. - Namespaces help to differentiate pieces of your code. - - The convention In this skeleton is to name the namespace to match the name of - the subdirectory where it lives. - So in this case, the namespace is called "standalone" because this method - lives within the "standalone" subdirectory. - If there is another "hello" function used for another example, the compiler - will know the difference between the two: - - standalone::hello - - VS - - potato::hello - -*/ namespace standalone_async { -// hello, custom sync method tied to module.cpp +// hello, custom sync method // method's logic lives in hello.cpp -NAN_METHOD(helloAsync); -} // namespace standalone_async \ No newline at end of file +Napi::Value helloAsync(Napi::CallbackInfo const& info); + +} // namespace standalone_async diff --git a/test/hello_object.test.js b/test/hello_object.test.js index 07a943c..3589064 100644 --- a/test/hello_object.test.js +++ b/test/hello_object.test.js @@ -4,7 +4,7 @@ var module = require('../lib/index.js'); test('success: prints expected string', function(t) { var H = new module.HelloObject('carol'); var check = H.helloMethod(); - t.equal(check, '...initialized an object...hello carol', 'returned expected string'); + t.equal(check, 'carol', 'returned expected string'); t.end(); }); @@ -13,7 +13,7 @@ test('error: throws when passing empty string', function(t) { var H = new module.HelloObject(''); } catch(err) { t.ok(err, 'expected error'); - t.equal(err.message, 'arg must be a non-empty string', 'expected error message') + t.equal(err.message, 'arg must be a non-empty string', 'expected error message'); t.end(); } }); @@ -23,7 +23,7 @@ test('error: throws when missing "new"', function(t) { var H = module.HelloObject(); } catch(err) { t.ok(err); - t.equal(err.message, 'Cannot call constructor as function, you need to use \'new\' keyword', 'expected error message') + t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message'); t.end(); } }); @@ -33,7 +33,7 @@ test('error: handles non-string arg within constructor', function(t) { var H = new module.HelloObject(24); } catch(err) { t.ok(err, 'expected error'); - t.ok(err.message.indexOf('arg must be a string') > -1, 'expected error message'); + t.ok(err.message, 'A string was expected', 'expected error message'); t.end(); } }); @@ -43,7 +43,7 @@ test('error: handles missing arg', function(t) { var H = new module.HelloObject(); } catch (err) { t.ok(err, 'expected error'); - t.ok(err.message.indexOf('must provide string arg') > -1, 'expected error message'); + t.ok(err.message, 'must provide string arg', 'expected error message'); t.end(); } -}); \ No newline at end of file +}); diff --git a/test/hello_object_async.test.js b/test/hello_object_async.test.js index 74452c7..e2c4e64 100644 --- a/test/hello_object_async.test.js +++ b/test/hello_object_async.test.js @@ -45,7 +45,7 @@ test('error: throws when missing "new"', function(t) { var H = module.HelloObjectAsync('world'); } catch(err) { t.ok(err, 'expected error'); - t.equal(err.message, 'Cannot call constructor as function, you need to use \'new\' keyword', 'expected error message') + t.equal(err.message, 'Class constructors cannot be invoked without \'new\'', 'expected error message') t.end(); } }); @@ -54,8 +54,8 @@ test('error: handles non-string arg within constructor', function(t) { try { var H = new module.HelloObjectAsync(24); } catch(err) { - t.ok(err, 'expected error'); - t.ok(err.message.indexOf('arg must be a string') > -1, 'expected error message'); + console.log(err.message); + t.equal(err.message, 'String expected', 'expected error message'); t.end(); } }); @@ -103,7 +103,7 @@ test('error: handles missing arg', function(t) { var H = new module.HelloObjectAsync(); } catch (err) { t.ok(err, 'expected error'); - t.ok(err.message.indexOf('must provide string arg') > -1, 'expected error message'); + t.equal(err.message, 'String expected', 'expected error message'); t.end(); } });