diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..4aad29c3 --- /dev/null +++ b/.clang-format @@ -0,0 +1,111 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..1472cc9c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..3133f12a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + all: + patterns: + - '*' + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + + - package-ecosystem: npm + directories: + - /src/**/* + schedule: + interval: weekly + groups: + all: + patterns: + - '*' + diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..e2dd6313 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,89 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: ["main"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["main"] + schedule: + - cron: "0 0 * * 1" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: ["cpp", "javascript", "typescript"] + # CodeQL supports [ $supported-codeql-languages ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@46ed16ded91731b2df79a2893d3aea8e9f03b5c4 # v2.20.3 + + - name: Use Node.js v18.x + if: matrix.language == 'cpp' + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 18.x + + - name: Build cpp + if: matrix.language == 'cpp' + run: | + npm ci --ignore-scripts && npm test + + # ℹ️ Command-line programs to run using the OS shell. + # πŸ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 00000000..13a21083 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,27 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - name: 'Checkout Repository' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000..2e07e1e9 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,23 @@ +name: clang-format + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + check-clang-format: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - run: npm ci --ignore-scripts + - name: check clang-format + run: | + git config clangFormat.binary node_modules/.bin/clang-format + git config clangFormat.style file + npx check-clang-format diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 00000000..63c18a84 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,57 @@ +name: Node.js CI + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +permissions: + contents: read + +jobs: + test: + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + node-version: [20.x, 22.x, 24.x] + operating-system: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci --ignore-scripts + - name: Environment Information + run: npx envinfo + - name: Run Test + run: npm test + + nightly-daily-test: + runs-on: ubuntu-latest + container: node + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - run: npm i -g n && n nightly + - run: node -p process.versions + - name: Environment Information + run: npx envinfo + - run: npm ci --ignore-scripts + - name: Environment Information + run: npx envinfo + - name: Run Test + continue-on-error: true + run: npm test diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 00000000..595c6ce0 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,76 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '20 7 * * 2' + push: + branches: ["main"] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + contents: read + actions: read + + steps: + - name: Harden Runner + uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 + with: + egress-policy: audit + + - name: "Checkout code" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecards on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 3e2e84b0..cacbcae0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,11 @@ build/ node_modules/ +Debug/ +Release/ +*.lock +*.log +npm-debug.log* + +.idea/ +.vscode/ +.DS_Store diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9296d70d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +repos: +- repo: https://github.com/gitleaks/gitleaks + rev: v8.16.3 + hooks: + - id: gitleaks +- repo: https://github.com/pocc/pre-commit-hooks + rev: v1.3.5 + hooks: + - id: cpplint +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.38.0 + hooks: + - id: eslint +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/pylint-dev/pylint + rev: v2.17.2 + hooks: + - id: pylint diff --git a/1_hello_world/README.md b/1_hello_world/README.md deleted file mode 100644 index 2b5314eb..00000000 --- a/1_hello_world/README.md +++ /dev/null @@ -1,135 +0,0 @@ -## Example 1: *Hello world* - -To get started let's make a small addon which is the C++ equivalent of -the following JavaScript code: - -```js -module.exports.hello = function() { return 'world'; }; -``` - -### Step 1 - -First we need to set up an npm-compatible package. Use `npm init` in a new directory to create the skeleton *package.json*. - -### Step 2 - -Next we need to install **[NAN](https://github.com/rvagg/nan)**. NAN will serve as a thin abstraction layer between the C++ code we write and the Node and V8 APIs so that we can target multiple versions of Node without worrying too much about the changing V8 or Node APIs. Run `npm install nan@latest --save` to install NAN and save it as a `"dependency"` in your **package.json*. - -### Step 3 - -All Node addons are compiled using the **[GYP](http://code.google.com/p/gyp/wiki/GypUserDocumentation)** build tool via **[node-gyp](https://github.com/TooTallNate/node-gyp)**. Add a `"gypfile": true` entry to your *package.json* so that npm knows this is a binary addon that needs compiling and it needs to invoke node-gyp. When node-gyp is invoked, it looks for a *binding.gyp* file in the same directory as your *package.json*. This file is written in YAML and describes the particulars of your build, including the source files and any binary dependencies. node-gyp will invoke GYP with your *binding.gyp* and also the [common.gypi](https://github.com/joyent/node/blob/master/common.gypi) settings file found in the source of the version of Node it is invoked with. Because your code will be compiled against the Node source and it requires the common.gypi file for that source, it must also download the complete tarball of the particular release you are running. These tarballs are stored in ~/.node-gyp/ so they only need to be installed once. - -Create a *binding.gyp* file with the following contents: - -```yaml -{ - "targets": [ - { - "target_name": "hello", - "sources": [ "hello.cc" ], - "include_dirs": [ - " - -using namespace v8; - -NAN_METHOD(Method) { - NanScope(); - NanReturnValue(String::New("world")); -} - -void Init(Handle exports) { - exports->Set( - String::NewSymbol("hello"), - FunctionTemplate::New(Method)->GetFunction()); -} - -NODE_MODULE(hello, Init) -``` - -This code contains three main components, starting from the bottom: - -```c++ -NODE_MODULE(hello, init) -``` - -This code defines the entry-point for the Node addon, it tells Node where to go once the library has been loaded into active memory. The first argument **must match the "target" in our *binding.gyp***. The second argument points to the function to invoke. - -```c++ -void Init(Handle exports) { - exports->Set( - String::NewSymbol("hello"), - FunctionTemplate::New(Method)->GetFunction()); -} -``` - -This code is our entry-point. We can receive up to two arguments here, the first is `exports`, the same as `module.exports` in a .js file and the second argument (omitted in this case) is `module` which is the same as `module` in a .js file. Normally you would attach properties to `exports` but you can use the `module` argument to *replace* its `exports` property so you are exporting a single thing, the equivalent of: `module.exports = function () { ... }`. - -In our case we just want to attach a `"hello"` property to `module.exports` so we set a V8 `String` property to a V8 `Function` object. We use a V8 `FunctionTemplate` to turn a regular (but compatible) C++ function into a V8-callable function. In this case, the `Method` function. - -```c++ -NAN_METHOD(Method) { - NanScope(); - NanReturnValue(String::New("world")); -} -``` - -This is where NAN first comes in useful for us. The changing V8 API has made it difficult to target different versions of Node with the same C++ code so NAN helps provide a simple mapping so we can define a V8 compatible function that `FunctionTemplate` will accept. In recent versions of V8, `NAN_METHOD(Method)` would expand to: `void Method(const v8::FunctionCallbackInfo& args)` which is the standard signature for a function that can be called by V8. The `args` parameter contains call information, such as JavaScript function parameters, and allows us to set return values. - -`NanScope()` is used here to set a V8 "handle scope" which is much like the function-scope in JavaScript. It defines the lifetime for which any created "handles" are safe from the garbage collector. When we use this at the top of our function we are declaring that any V8 object we create should live for the life of that function. If we omit the handle scope then created objects may attach to the global scope and may end up not being garbage collected, leading to a memory leak. - -`NanReturnValue()` sets the return value for our function. In this case we are creating a simple V8 `String` object with the contents `"world"`, this will be exposed as a standard JavaScript `String` with the value `"world"`. Since this object is created within the handle scope we declared above, its freedom from garbage collection will be lost as soon as it leaves our function *unless* it is referenced by another function, either in JavaScript or in a new handle scope in our C++. In our case we will be printing the string so it will be attached to a new scope in our JavaScript code. - -### Step 5 - -Compile your addon: if you don't have `node-gyp` installed, use `sudo npm install node-gyp -g` to install it. It comes bundled with npm but is not normally linked as an executable in your `PATH` so it's best to install it separately. - -Run `node-gyp configure` to set up the build fixtures. In the *./build/* directory, a *Makefile* and associated property files will be created on Unix systems and a *vcxproj* fill will be created on Windows. - -Next, run `node-gyp build` to start the build process and watch your addon compile. You can use `node-gyp build` again to incrementally recompile changed source files. Alternatively you can use `node-gyp rebuild` to combine the `configure` and `build` steps in one. - -You should now have a compiled binary addon ready for use. Node will load it just like it loads a .js module file and include it in your running application. The binary file should be be located at *./build/Release/hello.node*. - -### Step 6 - -Write some JavaScript! - -Create a file called *hello.js* with the following contents: - -```js -var addon = require('./build/Release/hello.node'); - -console.log(addon.hello()); -``` - -The first line is responsible for loading our compiled add-on and pulling in the exports to our module. The path may substitute Debug for Release if you have run node-gyp with the `--debug` flag or you have used another method to signal to GYP that this should be a debug build. - -The best approach to this uncertainty of where the module file is located is to pull in an additional dependency, **[node-bindings](https://github.com/TooTallNate/node-bindings)** which will locate the binary for you, use it like so: - -```js -var addon = require('bindings')('hello.node') -``` - -The invocation of our addon comes from `console.log(addon.hello())` where we fetch the `Method` function in our C++ code and execute it. It returns a `"hello"` String which is printed to the console. - -***[Proceed to example 2 »](../2_function_arguments/)*** - -***[Index](../#readme)*** diff --git a/1_hello_world/nan/hello.cc b/1_hello_world/nan/hello.cc deleted file mode 100644 index 897cca7f..00000000 --- a/1_hello_world/nan/hello.cc +++ /dev/null @@ -1,15 +0,0 @@ -#include - -using namespace v8; - -NAN_METHOD(Method) { - NanScope(); - NanReturnValue(String::New("world")); -} - -void Init(Handle exports) { - exports->Set(String::NewSymbol("hello"), - FunctionTemplate::New(Method)->GetFunction()); -} - -NODE_MODULE(hello, Init) diff --git a/1_hello_world/nan/hello.js b/1_hello_world/nan/hello.js deleted file mode 100644 index f36998b1..00000000 --- a/1_hello_world/nan/hello.js +++ /dev/null @@ -1,3 +0,0 @@ -var addon = require('./build/Release/hello'); - -console.log(addon.hello()); // 'world' \ No newline at end of file diff --git a/1_hello_world/node_0.10/hello.cc b/1_hello_world/node_0.10/hello.cc deleted file mode 100644 index 815606b5..00000000 --- a/1_hello_world/node_0.10/hello.cc +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include - -using namespace v8; - -Handle Method(const Arguments& args) { - HandleScope scope; - return scope.Close(String::New("world")); -} - -void Init(Handle exports) { - exports->Set(String::NewSymbol("hello"), - FunctionTemplate::New(Method)->GetFunction()); -} - -NODE_MODULE(hello, Init) diff --git a/1_hello_world/node_0.10/hello.js b/1_hello_world/node_0.10/hello.js deleted file mode 100644 index f36998b1..00000000 --- a/1_hello_world/node_0.10/hello.js +++ /dev/null @@ -1,3 +0,0 @@ -var addon = require('./build/Release/hello'); - -console.log(addon.hello()); // 'world' \ No newline at end of file diff --git a/1_hello_world/node_0.12/binding.gyp b/1_hello_world/node_0.12/binding.gyp deleted file mode 100644 index 39b171f3..00000000 --- a/1_hello_world/node_0.12/binding.gyp +++ /dev/null @@ -1,8 +0,0 @@ -{ - "targets": [ - { - "target_name": "hello", - "sources": [ "hello.cc" ] - } - ] -} diff --git a/1_hello_world/node_0.12/hello.cc b/1_hello_world/node_0.12/hello.cc deleted file mode 100644 index 9bc282ba..00000000 --- a/1_hello_world/node_0.12/hello.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include - -using namespace v8; - -void Method(const v8::FunctionCallbackInfo& args) { - Isolate* isolate = Isolate::GetCurrent(); - HandleScope scope(isolate); - args.GetReturnValue().Set(String::New("world")); -} - -void Init(Handle exports) { - exports->Set(String::NewSymbol("hello"), - FunctionTemplate::New(Method)->GetFunction()); -} - -NODE_MODULE(hello, Init) diff --git a/1_hello_world/node_0.12/hello.js b/1_hello_world/node_0.12/hello.js deleted file mode 100644 index f36998b1..00000000 --- a/1_hello_world/node_0.12/hello.js +++ /dev/null @@ -1,3 +0,0 @@ -var addon = require('./build/Release/hello'); - -console.log(addon.hello()); // 'world' \ No newline at end of file diff --git a/2_function_arguments/addon.cc b/2_function_arguments/addon.cc deleted file mode 100644 index 5513dd15..00000000 --- a/2_function_arguments/addon.cc +++ /dev/null @@ -1,29 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include - -using namespace v8; - -Handle Add(const Arguments& args) { - HandleScope scope; - - if (args.Length() < 2) { - ThrowException(Exception::TypeError(String::New("Wrong number of arguments"))); - return scope.Close(Undefined()); - } - - if (!args[0]->IsNumber() || !args[1]->IsNumber()) { - ThrowException(Exception::TypeError(String::New("Wrong arguments"))); - return scope.Close(Undefined()); - } - - Local num = Number::New(args[0]->NumberValue() + - args[1]->NumberValue()); - return scope.Close(num); -} - -void Init(Handle exports) { - exports->Set(String::NewSymbol("add"), - FunctionTemplate::New(Add)->GetFunction()); -} - -NODE_MODULE(addon, Init) diff --git a/2_function_arguments/addon.js b/2_function_arguments/addon.js deleted file mode 100644 index e22dc64f..00000000 --- a/2_function_arguments/addon.js +++ /dev/null @@ -1,3 +0,0 @@ -var addon = require('./build/Release/addon'); - -console.log( 'This should be eight:', addon.add(3,5) ); \ No newline at end of file diff --git a/2_function_arguments/package.json b/2_function_arguments/package.json deleted file mode 100644 index ee1820e9..00000000 --- a/2_function_arguments/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "function_arguments", - "version": "0.0.0", - "description": "Node.js Addons Example #2", - "main": "addon.js", - "private": true, - "gypfile": true -} \ No newline at end of file diff --git a/3_callbacks/addon.cc b/3_callbacks/addon.cc deleted file mode 100644 index 12a8f9e6..00000000 --- a/3_callbacks/addon.cc +++ /dev/null @@ -1,22 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include - -using namespace v8; - -Handle RunCallback(const Arguments& args) { - HandleScope scope; - - Local cb = Local::Cast(args[0]); - const unsigned argc = 1; - Local argv[argc] = { Local::New(String::New("hello world")) }; - cb->Call(Context::GetCurrent()->Global(), argc, argv); - - return scope.Close(Undefined()); -} - -void Init(Handle exports, Handle module) { - module->Set(String::NewSymbol("exports"), - FunctionTemplate::New(RunCallback)->GetFunction()); -} - -NODE_MODULE(addon, Init) diff --git a/4_object_factory/addon.cc b/4_object_factory/addon.cc deleted file mode 100644 index 680ee878..00000000 --- a/4_object_factory/addon.cc +++ /dev/null @@ -1,20 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include - -using namespace v8; - -Handle CreateObject(const Arguments& args) { - HandleScope scope; - - Local obj = Object::New(); - obj->Set(String::NewSymbol("msg"), args[0]->ToString()); - - return scope.Close(obj); -} - -void Init(Handle exports, Handle module) { - module->Set(String::NewSymbol("exports"), - FunctionTemplate::New(CreateObject)->GetFunction()); -} - -NODE_MODULE(addon, Init) diff --git a/4_object_factory/addon.js b/4_object_factory/addon.js deleted file mode 100644 index a6287f77..00000000 --- a/4_object_factory/addon.js +++ /dev/null @@ -1,5 +0,0 @@ -var addon = require('./build/Release/addon'); - -var obj1 = addon('hello'); -var obj2 = addon('world'); -console.log(obj1.msg+' '+obj2.msg); // 'hello world' \ No newline at end of file diff --git a/4_object_factory/package.json b/4_object_factory/package.json deleted file mode 100644 index a7476001..00000000 --- a/4_object_factory/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "object_factory", - "version": "0.0.0", - "description": "Node.js Addons Example #4", - "main": "addon.js", - "private": true, - "gypfile": true -} \ No newline at end of file diff --git a/5_function_factory/addon.cc b/5_function_factory/addon.cc deleted file mode 100644 index b28de290..00000000 --- a/5_function_factory/addon.cc +++ /dev/null @@ -1,26 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include - -using namespace v8; - -Handle MyFunction(const Arguments& args) { - HandleScope scope; - return scope.Close(String::New("hello world")); -} - -Handle CreateFunction(const Arguments& args) { - HandleScope scope; - - Local tpl = FunctionTemplate::New(MyFunction); - Local fn = tpl->GetFunction(); - fn->SetName(String::NewSymbol("theFunction")); // omit this to make it anonymous - - return scope.Close(fn); -} - -void Init(Handle exports, Handle module) { - module->Set(String::NewSymbol("exports"), - FunctionTemplate::New(CreateFunction)->GetFunction()); -} - -NODE_MODULE(addon, Init) diff --git a/5_function_factory/addon.js b/5_function_factory/addon.js deleted file mode 100644 index 619ea50c..00000000 --- a/5_function_factory/addon.js +++ /dev/null @@ -1,4 +0,0 @@ -var addon = require('./build/Release/addon'); - -var fn = addon(); -console.log(fn()); // 'hello world' \ No newline at end of file diff --git a/6_object_wrap/addon.cc b/6_object_wrap/addon.cc deleted file mode 100644 index e032ceab..00000000 --- a/6_object_wrap/addon.cc +++ /dev/null @@ -1,11 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -void InitAll(Handle exports) { - MyObject::Init(exports); -} - -NODE_MODULE(addon, InitAll) diff --git a/6_object_wrap/addon.js b/6_object_wrap/addon.js deleted file mode 100644 index ff6a1419..00000000 --- a/6_object_wrap/addon.js +++ /dev/null @@ -1,6 +0,0 @@ -var addon = require('./build/Release/addon'); - -var obj = new addon.MyObject(10); -console.log( obj.plusOne() ); // 11 -console.log( obj.plusOne() ); // 12 -console.log( obj.plusOne() ); // 13 \ No newline at end of file diff --git a/6_object_wrap/myobject.cc b/6_object_wrap/myobject.cc deleted file mode 100644 index c7b8b2f3..00000000 --- a/6_object_wrap/myobject.cc +++ /dev/null @@ -1,40 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -MyObject::MyObject() {}; -MyObject::~MyObject() {}; - -void MyObject::Init(Handle target) { - // Prepare constructor template - Local tpl = FunctionTemplate::New(New); - tpl->SetClassName(String::NewSymbol("MyObject")); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - // Prototype - tpl->PrototypeTemplate()->Set(String::NewSymbol("plusOne"), - FunctionTemplate::New(PlusOne)->GetFunction()); - - Persistent constructor = Persistent::New(tpl->GetFunction()); - target->Set(String::NewSymbol("MyObject"), constructor); -} - -Handle MyObject::New(const Arguments& args) { - HandleScope scope; - - MyObject* obj = new MyObject(); - obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); - obj->Wrap(args.This()); - - return args.This(); -} - -Handle MyObject::PlusOne(const Arguments& args) { - HandleScope scope; - - MyObject* obj = ObjectWrap::Unwrap(args.This()); - obj->counter_ += 1; - - return scope.Close(Number::New(obj->counter_)); -} diff --git a/6_object_wrap/myobject.h b/6_object_wrap/myobject.h deleted file mode 100644 index 03512f83..00000000 --- a/6_object_wrap/myobject.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include - -class MyObject : public node::ObjectWrap { - public: - static void Init(v8::Handle target); - - private: - MyObject(); - ~MyObject(); - - static v8::Handle New(const v8::Arguments& args); - static v8::Handle PlusOne(const v8::Arguments& args); - double counter_; -}; - -#endif diff --git a/7_factory_wrap/addon.cc b/7_factory_wrap/addon.cc deleted file mode 100644 index c6cb6a04..00000000 --- a/7_factory_wrap/addon.cc +++ /dev/null @@ -1,19 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -Handle CreateObject(const Arguments& args) { - HandleScope scope; - return scope.Close(MyObject::NewInstance(args)); -} - -void InitAll(Handle exports, Handle module) { - MyObject::Init(); - - module->Set(String::NewSymbol("exports"), - FunctionTemplate::New(CreateObject)->GetFunction()); -} - -NODE_MODULE(addon, InitAll) diff --git a/7_factory_wrap/myobject.cc b/7_factory_wrap/myobject.cc deleted file mode 100644 index 93c53691..00000000 --- a/7_factory_wrap/myobject.cc +++ /dev/null @@ -1,51 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -MyObject::MyObject() {}; -MyObject::~MyObject() {}; - -Persistent MyObject::constructor; - -void MyObject::Init() { - // Prepare constructor template - Local tpl = FunctionTemplate::New(New); - tpl->SetClassName(String::NewSymbol("MyObject")); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - // Prototype - tpl->PrototypeTemplate()->Set(String::NewSymbol("plusOne"), - FunctionTemplate::New(PlusOne)->GetFunction()); - - constructor = Persistent::New(tpl->GetFunction()); -} - -Handle MyObject::New(const Arguments& args) { - HandleScope scope; - - MyObject* obj = new MyObject(); - obj->counter_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); - obj->Wrap(args.This()); - - return args.This(); -} - -Handle MyObject::NewInstance(const Arguments& args) { - HandleScope scope; - - const unsigned argc = 1; - Handle argv[argc] = { args[0] }; - Local instance = constructor->NewInstance(argc, argv); - - return scope.Close(instance); -} - -Handle MyObject::PlusOne(const Arguments& args) { - HandleScope scope; - - MyObject* obj = ObjectWrap::Unwrap(args.This()); - obj->counter_ += 1; - - return scope.Close(Number::New(obj->counter_)); -} diff --git a/7_factory_wrap/myobject.h b/7_factory_wrap/myobject.h deleted file mode 100644 index 1507a82b..00000000 --- a/7_factory_wrap/myobject.h +++ /dev/null @@ -1,22 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include - -class MyObject : public node::ObjectWrap { - public: - static void Init(); - static v8::Handle NewInstance(const v8::Arguments& args); - - private: - MyObject(); - ~MyObject(); - - static v8::Persistent constructor; - static v8::Handle New(const v8::Arguments& args); - static v8::Handle PlusOne(const v8::Arguments& args); - double counter_; -}; - -#endif diff --git a/8_passing_wrapped/addon.cc b/8_passing_wrapped/addon.cc deleted file mode 100644 index dab9b9fe..00000000 --- a/8_passing_wrapped/addon.cc +++ /dev/null @@ -1,34 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -Handle CreateObject(const Arguments& args) { - HandleScope scope; - return scope.Close(MyObject::NewInstance(args)); -} - -Handle Add(const Arguments& args) { - HandleScope scope; - - MyObject* obj1 = node::ObjectWrap::Unwrap( - args[0]->ToObject()); - MyObject* obj2 = node::ObjectWrap::Unwrap( - args[1]->ToObject()); - - double sum = obj1->Val() + obj2->Val(); - return scope.Close(Number::New(sum)); -} - -void InitAll(Handle exports) { - MyObject::Init(); - - exports->Set(String::NewSymbol("createObject"), - FunctionTemplate::New(CreateObject)->GetFunction()); - - exports->Set(String::NewSymbol("add"), - FunctionTemplate::New(Add)->GetFunction()); -} - -NODE_MODULE(addon, InitAll) diff --git a/8_passing_wrapped/addon.js b/8_passing_wrapped/addon.js deleted file mode 100644 index 79981e1d..00000000 --- a/8_passing_wrapped/addon.js +++ /dev/null @@ -1,7 +0,0 @@ -var addon = require('./build/Release/addon'); - -var obj1 = addon.createObject(10); -var obj2 = addon.createObject(20); -var result = addon.add(obj1, obj2); - -console.log(result); // 30 diff --git a/8_passing_wrapped/myobject.cc b/8_passing_wrapped/myobject.cc deleted file mode 100644 index 9fff03d2..00000000 --- a/8_passing_wrapped/myobject.cc +++ /dev/null @@ -1,39 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#include -#include "myobject.h" - -using namespace v8; - -MyObject::MyObject() {}; -MyObject::~MyObject() {}; - -Persistent MyObject::constructor; - -void MyObject::Init() { - // Prepare constructor template - Local tpl = FunctionTemplate::New(New); - tpl->SetClassName(String::NewSymbol("MyObject")); - tpl->InstanceTemplate()->SetInternalFieldCount(1); - - constructor = Persistent::New(tpl->GetFunction()); -} - -Handle MyObject::New(const Arguments& args) { - HandleScope scope; - - MyObject* obj = new MyObject(); - obj->val_ = args[0]->IsUndefined() ? 0 : args[0]->NumberValue(); - obj->Wrap(args.This()); - - return args.This(); -} - -Handle MyObject::NewInstance(const Arguments& args) { - HandleScope scope; - - const unsigned argc = 1; - Handle argv[argc] = { args[0] }; - Local instance = constructor->NewInstance(argc, argv); - - return scope.Close(instance); -} diff --git a/8_passing_wrapped/myobject.h b/8_passing_wrapped/myobject.h deleted file mode 100644 index 0d44621b..00000000 --- a/8_passing_wrapped/myobject.h +++ /dev/null @@ -1,22 +0,0 @@ -#define BUILDING_NODE_EXTENSION -#ifndef MYOBJECT_H -#define MYOBJECT_H - -#include - -class MyObject : public node::ObjectWrap { - public: - static void Init(); - static v8::Handle NewInstance(const v8::Arguments& args); - double Val() const { return val_; } - - private: - MyObject(); - ~MyObject(); - - static v8::Persistent constructor; - static v8::Handle New(const v8::Arguments& args); - double val_; -}; - -#endif diff --git a/9_async_work/addon.cc b/9_async_work/addon.cc deleted file mode 100644 index eab172bd..00000000 --- a/9_async_work/addon.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include -#include "sync.h" -#include "async.h" - -using namespace v8; - -// Expose synchronous and asynchronous access to our -// Estimate() function -void InitAll(Handle exports) { - exports->Set(String::NewSymbol("calculateSync"), - FunctionTemplate::New(CalculateSync)->GetFunction()); - - exports->Set(String::NewSymbol("calculateAsync"), - FunctionTemplate::New(CalculateAsync)->GetFunction()); -} - -NODE_MODULE(addon, InitAll) diff --git a/9_async_work/addon.js b/9_async_work/addon.js deleted file mode 100644 index abd3e007..00000000 --- a/9_async_work/addon.js +++ /dev/null @@ -1,44 +0,0 @@ -var addon = require('./build/Release/addon'); -var calculations = process.argv[2] || 100000000; - -function printResult(type, pi, ms) { - console.log(type, 'method:') - console.log('\tΟ€ β‰ˆ ' + pi - + ' (' + Math.abs(pi - Math.PI) + ' away from actual)') - console.log('\tTook ' + ms + 'ms'); - console.log() -} - -function runSync () { - var start = Date.now(); - // Estimate() will execute in the current thread, - // the next line won't return until it is finished - var result = addon.calculateSync(calculations); - printResult('Sync', result, Date.now() - start) -} - -function runAsync () { - // how many batches should we split the work in to? - var batches = process.argv[3] || 16; - var ended = 0; - var total = 0; - var start = Date.now(); - - function done (err, result) { - total += result; - - // have all the batches finished executing? - if (++ended == batches) { - printResult('Async', total / batches, Date.now() - start) - } - } - - // for each batch of work, request an async Estimate() for - // a portion of the total number of calculations - for (var i = 0; i < batches; i++) { - addon.calculateAsync(calculations / batches, done); - } -} - -runSync() -runAsync() diff --git a/9_async_work/async.cc b/9_async_work/async.cc deleted file mode 100644 index 63077ba8..00000000 --- a/9_async_work/async.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include "pi_est.h" -#include "async.h" - -using namespace v8; - -// libuv allows us to pass around a pointer to an arbitrary -// object when running asynchronous functions. We create a -// data structure to hold the data we need during and after -// the async work. -typedef struct AsyncData { - int points; // estimation points - Persistent callback; // callback function - double estimate; // estimation result -} AsyncData; - -// Function to execute inside the worker-thread. -// It is not safe to access V8, or V8 data structures -// here, so everything we need for input and output -// should go on our req->data object. -void AsyncWork(uv_work_t *req) { - // fetch our data structure - AsyncData *asyncData = (AsyncData *)req->data; - // run Estimate() and assign the result to our data structure - asyncData->estimate = Estimate(asyncData->points); -} - -// Function to execute when the async work is complete -// this function will be run inside the main event loop -// so it is safe to use V8 again -void AsyncAfter(uv_work_t *req, int status) { - HandleScope scope; - - // fetch our data structure - AsyncData *asyncData = (AsyncData *)req->data; - // create an arguments array for the callback - Handle argv[] = { - Null(), - Number::New(asyncData->estimate) - }; - - // surround in a try/catch for safety - TryCatch try_catch; - // execute the callback function - asyncData->callback->Call(Context::GetCurrent()->Global(), 2, argv); - if (try_catch.HasCaught()) - node::FatalException(try_catch); - - // dispose the Persistent handle so the callback - // function can be garbage-collected - asyncData->callback.Dispose(); - // clean up any memory we allocated - delete asyncData; - delete req; -} - -// Asynchronous access to the `Estimate()` function -Handle CalculateAsync(const Arguments& args) { - HandleScope scope; - - // create an async work token - uv_work_t *req = new uv_work_t; - // assign our data structure that will be passed around - AsyncData *asyncData = new AsyncData; - req->data = asyncData; - - // expect a number as the first argument - asyncData->points = args[0]->Uint32Value(); - // expect a function as the second argument - // we create a Persistent reference to it so - // it won't be garbage-collected - asyncData->callback = Persistent::New( - Local::Cast(args[1])); - - // pass the work token to libuv to be run when a - // worker-thread is available to - uv_queue_work( - uv_default_loop(), - req, // work token - AsyncWork, // work function - // function called when complete: - static_cast(AsyncAfter) - ); - - return scope.Close(Undefined()); -} - diff --git a/9_async_work/async.h b/9_async_work/async.h deleted file mode 100644 index 29d0199e..00000000 --- a/9_async_work/async.h +++ /dev/null @@ -1,3 +0,0 @@ -#include - -v8::Handle CalculateAsync(const v8::Arguments& args); diff --git a/9_async_work/package.json b/9_async_work/package.json deleted file mode 100644 index fd71bc76..00000000 --- a/9_async_work/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "async_work", - "version": "0.0.0", - "description": "Node.js Addons Example #9", - "main": "addon.js", - "private": true, - "gypfile": true -} \ No newline at end of file diff --git a/9_async_work/pi_est.h b/9_async_work/pi_est.h deleted file mode 100644 index 556d75c6..00000000 --- a/9_async_work/pi_est.h +++ /dev/null @@ -1 +0,0 @@ -double Estimate (int points); diff --git a/9_async_work/sync.cc b/9_async_work/sync.cc deleted file mode 100644 index f9c5262b..00000000 --- a/9_async_work/sync.cc +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include "pi_est.h" -#include "sync.h" - -using namespace v8; - -// Simple synchronous access to the `Estimate()` function -Handle CalculateSync(const Arguments& args) { - HandleScope scope; - - // expect a number as the first argument - int points = args[0]->Uint32Value(); - double est = Estimate(points); - - return scope.Close(Number::New(est)); -} diff --git a/9_async_work/sync.h b/9_async_work/sync.h deleted file mode 100644 index 36df10ee..00000000 --- a/9_async_work/sync.h +++ /dev/null @@ -1,3 +0,0 @@ -#include - -v8::Handle CalculateSync(const v8::Arguments& args); diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..97e54b4d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +# Code of Conduct + +The Node.js Code of Conduct, which applies to this project, can be found at +https://github.com/nodejs/admin/blob/HEAD/CODE_OF_CONDUCT.md. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2aaa3b0d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,28 @@ + +# Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open-source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open-source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b), or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..251b56e6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,13 @@ +The MIT License (MIT) +===================== + +Copyright (c) 2017 Node.js node-addon-examples collaborators +----------------------------------- + +*Node.js node-addon-examples collaborators listed at * + +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: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +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. diff --git a/README.md b/README.md index 08fd4467..601754b8 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,59 @@ Node.js Addon Examples -====================== +========================================= -See the Node.js C++ [addons page](http://nodejs.org/docs/latest/api/addons.html) for details of the examples here. +**A repository of [Node.js Addons](https://nodejs.org/api/addons.html#addons_c_addons) examples.** -In each directory, run: +Implementations of examples are named either after Node.js versions (`node_0.10`, +`node_0.12`, etc), or Node.js addon implementation APIs: -```sh -$ node-gyp rebuild -$ node ./ -``` - -**See the [v0.11](https://github.com/rvagg/node-addon-examples/tree/v0.11/) branch for updated examples applicable to Node v0.11 and above.** - -## Compatibility notes +- [`nan`](https://github.com/nodejs/nan): C++-based abstraction between Node and direct V8 APIs. +- [`Node-API`](https://nodejs.org/api/n-api.html): C-based API guaranteeing [ABI stability](https://nodejs.org/en/docs/guides/abi-stability/) across different node versions as well as JavaScript engines. (Node-API was previously known as N-API.) +- [`node-addon-api`](https://github.com/nodejs/node-addon-api): header-only C++ wrapper classes which simplify the use of the C-based Node-API. +- [`node-addon-api-addon-class`](https://github.com/nodejs/node-addon-api/tree/main/doc/addon.md): Similar to `node-addon-api`, but deriving from the `Napi::Addon` class. [1_hello_world](./src/1-getting-started/1_hello_world) provides an example. -### Node v0.11: V8 requires current isolate +Implementations against unsupported versions of Node.js are provided for +completeness and historical context. They are not maintained. -The V8 upgrade that occured when v0.11 was released requires that the current *isolate* be passed to scope creation calls and most `New()` calls. +The examples are primarily maintained for Node-API and node-addon-api and as outlined in +the Node.js [documentation](https://nodejs.org/dist/latest/docs/api/addons.html), +unless there is a need for direct access to functionality which +is not exposed by Node-API, use Node-API. -```c++ -Isolate* isolate = Isolate::GetCurrent(); -// ... -HandleScope scope(isolate); -// ... -Persistent constructor = Persistent::New(isolate, tpl->GetFunction()); -``` - -Omission of the current isolate will only trigger a compile-time warning at this stage but addon authors wishing to remove those warnings and remain backward-compatible with v0.10 and prior may need to get creative with macros: +The [Node-API Resource](http://nodejs.github.io/node-addon-examples/)Β offers an +excellent orientation and tips for developers just getting started with Node-API +and `node-addon-api`. -```c++ -// NODE_MODULE_VERSION was incremented for v0.11 +## Usage -#if NODE_MODULE_VERSION > 0x000B -# define MY_NODE_ISOLATE_DECL Isolate* isolate = Isolate::GetCurrent(); -# define MY_NODE_ISOLATE isolate -# define MY_NODE_ISOLATE_PRE isolate, -# define MY_NODE_ISOLATE_POST , isolate -# define MY_HANDLESCOPE v8::HandleScope scope(MY_NODE_ISOLATE); -#else -# define MY_NODE_ISOLATE_DECL -# define MY_NODE_ISOLATE -# define MY_NODE_ISOLATE_PRE -# define MY_NODE_ISOLATE_POST -# define MY_HANDLESCOPE v8::HandleScope scope; -#endif +The directory structure is as follows: -MY_NODE_ISOLATE_DECL -MY_HANDLESCOPE +```sh +REPO_ROOT +β”œβ”€β”€ test_all.js +β”œβ”€β”€ package.json +β”œβ”€β”€ README.md +└── src + β”œβ”€β”€ 1-getting-started + β”‚ β”œβ”€β”€ example1 + β”‚ β”‚ β”œβ”€β”€ nan + β”‚ β”‚ β”œβ”€β”€ node-addon-api + β”‚ β”‚ └── napi + β”‚ β”œβ”€β”€ example2 + β”‚ └── example3 + β”œβ”€β”€ 2-js-to-native-conversion + β”œβ”€β”€ 3-context-awareness + β”œβ”€β”€ 4-references-and-handle-scope + β”œβ”€β”€ 5-async-work + β”œβ”€β”€ 6-threadsafe-function + β”œβ”€β”€ 7-events + └── 8-tooling +``` -// ... +In each example's implementation subdirectory, run -Persistent constructor = Persistent::New(MY_NODE_ISOLATE_PRE tpl->GetFunction()); +```text +npm install +node ./ ``` ----------------------------------------------------- +to see the example in action. diff --git a/original_docs_source.md b/original_docs_source.md index 1a7390ed..12f5302e 100644 --- a/original_docs_source.md +++ b/original_docs_source.md @@ -16,7 +16,7 @@ knowledge of several libraries: tree), which is also available [online](http://izs.me/v8-docs/main.html). - - [libuv](https://github.com/joyent/libuv), C event loop library. + - [libuv](https://github.com/libuv/libuv), C event loop library. Anytime one needs to wait for a file descriptor to become readable, wait for a timer, or wait for a signal to be received one will need to interface with libuv. That is, if you perform any I/O, libuv will @@ -98,7 +98,7 @@ in `build/Release/`. You can now use the binary addon in a Node project `hello.js` by pointing `require` to the recently built `hello.node` module: - var addon = require('./build/Release/hello'); + const addon = require('./build/Release/hello'); console.log(addon.hello()); // 'world' @@ -175,7 +175,7 @@ function calls and return a result. This is the main and only needed source You can test it with the following JavaScript snippet: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); console.log( 'This should be eight:', addon.add(3,5) ); @@ -215,7 +215,7 @@ adding the function as a property of `exports`. To test it run the following JavaScript snippet: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); addon(function(msg){ console.log(msg); // 'hello world' @@ -251,10 +251,10 @@ the string passed to `createObject()`: To test it in JavaScript: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); - var obj1 = addon('hello'); - var obj2 = addon('world'); + const obj1 = addon('hello'); + const obj2 = addon('world'); console.log(obj1.msg+' '+obj2.msg); // 'hello world' @@ -293,9 +293,9 @@ wraps a C++ function: To test: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); - var fn = addon(); + const fn = addon(); console.log(fn()); // 'hello world' @@ -398,9 +398,9 @@ prototype: Test it with: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); - var obj = new addon.MyObject(10); + const obj = new addon.MyObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 @@ -411,9 +411,9 @@ Test it with: This is useful when you want to be able to create native objects without explicitly instantiating them with the `new` operator in JavaScript, e.g. - var obj = addon.createObject(); + const obj = addon.createObject(); // instead of: - // var obj = new addon.Object(); + // const obj = new addon.Object(); Let's register our `createObject` method in `addon.cc`: @@ -528,14 +528,14 @@ The implementation is similar to the above in `myobject.cc`: Test it with: - var createObject = require('./build/Release/addon'); + const createObject = require('./build/Release/addon'); - var obj = createObject(10); + const obj = createObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 - var obj2 = createObject(20); + const obj2 = createObject(20); console.log( obj2.plusOne() ); // 21 console.log( obj2.plusOne() ); // 22 console.log( obj2.plusOne() ); // 23 @@ -662,10 +662,10 @@ The implementation of `myobject.cc` is similar as before: Test it with: - var addon = require('./build/Release/addon'); + const addon = require('./build/Release/addon'); - var obj1 = addon.createObject(10); - var obj2 = addon.createObject(20); - var result = addon.add(obj1, obj2); + const obj1 = addon.createObject(10); + const obj2 = addon.createObject(20); + const result = addon.add(obj1, obj2); console.log(result); // 30 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..11094190 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3022 @@ +{ + "name": "node-addon-examples", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-addon-examples", + "version": "1.0.0", + "workspaces": [ + "src/1-getting-started/*/*", + "src/2-js-to-native/*/*", + "src/3-context-awareness/*", + "src/4-references-and-handle-scope/*", + "src/5-async-work/*/*", + "src/6-threadsafe-function/*/*", + "src/7-events/*/*", + "src/8-tooling/*/*" + ], + "dependencies": { + "chalk": "^5.4.1", + "semver": "^7.1.3" + }, + "devDependencies": { + "clang-format": "^1.4.0", + "husky": "^4.3.0", + "lint-staged": "^16.1.0", + "node-gyp": "^12.2.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@npmcli/agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async_work_nan": { + "resolved": "src/5-async-work/async_pi_estimate/nan", + "link": true + }, + "node_modules/async_work_node_addon_api": { + "resolved": "src/5-async-work/async_pi_estimate/node-addon-api", + "link": true + }, + "node_modules/async_work_promise": { + "resolved": "src/5-async-work/async_work_promise/napi", + "link": true + }, + "node_modules/async_work_thread_safe_function": { + "resolved": "src/5-async-work/async_work_thread_safe_function/napi", + "link": true + }, + "node_modules/async_worker_promise": { + "resolved": "src/5-async-work/async_work_promise/node-addon-api", + "link": true + }, + "node_modules/async-iterator-example": { + "resolved": "src/5-async-work/async-iterator/node-addon-api", + "link": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/build-napi-with-cmake": { + "resolved": "src/8-tooling/build_with_cmake/napi", + "link": true + }, + "node_modules/build-node-addon-api-with-cmake": { + "resolved": "src/8-tooling/build_with_cmake/node-addon-api", + "link": true + }, + "node_modules/cacache": { + "version": "20.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.4.tgz", + "integrity": "sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/cacache/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cacache/node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/call-js-from-async-worker-execute": { + "resolved": "src/5-async-work/call-js-from-async-worker-execute/node-addon-api", + "link": true + }, + "node_modules/callbacks_nan": { + "resolved": "src/1-getting-started/3_callbacks/nan", + "link": true + }, + "node_modules/callbacks_napi": { + "resolved": "src/1-getting-started/3_callbacks/napi", + "link": true + }, + "node_modules/callbacks_node_addon_api": { + "resolved": "src/1-getting-started/3_callbacks/node-addon-api", + "link": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/clang-format": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.8.0.tgz", + "integrity": "sha512-pK8gzfu55/lHzIpQ1givIbWfn3eXnU7SfxqIwVgnn5jEM6j4ZJYjpFqFs4iSBPNedzRMmfjYjuQhu657WAXHXw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "glob": "^7.0.0", + "resolve": "^1.1.6" + }, + "bin": { + "check-clang-format": "bin/check-clang-format.js", + "clang-format": "index.js", + "git-clang-format": "bin/git-clang-format" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cmake-js": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/cmake-js/-/cmake-js-8.0.0.tgz", + "integrity": "sha512-YbUP88RDwCvoQkZhRtGURYm9RIpWdtvZuhT87fKNoLjk8kIFIFeARpKfuZQGdwfH99GZpUmqSfcDrK62X7lTgg==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "fs-extra": "^11.3.3", + "node-api-headers": "^1.8.0", + "rc": "1.2.8", + "semver": "^7.7.3", + "tar": "^7.5.6", + "url-join": "^4.0.1", + "which": "^6.0.0", + "yargs": "^17.7.2" + }, + "bin": { + "cmake-js": "bin/cmake-js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emit-event-from-cpp-example": { + "resolved": "src/7-events/emit_event_from_cpp/node-addon-api", + "link": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/example-typedthreadsafefunction": { + "resolved": "src/6-threadsafe-function/typed_threadsafe_function/node-addon-api", + "link": true + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/factory_wrap_nan": { + "resolved": "src/1-getting-started/7_factory_wrap/nan", + "link": true + }, + "node_modules/factory_wrap_napi": { + "resolved": "src/1-getting-started/7_factory_wrap/napi", + "link": true + }, + "node_modules/factory_wrap_node_addon_api": { + "resolved": "src/1-getting-started/7_factory_wrap/node-addon-api", + "link": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^3.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function_arguments_nan": { + "resolved": "src/1-getting-started/2_function_arguments/nan", + "link": true + }, + "node_modules/function_arguments_napi": { + "resolved": "src/1-getting-started/2_function_arguments/napi", + "link": true + }, + "node_modules/function_arguments_node_addon_api": { + "resolved": "src/1-getting-started/2_function_arguments/node-addon-api", + "link": true + }, + "node_modules/function_factory_nan": { + "resolved": "src/1-getting-started/5_function_factory/nan", + "link": true + }, + "node_modules/function_factory_napi": { + "resolved": "src/1-getting-started/5_function_factory/napi", + "link": true + }, + "node_modules/function_factory_node_addon_api": { + "resolved": "src/1-getting-started/5_function_factory/node-addon-api", + "link": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hello_world_nan": { + "resolved": "src/1-getting-started/1_hello_world/nan", + "link": true + }, + "node_modules/hello_world_napi": { + "resolved": "src/1-getting-started/1_hello_world/napi", + "link": true + }, + "node_modules/hello_world_node_addon_api": { + "resolved": "src/1-getting-started/1_hello_world/node-addon-api", + "link": true + }, + "node_modules/hello_world_node_addon_api_addon_class": { + "resolved": "src/1-getting-started/1_hello_world/node-addon-api-addon-class", + "link": true + }, + "node_modules/hello-world": { + "resolved": "src/1-getting-started/a-first-project/node-addon-api", + "link": true + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/husky": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz", + "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^7.0.0", + "find-versions": "^4.0.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "bin": { + "husky-run": "bin/run.js", + "husky-upgrade": "lib/upgrader/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/husky" + } + }, + "node_modules/husky/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/husky/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits-from-event-emitter-example": { + "resolved": "src/7-events/inherits_from_event_emitter/node-addon-api", + "link": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/lru-cache": { + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.5.tgz", + "integrity": "sha512-uCbIa8jWWmQZt4dSnEStkVC6gdakiinAm4PiGsywIkguF0eWMdcjDz0ECYhUolFU3pFLOev9VNPCEygydXnddg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^4.0.0", + "@npmcli/redact": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", + "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multiple_load_napi": { + "resolved": "src/3-context-awareness/napi", + "link": true + }, + "node_modules/multiple_load_node_10": { + "resolved": "src/3-context-awareness/node_10", + "link": true + }, + "node_modules/nan": { + "version": "2.26.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", + "integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==", + "license": "MIT" + }, + "node_modules/napi-asyncworker-example": { + "resolved": "src/5-async-work/napi-asyncworker-example/node-addon-api", + "link": true + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", + "integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-api-headers": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/node-api-headers/-/node-api-headers-1.8.0.tgz", + "integrity": "sha512-jfnmiKWjRAGbdD1yQS28bknFM1tbHC1oucyuMPjmkEs+kpiu76aRs40WlTmBmyEgzDM76ge1DQ7XJ3R5deiVjQ==", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", + "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/object_factory_nan": { + "resolved": "src/1-getting-started/4_object_factory/nan", + "link": true + }, + "node_modules/object_factory_napi": { + "resolved": "src/1-getting-started/4_object_factory/napi", + "link": true + }, + "node_modules/object_factory_node_addon_api": { + "resolved": "src/1-getting-started/4_object_factory/node-addon-api", + "link": true + }, + "node_modules/object_wrap_nan": { + "resolved": "src/1-getting-started/6_object_wrap/nan", + "link": true + }, + "node_modules/object_wrap_napi": { + "resolved": "src/1-getting-started/6_object_wrap/napi", + "link": true + }, + "node_modules/object_wrap_node_addon_api": { + "resolved": "src/1-getting-started/6_object_wrap/node-addon-api", + "link": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "dev": true, + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", + "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-callback-demo": { + "resolved": "src/6-threadsafe-function/promise-callback-demo/node-addon-api", + "link": true + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver-regex": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", + "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ssri": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", + "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", + "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "7.5.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", + "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/thread_safe_function_round_trip": { + "resolved": "src/6-threadsafe-function/thread_safe_function_round_trip/napi", + "link": true + }, + "node_modules/threadsafe-async-iterator-example": { + "resolved": "src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api", + "link": true + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsfn-counting": { + "resolved": "src/6-threadsafe-function/thread_safe_function_counting/node-addon-api", + "link": true + }, + "node_modules/tsfn-test": { + "resolved": "src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api", + "link": true + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript_with_addon": { + "resolved": "src/8-tooling/typescript_with_addon/node-addon-api", + "link": true + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "src/1-getting-started/1_hello_world/nan": { + "name": "hello_world_nan", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } + }, + "src/1-getting-started/1_hello_world/napi": { + "name": "hello_world_napi", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/1_hello_world/node-addon-api": { + "name": "hello_world_node_addon_api", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } + }, + "src/1-getting-started/1_hello_world/node-addon-api-addon-class": { + "name": "hello_world_node_addon_api_addon_class", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } + }, + "src/1-getting-started/2_function_arguments/nan": { + "name": "function_arguments_nan", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } + }, + "src/1-getting-started/2_function_arguments/napi": { + "name": "function_arguments_napi", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/2_function_arguments/node-addon-api": { + "name": "function_arguments_node_addon_api", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } + }, + "src/1-getting-started/3_callbacks/nan": { + "name": "callbacks_nan", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } + }, + "src/1-getting-started/3_callbacks/napi": { + "name": "callbacks_napi", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/3_callbacks/node-addon-api": { + "name": "callbacks_node_addon_api", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } + }, + "src/1-getting-started/4_object_factory/nan": { + "name": "object_factory_nan", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } + }, + "src/1-getting-started/4_object_factory/napi": { + "name": "object_factory_napi", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/4_object_factory/node-addon-api": { + "name": "object_factory_node_addon_api", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.0.0" + } + }, + "src/1-getting-started/5_function_factory/nan": { + "name": "function_factory_nan", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } + }, + "src/1-getting-started/5_function_factory/napi": { + "name": "function_factory_napi", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/5_function_factory/node-addon-api": { + "name": "function_factory_node_addon_api", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } + }, + "src/1-getting-started/6_object_wrap/nan": { + "name": "object_wrap_nan", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } + }, + "src/1-getting-started/6_object_wrap/napi": { + "name": "object_wrap_napi", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/6_object_wrap/node-addon-api": { + "name": "object_wrap_node_addon_api", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "engines": { + "node": "~10 >=10.20 || >=12.17" + } + }, + "src/1-getting-started/7_factory_wrap/nan": { + "name": "factory_wrap_nan", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } + }, + "src/1-getting-started/7_factory_wrap/napi": { + "name": "factory_wrap_napi", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0" + } + }, + "src/1-getting-started/7_factory_wrap/node-addon-api": { + "name": "factory_wrap_node_addon_api", + "version": "0.0.0", + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "engines": { + "node": "~10 >=10.20 || >=12.17" + } + }, + "src/1-getting-started/a-first-project/node-addon-api": { + "name": "hello-world", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-addon-api": "^8.1.0" + } + }, + "src/3-context-awareness/napi": { + "name": "multiple_load_napi", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.10.0" + } + }, + "src/3-context-awareness/node_10": { + "name": "multiple_load_node_10", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.10.0" + } + }, + "src/5-async-work/async_pi_estimate/nan": { + "name": "async_work_nan", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "nan": "*" + } + }, + "src/5-async-work/async_pi_estimate/node-addon-api": { + "name": "async_work_node_addon_api", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "*", + "node-addon-api": "*" + } + }, + "src/5-async-work/async_work_promise/napi": { + "name": "async_work_promise", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + } + }, + "src/5-async-work/async_work_promise/node-addon-api": { + "name": "async_worker_promise", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^8.0.0" + } + }, + "src/5-async-work/async_work_thread_safe_function/napi": { + "name": "async_work_thread_safe_function", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + } + }, + "src/5-async-work/async-iterator/node-addon-api": { + "name": "async-iterator-example", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + } + }, + "src/5-async-work/call-js-from-async-worker-execute/node-addon-api": { + "name": "call-js-from-async-worker-execute", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "*", + "node-addon-api": "*" + } + }, + "src/5-async-work/napi-asyncworker-example/node-addon-api": { + "name": "napi-asyncworker-example", + "version": "1.0.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.1.0" + } + }, + "src/6-threadsafe-function/promise-callback-demo/node-addon-api": { + "name": "promise-callback-demo", + "version": "1.0.0", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-addon-api": "^8.1.0" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "src/6-threadsafe-function/thread_safe_function_counting/node-addon-api": { + "name": "tsfn-counting", + "version": "0.0.0", + "dependencies": { + "bindings": "*", + "node-addon-api": "*" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "src/6-threadsafe-function/thread_safe_function_round_trip/napi": { + "name": "thread_safe_function_round_trip", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + } + }, + "src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api": { + "name": "tsfn-test", + "version": "1.0.0", + "dependencies": { + "bindings": "*", + "node-addon-api": "^8.1.0" + } + }, + "src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api": { + "name": "threadsafe-async-iterator-example", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + } + }, + "src/6-threadsafe-function/typed_threadsafe_function/node-addon-api": { + "name": "example-typedthreadsafefunction", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + } + }, + "src/7-events/emit_event_from_cpp/node-addon-api": { + "name": "emit-event-from-cpp-example", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "*", + "node-addon-api": "*" + } + }, + "src/7-events/inherits_from_event_emitter/node-addon-api": { + "name": "inherits-from-event-emitter-example", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "*", + "node-addon-api": "*" + } + }, + "src/8-tooling/build_with_cmake/napi": { + "name": "build-napi-with-cmake", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "devDependencies": { + "cmake-js": "^8.0.0" + } + }, + "src/8-tooling/build_with_cmake/node-addon-api": { + "name": "build-node-addon-api-with-cmake", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "devDependencies": { + "cmake-js": "^8.0.0" + } + }, + "src/8-tooling/typescript_with_addon/node-addon-api": { + "name": "typescript_with_addon", + "version": "0.0.0", + "hasInstallScript": true, + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^8.1.0" + }, + "devDependencies": { + "cmake-js": "^8.0.0", + "ts-node": "^10.9.2", + "typescript": "^6.0.2" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..be6522d0 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "node-addon-examples", + "version": "1.0.0", + "description": "Node.js Addon Examples", + "main": "test_all.js", + "scripts": { + "format": "clang-format -i --glob=*/**/*.{h,cpp,cc}", + "test": "node test_all.js" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{h,cpp,cc}": [ + "npm run format", + "git add" + ] + }, + "dependencies": { + "chalk": "^5.4.1", + "semver": "^7.1.3" + }, + "devDependencies": { + "clang-format": "^1.4.0", + "node-gyp": "^12.2.0", + "husky": "^4.3.0", + "lint-staged": "^16.1.0" + }, + "workspaces": [ + "src/1-getting-started/*/*", + "src/2-js-to-native/*/*", + "src/3-context-awareness/*", + "src/4-references-and-handle-scope/*", + "src/5-async-work/*/*", + "src/6-threadsafe-function/*/*", + "src/7-events/*/*", + "src/8-tooling/*/*" + ] +} diff --git a/src/1-getting-started/1_hello_world/README.md b/src/1-getting-started/1_hello_world/README.md new file mode 100644 index 00000000..c604e780 --- /dev/null +++ b/src/1-getting-started/1_hello_world/README.md @@ -0,0 +1,8 @@ +## Example 1: *Hello world* + +To get started let's make a small addon which is the C++ equivalent of +the following JavaScript code: + +```js +module.exports.hello = function() { return 'world'; }; +``` diff --git a/1_hello_world/nan/binding.gyp b/src/1-getting-started/1_hello_world/nan/binding.gyp similarity index 100% rename from 1_hello_world/nan/binding.gyp rename to src/1-getting-started/1_hello_world/nan/binding.gyp diff --git a/src/1-getting-started/1_hello_world/nan/hello.cc b/src/1-getting-started/1_hello_world/nan/hello.cc new file mode 100644 index 00000000..8da0e9e3 --- /dev/null +++ b/src/1-getting-started/1_hello_world/nan/hello.cc @@ -0,0 +1,17 @@ +#include + +void Method(const Nan::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(Nan::New("world").ToLocalChecked()); +} + +void Init(v8::Local exports) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + exports->Set(context, + Nan::New("hello").ToLocalChecked(), + Nan::New(Method) + ->GetFunction(context) + .ToLocalChecked()); +} + +NODE_MODULE(hello, Init) diff --git a/src/1-getting-started/1_hello_world/nan/hello.js b/src/1-getting-started/1_hello_world/nan/hello.js new file mode 100644 index 00000000..fba88920 --- /dev/null +++ b/src/1-getting-started/1_hello_world/nan/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('hello'); + +console.log(addon.hello()); // 'world' \ No newline at end of file diff --git a/1_hello_world/node_0.12/package.json b/src/1-getting-started/1_hello_world/nan/package.json similarity index 63% rename from 1_hello_world/node_0.12/package.json rename to src/1-getting-started/1_hello_world/nan/package.json index 17972ff9..478d3188 100644 --- a/1_hello_world/node_0.12/package.json +++ b/src/1-getting-started/1_hello_world/nan/package.json @@ -1,11 +1,15 @@ { - "name": "hello_world", + "name": "hello_world_nan", "version": "0.0.0", "description": "Node.js Addons Example #1", "main": "hello.js", "private": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + }, "scripts": { "test": "node hello.js" }, "gypfile": true -} \ No newline at end of file +} diff --git a/1_hello_world/node_0.10/binding.gyp b/src/1-getting-started/1_hello_world/napi/binding.gyp similarity index 67% rename from 1_hello_world/node_0.10/binding.gyp rename to src/1-getting-started/1_hello_world/napi/binding.gyp index 39b171f3..4dc6017f 100644 --- a/1_hello_world/node_0.10/binding.gyp +++ b/src/1-getting-started/1_hello_world/napi/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "hello", - "sources": [ "hello.cc" ] + "sources": [ "hello.c" ] } ] } diff --git a/src/1-getting-started/1_hello_world/napi/hello.c b/src/1-getting-started/1_hello_world/napi/hello.c new file mode 100644 index 00000000..19207c82 --- /dev/null +++ b/src/1-getting-started/1_hello_world/napi/hello.c @@ -0,0 +1,23 @@ +#include +#include + +static napi_value Method(napi_env env, napi_callback_info info) { + napi_status status; + napi_value world; + status = napi_create_string_utf8(env, "world", 5, &world); + assert(status == napi_ok); + return world; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +static napi_value Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method); + status = napi_define_properties(env, exports, 1, &desc); + assert(status == napi_ok); + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/1_hello_world/napi/hello.js b/src/1-getting-started/1_hello_world/napi/hello.js new file mode 100644 index 00000000..00a05a11 --- /dev/null +++ b/src/1-getting-started/1_hello_world/napi/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('hello'); + +console.log(addon.hello()); // 'world' diff --git a/1_hello_world/nan/package.json b/src/1-getting-started/1_hello_world/napi/package.json similarity index 77% rename from 1_hello_world/nan/package.json rename to src/1-getting-started/1_hello_world/napi/package.json index 93be3acc..9c6719a3 100644 --- a/1_hello_world/nan/package.json +++ b/src/1-getting-started/1_hello_world/napi/package.json @@ -1,14 +1,14 @@ { - "name": "hello_world", + "name": "hello_world_napi", "version": "0.0.0", "description": "Node.js Addons Example #1", "main": "hello.js", "private": true, "dependencies": { - "nan": "~0.6.0" + "bindings": "~1.5.0" }, "scripts": { "test": "node hello.js" }, "gypfile": true -} \ No newline at end of file +} diff --git a/src/1-getting-started/1_hello_world/node-addon-api-addon-class/binding.gyp b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/binding.gyp new file mode 100644 index 00000000..1205566e --- /dev/null +++ b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "hello", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "hello.cc" ], + "include_dirs": [ + " + +class HelloAddon : public Napi::Addon { + public: + HelloAddon(Napi::Env env, Napi::Object exports) { + DefineAddon(exports, + {InstanceMethod("hello", &HelloAddon::Hello, napi_enumerable)}); + } + + private: + Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), "world"); + } +}; + +NODE_API_ADDON(HelloAddon) diff --git a/src/1-getting-started/1_hello_world/node-addon-api-addon-class/hello.js b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/hello.js new file mode 100644 index 00000000..00a05a11 --- /dev/null +++ b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('hello'); + +console.log(addon.hello()); // 'world' diff --git a/src/1-getting-started/1_hello_world/node-addon-api-addon-class/package.json b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/package.json new file mode 100644 index 00000000..ece3822a --- /dev/null +++ b/src/1-getting-started/1_hello_world/node-addon-api-addon-class/package.json @@ -0,0 +1,15 @@ +{ + "name": "hello_world_node_addon_api_addon_class", + "version": "0.0.0", + "description": "Node.js Addons Example #1", + "main": "hello.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "test": "node hello.js" + }, + "gypfile": true +} diff --git a/src/1-getting-started/1_hello_world/node-addon-api/binding.gyp b/src/1-getting-started/1_hello_world/node-addon-api/binding.gyp new file mode 100644 index 00000000..b819abbd --- /dev/null +++ b/src/1-getting-started/1_hello_world/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "hello", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "hello.cc" ], + "include_dirs": [ + " + +Napi::String Method(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return Napi::String::New(env, "world"); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "hello"), + Napi::Function::New(env, Method)); + return exports; +} + +NODE_API_MODULE(hello, Init) diff --git a/src/1-getting-started/1_hello_world/node-addon-api/hello.js b/src/1-getting-started/1_hello_world/node-addon-api/hello.js new file mode 100644 index 00000000..00a05a11 --- /dev/null +++ b/src/1-getting-started/1_hello_world/node-addon-api/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('hello'); + +console.log(addon.hello()); // 'world' diff --git a/1_hello_world/node_0.10/package.json b/src/1-getting-started/1_hello_world/node-addon-api/package.json similarity index 58% rename from 1_hello_world/node_0.10/package.json rename to src/1-getting-started/1_hello_world/node-addon-api/package.json index 17972ff9..bab8c774 100644 --- a/1_hello_world/node_0.10/package.json +++ b/src/1-getting-started/1_hello_world/node-addon-api/package.json @@ -1,11 +1,15 @@ { - "name": "hello_world", + "name": "hello_world_node_addon_api", "version": "0.0.0", "description": "Node.js Addons Example #1", "main": "hello.js", "private": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, "scripts": { "test": "node hello.js" }, "gypfile": true -} \ No newline at end of file +} diff --git a/src/1-getting-started/2_function_arguments/README.md b/src/1-getting-started/2_function_arguments/README.md new file mode 100644 index 00000000..3f51ebe7 --- /dev/null +++ b/src/1-getting-started/2_function_arguments/README.md @@ -0,0 +1,2 @@ +## Example 2: *Function arguments* + diff --git a/src/1-getting-started/2_function_arguments/nan/addon.cc b/src/1-getting-started/2_function_arguments/nan/addon.cc new file mode 100644 index 00000000..bf4a6876 --- /dev/null +++ b/src/1-getting-started/2_function_arguments/nan/addon.cc @@ -0,0 +1,33 @@ +#include + +void Add(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + + if (info.Length() < 2) { + Nan::ThrowTypeError("Wrong number of arguments"); + return; + } + + if (!info[0]->IsNumber() || !info[1]->IsNumber()) { + Nan::ThrowTypeError("Wrong arguments"); + return; + } + + double arg0 = info[0]->NumberValue(context).FromJust(); + double arg1 = info[1]->NumberValue(context).FromJust(); + v8::Local num = Nan::New(arg0 + arg1); + + info.GetReturnValue().Set(num); +} + +void Init(v8::Local exports) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + exports->Set(context, + Nan::New("add").ToLocalChecked(), + Nan::New(Add) + ->GetFunction(context) + .ToLocalChecked()); +} + +NODE_MODULE(addon, Init) diff --git a/src/1-getting-started/2_function_arguments/nan/addon.js b/src/1-getting-started/2_function_arguments/nan/addon.js new file mode 100644 index 00000000..f6120126 --- /dev/null +++ b/src/1-getting-started/2_function_arguments/nan/addon.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('addon.node') + +console.log('This should be eight:', addon.add(3, 5)) \ No newline at end of file diff --git a/src/1-getting-started/2_function_arguments/nan/binding.gyp b/src/1-getting-started/2_function_arguments/nan/binding.gyp new file mode 100644 index 00000000..170c85fd --- /dev/null +++ b/src/1-getting-started/2_function_arguments/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ], + "include_dirs": [ + " +#include +#include +static napi_value Add(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 2; + napi_value args[2]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + if (argc < 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + napi_valuetype valuetype0; + status = napi_typeof(env, args[0], &valuetype0); + assert(status == napi_ok); + + napi_valuetype valuetype1; + status = napi_typeof(env, args[1], &valuetype1); + assert(status == napi_ok); + + if (valuetype0 != napi_number || valuetype1 != napi_number) { + napi_throw_type_error(env, NULL, "Wrong arguments"); + return NULL; + } + + double value0; + status = napi_get_value_double(env, args[0], &value0); + assert(status == napi_ok); + + double value1; + status = napi_get_value_double(env, args[1], &value1); + assert(status == napi_ok); + + napi_value sum; + status = napi_create_double(env, value0 + value1, &sum); + assert(status == napi_ok); + + return sum; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +napi_value Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor addDescriptor = DECLARE_NAPI_METHOD("add", Add); + status = napi_define_properties(env, exports, 1, &addDescriptor); + assert(status == napi_ok); + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/2_function_arguments/napi/addon.js b/src/1-getting-started/2_function_arguments/napi/addon.js new file mode 100644 index 00000000..f6120126 --- /dev/null +++ b/src/1-getting-started/2_function_arguments/napi/addon.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('addon.node') + +console.log('This should be eight:', addon.add(3, 5)) \ No newline at end of file diff --git a/4_object_factory/binding.gyp b/src/1-getting-started/2_function_arguments/napi/binding.gyp similarity index 67% rename from 4_object_factory/binding.gyp rename to src/1-getting-started/2_function_arguments/napi/binding.gyp index 3c79ca84..80f9fa87 100644 --- a/4_object_factory/binding.gyp +++ b/src/1-getting-started/2_function_arguments/napi/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "addon", - "sources": [ "addon.cc" ] + "sources": [ "addon.c" ] } ] } diff --git a/src/1-getting-started/2_function_arguments/napi/package.json b/src/1-getting-started/2_function_arguments/napi/package.json new file mode 100644 index 00000000..8aefbb6b --- /dev/null +++ b/src/1-getting-started/2_function_arguments/napi/package.json @@ -0,0 +1,14 @@ +{ + "name": "function_arguments_napi", + "version": "0.0.0", + "description": "Node.js Addons Example #2", + "main": "addon.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "scripts": { + "test": "node addon.js" + }, + "gypfile": true +} diff --git a/src/1-getting-started/2_function_arguments/node-addon-api/addon.cc b/src/1-getting-started/2_function_arguments/node-addon-api/addon.cc new file mode 100644 index 00000000..c902bfea --- /dev/null +++ b/src/1-getting-started/2_function_arguments/node-addon-api/addon.cc @@ -0,0 +1,29 @@ +#include + +Napi::Value Add(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 2) { + Napi::TypeError::New(env, "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + if (!info[0].IsNumber() || !info[1].IsNumber()) { + Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException(); + return env.Null(); + } + + double arg0 = info[0].As().DoubleValue(); + double arg1 = info[1].As().DoubleValue(); + Napi::Number num = Napi::Number::New(env, arg0 + arg1); + + return num; +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "add"), Napi::Function::New(env, Add)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/src/1-getting-started/2_function_arguments/node-addon-api/addon.js b/src/1-getting-started/2_function_arguments/node-addon-api/addon.js new file mode 100644 index 00000000..7006004d --- /dev/null +++ b/src/1-getting-started/2_function_arguments/node-addon-api/addon.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('addon.node') + +console.log('This should be eight:', addon.add(3, 5)) diff --git a/src/1-getting-started/2_function_arguments/node-addon-api/binding.gyp b/src/1-getting-started/2_function_arguments/node-addon-api/binding.gyp new file mode 100644 index 00000000..cb1e3628 --- /dev/null +++ b/src/1-getting-started/2_function_arguments/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc" ], + "include_dirs": [ + " + +void RunCallback(const Nan::FunctionCallbackInfo& info) { + v8::Local cb = info[0].As(); + const unsigned argc = 1; + v8::Local argv[argc] = {Nan::New("hello world").ToLocalChecked()}; + Nan::AsyncResource resource("nan:makeCallback"); + resource.runInAsyncScope(Nan::GetCurrentContext()->Global(), cb, argc, argv); +} + +void Init(v8::Local exports, v8::Local module) { + Nan::SetMethod(module, "exports", RunCallback); +} + +NODE_MODULE(addon, Init) diff --git a/3_callbacks/addon.js b/src/1-getting-started/3_callbacks/nan/addon.js similarity index 57% rename from 3_callbacks/addon.js rename to src/1-getting-started/3_callbacks/nan/addon.js index 74d1a36b..f4de1464 100644 --- a/3_callbacks/addon.js +++ b/src/1-getting-started/3_callbacks/nan/addon.js @@ -1,4 +1,4 @@ -var addon = require('./build/Release/addon'); +const addon = require('bindings')('addon'); addon(function(msg){ console.log(msg); // 'hello world' diff --git a/src/1-getting-started/3_callbacks/nan/binding.gyp b/src/1-getting-started/3_callbacks/nan/binding.gyp new file mode 100644 index 00000000..170c85fd --- /dev/null +++ b/src/1-getting-started/3_callbacks/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ], + "include_dirs": [ + " +#include + +static napi_value RunCallback(napi_env env, const napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_value cb = args[0]; + + napi_value argv[1]; + status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, argv); + assert(status == napi_ok); + + napi_value global; + status = napi_get_global(env, &global); + assert(status == napi_ok); + + napi_value result; + status = napi_call_function(env, global, cb, 1, argv, &result); + assert(status == napi_ok); + + return NULL; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value new_exports; + napi_status status = napi_create_function( + env, "", NAPI_AUTO_LENGTH, RunCallback, NULL, &new_exports); + assert(status == napi_ok); + return new_exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/3_callbacks/napi/addon.js b/src/1-getting-started/3_callbacks/napi/addon.js new file mode 100644 index 00000000..e2e36594 --- /dev/null +++ b/src/1-getting-started/3_callbacks/napi/addon.js @@ -0,0 +1,5 @@ +const addon = require('bindings')('addon'); + +addon(function(msg){ + console.log(msg); // 'hello world' +}); diff --git a/5_function_factory/binding.gyp b/src/1-getting-started/3_callbacks/napi/binding.gyp similarity index 67% rename from 5_function_factory/binding.gyp rename to src/1-getting-started/3_callbacks/napi/binding.gyp index 3c79ca84..80f9fa87 100644 --- a/5_function_factory/binding.gyp +++ b/src/1-getting-started/3_callbacks/napi/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "addon", - "sources": [ "addon.cc" ] + "sources": [ "addon.c" ] } ] } diff --git a/3_callbacks/package.json b/src/1-getting-started/3_callbacks/napi/package.json similarity index 53% rename from 3_callbacks/package.json rename to src/1-getting-started/3_callbacks/napi/package.json index defec3a4..f6bf4368 100644 --- a/3_callbacks/package.json +++ b/src/1-getting-started/3_callbacks/napi/package.json @@ -1,8 +1,11 @@ { - "name": "callbacks", + "name": "callbacks_napi", "version": "0.0.0", "description": "Node.js Addons Example #3", "main": "addon.js", "private": true, - "gypfile": true -} \ No newline at end of file + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/1-getting-started/3_callbacks/node-addon-api/addon.cc b/src/1-getting-started/3_callbacks/node-addon-api/addon.cc new file mode 100644 index 00000000..129be800 --- /dev/null +++ b/src/1-getting-started/3_callbacks/node-addon-api/addon.cc @@ -0,0 +1,13 @@ +#include + +void RunCallback(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Function cb = info[0].As(); + cb.Call(env.Global(), {Napi::String::New(env, "hello world")}); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + return Napi::Function::New(env, RunCallback); +} + +NODE_API_MODULE(addon, Init) diff --git a/src/1-getting-started/3_callbacks/node-addon-api/addon.js b/src/1-getting-started/3_callbacks/node-addon-api/addon.js new file mode 100644 index 00000000..e2e36594 --- /dev/null +++ b/src/1-getting-started/3_callbacks/node-addon-api/addon.js @@ -0,0 +1,5 @@ +const addon = require('bindings')('addon'); + +addon(function(msg){ + console.log(msg); // 'hello world' +}); diff --git a/src/1-getting-started/3_callbacks/node-addon-api/binding.gyp b/src/1-getting-started/3_callbacks/node-addon-api/binding.gyp new file mode 100644 index 00000000..cb1e3628 --- /dev/null +++ b/src/1-getting-started/3_callbacks/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc" ], + "include_dirs": [ + " + +void CreateObject(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + v8::Local obj = Nan::New(); + obj->Set(context, + Nan::New("msg").ToLocalChecked(), + info[0]->ToString(context).ToLocalChecked()); + + info.GetReturnValue().Set(obj); +} + +void Init(v8::Local exports, v8::Local module) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + module->Set(context, + Nan::New("exports").ToLocalChecked(), + Nan::New(CreateObject) + ->GetFunction(context) + .ToLocalChecked()); +} + +NODE_MODULE(addon, Init) diff --git a/src/1-getting-started/4_object_factory/nan/addon.js b/src/1-getting-started/4_object_factory/nan/addon.js new file mode 100644 index 00000000..376e628b --- /dev/null +++ b/src/1-getting-started/4_object_factory/nan/addon.js @@ -0,0 +1,5 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +console.log(obj1.msg+' '+obj2.msg); // 'hello world' \ No newline at end of file diff --git a/src/1-getting-started/4_object_factory/nan/binding.gyp b/src/1-getting-started/4_object_factory/nan/binding.gyp new file mode 100644 index 00000000..170c85fd --- /dev/null +++ b/src/1-getting-started/4_object_factory/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ], + "include_dirs": [ + " +#include + +static napi_value CreateObject(napi_env env, const napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_value obj; + status = napi_create_object(env, &obj); + assert(status == napi_ok); + + status = napi_set_named_property(env, obj, "msg", args[0]); + assert(status == napi_ok); + + return obj; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value new_exports; + napi_status status = napi_create_function( + env, "", NAPI_AUTO_LENGTH, CreateObject, NULL, &new_exports); + assert(status == napi_ok); + return new_exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/4_object_factory/napi/addon.js b/src/1-getting-started/4_object_factory/napi/addon.js new file mode 100644 index 00000000..cc01629a --- /dev/null +++ b/src/1-getting-started/4_object_factory/napi/addon.js @@ -0,0 +1,5 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +console.log(obj1.msg+' '+obj2.msg); // 'hello world' diff --git a/2_function_arguments/binding.gyp b/src/1-getting-started/4_object_factory/napi/binding.gyp similarity index 67% rename from 2_function_arguments/binding.gyp rename to src/1-getting-started/4_object_factory/napi/binding.gyp index 3c79ca84..80f9fa87 100644 --- a/2_function_arguments/binding.gyp +++ b/src/1-getting-started/4_object_factory/napi/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "addon", - "sources": [ "addon.cc" ] + "sources": [ "addon.c" ] } ] } diff --git a/src/1-getting-started/4_object_factory/napi/package.json b/src/1-getting-started/4_object_factory/napi/package.json new file mode 100644 index 00000000..ac142e58 --- /dev/null +++ b/src/1-getting-started/4_object_factory/napi/package.json @@ -0,0 +1,14 @@ +{ + "name": "object_factory_napi", + "version": "0.0.0", + "description": "Node.js Addons Example #4", + "main": "addon.js", + "private": true, + "gypfile": true, + "scripts": { + "test": "node addon.js" + }, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/1-getting-started/4_object_factory/node-addon-api/addon.cc b/src/1-getting-started/4_object_factory/node-addon-api/addon.cc new file mode 100644 index 00000000..a0c9c114 --- /dev/null +++ b/src/1-getting-started/4_object_factory/node-addon-api/addon.cc @@ -0,0 +1,15 @@ +#include + +Napi::Object CreateObject(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Object obj = Napi::Object::New(env); + obj.Set(Napi::String::New(env, "msg"), info[0].ToString()); + + return obj; +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + return Napi::Function::New(env, CreateObject, "createObject"); +} + +NODE_API_MODULE(addon, Init) diff --git a/src/1-getting-started/4_object_factory/node-addon-api/addon.js b/src/1-getting-started/4_object_factory/node-addon-api/addon.js new file mode 100644 index 00000000..376e628b --- /dev/null +++ b/src/1-getting-started/4_object_factory/node-addon-api/addon.js @@ -0,0 +1,5 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon('hello'); +const obj2 = addon('world'); +console.log(obj1.msg+' '+obj2.msg); // 'hello world' \ No newline at end of file diff --git a/src/1-getting-started/4_object_factory/node-addon-api/binding.gyp b/src/1-getting-started/4_object_factory/node-addon-api/binding.gyp new file mode 100644 index 00000000..cb1e3628 --- /dev/null +++ b/src/1-getting-started/4_object_factory/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc" ], + "include_dirs": [ + " + +void MyFunction(const Nan::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(Nan::New("hello world").ToLocalChecked()); +} + +void CreateFunction(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + v8::Local tpl = + Nan::New(MyFunction); + v8::Local fn = tpl->GetFunction(context).ToLocalChecked(); + + // omit this to make it anonymous + fn->SetName(Nan::New("theFunction").ToLocalChecked()); + + info.GetReturnValue().Set(fn); +} + +void Init(v8::Local exports, v8::Local module) { + Nan::SetMethod(module, "exports", CreateFunction); +} + +NODE_MODULE(addon, Init) diff --git a/src/1-getting-started/5_function_factory/nan/addon.js b/src/1-getting-started/5_function_factory/nan/addon.js new file mode 100644 index 00000000..b54d2747 --- /dev/null +++ b/src/1-getting-started/5_function_factory/nan/addon.js @@ -0,0 +1,4 @@ +const addon = require('bindings')('addon'); + +const fn = addon(); +console.log(fn()); // 'hello world' \ No newline at end of file diff --git a/src/1-getting-started/5_function_factory/nan/binding.gyp b/src/1-getting-started/5_function_factory/nan/binding.gyp new file mode 100644 index 00000000..170c85fd --- /dev/null +++ b/src/1-getting-started/5_function_factory/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc" ], + "include_dirs": [ + " +#include + +static napi_value MyFunction(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value str; + status = napi_create_string_utf8(env, "hello world", NAPI_AUTO_LENGTH, &str); + assert(status == napi_ok); + + return str; +} + +static napi_value CreateFunction(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value fn; + status = napi_create_function( + env, "theFunction", NAPI_AUTO_LENGTH, MyFunction, NULL, &fn); + assert(status == napi_ok); + + return fn; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_value new_exports; + napi_status status = napi_create_function( + env, "", NAPI_AUTO_LENGTH, CreateFunction, NULL, &new_exports); + assert(status == napi_ok); + return new_exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/5_function_factory/napi/addon.js b/src/1-getting-started/5_function_factory/napi/addon.js new file mode 100644 index 00000000..4c820f1c --- /dev/null +++ b/src/1-getting-started/5_function_factory/napi/addon.js @@ -0,0 +1,4 @@ +const addon = require('bindings')('addon'); + +const fn = addon(); +console.log(fn()); // 'hello world' diff --git a/3_callbacks/binding.gyp b/src/1-getting-started/5_function_factory/napi/binding.gyp similarity index 67% rename from 3_callbacks/binding.gyp rename to src/1-getting-started/5_function_factory/napi/binding.gyp index 3c79ca84..80f9fa87 100644 --- a/3_callbacks/binding.gyp +++ b/src/1-getting-started/5_function_factory/napi/binding.gyp @@ -2,7 +2,7 @@ "targets": [ { "target_name": "addon", - "sources": [ "addon.cc" ] + "sources": [ "addon.c" ] } ] } diff --git a/5_function_factory/package.json b/src/1-getting-started/5_function_factory/napi/package.json similarity index 51% rename from 5_function_factory/package.json rename to src/1-getting-started/5_function_factory/napi/package.json index ca15de4a..59dbd9fc 100644 --- a/5_function_factory/package.json +++ b/src/1-getting-started/5_function_factory/napi/package.json @@ -1,8 +1,11 @@ { - "name": "function_factory", + "name": "function_factory_napi", "version": "0.0.0", "description": "Node.js Addons Example #5", "main": "addon.js", "private": true, - "gypfile": true -} \ No newline at end of file + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/1-getting-started/5_function_factory/node-addon-api/addon.cc b/src/1-getting-started/5_function_factory/node-addon-api/addon.cc new file mode 100644 index 00000000..247d3d10 --- /dev/null +++ b/src/1-getting-started/5_function_factory/node-addon-api/addon.cc @@ -0,0 +1,18 @@ +#include + +Napi::String MyFunction(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return Napi::String::New(env, "hello world"); +} + +Napi::Function CreateFunction(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Function fn = Napi::Function::New(env, MyFunction, "theFunction"); + return fn; +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + return Napi::Function::New(env, CreateFunction, "createObject"); +} + +NODE_API_MODULE(addon, Init) diff --git a/src/1-getting-started/5_function_factory/node-addon-api/addon.js b/src/1-getting-started/5_function_factory/node-addon-api/addon.js new file mode 100644 index 00000000..4c820f1c --- /dev/null +++ b/src/1-getting-started/5_function_factory/node-addon-api/addon.js @@ -0,0 +1,4 @@ +const addon = require('bindings')('addon'); + +const fn = addon(); +console.log(fn()); // 'hello world' diff --git a/src/1-getting-started/5_function_factory/node-addon-api/binding.gyp b/src/1-getting-started/5_function_factory/node-addon-api/binding.gyp new file mode 100644 index 00000000..cb1e3628 --- /dev/null +++ b/src/1-getting-started/5_function_factory/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc" ], + "include_dirs": [ + " +#include "myobject.h" + +void InitAll(v8::Local exports) { + MyObject::Init(exports); +} + +NODE_MODULE(addon, InitAll) diff --git a/src/1-getting-started/6_object_wrap/nan/addon.js b/src/1-getting-started/6_object_wrap/nan/addon.js new file mode 100644 index 00000000..ba70b5a0 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/nan/addon.js @@ -0,0 +1,13 @@ +const addon = require('bindings')('addon'); + +const obj = new addon.MyObject(10); +console.log( obj.plusOne() ); // 11 +console.log( obj.plusOne() ); // 12 +console.log( obj.plusOne() ); // 13 + +console.log( obj.multiply().value() ); // 13 +console.log( obj.multiply(10).value() ); // 130 + +const newobj = obj.multiply(-1); +console.log( newobj.value() ); // -13 +console.log( obj === newobj ); // false diff --git a/src/1-getting-started/6_object_wrap/nan/binding.gyp b/src/1-getting-started/6_object_wrap/nan/binding.gyp new file mode 100644 index 00000000..d92e07a4 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + " MyObject::constructor; + +MyObject::MyObject(double value) : value_(value) {} + +MyObject::~MyObject() {} + +void MyObject::Init(v8::Local exports) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + Nan::HandleScope scope; + + // Prepare constructor template + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New("MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + // Prototype + Nan::SetPrototypeMethod(tpl, "value", GetValue); + Nan::SetPrototypeMethod(tpl, "plusOne", PlusOne); + Nan::SetPrototypeMethod(tpl, "multiply", Multiply); + + constructor.Reset(tpl->GetFunction(context).ToLocalChecked()); + exports->Set(context, + Nan::New("MyObject").ToLocalChecked(), + tpl->GetFunction(context).ToLocalChecked()); +} + +void MyObject::New(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + if (info.IsConstructCall()) { + // Invoked as constructor: `new MyObject(...)` + double value = + info[0]->IsUndefined() ? 0 : info[0]->NumberValue(context).FromJust(); + MyObject* obj = new MyObject(value); + obj->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + const int argc = 1; + v8::Local argv[argc] = {info[0]}; + v8::Local cons = Nan::New(constructor); + info.GetReturnValue().Set( + cons->NewInstance(context, argc, argv).ToLocalChecked()); + } +} + +void MyObject::GetValue(const Nan::FunctionCallbackInfo& info) { + MyObject* obj = ObjectWrap::Unwrap(info.Holder()); + info.GetReturnValue().Set(Nan::New(obj->value_)); +} + +void MyObject::PlusOne(const Nan::FunctionCallbackInfo& info) { + MyObject* obj = ObjectWrap::Unwrap(info.Holder()); + obj->value_ += 1; + info.GetReturnValue().Set(Nan::New(obj->value_)); +} + +void MyObject::Multiply(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + MyObject* obj = ObjectWrap::Unwrap(info.Holder()); + double multiple = + info[0]->IsUndefined() ? 1 : info[0]->NumberValue(context).FromJust(); + + v8::Local cons = Nan::New(constructor); + + const int argc = 1; + v8::Local argv[argc] = {Nan::New(obj->value_ * multiple)}; + + info.GetReturnValue().Set( + cons->NewInstance(context, argc, argv).ToLocalChecked()); +} diff --git a/src/1-getting-started/6_object_wrap/nan/myobject.h b/src/1-getting-started/6_object_wrap/nan/myobject.h new file mode 100644 index 00000000..003c1f95 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/nan/myobject.h @@ -0,0 +1,22 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Nan::ObjectWrap { + public: + static void Init(v8::Local exports); + + private: + explicit MyObject(double value = 0); + ~MyObject(); + + static void New(const Nan::FunctionCallbackInfo& info); + static void GetValue(const Nan::FunctionCallbackInfo& info); + static void PlusOne(const Nan::FunctionCallbackInfo& info); + static void Multiply(const Nan::FunctionCallbackInfo& info); + static Nan::Persistent constructor; + double value_; +}; + +#endif diff --git a/src/1-getting-started/6_object_wrap/nan/package.json b/src/1-getting-started/6_object_wrap/nan/package.json new file mode 100644 index 00000000..9a412cda --- /dev/null +++ b/src/1-getting-started/6_object_wrap/nan/package.json @@ -0,0 +1,12 @@ +{ + "name": "object_wrap_nan", + "version": "0.0.0", + "description": "Node.js Addons Example #6", + "main": "addon.js", + "private": true, + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } +} diff --git a/src/1-getting-started/6_object_wrap/napi/addon.cc b/src/1-getting-started/6_object_wrap/napi/addon.cc new file mode 100644 index 00000000..86b93743 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/napi/addon.cc @@ -0,0 +1,7 @@ +#include "myobject.h" + +napi_value Init(napi_env env, napi_value exports) { + return MyObject::Init(env, exports); +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/6_object_wrap/napi/addon.js b/src/1-getting-started/6_object_wrap/napi/addon.js new file mode 100644 index 00000000..eb8d33aa --- /dev/null +++ b/src/1-getting-started/6_object_wrap/napi/addon.js @@ -0,0 +1,13 @@ +const addon = require('bindings')('addon'); + +const obj = new addon.MyObject(10); +console.log( obj.plusOne() ); // 11 +console.log( obj.plusOne() ); // 12 +console.log( obj.plusOne() ); // 13 + +console.log( obj.multiply().value ); // 13 +console.log( obj.multiply(10).value ); // 130 + +const newobj = obj.multiply(-1); +console.log( newobj.value ); // -13 +console.log( obj === newobj ); // false diff --git a/6_object_wrap/binding.gyp b/src/1-getting-started/6_object_wrap/napi/binding.gyp similarity index 100% rename from 6_object_wrap/binding.gyp rename to src/1-getting-started/6_object_wrap/napi/binding.gyp diff --git a/src/1-getting-started/6_object_wrap/napi/myobject.cc b/src/1-getting-started/6_object_wrap/napi/myobject.cc new file mode 100644 index 00000000..d2cca7d8 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/napi/myobject.cc @@ -0,0 +1,223 @@ +#include "myobject.h" +#include + +MyObject::MyObject(double value) + : value_(value), env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + delete reinterpret_cast(nativeObject); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +napi_value MyObject::Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor properties[] = { + {"value", 0, 0, GetValue, SetValue, 0, napi_default, 0}, + DECLARE_NAPI_METHOD("plusOne", PlusOne), + DECLARE_NAPI_METHOD("multiply", Multiply), + }; + + napi_value cons; + status = napi_define_class( + env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr, 3, properties, &cons); + assert(status == napi_ok); + + // We will need the constructor `cons` later during the life cycle of the + // addon, so we store a persistent reference to it as the instance data for + // our addon. This will enable us to use `napi_get_instance_data` at any + // point during the life cycle of our addon to retrieve it. We cannot simply + // store it as a global static variable, because that will render our addon + // unable to support Node.js worker threads and multiple contexts on a single + // thread. + // + // The finalizer we pass as a lambda will be called when our addon is unloaded + // and is responsible for releasing the persistent reference and freeing the + // heap memory where we stored the persistent reference. + napi_ref* constructor = new napi_ref; + status = napi_create_reference(env, cons, 1, constructor); + assert(status == napi_ok); + status = napi_set_instance_data( + env, + constructor, + [](napi_env env, void* data, void* hint) { + napi_ref* constructor = static_cast(data); + napi_status status = napi_delete_reference(env, *constructor); + assert(status == napi_ok); + delete constructor; + }, + nullptr); + assert(status == napi_ok); + + status = napi_set_named_property(env, exports, "MyObject", cons); + assert(status == napi_ok); + return exports; +} + +napi_value MyObject::Constructor(napi_env env) { + void* instance_data = nullptr; + napi_status status = napi_get_instance_data(env, &instance_data); + assert(status == napi_ok); + napi_ref* constructor = static_cast(instance_data); + + napi_value cons; + status = napi_get_reference_value(env, *constructor, &cons); + assert(status == napi_ok); + return cons; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value target; + status = napi_get_new_target(env, info, &target); + assert(status == napi_ok); + bool is_constructor = target != nullptr; + + if (is_constructor) { + // Invoked as constructor: `new MyObject(...)` + size_t argc = 1; + napi_value args[1]; + napi_value jsthis; + status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr); + assert(status == napi_ok); + + double value = 0; + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + assert(status == napi_ok); + + if (valuetype != napi_undefined) { + status = napi_get_value_double(env, args[0], &value); + assert(status == napi_ok); + } + + MyObject* obj = new MyObject(value); + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_); + assert(status == napi_ok); + + return jsthis; + } else { + // Invoked as plain function `MyObject(...)`, turn into construct call. + size_t argc_ = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc_, args, nullptr, nullptr); + assert(status == napi_ok); + + const size_t argc = 1; + napi_value argv[argc] = {args[0]}; + + napi_value instance; + status = napi_new_instance(env, Constructor(env), argc, argv, &instance); + assert(status == napi_ok); + + return instance; + } +} + +napi_value MyObject::GetValue(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr); + assert(status == napi_ok); + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + assert(status == napi_ok); + + napi_value num; + status = napi_create_double(env, obj->value_, &num); + assert(status == napi_ok); + + return num; +} + +napi_value MyObject::SetValue(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value value; + napi_value jsthis; + status = napi_get_cb_info(env, info, &argc, &value, &jsthis, nullptr); + assert(status == napi_ok); + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + assert(status == napi_ok); + + status = napi_get_value_double(env, value, &obj->value_); + assert(status == napi_ok); + + return nullptr; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr); + assert(status == napi_ok); + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + assert(status == napi_ok); + + obj->value_ += 1; + + napi_value num; + status = napi_create_double(env, obj->value_, &num); + assert(status == napi_ok); + + return num; +} + +napi_value MyObject::Multiply(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + napi_value jsthis; + status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr); + assert(status == napi_ok); + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + assert(status == napi_ok); + + double multiple = 1; + if (valuetype != napi_undefined) { + status = napi_get_value_double(env, args[0], &multiple); + assert(status == napi_ok); + } + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + assert(status == napi_ok); + + const int kArgCount = 1; + napi_value argv[kArgCount]; + status = napi_create_double(env, obj->value_ * multiple, argv); + assert(status == napi_ok); + + napi_value instance; + status = napi_new_instance(env, Constructor(env), kArgCount, argv, &instance); + assert(status == napi_ok); + + return instance; +} diff --git a/src/1-getting-started/6_object_wrap/napi/myobject.h b/src/1-getting-started/6_object_wrap/napi/myobject.h new file mode 100644 index 00000000..2e057733 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/napi/myobject.h @@ -0,0 +1,27 @@ +#ifndef TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_value Init(napi_env env, napi_value exports); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + + private: + explicit MyObject(double value_ = 0); + ~MyObject(); + + static napi_value New(napi_env env, napi_callback_info info); + static napi_value GetValue(napi_env env, napi_callback_info info); + static napi_value SetValue(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + static napi_value Multiply(napi_env env, napi_callback_info info); + static inline napi_value Constructor(napi_env env); + + double value_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_6_OBJECT_WRAP_MYOBJECT_H_ diff --git a/6_object_wrap/package.json b/src/1-getting-started/6_object_wrap/napi/package.json similarity index 52% rename from 6_object_wrap/package.json rename to src/1-getting-started/6_object_wrap/napi/package.json index d18fe6c2..f65d2850 100644 --- a/6_object_wrap/package.json +++ b/src/1-getting-started/6_object_wrap/napi/package.json @@ -1,8 +1,11 @@ { - "name": "object_wrap", + "name": "object_wrap_napi", "version": "0.0.0", "description": "Node.js Addons Example #6", "main": "addon.js", "private": true, - "gypfile": true -} \ No newline at end of file + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/1-getting-started/6_object_wrap/node-addon-api/addon.cc b/src/1-getting-started/6_object_wrap/node-addon-api/addon.cc new file mode 100644 index 00000000..d79fbc42 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/node-addon-api/addon.cc @@ -0,0 +1,8 @@ +#include +#include "myobject.h" + +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + return MyObject::Init(env, exports); +} + +NODE_API_MODULE(addon, InitAll) diff --git a/src/1-getting-started/6_object_wrap/node-addon-api/addon.js b/src/1-getting-started/6_object_wrap/node-addon-api/addon.js new file mode 100644 index 00000000..ba70b5a0 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/node-addon-api/addon.js @@ -0,0 +1,13 @@ +const addon = require('bindings')('addon'); + +const obj = new addon.MyObject(10); +console.log( obj.plusOne() ); // 11 +console.log( obj.plusOne() ); // 12 +console.log( obj.plusOne() ); // 13 + +console.log( obj.multiply().value() ); // 13 +console.log( obj.multiply(10).value() ); // 130 + +const newobj = obj.multiply(-1); +console.log( newobj.value() ); // -13 +console.log( obj === newobj ); // false diff --git a/src/1-getting-started/6_object_wrap/node-addon-api/binding.gyp b/src/1-getting-started/6_object_wrap/node-addon-api/binding.gyp new file mode 100644 index 00000000..85a1a2fa --- /dev/null +++ b/src/1-getting-started/6_object_wrap/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + "(info) { + Napi::Env env = info.Env(); + + int length = info.Length(); + + if (length <= 0 || !info[0].IsNumber()) { + Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException(); + return; + } + + Napi::Number value = info[0].As(); + this->value_ = value.DoubleValue(); +} + +Napi::Value MyObject::GetValue(const Napi::CallbackInfo& info) { + double num = this->value_; + + return Napi::Number::New(info.Env(), num); +} + +Napi::Value MyObject::PlusOne(const Napi::CallbackInfo& info) { + this->value_ = this->value_ + 1; + + return MyObject::GetValue(info); +} + +Napi::Value MyObject::Multiply(const Napi::CallbackInfo& info) { + Napi::Number multiple; + if (info.Length() <= 0 || !info[0].IsNumber()) { + multiple = Napi::Number::New(info.Env(), 1); + } else { + multiple = info[0].As(); + } + + Napi::Object obj = info.Env().GetInstanceData()->New( + {Napi::Number::New(info.Env(), this->value_ * multiple.DoubleValue())}); + + return obj; +} diff --git a/src/1-getting-started/6_object_wrap/node-addon-api/myobject.h b/src/1-getting-started/6_object_wrap/node-addon-api/myobject.h new file mode 100644 index 00000000..e8ccad93 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/node-addon-api/myobject.h @@ -0,0 +1,19 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + MyObject(const Napi::CallbackInfo& info); + + private: + Napi::Value GetValue(const Napi::CallbackInfo& info); + Napi::Value PlusOne(const Napi::CallbackInfo& info); + Napi::Value Multiply(const Napi::CallbackInfo& info); + + double value_; +}; + +#endif diff --git a/src/1-getting-started/6_object_wrap/node-addon-api/package.json b/src/1-getting-started/6_object_wrap/node-addon-api/package.json new file mode 100644 index 00000000..b45a44c7 --- /dev/null +++ b/src/1-getting-started/6_object_wrap/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "object_wrap_node_addon_api", + "version": "0.0.0", + "description": "Node.js Addons Example #6", + "main": "addon.js", + "private": true, + "gypfile": true, + "engines": { + "node": "~10 >=10.20 || >=12.17" + }, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } +} diff --git a/src/1-getting-started/7_factory_wrap/nan/addon.cc b/src/1-getting-started/7_factory_wrap/nan/addon.cc new file mode 100644 index 00000000..64ee79ec --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/nan/addon.cc @@ -0,0 +1,23 @@ +#include +#include "myobject.h" + +void CreateObject(const Nan::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(MyObject::NewInstance(info[0])); +} + +void InitAll(v8::Local exports, v8::Local module) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + + Nan::HandleScope scope; + + MyObject::Init(); + + module->Set(context, + Nan::New("exports").ToLocalChecked(), + Nan::New(CreateObject) + ->GetFunction(context) + .ToLocalChecked()); +} + +NODE_MODULE(addon, InitAll) diff --git a/7_factory_wrap/addon.js b/src/1-getting-started/7_factory_wrap/nan/addon.js similarity index 66% rename from 7_factory_wrap/addon.js rename to src/1-getting-started/7_factory_wrap/nan/addon.js index e15e3c8d..0547abe0 100644 --- a/7_factory_wrap/addon.js +++ b/src/1-getting-started/7_factory_wrap/nan/addon.js @@ -1,11 +1,11 @@ -var createObject = require('./build/Release/addon'); +const createObject = require('bindings')('addon'); -var obj = createObject(10); +const obj = createObject(10); console.log( obj.plusOne() ); // 11 console.log( obj.plusOne() ); // 12 console.log( obj.plusOne() ); // 13 -var obj2 = createObject(20); +const obj2 = createObject(20); console.log( obj2.plusOne() ); // 21 console.log( obj2.plusOne() ); // 22 console.log( obj2.plusOne() ); // 23 diff --git a/src/1-getting-started/7_factory_wrap/nan/binding.gyp b/src/1-getting-started/7_factory_wrap/nan/binding.gyp new file mode 100644 index 00000000..d92e07a4 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + " + +using namespace v8; + +MyObject::MyObject(){}; +MyObject::~MyObject(){}; + +Nan::Persistent MyObject::constructor; + +void MyObject::Init() { + Nan::HandleScope scope; + + // Prepare constructor template + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New("MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + // Prototype + tpl->PrototypeTemplate()->Set(Nan::New("plusOne").ToLocalChecked(), + Nan::New(PlusOne)); + + constructor.Reset( + tpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); +} + +void MyObject::New(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + + MyObject* obj = new MyObject(); + obj->counter_ = + info[0]->IsUndefined() ? 0 : info[0]->NumberValue(context).FromJust(); + obj->Wrap(info.This()); + + info.GetReturnValue().Set(info.This()); +} + +v8::Local MyObject::NewInstance(v8::Local arg) { + Nan::EscapableHandleScope scope; + + const unsigned argc = 1; + v8::Local argv[argc] = {arg}; + v8::Local cons = Nan::New(constructor); + v8::Local context = + v8::Isolate::GetCurrent()->GetCurrentContext(); + v8::Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + + return scope.Escape(instance); +} + +void MyObject::PlusOne(const Nan::FunctionCallbackInfo& info) { + MyObject* obj = ObjectWrap::Unwrap(info.This()); + obj->counter_ += 1; + + info.GetReturnValue().Set(Nan::New(obj->counter_)); +} diff --git a/src/1-getting-started/7_factory_wrap/nan/myobject.h b/src/1-getting-started/7_factory_wrap/nan/myobject.h new file mode 100644 index 00000000..f00c4bd6 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/nan/myobject.h @@ -0,0 +1,21 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Nan::ObjectWrap { + public: + static void Init(); + static v8::Local NewInstance(v8::Local arg); + + private: + MyObject(); + ~MyObject(); + + static Nan::Persistent constructor; + static void New(const Nan::FunctionCallbackInfo& info); + static void PlusOne(const Nan::FunctionCallbackInfo& info); + double counter_; +}; + +#endif diff --git a/src/1-getting-started/7_factory_wrap/nan/package.json b/src/1-getting-started/7_factory_wrap/nan/package.json new file mode 100644 index 00000000..e7378e34 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/nan/package.json @@ -0,0 +1,12 @@ +{ + "name": "factory_wrap_nan", + "version": "0.0.0", + "description": "Node.js Addons Example #7", + "main": "addon.js", + "private": true, + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } +} diff --git a/src/1-getting-started/7_factory_wrap/napi/addon.cc b/src/1-getting-started/7_factory_wrap/napi/addon.cc new file mode 100644 index 00000000..80a78c7a --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/napi/addon.cc @@ -0,0 +1,30 @@ +#include +#include "myobject.h" + +napi_value CreateObject(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + assert(status == napi_ok); + + napi_value instance; + status = MyObject::NewInstance(env, args[0], &instance); + assert(status == napi_ok); + + return instance; +} + +napi_value Init(napi_env env, napi_value exports) { + napi_status status = MyObject::Init(env); + assert(status == napi_ok); + + napi_value new_exports; + status = napi_create_function( + env, "", NAPI_AUTO_LENGTH, CreateObject, nullptr, &new_exports); + assert(status == napi_ok); + return new_exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/1-getting-started/7_factory_wrap/napi/addon.js b/src/1-getting-started/7_factory_wrap/napi/addon.js new file mode 100644 index 00000000..0547abe0 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/napi/addon.js @@ -0,0 +1,11 @@ +const createObject = require('bindings')('addon'); + +const obj = createObject(10); +console.log( obj.plusOne() ); // 11 +console.log( obj.plusOne() ); // 12 +console.log( obj.plusOne() ); // 13 + +const obj2 = createObject(20); +console.log( obj2.plusOne() ); // 21 +console.log( obj2.plusOne() ); // 22 +console.log( obj2.plusOne() ); // 23 diff --git a/7_factory_wrap/binding.gyp b/src/1-getting-started/7_factory_wrap/napi/binding.gyp similarity index 100% rename from 7_factory_wrap/binding.gyp rename to src/1-getting-started/7_factory_wrap/napi/binding.gyp diff --git a/src/1-getting-started/7_factory_wrap/napi/myobject.cc b/src/1-getting-started/7_factory_wrap/napi/myobject.cc new file mode 100644 index 00000000..f3161da8 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/napi/myobject.cc @@ -0,0 +1,137 @@ +#include "myobject.h" +#include + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + delete reinterpret_cast(nativeObject); +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +napi_status MyObject::Init(napi_env env) { + napi_status status; + napi_property_descriptor properties[] = { + DECLARE_NAPI_METHOD("plusOne", PlusOne), + }; + + napi_value cons; + status = napi_define_class( + env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr, 1, properties, &cons); + if (status != napi_ok) return status; + + // We will need the constructor `cons` later during the life cycle of the + // addon, so we store a persistent reference to it as the instance data for + // our addon. This will enable us to use `napi_get_instance_data` at any + // point during the life cycle of our addon to retrieve it. We cannot simply + // store it as a global static variable, because that will render our addon + // unable to support Node.js worker threads and multiple contexts on a single + // thread. + // + // The finalizer we pass as a lambda will be called when our addon is unloaded + // and is responsible for releasing the persistent reference and freeing the + // heap memory where we stored the persistent reference. + napi_ref* constructor = new napi_ref; + status = napi_create_reference(env, cons, 1, constructor); + assert(status == napi_ok); + status = napi_set_instance_data( + env, + constructor, + [](napi_env env, void* data, void* hint) { + napi_ref* constructor = static_cast(data); + napi_status status = napi_delete_reference(env, *constructor); + assert(status == napi_ok); + delete constructor; + }, + nullptr); + assert(status == napi_ok); + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + napi_value jsthis; + status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr); + assert(status == napi_ok); + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + assert(status == napi_ok); + + MyObject* obj = new MyObject(); + + if (valuetype == napi_undefined) { + obj->counter_ = 0; + } else { + status = napi_get_value_double(env, args[0], &obj->counter_); + assert(status == napi_ok); + } + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, /* finalize_hint */ + &obj->wrapper_); + assert(status == napi_ok); + + return jsthis; +} + +napi_value MyObject::Constructor(napi_env env) { + void* instance_data = nullptr; + napi_status status = napi_get_instance_data(env, &instance_data); + assert(status == napi_ok); + napi_ref* constructor = static_cast(instance_data); + + napi_value cons; + status = napi_get_reference_value(env, *constructor, &cons); + assert(status == napi_ok); + return cons; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + status = napi_new_instance(env, Constructor(env), argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} + +napi_value MyObject::PlusOne(napi_env env, napi_callback_info info) { + napi_status status; + + napi_value jsthis; + status = napi_get_cb_info(env, info, nullptr, nullptr, &jsthis, nullptr); + assert(status == napi_ok); + + MyObject* obj; + status = napi_unwrap(env, jsthis, reinterpret_cast(&obj)); + assert(status == napi_ok); + + obj->counter_ += 1; + + napi_value num; + status = napi_create_double(env, obj->counter_, &num); + assert(status == napi_ok); + + return num; +} diff --git a/src/1-getting-started/7_factory_wrap/napi/myobject.h b/src/1-getting-started/7_factory_wrap/napi/myobject.h new file mode 100644 index 00000000..b14e3ad4 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/napi/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + + private: + MyObject(); + ~MyObject(); + + static inline napi_value Constructor(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + static napi_value PlusOne(napi_env env, napi_callback_info info); + double counter_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_7_FACTORY_WRAP_MYOBJECT_H_ diff --git a/7_factory_wrap/package.json b/src/1-getting-started/7_factory_wrap/napi/package.json similarity index 52% rename from 7_factory_wrap/package.json rename to src/1-getting-started/7_factory_wrap/napi/package.json index 3254a77a..d794db6f 100644 --- a/7_factory_wrap/package.json +++ b/src/1-getting-started/7_factory_wrap/napi/package.json @@ -1,8 +1,11 @@ { - "name": "factory_wrap", + "name": "factory_wrap_napi", "version": "0.0.0", "description": "Node.js Addons Example #7", "main": "addon.js", "private": true, - "gypfile": true -} \ No newline at end of file + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/1-getting-started/7_factory_wrap/node-addon-api/addon.cc b/src/1-getting-started/7_factory_wrap/node-addon-api/addon.cc new file mode 100644 index 00000000..9f90488e --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/node-addon-api/addon.cc @@ -0,0 +1,14 @@ +#include +#include "myobject.h" + +Napi::Object CreateObject(const Napi::CallbackInfo& info) { + return MyObject::NewInstance(info.Env(), info[0]); +} + +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + Napi::Object new_exports = + Napi::Function::New(env, CreateObject, "CreateObject"); + return MyObject::Init(env, new_exports); +} + +NODE_API_MODULE(addon, InitAll) diff --git a/src/1-getting-started/7_factory_wrap/node-addon-api/addon.js b/src/1-getting-started/7_factory_wrap/node-addon-api/addon.js new file mode 100644 index 00000000..0547abe0 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/node-addon-api/addon.js @@ -0,0 +1,11 @@ +const createObject = require('bindings')('addon'); + +const obj = createObject(10); +console.log( obj.plusOne() ); // 11 +console.log( obj.plusOne() ); // 12 +console.log( obj.plusOne() ); // 13 + +const obj2 = createObject(20); +console.log( obj2.plusOne() ); // 21 +console.log( obj2.plusOne() ); // 22 +console.log( obj2.plusOne() ); // 23 diff --git a/src/1-getting-started/7_factory_wrap/node-addon-api/binding.gyp b/src/1-getting-started/7_factory_wrap/node-addon-api/binding.gyp new file mode 100644 index 00000000..4d213278 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + " +#include + +using namespace Napi; + +Napi::Object MyObject::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = DefineClass( + env, "MyObject", {InstanceMethod("plusOne", &MyObject::PlusOne)}); + + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); + + exports.Set("MyObject", func); + return exports; +} + +MyObject::MyObject(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + this->counter_ = info[0].As().DoubleValue(); +}; + +Napi::Object MyObject::NewInstance(Napi::Env env, Napi::Value arg) { + Napi::EscapableHandleScope scope(env); + Napi::Object obj = env.GetInstanceData()->New({arg}); + return scope.Escape(napi_value(obj)).ToObject(); +} + +Napi::Value MyObject::PlusOne(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + this->counter_ = this->counter_ + 1; + + return Napi::Number::New(env, this->counter_); +} diff --git a/src/1-getting-started/7_factory_wrap/node-addon-api/myobject.h b/src/1-getting-started/7_factory_wrap/node-addon-api/myobject.h new file mode 100644 index 00000000..cfc97a2d --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/node-addon-api/myobject.h @@ -0,0 +1,17 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + static Napi::Object NewInstance(Napi::Env env, Napi::Value arg); + MyObject(const Napi::CallbackInfo& info); + + private: + Napi::Value PlusOne(const Napi::CallbackInfo& info); + double counter_; +}; + +#endif diff --git a/src/1-getting-started/7_factory_wrap/node-addon-api/package.json b/src/1-getting-started/7_factory_wrap/node-addon-api/package.json new file mode 100644 index 00000000..353a28a7 --- /dev/null +++ b/src/1-getting-started/7_factory_wrap/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "factory_wrap_node_addon_api", + "version": "0.0.0", + "description": "Node.js Addons Example #7", + "main": "addon.js", + "private": true, + "gypfile": true, + "engines": { + "node": "~10 >=10.20 || >=12.17" + }, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } +} diff --git a/src/1-getting-started/a-first-project/node-addon-api/README.md b/src/1-getting-started/a-first-project/node-addon-api/README.md new file mode 100644 index 00000000..fb54a3f0 --- /dev/null +++ b/src/1-getting-started/a-first-project/node-addon-api/README.md @@ -0,0 +1,14 @@ +# Node-API A First Project + +This is an example project that accompanies the Node-API workshop tutorials + +A tutorial describing this project can be found at the [Node-API Resource](https://napi.inspiredware.com/getting-started/first.html). + +To build and run this program on your system, clone it to your computer and run these two commands inside your clone: + +``` +npm install +npm test +``` + +> You need to have Node 10.5.0 or later installed. \ No newline at end of file diff --git a/src/1-getting-started/a-first-project/node-addon-api/binding.gyp b/src/1-getting-started/a-first-project/node-addon-api/binding.gyp new file mode 100644 index 00000000..7150ad93 --- /dev/null +++ b/src/1-getting-started/a-first-project/node-addon-api/binding.gyp @@ -0,0 +1,20 @@ +{ + 'targets': [ + { + 'target_name': 'hello-world-native', + 'sources': [ 'src/hello_world.cc' ], + 'include_dirs': [" + +using namespace Napi; + +Napi::String Method(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + return Napi::String::New(env, "world"); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "HelloWorld"), + Napi::Function::New(env, Method)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/src/1-getting-started/a-first-project/node-addon-api/test/test_binding.js b/src/1-getting-started/a-first-project/node-addon-api/test/test_binding.js new file mode 100644 index 00000000..a59e7cb7 --- /dev/null +++ b/src/1-getting-started/a-first-project/node-addon-api/test/test_binding.js @@ -0,0 +1,14 @@ +const HelloWorld = require("../lib/binding.js"); +const assert = require("assert"); + +assert(HelloWorld, "The expected function is undefined"); + +function testBasic() +{ + const result = HelloWorld("hello"); + assert.strictEqual(result, "world", "Unexpected value returned"); +} + +assert.doesNotThrow(testBasic, undefined, "testBasic threw an expection"); + +console.log("Tests passed- everything looks OK!"); \ No newline at end of file diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.cc b/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.cc new file mode 100644 index 00000000..f1c0e98c --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.cc @@ -0,0 +1,40 @@ +#include +#include "myobject.h" + +using namespace v8; + +void CreateObject(const Nan::FunctionCallbackInfo& info) { + info.GetReturnValue().Set(MyObject::NewInstance(info[0])); +} + +void Add(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + + MyObject* obj1 = Nan::ObjectWrap::Unwrap( + info[0]->ToObject(context).ToLocalChecked()); + MyObject* obj2 = Nan::ObjectWrap::Unwrap( + info[1]->ToObject(context).ToLocalChecked()); + double sum = obj1->Val() + obj2->Val(); + info.GetReturnValue().Set(Nan::New(sum)); +} + +void InitAll(v8::Local exports) { + v8::Local context = + exports->GetCreationContext().ToLocalChecked(); + + MyObject::Init(); + + exports->Set(context, + Nan::New("createObject").ToLocalChecked(), + Nan::New(CreateObject) + ->GetFunction(context) + .ToLocalChecked()); + + exports->Set(context, + Nan::New("add").ToLocalChecked(), + Nan::New(Add) + ->GetFunction(context) + .ToLocalChecked()); +} + +NODE_MODULE(addon, InitAll) diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.js b/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.js new file mode 100644 index 00000000..42f65e67 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/nan/addon.js @@ -0,0 +1,7 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); + +console.log(result); // 30 diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/nan/binding.gyp b/src/2-js-to-native-conversion/8_passing_wrapped/nan/binding.gyp new file mode 100644 index 00000000..d92e07a4 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/nan/binding.gyp @@ -0,0 +1,11 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + " + +MyObject::MyObject(){}; +MyObject::~MyObject(){}; + +Nan::Persistent MyObject::constructor; + +void MyObject::Init() { + Nan::HandleScope scope; + + // Prepare constructor template + v8::Local tpl = Nan::New(New); + tpl->SetClassName(Nan::New("MyObject").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + + constructor.Reset( + tpl->GetFunction(Nan::GetCurrentContext()).ToLocalChecked()); +} + +void MyObject::New(const Nan::FunctionCallbackInfo& info) { + v8::Local context = info.GetIsolate()->GetCurrentContext(); + MyObject* obj = new MyObject(); + obj->val_ = + info[0]->IsUndefined() ? 0 : info[0]->NumberValue(context).FromJust(); + obj->Wrap(info.This()); + + info.GetReturnValue().Set(info.This()); +} + +v8::Local MyObject::NewInstance(v8::Local arg) { + Nan::EscapableHandleScope scope; + + const unsigned argc = 1; + v8::Local argv[argc] = {arg}; + v8::Local cons = Nan::New(constructor); + v8::Local context = + v8::Isolate::GetCurrent()->GetCurrentContext(); + v8::Local instance = + cons->NewInstance(context, argc, argv).ToLocalChecked(); + + return scope.Escape(instance); +} diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/nan/myobject.h b/src/2-js-to-native-conversion/8_passing_wrapped/nan/myobject.h new file mode 100644 index 00000000..4e5c8115 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/nan/myobject.h @@ -0,0 +1,21 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Nan::ObjectWrap { + public: + static void Init(); + static v8::Local NewInstance(v8::Local arg); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static Nan::Persistent constructor; + static void New(const Nan::FunctionCallbackInfo& info); + double val_; +}; + +#endif diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/nan/package.json b/src/2-js-to-native-conversion/8_passing_wrapped/nan/package.json new file mode 100644 index 00000000..3de4b71f --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/nan/package.json @@ -0,0 +1,12 @@ +{ + "name": "passing_wrapped", + "version": "0.0.0", + "description": "Node.js Addons Example #8", + "main": "addon.js", + "private": true, + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.0.0" + } +} diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.cc b/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.cc new file mode 100644 index 00000000..a49f5213 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.cc @@ -0,0 +1,59 @@ +#include +#include "myobject.h" + +napi_value CreateObject(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + assert(status == napi_ok); + + napi_value instance; + status = MyObject::NewInstance(env, args[0], &instance); + + return instance; +} + +napi_value Add(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 2; + napi_value args[2]; + status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + assert(status == napi_ok); + + MyObject* obj1; + status = napi_unwrap(env, args[0], reinterpret_cast(&obj1)); + assert(status == napi_ok); + + MyObject* obj2; + status = napi_unwrap(env, args[1], reinterpret_cast(&obj2)); + assert(status == napi_ok); + + napi_value sum; + status = napi_create_double(env, obj1->Val() + obj2->Val(), &sum); + assert(status == napi_ok); + + return sum; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +napi_value Init(napi_env env, napi_value exports) { + napi_status status; + + MyObject::Init(env); + + napi_property_descriptor desc[] = { + DECLARE_NAPI_METHOD("createObject", CreateObject), + DECLARE_NAPI_METHOD("add", Add), + }; + status = + napi_define_properties(env, exports, sizeof(desc) / sizeof(*desc), desc); + assert(status == napi_ok); + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.js b/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.js new file mode 100644 index 00000000..42f65e67 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/napi/addon.js @@ -0,0 +1,7 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); + +console.log(result); // 30 diff --git a/8_passing_wrapped/binding.gyp b/src/2-js-to-native-conversion/8_passing_wrapped/napi/binding.gyp similarity index 100% rename from 8_passing_wrapped/binding.gyp rename to src/2-js-to-native-conversion/8_passing_wrapped/napi/binding.gyp diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.cc b/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.cc new file mode 100644 index 00000000..17e714a2 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.cc @@ -0,0 +1,111 @@ +#include "myobject.h" +#include + +MyObject::MyObject() : env_(nullptr), wrapper_(nullptr) {} + +MyObject::~MyObject() { + napi_delete_reference(env_, wrapper_); +} + +void MyObject::Destructor(napi_env env, + void* nativeObject, + void* /*finalize_hint*/) { + delete reinterpret_cast(nativeObject); +} + +napi_status MyObject::Init(napi_env env) { + napi_status status; + + napi_value cons; + status = napi_define_class( + env, "MyObject", NAPI_AUTO_LENGTH, New, nullptr, 0, nullptr, &cons); + if (status != napi_ok) return status; + + // We will need the constructor `cons` later during the life cycle of the + // application, so we store a persistent reference to it as the instance data + // for our addon. This will enable us to use `napi_get_instance_data` at any + // point during the life cycle of our addon to retrieve it. We cannot simply + // store it as a global static variable, because that will render our addon + // unable to support Node.js worker threads and multiple contexts on a single + // thread. + // + // The finalizer we pass as a lambda will be called when our addon is unloaded + // and is responsible for releasing the persistent reference and freeing the + // heap memory where we stored the persistent reference. + napi_ref* constructor = new napi_ref; + status = napi_create_reference(env, cons, 1, constructor); + assert(status == napi_ok); + status = napi_set_instance_data( + env, + constructor, + [](napi_env env, void* data, void* hint) { + napi_ref* constructor = static_cast(data); + napi_status status = napi_delete_reference(env, *constructor); + assert(status == napi_ok); + delete constructor; + }, + nullptr); + assert(status == napi_ok); + + return napi_ok; +} + +napi_value MyObject::New(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + napi_value jsthis; + status = napi_get_cb_info(env, info, &argc, args, &jsthis, nullptr); + assert(status == napi_ok); + + MyObject* obj = new MyObject(); + + napi_valuetype valuetype; + status = napi_typeof(env, args[0], &valuetype); + assert(status == napi_ok); + + if (valuetype == napi_undefined) { + obj->val_ = 0; + } else { + status = napi_get_value_double(env, args[0], &obj->val_); + assert(status == napi_ok); + } + + obj->env_ = env; + status = napi_wrap(env, + jsthis, + reinterpret_cast(obj), + MyObject::Destructor, + nullptr, // finalize_hint + &obj->wrapper_); + assert(status == napi_ok); + + return jsthis; +} + +napi_value MyObject::Constructor(napi_env env) { + void* instance_data = nullptr; + napi_status status = napi_get_instance_data(env, &instance_data); + assert(status == napi_ok); + napi_ref* constructor = static_cast(instance_data); + + napi_value cons; + status = napi_get_reference_value(env, *constructor, &cons); + assert(status == napi_ok); + return cons; +} + +napi_status MyObject::NewInstance(napi_env env, + napi_value arg, + napi_value* instance) { + napi_status status; + + const int argc = 1; + napi_value argv[argc] = {arg}; + + status = napi_new_instance(env, Constructor(env), argc, argv, instance); + if (status != napi_ok) return status; + + return napi_ok; +} diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.h b/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.h new file mode 100644 index 00000000..1437a3cf --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/napi/myobject.h @@ -0,0 +1,26 @@ +#ifndef TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ +#define TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ + +#include + +class MyObject { + public: + static napi_status Init(napi_env env); + static void Destructor(napi_env env, void* nativeObject, void* finalize_hint); + static napi_status NewInstance(napi_env env, + napi_value arg, + napi_value* instance); + double Val() const { return val_; } + + private: + MyObject(); + ~MyObject(); + + static inline napi_value Constructor(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + double val_; + napi_env env_; + napi_ref wrapper_; +}; + +#endif // TEST_ADDONS_NAPI_8_PASSING_WRAPPED_MYOBJECT_H_ diff --git a/8_passing_wrapped/package.json b/src/2-js-to-native-conversion/8_passing_wrapped/napi/package.json similarity index 66% rename from 8_passing_wrapped/package.json rename to src/2-js-to-native-conversion/8_passing_wrapped/napi/package.json index d3d5c22d..cfec8970 100644 --- a/8_passing_wrapped/package.json +++ b/src/2-js-to-native-conversion/8_passing_wrapped/napi/package.json @@ -4,5 +4,8 @@ "description": "Node.js Addons Example #8", "main": "addon.js", "private": true, - "gypfile": true -} \ No newline at end of file + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.cc b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.cc new file mode 100644 index 00000000..8b48589c --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.cc @@ -0,0 +1,31 @@ +#include +#include "myobject.h" + +using namespace Napi; + +Napi::Object CreateObject(const Napi::CallbackInfo& info) { + return MyObject::NewInstance(info.Env(), info[0]); +} + +Napi::Number Add(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + MyObject* obj1 = + Napi::ObjectWrap::Unwrap(info[0].As()); + MyObject* obj2 = + Napi::ObjectWrap::Unwrap(info[1].As()); + double sum = obj1->Val() + obj2->Val(); + return Napi::Number::New(env, sum); +} + +Napi::Object InitAll(Napi::Env env, Napi::Object exports) { + MyObject::Init(env, exports); + + exports.Set(Napi::String::New(env, "createObject"), + Napi::Function::New(env, CreateObject)); + + exports.Set(Napi::String::New(env, "add"), Napi::Function::New(env, Add)); + + return exports; +} + +NODE_API_MODULE(addon, InitAll) diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.js b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.js new file mode 100644 index 00000000..42f65e67 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/addon.js @@ -0,0 +1,7 @@ +const addon = require('bindings')('addon'); + +const obj1 = addon.createObject(10); +const obj2 = addon.createObject(20); +const result = addon.add(obj1, obj2); + +console.log(result); // 30 diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/binding.gyp b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/binding.gyp new file mode 100644 index 00000000..61ea97bb --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/binding.gyp @@ -0,0 +1,16 @@ +{ + "targets": [ + { + "target_name": "addon", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "addon.cc", "myobject.cc" ], + "include_dirs": [ + " +#include + +MyObject::MyObject(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + this->val_ = info[0].As().DoubleValue(); +}; + +void MyObject::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = DefineClass(env, "MyObject", {}); + + Napi::FunctionReference* constructor = new Napi::FunctionReference(); + *constructor = Napi::Persistent(func); + env.SetInstanceData(constructor); // NOTE: this assumes only 1 class is + // exported for multiple exported classes, + // need a struct or other mechanism + + exports.Set("MyObject", func); +} + +Napi::Object MyObject::NewInstance(Napi::Env env, Napi::Value arg) { + Napi::Object obj = env.GetInstanceData()->New({arg}); + return obj; +} diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/myobject.h b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/myobject.h new file mode 100644 index 00000000..b8752b20 --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/myobject.h @@ -0,0 +1,17 @@ +#ifndef MYOBJECT_H +#define MYOBJECT_H + +#include + +class MyObject : public Napi::ObjectWrap { + public: + static void Init(Napi::Env env, Napi::Object exports); + static Napi::Object NewInstance(Napi::Env env, Napi::Value arg); + double Val() const { return val_; } + MyObject(const Napi::CallbackInfo& info); + + private: + double val_; +}; + +#endif diff --git a/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/package.json b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/package.json new file mode 100644 index 00000000..133916cb --- /dev/null +++ b/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "passing_wrapped", + "version": "0.0.0", + "description": "Node.js Addons Example #8", + "main": "addon.js", + "private": true, + "gypfile": true, + "engines": { + "node": "~10 >=10.20 || >=12.17" + }, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + } +} diff --git a/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/array_buffer_to_native.cc b/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/array_buffer_to_native.cc new file mode 100644 index 00000000..30e7e996 --- /dev/null +++ b/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/array_buffer_to_native.cc @@ -0,0 +1,35 @@ +#include +#include + +static void ArrayConsumer(const int32_t* array, size_t length) { + for (size_t index = 0; index < length; index++) { + fprintf(stderr, "array[%lu] = %d\n", index, array[index]); + } +} + +static Napi::Value AcceptArrayBuffer(const Napi::CallbackInfo& info) { + if (info.Length() != 1) { + Napi::Error::New(info.Env(), "Expected exactly one argument") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + if (!info[0].IsArrayBuffer()) { + Napi::Error::New(info.Env(), "Expected an ArrayBuffer") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + Napi::ArrayBuffer buf = info[0].As(); + + ArrayConsumer(reinterpret_cast(buf.Data()), + buf.ByteLength() / sizeof(int32_t)); + + return info.Env().Undefined(); +} + +static Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports["AcceptArrayBuffer"] = Napi::Function::New(env, AcceptArrayBuffer); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/binding.gyp b/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/binding.gyp new file mode 100644 index 00000000..b35ba0ed --- /dev/null +++ b/src/2-js-to-native-conversion/array_buffer_to_native/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "array_buffer_to_native", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "array_buffer_to_native.cc" ], + "include_dirs": [ + " + ********************************************************************/ + +#include +#include + +using namespace Nan; // NOLINT(build/namespaces) + +class NamedInterceptor : public ObjectWrap { + char buf[256]; + + public: + NamedInterceptor() { std::strncpy(this->buf, "foo", sizeof(this->buf)); } + static NAN_MODULE_INIT(Init); + static v8::Local NewInstance(); + static NAN_METHOD(New); + + static NAN_PROPERTY_GETTER(PropertyGetter); + static NAN_PROPERTY_SETTER(PropertySetter); + static NAN_PROPERTY_ENUMERATOR(PropertyEnumerator); + static NAN_PROPERTY_DELETER(PropertyDeleter); + static NAN_PROPERTY_QUERY(PropertyQuery); +}; + +static Persistent namedinterceptors_constructor; + +NAN_METHOD(CreateNew) { + info.GetReturnValue().Set(NamedInterceptor::NewInstance()); +} + +NAN_MODULE_INIT(NamedInterceptor::Init) { + v8::Local tpl = + Nan::New(NamedInterceptor::New); + namedinterceptors_constructor.Reset(tpl); + tpl->SetClassName(Nan::New("NamedInterceptor").ToLocalChecked()); + tpl->InstanceTemplate()->SetInternalFieldCount(1); + v8::Local inst = tpl->InstanceTemplate(); + + SetNamedPropertyHandler(inst, + NamedInterceptor::PropertyGetter, + NamedInterceptor::PropertySetter, + NamedInterceptor::PropertyQuery, + NamedInterceptor::PropertyDeleter, + NamedInterceptor::PropertyEnumerator); + + v8::Local createnew = + Nan::GetFunction(Nan::New(CreateNew)) + .ToLocalChecked(); + Set(target, Nan::New("create").ToLocalChecked(), createnew); +} + +v8::Local NamedInterceptor::NewInstance() { + EscapableHandleScope scope; + v8::Local constructorHandle = + Nan::New(namedinterceptors_constructor); + v8::Local instance = + Nan::NewInstance(GetFunction(constructorHandle).ToLocalChecked()) + .ToLocalChecked(); + return scope.Escape(instance); +} + +NAN_METHOD(NamedInterceptor::New) { + NamedInterceptor* interceptor = new NamedInterceptor(); + interceptor->Wrap(info.This()); + info.GetReturnValue().Set(info.This()); +} + +NAN_PROPERTY_GETTER(NamedInterceptor::PropertyGetter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + if (!std::strcmp(*Nan::Utf8String(property), "prop")) { + info.GetReturnValue().Set(Nan::New(interceptor->buf).ToLocalChecked()); + } else { + info.GetReturnValue().Set(Nan::New("bar").ToLocalChecked()); + } +} + +NAN_PROPERTY_SETTER(NamedInterceptor::PropertySetter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + if (!std::strcmp(*Nan::Utf8String(property), "prop")) { + std::strncpy( + interceptor->buf, *Nan::Utf8String(value), sizeof(interceptor->buf)); + info.GetReturnValue().Set(info.This()); + } else { + info.GetReturnValue().Set(info.This()); + } +} + +NAN_PROPERTY_ENUMERATOR(NamedInterceptor::PropertyEnumerator) { + v8::Local arr = Nan::New(); + Set(arr, 0, Nan::New("value").ToLocalChecked()); + info.GetReturnValue().Set(arr); +} + +NAN_PROPERTY_DELETER(NamedInterceptor::PropertyDeleter) { + NamedInterceptor* interceptor = + ObjectWrap::Unwrap(info.Holder()); + std::strncpy(interceptor->buf, "goober", sizeof(interceptor->buf)); + info.GetReturnValue().Set(True()); +} + +NAN_PROPERTY_QUERY(NamedInterceptor::PropertyQuery) { + Nan::Utf8String s(property); + if (!std::strcmp(*s, "thing")) { + info.GetReturnValue().Set(Nan::New(v8::DontEnum)); + } + if (!std::strcmp(*s, "value")) { + info.GetReturnValue().Set(Nan::New(0)); + } +} + +NODE_MODULE(namedinterceptors, NamedInterceptor::Init) diff --git a/src/2-js-to-native-conversion/object-template-demo/nan/package.json b/src/2-js-to-native-conversion/object-template-demo/nan/package.json new file mode 100644 index 00000000..75defd72 --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/nan/package.json @@ -0,0 +1,15 @@ +{ + "name": "object-template-demo", + "version": "0.0.0", + "description": "Intercept named property access using V8 ObjectTemplate", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "test": "node index.js" + }, + "dependencies": { + "bindings": "~1.5.0", + "nan": "^2.14.0" + } +} diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/binding.gyp b/src/2-js-to-native-conversion/object-template-demo/napi/binding.gyp new file mode 100644 index 00000000..c1e8cfca --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "object_template_demo", + "sources": [ "object-template-demo.cc", "proxy-template.cc" ] + } + ] +} diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/index.js b/src/2-js-to-native-conversion/object-template-demo/napi/index.js new file mode 100644 index 00000000..bcf2920e --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/index.js @@ -0,0 +1,10 @@ +const addon = require('bindings')('object_template_demo'); + +const interceptor = addon.create(); +console.log(interceptor.prop); // 'foo' +interceptor.prop = 'setting a value'; +console.log(interceptor.prop); // 'setting a value' +delete interceptor.something; +console.log(interceptor.prop); // 'goober'; +console.log(Object.prototype.hasOwnProperty.call(interceptor, "thing")); // true +console.log(Object.keys(interceptor)[0]); // 'value' diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/node-api-common.h b/src/2-js-to-native-conversion/object-template-demo/napi/node-api-common.h new file mode 100644 index 00000000..cc7bd91e --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/node-api-common.h @@ -0,0 +1,141 @@ +#include +#include +#include + +// Empty value so that macros here are able to return NULL or void +#define NODE_API_RETVAL_NOTHING // Intentionally blank #define + +#define GET_AND_THROW_LAST_ERROR(env) \ + do { \ + const napi_extended_error_info* error_info; \ + napi_get_last_error_info((env), &error_info); \ + bool is_pending; \ + const char* err_message = error_info->error_message; \ + napi_is_exception_pending((env), &is_pending); \ + /* If an exception is already pending, don't rethrow it */ \ + if (!is_pending) { \ + const char* error_message = \ + err_message != NULL ? err_message : "empty error message"; \ + napi_throw_error((env), NULL, error_message); \ + } \ + } while (0) + +#define NODE_API_ASSERT_BASE(env, assertion, message, ret_val) \ + do { \ + if (!(assertion)) { \ + napi_throw_error( \ + (env), NULL, "assertion (" #assertion ") failed: " message); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL on failed assertion. +// This is meant to be used inside napi_callback methods. +#define NODE_API_ASSERT(env, assertion, message) \ + NODE_API_ASSERT_BASE(env, assertion, message, NULL) + +#define NODE_API_CALL_BASE(env, the_call, ret_val) \ + do { \ + if ((the_call) != napi_ok) { \ + GET_AND_THROW_LAST_ERROR((env)); \ + return ret_val; \ + } \ + } while (0) + +// Returns NULL if the_call doesn't return napi_ok. +#define NODE_API_CALL(env, the_call) NODE_API_CALL_BASE(env, the_call, NULL) + +// Returns empty if the_call doesn't return napi_ok. +#define NODE_API_CALL_RETURN_VOID(env, the_call) \ + NODE_API_CALL_BASE(env, the_call, NODE_API_RETVAL_NOTHING) + +#define CHECK_NAPI(...) \ + do { \ + napi_status res__ = (__VA_ARGS__); \ + if (res__ != napi_ok) { \ + return res__; \ + } \ + } while (0) + +#define NAPI_CALL(expr) NODE_API_CALL(env, expr); + +#ifdef __cpp_lib_span +#include +using std::span; +#else +/** + * @brief A span of values that can be used to pass arguments to function. + * + * For C++20 we should consider to replace it with std::span. + */ +template +struct span { + constexpr span(std::initializer_list il) noexcept + : data_{const_cast(il.begin())}, size_{il.size()} {} + constexpr span(T* data, size_t size) noexcept : data_{data}, size_{size} {} + [[nodiscard]] constexpr T* data() const noexcept { return data_; } + [[nodiscard]] constexpr size_t size() const noexcept { return size_; } + [[nodiscard]] constexpr T* begin() const noexcept { return data_; } + [[nodiscard]] constexpr T* end() const noexcept { return data_ + size_; } + const T& operator[](size_t index) const noexcept { return *(data_ + index); } + + private: + T* data_; + size_t size_; +}; +#endif // __cpp_lib_span + +struct RefHolder { + RefHolder(std::nullptr_t = nullptr) noexcept {} + explicit RefHolder(napi_env env, napi_value value) : env_(env) { + // Start with 2 to avoid ever going to 0 that creates a weak ref. + napi_create_reference(env, value, 2, &ref_); + } + + // The class is movable. + RefHolder(RefHolder&& other) noexcept + : env_(std::exchange(other.env_, nullptr)), + ref_(std::exchange(other.ref_, nullptr)) {} + + RefHolder& operator=(RefHolder&& other) noexcept { + if (this != &other) { + swap(*this, other); + RefHolder temp(std::move(other)); + } + return *this; + } + + // The class is not copyable. + RefHolder(const RefHolder& other) = delete; + RefHolder& operator=(const RefHolder& other) = delete; + + ~RefHolder() noexcept { + if (env_ != nullptr && ref_ != nullptr) { + uint32_t refCount{}; + napi_reference_unref(env_, ref_, &refCount); + if (refCount == 1) { + napi_delete_reference(env_, ref_); + } + } + } + + operator napi_value() const { + napi_value result{}; + if (ref_ != nullptr) { + napi_get_reference_value(env_, ref_, &result); + } + return result; + } + + explicit operator bool() const noexcept { return ref_ != nullptr; } + + friend void swap(RefHolder& left, RefHolder& right) noexcept { + using std::swap; + swap(left.env_, right.env_); + swap(left.ref_, right.ref_); + } + + private: + napi_env env_{}; + napi_ref ref_{}; +}; diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/object-template-demo.cc b/src/2-js-to-native-conversion/object-template-demo/napi/object-template-demo.cc new file mode 100644 index 00000000..5b049202 --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/object-template-demo.cc @@ -0,0 +1,306 @@ +#include +#include +#include +#include "proxy-template.h" + +struct InstanceData { + RefHolder constructor_; + std::unique_ptr proxyTemplate_; +}; + +class NamedInterceptor { + char buf[256]; + + public: + NamedInterceptor() { std::strncpy(this->buf, "foo", sizeof(this->buf)); } + static napi_value Init(napi_env env, napi_value exports); + static napi_value Constructor(napi_env env); + static napi_value NewInstance(napi_env env); + static napi_value New(napi_env env, napi_callback_info info); + static napi_value CreateNew(napi_env env, napi_callback_info info); + + static napi_status ToUtf8String(napi_env env, + napi_value value, + std::string* result); + static napi_status PropertyGetter(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result); + static napi_status PropertySetter(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result); + static napi_status PropertyDeleter(napi_env env, + napi_value target, + napi_value key, + bool* result); + static napi_status PropertyQuery(napi_env env, + napi_value target, + napi_value key, + bool* result); + static napi_status GetOwnPropertyDescriptor(napi_env env, + napi_value target, + napi_value key, + napi_value* result); + static napi_status OwnKeys(napi_env env, + napi_value target, + napi_value* result); +}; + +napi_value NamedInterceptor::CreateNew(napi_env env, napi_callback_info info) { + return NamedInterceptor::NewInstance(env); +} + +napi_value NamedInterceptor::Init(napi_env env, napi_value exports) { + napi_value constructor{}; + NAPI_CALL(napi_define_class(env, + "NamedInterceptor", + NAPI_AUTO_LENGTH, + NamedInterceptor::New, + nullptr, + 0, + nullptr, + &constructor)); + + ProxyHandlerBuilder handlerBuilder{}; + NAPI_CALL(handlerBuilder.Get(env, &PropertyGetter)); + NAPI_CALL(handlerBuilder.Set(env, &PropertySetter)); + NAPI_CALL(handlerBuilder.DeleteProperty(env, &PropertyDeleter)); + NAPI_CALL(handlerBuilder.Has(env, &PropertyQuery)); + NAPI_CALL( + handlerBuilder.GetOwnPropertyDescriptor(env, &GetOwnPropertyDescriptor)); + NAPI_CALL(handlerBuilder.OwnKeys(env, &OwnKeys)); + napi_value proxyHandler{}; + NAPI_CALL(handlerBuilder.NewHandler(env, &proxyHandler)); + NAPI_CALL(napi_set_instance_data( + env, + new InstanceData{RefHolder(env, constructor), + std::make_unique(env, proxyHandler)}, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete static_cast(data); + }, + nullptr)); + + napi_value createFunc{}; + NAPI_CALL(napi_create_function( + env, "create", NAPI_AUTO_LENGTH, CreateNew, nullptr, &createFunc)); + + NAPI_CALL(napi_set_named_property(env, exports, "create", createFunc)); + return exports; +} + +napi_value NamedInterceptor::Constructor(napi_env env) { + InstanceData* instanceData{}; + NAPI_CALL( + napi_get_instance_data(env, reinterpret_cast(&instanceData))); + return instanceData->constructor_; +} + +napi_value NamedInterceptor::NewInstance(napi_env env) { + napi_escapable_handle_scope scope{}; + NAPI_CALL(napi_open_escapable_handle_scope(env, &scope)); + + napi_value instance{}; + NAPI_CALL(napi_new_instance(env, Constructor(env), 0, nullptr, &instance)); + + napi_value result{}; + NAPI_CALL(napi_escape_handle(env, scope, instance, &result)); + NAPI_CALL(napi_close_escapable_handle_scope(env, scope)); + return result; +} + +napi_value NamedInterceptor::New(napi_env env, napi_callback_info info) { + napi_value newTarget{}; + NAPI_CALL(napi_get_new_target(env, info, &newTarget)); + NODE_API_ASSERT_BASE( + env, newTarget != nullptr, "Must be invoked as a constructor.", nullptr); + + napi_value proxyTarget{}, result{}; + NAPI_CALL(napi_create_function( + env, + "target", + NAPI_AUTO_LENGTH, + [](napi_env, napi_callback_info) -> napi_value { return nullptr; }, + nullptr, + &proxyTarget)); + + NamedInterceptor* obj = new NamedInterceptor(); + + NAPI_CALL(napi_wrap( + env, + proxyTarget, + obj, + [](napi_env /*env*/, void* data, void* /*hint*/) { + delete reinterpret_cast(data); + }, + nullptr, + nullptr)); + + InstanceData* instanceData{}; + NAPI_CALL( + napi_get_instance_data(env, reinterpret_cast(&instanceData))); + NAPI_CALL( + instanceData->proxyTemplate_->NewInstance(env, proxyTarget, &result)); + return result; +} + +napi_status NamedInterceptor::ToUtf8String(napi_env env, + napi_value value, + std::string* result) { + size_t size{}; + CHECK_NAPI(napi_get_value_string_utf8(env, value, nullptr, 0, &size)); + result->assign(size, ' '); + return napi_get_value_string_utf8( + env, value, &(*result)[0], size + 1, nullptr); +} + +napi_status NamedInterceptor::PropertyGetter(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + if (keyStr == "prop") { + return napi_create_string_utf8( + env, interceptor->buf, NAPI_AUTO_LENGTH, result); + } else { + return napi_create_string_utf8(env, "bar", NAPI_AUTO_LENGTH, result); + } +} + +napi_status NamedInterceptor::PropertySetter(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + if (keyStr == "prop") { + std::string valueStr; + CHECK_NAPI(ToUtf8String(env, value, &valueStr)); + std::strncpy(interceptor->buf, valueStr.data(), sizeof(interceptor->buf)); + *result = true; + } else { + *result = false; + } + return napi_ok; +} + +napi_status NamedInterceptor::PropertyDeleter(napi_env env, + napi_value target, + napi_value key, + bool* result) { + NamedInterceptor* interceptor{}; + CHECK_NAPI(napi_unwrap(env, target, reinterpret_cast(&interceptor))); + std::strncpy(interceptor->buf, "goober", sizeof(interceptor->buf)); + *result = true; + return napi_ok; +} + +napi_status NamedInterceptor::PropertyQuery(napi_env env, + napi_value target, + napi_value key, + bool* result) { + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + + if (keyStr == "thing") { + *result = true; + } + if (keyStr == "value") { + *result = true; + } + return napi_ok; +} + +napi_status NamedInterceptor::GetOwnPropertyDescriptor(napi_env env, + napi_value target, + napi_value key, + napi_value* result) { + std::string keyStr; + CHECK_NAPI(ToUtf8String(env, key, &keyStr)); + + if (keyStr == "thing") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}; + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + } + if (keyStr == "value") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}; + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", trueValue)); + } + if (keyStr == "arguments" || keyStr == "caller") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}, falseValue{}; + CHECK_NAPI(napi_get_boolean(env, false, &falseValue)); + CHECK_NAPI(napi_get_boolean(env, false, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", falseValue)); + napi_value nullValue{}; + CHECK_NAPI(napi_get_null(env, &nullValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "value", nullValue)); + } + if (keyStr == "prototype") { + CHECK_NAPI(napi_create_object(env, result)); + napi_value trueValue{}, falseValue{}; + CHECK_NAPI(napi_get_boolean(env, false, &falseValue)); + CHECK_NAPI(napi_get_boolean(env, true, &trueValue)); + CHECK_NAPI( + napi_set_named_property(env, *result, "configurable", falseValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "writable", trueValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "enumerable", falseValue)); + napi_value nullValue{}; + CHECK_NAPI(napi_get_null(env, &nullValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "value", nullValue)); + } + return napi_ok; +} + +napi_status NamedInterceptor::OwnKeys(napi_env env, + napi_value target, + napi_value* result) { + CHECK_NAPI(napi_get_all_property_names(env, + target, + napi_key_own_only, + napi_key_all_properties, + napi_key_keep_numbers, + result)); + + uint32_t arraySize{}; + CHECK_NAPI(napi_get_array_length(env, *result, &arraySize)); + napi_value firstElement{}; + CHECK_NAPI(napi_get_element(env, *result, 0, &firstElement)); + CHECK_NAPI(napi_set_element(env, *result, arraySize, firstElement)); + napi_value arraySizeValue{}; + CHECK_NAPI(napi_create_uint32(env, arraySize, &arraySizeValue)); + CHECK_NAPI(napi_set_named_property(env, *result, "length", arraySizeValue)); + napi_value value{}; + CHECK_NAPI(napi_create_string_utf8(env, "value", NAPI_AUTO_LENGTH, &value)); + CHECK_NAPI(napi_set_element(env, *result, 0, value)); + return napi_ok; +} + +extern "C" napi_value Init(napi_env env, napi_value exports) { + return NamedInterceptor::Init(env, exports); +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init); diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/package.json b/src/2-js-to-native-conversion/object-template-demo/napi/package.json new file mode 100644 index 00000000..6f4da065 --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/package.json @@ -0,0 +1,14 @@ +{ + "name": "object_template_demo", + "version": "0.0.0", + "description": "Intercept named property access using Proxy", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "test": "node index.js" + }, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.cc b/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.cc new file mode 100644 index 00000000..d64f5734 --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.cc @@ -0,0 +1,726 @@ +#include "proxy-template.h" +#include + +namespace { + +static napi_status SetTrap(napi_env env, + napi_value handler, + const char* propertyName, + napi_value trap) noexcept { + if (trap != nullptr) { + CHECK_NAPI(napi_set_named_property(env, handler, propertyName, trap)); + } + return napi_ok; +} + +using TrapCaller = napi_status (*)(napi_env env, + void* trap, + napi_span args, + napi_value* result); + +struct TrapCallInfo { + TrapCaller Caller; + void* trap; +}; + +template +napi_callback CreateTrapCallback() noexcept { + return [](napi_env env, napi_callback_info info) noexcept -> napi_value { + TrapCallInfo* trapCallInfo{}; + napi_value args[argCount]{}; + size_t actualArgCount{argCount}; + NAPI_CALL(napi_get_cb_info(env, + info, + &actualArgCount, + args, + nullptr, + reinterpret_cast(&trapCallInfo))); + NODE_API_ASSERT_BASE(env, + actualArgCount == argCount, + "proxy trap requires argCount arguments.", + nullptr); + napi_value result{}; + NAPI_CALL(trapCallInfo->Caller( + env, trapCallInfo->trap, span(args, argCount), &result)); + return result; + }; +} + +napi_status ToVector(napi_env env, + napi_value arrayValue, + std::vector* result) { + bool isArray{}; + CHECK_NAPI(napi_is_array(env, arrayValue, &isArray)); + if (isArray) { + uint32_t arraySize{}; + CHECK_NAPI(napi_get_array_length(env, arrayValue, &arraySize)); + result->reserve(result->size() + arraySize); + for (uint32_t i = 0; i < arraySize; ++i) { + napi_value arrayElement{}; + CHECK_NAPI(napi_get_element(env, arrayValue, i, &arrayElement)); + result->push_back(arrayElement); + } + } + return napi_ok; +} + +} // namespace + +napi_status ProxyHandlerBuilder::NewHandler(napi_env env, + napi_value* result) noexcept { + CHECK_NAPI(napi_create_object(env, result)); + + CHECK_NAPI(SetTrap(env, *result, "apply", apply_)); + CHECK_NAPI(SetTrap(env, *result, "construct", construct_)); + CHECK_NAPI(SetTrap(env, *result, "defineProperty", defineProperty_)); + CHECK_NAPI(SetTrap(env, *result, "deleteProperty", deleteProperty_)); + CHECK_NAPI(SetTrap(env, *result, "get", get_)); + CHECK_NAPI(SetTrap( + env, *result, "getOwnPropertyDescriptor", getOwnPropertyDescriptor_)); + CHECK_NAPI(SetTrap(env, *result, "getPrototypeOf", getPrototypeOf_)); + CHECK_NAPI(SetTrap(env, *result, "has", has_)); + CHECK_NAPI(SetTrap(env, *result, "isExtensible", isExtensible_)); + CHECK_NAPI(SetTrap(env, *result, "ownKeys", ownKeys_)); + CHECK_NAPI(SetTrap(env, *result, "preventExtensions", preventExtensions_)); + CHECK_NAPI(SetTrap(env, *result, "set", set_)); + CHECK_NAPI(SetTrap(env, *result, "setPrototypeOf", setPrototypeOf_)); + return napi_ok; +} + +napi_value ProxyHandlerBuilder::Apply() noexcept { + return apply_; +} + +void ProxyHandlerBuilder::Apply(napi_value trap) noexcept { + apply_ = trap; +} + +napi_status ProxyHandlerBuilder::Apply(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "apply", NAPI_AUTO_LENGTH, trap, data, &apply_); +} + +napi_status ProxyHandlerBuilder::Apply( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_value argumentList, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Apply(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Apply( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_span argumentList, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + std::vector argVector; + CHECK_NAPI(ToVector(env, args[2], &argVector)); + return reinterpret_cast(trap)( + env, + args[0], + args[1], + span(argVector.data(), argVector.size()), + result); + }, + reinterpret_cast(trap)}; + return Apply(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Construct() noexcept { + return construct_; +} + +void ProxyHandlerBuilder::Construct(napi_value trap) noexcept { + construct_ = trap; +} + +napi_status ProxyHandlerBuilder::Construct(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "construct", NAPI_AUTO_LENGTH, trap, data, &construct_); +} + +napi_status ProxyHandlerBuilder::Construct( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value argumentList, + napi_value newTarget, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Construct(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Construct( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_span argumentList, + napi_value newTarget, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + std::vector argVector; + CHECK_NAPI(ToVector(env, args[1], &argVector)); + return reinterpret_cast(trap)( + env, + args[0], + span(argVector.data(), argVector.size()), + args[2], + result); + }, + reinterpret_cast(trap)}; + return Construct(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::DefineProperty() noexcept { + return defineProperty_; +} + +void ProxyHandlerBuilder::DefineProperty(napi_value trap) noexcept { + defineProperty_ = trap; +} + +napi_status ProxyHandlerBuilder::DefineProperty(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "defineProperty", NAPI_AUTO_LENGTH, trap, data, &defineProperty_); +} + +napi_status ProxyHandlerBuilder::DefineProperty( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return DefineProperty(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::DefineProperty( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], args[2], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return DefineProperty(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::DeleteProperty() noexcept { + return deleteProperty_; +} + +void ProxyHandlerBuilder::DeleteProperty(napi_value trap) noexcept { + deleteProperty_ = trap; +} + +napi_status ProxyHandlerBuilder::DeleteProperty(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "deleteProperty", NAPI_AUTO_LENGTH, trap, data, &deleteProperty_); +} + +napi_status ProxyHandlerBuilder::DeleteProperty( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return DeleteProperty(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::DeleteProperty( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return DeleteProperty(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Get() noexcept { + return get_; +} + +void ProxyHandlerBuilder::Get(napi_value trap) noexcept { + get_ = trap; +} + +napi_status ProxyHandlerBuilder::Get(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "get", NAPI_AUTO_LENGTH, trap, data, &get_); +} + +napi_status ProxyHandlerBuilder::Get(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], result); + }, + reinterpret_cast(trap)}; + return Get(env, CreateTrapCallback<3>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::GetOwnPropertyDescriptor() noexcept { + return getOwnPropertyDescriptor_; +} + +void ProxyHandlerBuilder::GetOwnPropertyDescriptor(napi_value trap) noexcept { + getOwnPropertyDescriptor_ = trap; +} + +napi_status ProxyHandlerBuilder::GetOwnPropertyDescriptor(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, + "getOwnPropertyDescriptor", + NAPI_AUTO_LENGTH, + trap, + data, + &getOwnPropertyDescriptor_); +} + +napi_status ProxyHandlerBuilder::GetOwnPropertyDescriptor( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value key, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return GetOwnPropertyDescriptor(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::GetPrototypeOf() noexcept { + return getPrototypeOf_; +} + +void ProxyHandlerBuilder::GetPrototypeOf(napi_value trap) noexcept { + getPrototypeOf_ = trap; +} + +napi_status ProxyHandlerBuilder::GetPrototypeOf(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "getPrototypeOf", NAPI_AUTO_LENGTH, trap, data, &getPrototypeOf_); +} + +napi_status ProxyHandlerBuilder::GetPrototypeOf( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return GetPrototypeOf(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Has() noexcept { + return has_; +} + +void ProxyHandlerBuilder::Has(napi_value trap) noexcept { + has_ = trap; +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "has", NAPI_AUTO_LENGTH, trap, data, &has_); +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return Has(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return Has(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::IsExtensible() noexcept { + return isExtensible_; +} + +void ProxyHandlerBuilder::IsExtensible(napi_value trap) noexcept { + isExtensible_ = trap; +} + +napi_status ProxyHandlerBuilder::IsExtensible(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "isExtensible", NAPI_AUTO_LENGTH, trap, data, &isExtensible_); +} + +napi_status ProxyHandlerBuilder::IsExtensible( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return IsExtensible(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::IsExtensible( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)(env, args[0], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return IsExtensible(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::OwnKeys() noexcept { + return ownKeys_; +} + +void ProxyHandlerBuilder::OwnKeys(napi_value trap) noexcept { + ownKeys_ = trap; +} + +napi_status ProxyHandlerBuilder::OwnKeys(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "ownKeys", NAPI_AUTO_LENGTH, trap, data, &ownKeys_); +} + +napi_status ProxyHandlerBuilder::OwnKeys( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return OwnKeys(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::PreventExtensions() noexcept { + return preventExtensions_; +} + +void ProxyHandlerBuilder::PreventExtensions(napi_value trap) noexcept { + preventExtensions_ = trap; +} + +napi_status ProxyHandlerBuilder::PreventExtensions(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, + "preventExtensions", + NAPI_AUTO_LENGTH, + trap, + data, + &preventExtensions_); +} + +napi_status ProxyHandlerBuilder::PreventExtensions( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], result); + }, + reinterpret_cast(trap)}; + return PreventExtensions(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::PreventExtensions( + napi_env env, + napi_status (*trap)(napi_env env, napi_value target, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)(env, args[0], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return PreventExtensions(env, CreateTrapCallback<1>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::Set() noexcept { + return set_; +} + +void ProxyHandlerBuilder::Set(napi_value trap) noexcept { + set_ = trap; +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function(env, "set", NAPI_AUTO_LENGTH, trap, data, &set_); +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], args[2], args[3], result); + }, + reinterpret_cast(trap)}; + return Set(env, CreateTrapCallback<4>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{ + [](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], args[2], args[3], &boolResult)); + return napi_get_boolean(env, boolResult, result); + }, + reinterpret_cast(trap)}; + return Set(env, CreateTrapCallback<4>(), &callInfo); +} + +napi_value ProxyHandlerBuilder::SetPrototypeOf() noexcept { + return setPrototypeOf_; +} + +void ProxyHandlerBuilder::SetPrototypeOf(napi_value trap) noexcept { + setPrototypeOf_ = trap; +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf(napi_env env, + napi_callback trap, + void* data) noexcept { + return napi_create_function( + env, "setPrototypeOf", NAPI_AUTO_LENGTH, trap, data, &setPrototypeOf_); +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf( + napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + napi_value* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + return reinterpret_cast(trap)( + env, args[0], args[1], result); + }, + reinterpret_cast(trap)}; + return SetPrototypeOf(env, CreateTrapCallback<2>(), &callInfo); +} + +napi_status ProxyHandlerBuilder::SetPrototypeOf( + napi_env env, + napi_status (*trap)( + napi_env env, napi_value target, napi_value prototype, bool* result)) { + using TrapType = decltype(trap); + static TrapCallInfo callInfo{[](napi_env env, + void* trap, + napi_span args, + napi_value* result) noexcept { + bool boolResult{}; + CHECK_NAPI(reinterpret_cast(trap)( + env, args[0], args[1], &boolResult)); + return napi_get_boolean( + env, boolResult, result); + }, + reinterpret_cast(trap)}; + return SetPrototypeOf(env, CreateTrapCallback<2>(), &callInfo); +} + +//============= + +ProxyTemplate::ProxyTemplate(napi_env env, napi_value proxyHandler) noexcept + : proxyHandler_(RefHolder(env, proxyHandler)) {} + +napi_status ProxyTemplate::NewInstance(napi_env env, + napi_value target, + napi_value* result) noexcept { + napi_value proxyConstructor{}; + CHECK_NAPI(GetProxyConstructor(env, &proxyConstructor)); + + napi_value args[] = {target, static_cast(proxyHandler_)}; + return napi_new_instance(env, proxyConstructor, 2, args, result); +} + +napi_status ProxyTemplate::GetProxyConstructor(napi_env env, + napi_value* result) noexcept { + if (!proxyConstructor_) { + napi_value global{}, proxyConstructor{}; + CHECK_NAPI(napi_get_global(env, &global)); + CHECK_NAPI( + napi_get_named_property(env, global, "Proxy", &proxyConstructor)); + proxyConstructor_ = RefHolder(env, proxyConstructor); + } + *result = proxyConstructor_; + return napi_ok; +} diff --git a/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.h b/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.h new file mode 100644 index 00000000..85fd85f5 --- /dev/null +++ b/src/2-js-to-native-conversion/object-template-demo/napi/proxy-template.h @@ -0,0 +1,249 @@ +#include "node-api-common.h" + +template +using napi_span = span; + +// Creates a handler object for a JavaScript Proxy. +// See +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy +// for details about JavaScript Proxy. +// +// There are four different ways we can set each Proxy handler trap: +// - using napi_value function. +// - using napi_callback that creates function. +// - using a callback signature that matches Proxy handler definition arguments. +// - using a callback based on the Proxy handler trap signature where we replace +// napi_value with strongly typed values such as bool or +// napi_span. +// +// Note that ProxyHandlerBuilder keeps handler trap functions as napi_value +// and thus cannot be persisted. The typical usage is to create new +// ProxyHandlerBuilder instance on the call stack, set handler traps, create +// handler to pass to Proxy constructor or ProxyTemplate, and not to reuse the +// ProxyHandlerBuilder instance. +struct ProxyHandlerBuilder { + napi_status NewHandler(napi_env env, napi_value* result) noexcept; + + napi_value Apply() noexcept; + void Apply(napi_value trap) noexcept; + napi_status Apply(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Apply(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_value argumentList, + napi_value* result)); + napi_status Apply(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value thisArg, + napi_span argumentList, + napi_value* result)); + + napi_value Construct() noexcept; + void Construct(napi_value trap) noexcept; + napi_status Construct(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Construct(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value argumentList, + napi_value newTarget, + napi_value* result)); + napi_status Construct(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_span argumentList, + napi_value newTarget, + napi_value* result)); + + napi_value DefineProperty() noexcept; + void DefineProperty(napi_value trap) noexcept; + napi_status DefineProperty(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status DefineProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + napi_value* result)); + napi_status DefineProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value descriptor, + bool* result)); + + napi_value DeleteProperty() noexcept; + void DeleteProperty(napi_value trap) noexcept; + napi_status DeleteProperty(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status DeleteProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + napi_status DeleteProperty(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)); + + napi_value Get() noexcept; + void Get(napi_value trap) noexcept; + napi_status Get(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Get(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value receiver, + napi_value* result)); + + napi_value GetOwnPropertyDescriptor() noexcept; + void GetOwnPropertyDescriptor(napi_value trap) noexcept; + napi_status GetOwnPropertyDescriptor(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status GetOwnPropertyDescriptor(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + + napi_value GetPrototypeOf() noexcept; + void GetPrototypeOf(napi_value trap) noexcept; + napi_status GetPrototypeOf(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status GetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + + napi_value Has() noexcept; + void Has(napi_value trap) noexcept; + napi_status Has(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value* result)); + napi_status Has(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + bool* result)); + + napi_value IsExtensible() noexcept; + void IsExtensible(napi_value trap) noexcept; + napi_status IsExtensible(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status IsExtensible(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + napi_status IsExtensible(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + bool* result)); + + napi_value OwnKeys() noexcept; + void OwnKeys(napi_value trap) noexcept; + napi_status OwnKeys(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status OwnKeys(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + + napi_value PreventExtensions() noexcept; + void PreventExtensions(napi_value trap) noexcept; + napi_status PreventExtensions(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status PreventExtensions(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value* result)); + napi_status PreventExtensions(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + bool* result)); + + napi_value Set() noexcept; + void Set(napi_value trap) noexcept; + napi_status Set(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + napi_value* result)); + napi_status Set(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value key, + napi_value value, + napi_value receiver, + bool* result)); + + napi_value SetPrototypeOf() noexcept; + void SetPrototypeOf(napi_value trap) noexcept; + napi_status SetPrototypeOf(napi_env env, + napi_callback trap, + void* data = nullptr) noexcept; + napi_status SetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + napi_value* result)); + napi_status SetPrototypeOf(napi_env env, + napi_status (*trap)(napi_env env, + napi_value target, + napi_value prototype, + bool* result)); + + private: + napi_value apply_{}; + napi_value construct_{}; + napi_value defineProperty_{}; + napi_value deleteProperty_{}; + napi_value get_{}; + napi_value getOwnPropertyDescriptor_{}; + napi_value getPrototypeOf_{}; + napi_value has_{}; + napi_value isExtensible_{}; + napi_value ownKeys_{}; + napi_value preventExtensions_{}; + napi_value set_{}; + napi_value setPrototypeOf_{}; +}; + +struct ProxyTemplate { + ProxyTemplate(napi_env env, napi_value proxyHandler) noexcept; + + napi_status NewInstance(napi_env env, + napi_value target, + napi_value* result) noexcept; + + private: + napi_status GetProxyConstructor(napi_env env, napi_value* result) noexcept; + + private: + RefHolder proxyConstructor_{}; + RefHolder proxyHandler_{}; +}; diff --git a/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/README.md b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/README.md new file mode 100644 index 00000000..464a4fd8 --- /dev/null +++ b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/README.md @@ -0,0 +1,14 @@ +# Node-API Object Wrap Demo + +This is an example project that accompanies the Node-API workshop tutorials + +A tutorial describing this project can be found at the [Node-API Resource](https://napi.inspiredware.com/getting-started/objectwrap.html). + +To build and run this program on your system, clone it to your computer and run these two commands inside your clone: + +``` +npm install +npm test +``` + +> You need to have Node 10.5.0 or later installed. \ No newline at end of file diff --git a/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/binding.gyp b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/binding.gyp new file mode 100644 index 00000000..a9eae12b --- /dev/null +++ b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/binding.gyp @@ -0,0 +1,20 @@ +{ + 'targets': [ + { + 'target_name': 'object-wrap-demo-native', + 'sources': [ 'src/object_wrap_demo.cc' ], + 'include_dirs': ["_greeterName = info[0].As().Utf8Value(); +} + +Napi::Value ObjectWrapDemo::Greet(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1) { + Napi::TypeError::New(env, "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + if (!info[0].IsString()) { + Napi::TypeError::New(env, "You need to introduce yourself to greet") + .ThrowAsJavaScriptException(); + return env.Null(); + } + + Napi::String name = info[0].As(); + + printf("Hello %s\n", name.Utf8Value().c_str()); + printf("I am %s\n", this->_greeterName.c_str()); + + return Napi::String::New(env, this->_greeterName); +} + +Napi::Function ObjectWrapDemo::GetClass(Napi::Env env) { + return DefineClass( + env, + "ObjectWrapDemo", + { + ObjectWrapDemo::InstanceMethod("greet", &ObjectWrapDemo::Greet), + }); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + Napi::String name = Napi::String::New(env, "ObjectWrapDemo"); + exports.Set(name, ObjectWrapDemo::GetClass(env)); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/src/object_wrap_demo.h b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/src/object_wrap_demo.h new file mode 100644 index 00000000..7e2e3341 --- /dev/null +++ b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/src/object_wrap_demo.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +class ObjectWrapDemo : public Napi::ObjectWrap { + public: + ObjectWrapDemo(const Napi::CallbackInfo&); + Napi::Value Greet(const Napi::CallbackInfo&); + + static Napi::Function GetClass(Napi::Env); + + private: + std::string _greeterName; +}; diff --git a/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/test/test_binding.js b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/test/test_binding.js new file mode 100644 index 00000000..adbcf76a --- /dev/null +++ b/src/2-js-to-native-conversion/object-wrap-demo/node-addon-api/test/test_binding.js @@ -0,0 +1,21 @@ +const ObjectWrapDemo = require("../lib/binding.js"); +const assert = require("assert"); + +assert(ObjectWrapDemo, "The expected module is undefined"); + +function testBasic() +{ + const instance = new ObjectWrapDemo("mr-yeoman"); + assert(instance.greet, "The expected method is not defined"); + assert.strictEqual(instance.greet("kermit"), "mr-yeoman", "Unexpected value returned"); +} + +function testInvalidParams() +{ + const instance = new ObjectWrapDemo(); +} + +assert.doesNotThrow(testBasic, undefined, "testBasic threw an expection"); +assert.throws(testInvalidParams, undefined, "testInvalidParams didn't throw"); + +console.log("Tests passed- everything looks OK!"); \ No newline at end of file diff --git a/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/binding.gyp b/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/binding.gyp new file mode 100644 index 00000000..ef4bb4a9 --- /dev/null +++ b/src/2-js-to-native-conversion/typed_array_to_native/node-addon-api/binding.gyp @@ -0,0 +1,14 @@ +{ + "targets": [ + { + "target_name": "typed_array_to_native", + "cflags!": [ "-fno-exceptions" ], + "cflags_cc!": [ "-fno-exceptions" ], + "sources": [ "typed_array_to_native.cc" ], + "include_dirs": [ + " +#include + +static Napi::Value AcceptByteArray(const Napi::CallbackInfo& info) { + if (info.Length() != 1) { + Napi::Error::New(info.Env(), "Expected exactly one argument") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + if (!info[0].IsTypedArray()) { + Napi::Error::New(info.Env(), "Expected a TypedArray") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + Napi::TypedArray typedArray = info[0].As(); + + if (typedArray.TypedArrayType() != napi_uint8_array) { + Napi::Error::New(info.Env(), "Expected an Uint8Array") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + Napi::Uint8Array uint8Array = typedArray.As(); + + // Copy to std::vector: + std::vector bytes(uint8Array.Data(), + uint8Array.Data() + uint8Array.ElementLength()); + printf("std::vector from Uint8Array: ["); + for (uint8_t byte : bytes) { + printf("%d, ", byte); + } + printf("\b\b]\n"); + + return info.Env().Undefined(); +} + +static Napi::Value CreateByteArray(const Napi::CallbackInfo& info) { + if (info.Length() != 1) { + Napi::Error::New(info.Env(), "Expected exactly one argument") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + if (!info[0].IsArray()) { + Napi::Error::New(info.Env(), "Expected an Array") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + Napi::Array array = info[0].As(); + size_t arrayLength = array.Length(); + + // Create std::vector out of array. + // We allocate it on the heap to allow wrapping it up into ArrayBuffer. + std::unique_ptr> nativeArray = + std::make_unique>(arrayLength, 0); + for (size_t i = 0; i < arrayLength; ++i) { + Napi::Value arrayItem = array[i]; + if (!arrayItem.IsNumber()) { + Napi::Error::New(info.Env(), "Expected a Number as Array Item") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + Napi::Number number = arrayItem.As(); + double numberValue = number.DoubleValue(); + if (numberValue < 0 || numberValue > 255) { + Napi::Error::New(info.Env(), + "Array Item Number value is out of range [0..255]") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } + + (*nativeArray)[i] = static_cast(numberValue); + } + + printf("std::vector from Array: ["); + for (uint8_t byte : *nativeArray) { + printf("%d, ", byte); + } + printf("\b\b]\n"); + + // Wrap up the std::vector into the ArrayBuffer. + // Note: instead of wrapping the std::vector we could allow ArrayBuffer to + // create internal storage that copies the std::vector, but it is less + // efficient because it requires an extra memory allocation. + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New( + info.Env(), + nativeArray->data(), + arrayLength /* size in bytes */, + [](Napi::Env /*env*/, void* /*data*/, std::vector* hint) { + std::unique_ptr> vectorPtrToDelete(hint); + }, + nativeArray.get()); + // The finalizer is responsible for deleting the vector: release the + // unique_ptr ownership. + nativeArray.release(); + + Napi::Uint8Array byteArray = + Napi::Uint8Array::New(info.Env(), arrayLength, arrayBuffer, 0); + + return byteArray; +} + +static Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports["AcceptByteArray"] = Napi::Function::New(env, AcceptByteArray); + exports["CreateByteArray"] = Napi::Function::New(env, CreateByteArray); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/3-context-awareness/napi/binding.gyp b/src/3-context-awareness/napi/binding.gyp new file mode 100644 index 00000000..b5b5ee1d --- /dev/null +++ b/src/3-context-awareness/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'multiple_load', + 'sources': [ 'multiple_load.c' ] + } + ] +} diff --git a/src/3-context-awareness/napi/index.js b/src/3-context-awareness/napi/index.js new file mode 100644 index 00000000..d956f73f --- /dev/null +++ b/src/3-context-awareness/napi/index.js @@ -0,0 +1,40 @@ +// Example illustrating the case where a native addon is loaded multiple times. +// This entire file is executed twice, concurrently - once on the main thread, +// and once on a thread launched from the main thread. + +// We load the worker threads module, which allows us to launch multiple Node.js +// environments, each in its own thread. +const { + Worker, isMainThread +} = require('worker_threads'); + +// We load the native addon. +const addon = require('bindings')('multiple_load'); + +// The iteration count can be tweaked to ensure that the output from the two +// threads is interleaved. Too few iterations and the output of one thread +// follows the output of the other, not really illustrating the concurrency. +const iterations = 1000; + +// This function is an idle loop that performs a random walk from 0 by calling +// into the native addon to either increment or decrement the initial value. +function useAddon(addon, prefix, iterations) { + if (iterations >= 0) { + if (Math.random() < 0.5) { + console.log(prefix + ': new value (decremented): ' + addon.decrement()); + } else { + console.log(prefix + ': new value (incremented): ' + addon.increment()); + } + setImmediate(() => useAddon(addon, prefix, --iterations)); + } +} + +if (isMainThread) { + // On the main thread, we launch a worker and wait for it to come online. Then + // we start the loop. + (new Worker(__filename)).on('online', + () => useAddon(addon, "Main thread", iterations)); +} else { + // On the secondary thread we immediately start the loop. + useAddon(addon, "Worker thread", iterations); +} diff --git a/src/3-context-awareness/napi/multiple_load.c b/src/3-context-awareness/napi/multiple_load.c new file mode 100644 index 00000000..036f49a5 --- /dev/null +++ b/src/3-context-awareness/napi/multiple_load.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include + +#define CHECK(expr) \ + { \ + if ((expr) == 0) { \ + fprintf(stderr, "%s:%d: failed assertion `%s'\n", __FILE__, __LINE__, #expr); \ + fflush(stderr); \ + abort(); \ + } \ + } + +// Structure containing information needed for as long as the addon exists. It +// replaces the use of global static data with per-addon-instance data by +// associating an instance of this structure with each instance of this addon +// during addon initialization. The instance of this structure is then passed to +// each binding the addon provides. Thus, the data stored in an instance of this +// structure is available to each binding, just as global static data would be. +typedef struct { + double value; +} AddonData; + +// This is the actual, useful work performed: increment or decrement the value +// stored per addon instance after passing it through a CPU-consuming but +// otherwise useless calculation. +static int ModifyAddonData(AddonData* data, double offset) { + // Expensively increment or decrement the value. + data->value = tan(atan(exp(log(sqrt(data->value * data->value))))) + offset; + + // Round the value to the nearest integer. + data->value = + (double)(((int)data->value) + + (data->value - ((double)(int)data->value) > 0.5 ? 1 : 0)); + + // Return the value as an integer. + return (int)(data->value); +} + +// This is boilerplate. The instance of the `AddonData` structure created during +// addon initialization must be destroyed when the addon is unloaded. This +// function will be called when the addon's `exports` object is garbage collected. +static void DeleteAddonData(napi_env env, void* data, void* hint) { + // Avoid unused parameter warnings. + (void) env; + (void) hint; + + // Free the per-addon-instance data. + free(data); +} + +// This is also boilerplate. It creates and initializes an instance of the +// `AddonData` structure and ties its lifecycle to that of the addon instance's +// `exports` object. This means that the data will be available to this instance +// of the addon for as long as the JavaScript engine keeps it alive. +static AddonData* CreateAddonData(napi_env env, napi_value exports) { + AddonData* result = malloc(sizeof(*result)); + result->value = 0.0; + CHECK(napi_wrap(env, + exports, + result, + DeleteAddonData, + NULL, + NULL) == napi_ok); + return result; +} + +// This function is called from JavaScript. It uses an expensive operation to +// increment the value stored inside the `AddonData` structure by one. +static napi_value Increment(napi_env env, napi_callback_info info) { + // Retrieve the per-addon-instance data. + AddonData* addon_data = NULL; + CHECK(napi_get_cb_info(env, + info, + NULL, + NULL, + NULL, + ((void**)&addon_data)) == napi_ok); + + // Increment the per-addon-instance value and create a new JavaScript integer + // from it. + napi_value result; + CHECK(napi_create_int32(env, + ModifyAddonData(addon_data, 1.0), + &result) == napi_ok); + + // Return the JavaScript integer back to JavaScript. + return result; +} + +// This function is called from JavaScript. It uses an expensive operation to +// decrement the value stored inside the `AddonData` structure by one. +static napi_value Decrement(napi_env env, napi_callback_info info) { + // Retrieve the per-addon-instance data. + AddonData* addon_data = NULL; + CHECK(napi_get_cb_info(env, + info, + NULL, + NULL, + NULL, + ((void**)&addon_data)) == napi_ok); + + // Decrement the per-addon-instance value and create a new JavaScript integer + // from it. + napi_value result; + CHECK(napi_create_int32(env, + ModifyAddonData(addon_data, -1.0), + &result) == napi_ok); + + // Return the JavaScript integer back to JavaScript. + return result; +} + +// Initialize the addon in such a way that it may be initialized multiple times +// per process. The function body following this macro is provided the value +// `env` which has type `napi_env` and the value `exports` which has type +// `napi_value` and which refers to a JavaScript object that ultimately contains +// the functions this addon wishes to expose. At the end, it must return a +// `napi_value`. It may return `exports`, or it may create a new `napi_value` +// and return that instead. +NAPI_MODULE_INIT(/*env, exports*/) { + // Create a new instance of the per-instance-data that will be associated with + // the instance of the addon being initialized here and that will be destroyed + // along with the instance of the addon. + AddonData* addon_data = CreateAddonData(env, exports); + + // Declare the bindings this addon provides. The data created above is given + // as the last initializer parameter, and will be given to the binding when it + // is called. + napi_property_descriptor bindings[] = { + {"increment", NULL, Increment, NULL, NULL, NULL, napi_enumerable, addon_data}, + {"decrement", NULL, Decrement, NULL, NULL, NULL, napi_enumerable, addon_data} + }; + + // Expose the two bindings declared above to JavaScript. + CHECK(napi_define_properties(env, + exports, + sizeof(bindings) / sizeof(bindings[0]), + bindings) == napi_ok); + + // Return the `exports` object provided. It now has two new properties, which + // are the functions we wish to expose to JavaScript. + return exports; +} diff --git a/src/3-context-awareness/napi/package.json b/src/3-context-awareness/napi/package.json new file mode 100644 index 00000000..ffc17ee5 --- /dev/null +++ b/src/3-context-awareness/napi/package.json @@ -0,0 +1,17 @@ +{ + "name": "multiple_load_napi", + "version": "0.0.0", + "description": "Multiple load example", + "main": "index.js", + "private": true, + "scripts": { + "test": "node --experimental-worker index.js" + }, + "engines": { + "node": ">= 10.10.0" + }, + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/3-context-awareness/node_10/binding.gyp b/src/3-context-awareness/node_10/binding.gyp new file mode 100644 index 00000000..01196e00 --- /dev/null +++ b/src/3-context-awareness/node_10/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'multiple_load', + 'sources': [ 'multiple_load.cc' ] + } + ] +} diff --git a/src/3-context-awareness/node_10/index.js b/src/3-context-awareness/node_10/index.js new file mode 100644 index 00000000..d956f73f --- /dev/null +++ b/src/3-context-awareness/node_10/index.js @@ -0,0 +1,40 @@ +// Example illustrating the case where a native addon is loaded multiple times. +// This entire file is executed twice, concurrently - once on the main thread, +// and once on a thread launched from the main thread. + +// We load the worker threads module, which allows us to launch multiple Node.js +// environments, each in its own thread. +const { + Worker, isMainThread +} = require('worker_threads'); + +// We load the native addon. +const addon = require('bindings')('multiple_load'); + +// The iteration count can be tweaked to ensure that the output from the two +// threads is interleaved. Too few iterations and the output of one thread +// follows the output of the other, not really illustrating the concurrency. +const iterations = 1000; + +// This function is an idle loop that performs a random walk from 0 by calling +// into the native addon to either increment or decrement the initial value. +function useAddon(addon, prefix, iterations) { + if (iterations >= 0) { + if (Math.random() < 0.5) { + console.log(prefix + ': new value (decremented): ' + addon.decrement()); + } else { + console.log(prefix + ': new value (incremented): ' + addon.increment()); + } + setImmediate(() => useAddon(addon, prefix, --iterations)); + } +} + +if (isMainThread) { + // On the main thread, we launch a worker and wait for it to come online. Then + // we start the loop. + (new Worker(__filename)).on('online', + () => useAddon(addon, "Main thread", iterations)); +} else { + // On the secondary thread we immediately start the loop. + useAddon(addon, "Worker thread", iterations); +} diff --git a/src/3-context-awareness/node_10/multiple_load.cc b/src/3-context-awareness/node_10/multiple_load.cc new file mode 100644 index 00000000..447ec920 --- /dev/null +++ b/src/3-context-awareness/node_10/multiple_load.cc @@ -0,0 +1,121 @@ +#include +#include + +using namespace v8; + +// Class containing information needed for as long as the addon exists. It +// replaces the use of global static data with per-addon-instance data by +// associating an instance of this class with each instance of this addon during +// addon initialization. The instance of this class is then passed to each +// binding the addon provides. Thus, the data stored in an instance of this +// class is available to each binding, just as global static data would be. +class AddonData { + public: + // This is the actual, useful work performed: increment or decrement the value + // stored per addon instance after passing it through a CPU-consuming but + // otherwise useless calculation. Round the result to the nearest integer. + int GetNewValue(double offset) { + // Expensively increment or decrement the value. + value = tan(atan(exp(log(sqrt(value * value))))) + offset; + + // Round the value to the nearest integer. + value = + (double)(((int)value) + (value - ((double)(int)value) > 0.5 ? 1 : 0)); + + // Return the value as an integer. + return (int)value; + } + + // This is boilerplate. It creates a new instance of this class and wraps it + // into a v8::External. The isolate and the exports are necessary, because we + // want the instance of this class to be destroyed along with the exports + // object when the addon is eventually unloaded. + static Local New(Isolate* isolate, Local exports) { + return External::New(isolate, new AddonData(isolate, exports)); + } + + private: + // This is the actual, useful payload carried by an instance of this class. + // A double value is kept around for as long as an instance of this addon is + // loaded, and is incremented or decremented whenever the addon receives a + // call from JavaScript. + double value; + + explicit AddonData(Isolate* isolate, Local exports) + : // The payload is initialized here. The rest of the constructor is + // boilerplate that ensures that the instance of this addon data is + // destroyed along with the instance of this addon (`exports`). + value(0.0) { + exports_persistent.Reset(isolate, exports); + exports_persistent.SetWeak(this, DeleteMe, WeakCallbackType::kParameter); + } + + // The rest of the class definition is boilerplate. + + // The persistent reference must be reset before the instance of this class is + // destroyed, otherwise memory will leak on the V8 side. + ~AddonData() { exports_persistent.Reset(); } + + // This static function will be called when the addon instance is unloaded. It + // merely destroys the per-addon-instance data. + static void DeleteMe(const WeakCallbackInfo& info) { + delete info.GetParameter(); + } + + Persistent exports_persistent; +}; + +// Function called from JavaScript to increment the value stored in this addon. +void Increment(const FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* addon_data = + static_cast(info.Data().As()->Value()); + + info.GetReturnValue().Set( + Number::New(info.GetIsolate(), addon_data->GetNewValue(1.0))); +} + +// Function called from JavaScript to decrement the value stored in this addon. +void Decrement(const FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* addon_data = + static_cast(info.Data().As()->Value()); + + info.GetReturnValue().Set( + Number::New(info.GetIsolate(), addon_data->GetNewValue(-1.0))); +} + +// Initialize the addon in such a way that it may be initialized multiple times +// per process. The function body following this macro is provided the value +// `exports` of type `Local`, the value `module` of type +// `Local`, and `context` of type `Local`. It may either define +// new properties on the `exports` object, or define the property named +// "exports" on the `module` object. +NODE_MODULE_INIT(/*exports, module, context*/) { + Isolate* isolate = context->GetIsolate(); + + // Create a new instance of the addon data that will be associated with this + // instance of the addon, and that will be freed along with this instance of + // the addon. + Local addon_data = AddonData::New(isolate, exports); + + // Export the functions we wish to make available to JavaScript. + + exports + ->Set(context, + String::NewFromUtf8(isolate, "increment", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, Increment, addon_data) + ->GetFunction(context) + .ToLocalChecked()) + .FromJust(); + + exports + ->Set(context, + String::NewFromUtf8(isolate, "decrement", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, Decrement, addon_data) + ->GetFunction(context) + .ToLocalChecked()) + .FromJust(); +} diff --git a/src/3-context-awareness/node_10/package.json b/src/3-context-awareness/node_10/package.json new file mode 100644 index 00000000..c76c93b4 --- /dev/null +++ b/src/3-context-awareness/node_10/package.json @@ -0,0 +1,17 @@ +{ + "name": "multiple_load_node_10", + "version": "0.0.0", + "description": "Multiple load example", + "main": "index.js", + "private": true, + "scripts": { + "test": "node --experimental-worker index.js" + }, + "engines": { + "node": ">= 10.10.0" + }, + "gypfile": true, + "dependencies": { + "bindings": "~1.5.0" + } +} diff --git a/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/binding.gyp b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/binding.gyp new file mode 100644 index 00000000..1061bc45 --- /dev/null +++ b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/binding.gyp @@ -0,0 +1,31 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "src/binding.cc", + "src/native-addon.cc" + ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'include_dirs': [" +#include "native-addon.h" + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + NativeAddon::Init(env, exports); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) \ No newline at end of file diff --git a/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.cc b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.cc new file mode 100644 index 00000000..7093a9cd --- /dev/null +++ b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.cc @@ -0,0 +1,36 @@ +#include "native-addon.h" +#include + +Napi::FunctionReference NativeAddon::constructor; + +Napi::Object NativeAddon::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = + DefineClass(env, + "NativeAddon", + {InstanceMethod("tryCallByStoredReference", + &NativeAddon::TryCallByStoredReference), + InstanceMethod("tryCallByStoredFunction", + &NativeAddon::TryCallByStoredFunction)}); + + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + + exports.Set("NativeAddon", func); + return exports; +} + +NativeAddon::NativeAddon(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + jsFnRef = Napi::Persistent(info[0].As()); + jsFn = info[1].As(); +} + +void NativeAddon::TryCallByStoredReference(const Napi::CallbackInfo& info) { + // Napi::Env env = info.Env(); + jsFnRef.Call({}); +} + +void NativeAddon::TryCallByStoredFunction(const Napi::CallbackInfo& info) { + // Napi::Env env = info.Env(); + jsFn.Call({}); +} \ No newline at end of file diff --git a/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.h b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.h new file mode 100644 index 00000000..a3c052d9 --- /dev/null +++ b/src/4-references-and-handle-scope/function-reference-demo/node-addon-api/src/native-addon.h @@ -0,0 +1,15 @@ +#include + +class NativeAddon : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + NativeAddon(const Napi::CallbackInfo& info); + + private: + static Napi::FunctionReference constructor; + Napi::FunctionReference jsFnRef; + Napi::Function jsFn; + + void TryCallByStoredReference(const Napi::CallbackInfo& info); + void TryCallByStoredFunction(const Napi::CallbackInfo& info); +}; \ No newline at end of file diff --git a/src/5-async-work/async-iterator/node-addon-api/CMakeLists.txt b/src/5-async-work/async-iterator/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..22573d7d --- /dev/null +++ b/src/5-async-work/async-iterator/node-addon-api/CMakeLists.txt @@ -0,0 +1,23 @@ +project (example) +include_directories(${CMAKE_JS_INC} node_modules/node-addon-api/) +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_JS_INC}) +file(GLOB SOURCE_FILES "*.cc") +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Include Node-API wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + +# define NAPI_VERSION +add_definitions(-DNAPI_VERSION=6) diff --git a/src/5-async-work/async-iterator/node-addon-api/example.cc b/src/5-async-work/async-iterator/node-addon-api/example.cc new file mode 100644 index 00000000..297d0b4e --- /dev/null +++ b/src/5-async-work/async-iterator/node-addon-api/example.cc @@ -0,0 +1,84 @@ +#include +#include + +using namespace Napi; + +class AsyncIteratorExample : public ObjectWrap { + public: + AsyncIteratorExample(const CallbackInfo& info) + : ObjectWrap(info), + _from(info[0].As()), + _to(info[1].As()) {} + + static Object Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = DefineClass( + env, + "AsyncIteratorExample", + {InstanceMethod(Napi::Symbol::WellKnown(env, "asyncIterator"), + &AsyncIteratorExample::Iterator)}); + + exports.Set("AsyncIteratorExample", func); + return exports; + } + + Napi::Value Iterator(const CallbackInfo& info) { + auto env = info.Env(); + auto iteratorObject = Napi::Object::New(env); + + iteratorObject["current"] = Number::New(env, _from); + iteratorObject["last"] = Number::New(env, _to); + auto next = Function::New(env, [](const CallbackInfo& info) { + auto env = info.Env(); + auto deferred = + std::make_shared(Promise::Deferred::New(env)); + auto iteratorObject = info.This().As(); + auto callback = Function::New( + env, + [=](const CallbackInfo& info) { + auto env = info.Env(); + auto value = Object::New(env); + auto iteratorObject = info.This().As(); + + auto current = + iteratorObject.Get("current").As().Int32Value(); + auto last = iteratorObject.Get("last").As().Int32Value(); + auto done = current > last; + + if (done) { + value["done"] = Boolean::New(env, true); + } else { + value["done"] = Boolean::New(env, false); + value["value"] = Number::New(env, current); + iteratorObject["current"] = Number::New(env, current + 1); + } + deferred->Resolve(value); + }, + "next"); + + env.Global() + .Get("setTimeout") + .As() + .Call({callback.Get("bind").As().Call(callback, + {iteratorObject}), + Number::New(env, 1000)}); + + return deferred->Promise(); + }); + + iteratorObject["next"] = + next.Get("bind").As().Call(next, {iteratorObject}); + + return iteratorObject; + } + + private: + int _from; + int _to; +}; + +Napi::Object Init(Napi::Env env, Object exports) { + AsyncIteratorExample::Init(env, exports); + return exports; +} + +NODE_API_MODULE(example, Init) diff --git a/src/5-async-work/async-iterator/node-addon-api/index.js b/src/5-async-work/async-iterator/node-addon-api/index.js new file mode 100644 index 00000000..59680b26 --- /dev/null +++ b/src/5-async-work/async-iterator/node-addon-api/index.js @@ -0,0 +1,49 @@ +const { AsyncIteratorExample } = require('bindings')('example'); + +async function main(from, to) { + const iterator = new AsyncIteratorExample(from, to); + for await (const value of iterator) { + console.log(value); + } +} + +/* +// The JavaScript equivalent of the node-addon-api C++ code for reference +async function main(from, to) { + class AsyncIteratorExample { + constructor(from, to) { + this.from = from; + this.to = to; + } + + [Symbol.asyncIterator]() { + return { + current: this.from, + last: this.to, + next() { + return new Promise(resolve => { + setTimeout(() => { + if (this.current <= this.last) { + resolve({ done: false, value: this.current++ }); + } else { + resolve({ done: true }); + } + }, 1000) + }); + } + } + } + } + const iterator = new AsyncIteratorExample(from, to); + + for await (const value of iterator) { + console.log(value); + } +} +*/ + +main(0, 5) + .catch(e => { + console.error(e); + process.exit(1); + }); diff --git a/src/5-async-work/async-iterator/node-addon-api/package.json b/src/5-async-work/async-iterator/node-addon-api/package.json new file mode 100644 index 00000000..e3f6b6ae --- /dev/null +++ b/src/5-async-work/async-iterator/node-addon-api/package.json @@ -0,0 +1,16 @@ +{ + "name": "async-iterator-example", + "version": "0.0.0", + "description": "Async iterator example using node-addon-api", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "^1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "test": "node index.js", + "install": "cmake-js compile" + } +} diff --git a/src/5-async-work/async_pi_estimate/nan/README.md b/src/5-async-work/async_pi_estimate/nan/README.md new file mode 100755 index 00000000..a11d3f58 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/README.md @@ -0,0 +1 @@ +In this directory run `node-gyp rebuild` and then `node ./addon.js` \ No newline at end of file diff --git a/src/5-async-work/async_pi_estimate/nan/addon.cc b/src/5-async-work/async_pi_estimate/nan/addon.cc new file mode 100755 index 00000000..170a7b43 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/addon.cc @@ -0,0 +1,24 @@ +#include +#include "async.h" // NOLINT(build/include) +#include "sync.h" // NOLINT(build/include) + +using Nan::GetFunction; +using Nan::New; +using Nan::Set; +using v8::FunctionTemplate; +using v8::Object; +using v8::String; + +// Expose synchronous and asynchronous access to our +// Estimate() function +NAN_MODULE_INIT(InitAll) { + Set(target, + New("calculateSync").ToLocalChecked(), + GetFunction(New(CalculateSync)).ToLocalChecked()); + + Set(target, + New("calculateAsync").ToLocalChecked(), + GetFunction(New(CalculateAsync)).ToLocalChecked()); +} + +NODE_MODULE(addon, InitAll) diff --git a/src/5-async-work/async_pi_estimate/nan/addon.js b/src/5-async-work/async_pi_estimate/nan/addon.js new file mode 100755 index 00000000..80bc12e1 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/addon.js @@ -0,0 +1,44 @@ +const addon = require('./build/Release/addon'); +const calculations = process.argv[2] || 100000000; + +function printResult(type, pi, ms) { + console.log(type, 'method:'); + console.log('\tΟ€ β‰ˆ ' + pi + + ' (' + Math.abs(pi - Math.PI) + ' away from actual)'); + console.log('\tTook ' + ms + 'ms'); + console.log(); +} + +function runSync() { + const start = Date.now(); + // Estimate() will execute in the current thread, + // the next line won't return until it is finished + const result = addon.calculateSync(calculations); + printResult('Sync', result, Date.now() - start); +} + +function runAsync() { + // how many batches should we split the work in to? + const batches = process.argv[3] || 16; + let ended = 0; + let total = 0; + const start = Date.now(); + + function done (err, result) { + total += result; + + // have all the batches finished executing? + if (++ended === batches) { + printResult('Async', total / batches, Date.now() - start); + } + } + + // for each batch of work, request an async Estimate() for + // a portion of the total number of calculations + for (let i = 0; i < batches; i++) { + addon.calculateAsync(calculations / batches, done); + } +} + +runSync(); +runAsync(); diff --git a/src/5-async-work/async_pi_estimate/nan/async.cc b/src/5-async-work/async_pi_estimate/nan/async.cc new file mode 100755 index 00000000..3db56f42 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/async.cc @@ -0,0 +1,51 @@ +#include "async.h" // NOLINT(build/include) +#include +#include "pi_est.h" // NOLINT(build/include) + +using Nan::AsyncQueueWorker; +using Nan::AsyncWorker; +using Nan::Callback; +using Nan::HandleScope; +using Nan::New; +using Nan::Null; +using Nan::To; +using v8::Function; +using v8::Local; +using v8::Number; +using v8::Value; + +class PiWorker : public AsyncWorker { + public: + PiWorker(Callback* callback, int points) + : AsyncWorker(callback), points(points), estimate(0) {} + ~PiWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access V8, or V8 data structures + // here, so everything we need for input and output + // should go on `this`. + void Execute() { estimate = Estimate(points); } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use V8 again + void HandleOKCallback() { + HandleScope scope; + + Local argv[] = {Null(), New(estimate)}; + + callback->Call(2, argv, async_resource); + } + + private: + int points; + double estimate; +}; + +// Asynchronous access to the `Estimate()` function +NAN_METHOD(CalculateAsync) { + int points = To(info[0]).FromJust(); + Callback* callback = new Callback(To(info[1]).ToLocalChecked()); + + AsyncQueueWorker(new PiWorker(callback, points)); +} diff --git a/src/5-async-work/async_pi_estimate/nan/async.h b/src/5-async-work/async_pi_estimate/nan/async.h new file mode 100755 index 00000000..d45615a7 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/async.h @@ -0,0 +1,8 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ + +#include + +NAN_METHOD(CalculateAsync); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ diff --git a/9_async_work/binding.gyp b/src/5-async-work/async_pi_estimate/nan/binding.gyp old mode 100644 new mode 100755 similarity index 71% rename from 9_async_work/binding.gyp rename to src/5-async-work/async_pi_estimate/nan/binding.gyp index 390875eb..32f29259 --- a/9_async_work/binding.gyp +++ b/src/5-async-work/async_pi_estimate/nan/binding.gyp @@ -7,7 +7,8 @@ "pi_est.cc", "sync.cc", "async.cc" - ] + ], + "include_dirs": [" -#include "pi_est.h" /* Estimate the value of Ο€ by using a Monte Carlo method. @@ -14,25 +14,41 @@ See https://en.wikipedia.org/wiki/File:Pi_30K.gif for a visualization of how this works. */ -double Estimate (int points) { +inline int randall(unsigned int* p_seed) { +// windows has thread safe rand() +#ifdef _WIN32 + return rand(); // NOLINT(runtime/threadsafe_fn) +#else + return rand_r(p_seed); +#endif +} + +double Estimate(int points) { int i = points; int inside = 0; + unsigned int randseed = 1; + +#ifdef _WIN32 + srand(randseed); +#endif + // unique seed for each run, for threaded use - unsigned int seed = rand(); - double x, y; + unsigned int seed = randall(&randseed); + +#ifdef _WIN32 + srand(seed); +#endif while (i-- > 0) { - // rand_r() is used to avoid thread locking - x = rand_r(&seed) / (double)RAND_MAX; - y = rand_r(&seed) / (double)RAND_MAX; + double x = randall(&seed) / static_cast(RAND_MAX); + double y = randall(&seed) / static_cast(RAND_MAX); // x & y and now values between 0 and 1 // now do a pythagorean diagonal calculation // `1` represents our 1/4 circle - if ((x * x) + (y * y) <= 1) - inside++; + if ((x * x) + (y * y) <= 1) inside++; } // calculate ratio and multiply by 4 for Ο€ - return (inside / (double)points) * 4; + return (inside / static_cast(points)) * 4; } diff --git a/src/5-async-work/async_pi_estimate/nan/pi_est.h b/src/5-async-work/async_pi_estimate/nan/pi_est.h new file mode 100755 index 00000000..8c35fe86 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/pi_est.h @@ -0,0 +1,6 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ + +double Estimate(int points); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ diff --git a/src/5-async-work/async_pi_estimate/nan/sync.cc b/src/5-async-work/async_pi_estimate/nan/sync.cc new file mode 100755 index 00000000..aefbeef9 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/sync.cc @@ -0,0 +1,12 @@ +#include "sync.h" // NOLINT(build/include) +#include +#include "pi_est.h" // NOLINT(build/include) + +// Simple synchronous access to the `Estimate()` function +NAN_METHOD(CalculateSync) { + // expect a number as the first argument + int points = info[0]->Uint32Value(Nan::GetCurrentContext()).FromJust(); + double est = Estimate(points); + + info.GetReturnValue().Set(est); +} diff --git a/src/5-async-work/async_pi_estimate/nan/sync.h b/src/5-async-work/async_pi_estimate/nan/sync.h new file mode 100755 index 00000000..7321f483 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/nan/sync.h @@ -0,0 +1,8 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ + +#include + +NAN_METHOD(CalculateSync); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/README.md b/src/5-async-work/async_pi_estimate/node-addon-api/README.md new file mode 100755 index 00000000..a11d3f58 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/README.md @@ -0,0 +1 @@ +In this directory run `node-gyp rebuild` and then `node ./addon.js` \ No newline at end of file diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/addon.cc b/src/5-async-work/async_pi_estimate/node-addon-api/addon.cc new file mode 100755 index 00000000..0c597696 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/addon.cc @@ -0,0 +1,15 @@ +#include +#include "async.h" // NOLINT(build/include) +#include "sync.h" // NOLINT(build/include) + +// Expose synchronous and asynchronous access to our +// Estimate() function +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "calculateSync"), + Napi::Function::New(env, CalculateSync)); + exports.Set(Napi::String::New(env, "calculateAsync"), + Napi::Function::New(env, CalculateAsync)); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/addon.js b/src/5-async-work/async_pi_estimate/node-addon-api/addon.js new file mode 100755 index 00000000..5ce039ca --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/addon.js @@ -0,0 +1,44 @@ +const addon = require('bindings')('addon'); +const calculations = process.argv[2] || 100000000; + +function printResult(type, pi, ms) { + console.log(type, 'method:'); + console.log('\tΟ€ β‰ˆ ' + pi + + ' (' + Math.abs(pi - Math.PI) + ' away from actual)'); + console.log('\tTook ' + ms + 'ms'); + console.log(); +} + +function runSync() { + const start = Date.now(); + // Estimate() will execute in the current thread, + // the next line won't return until it is finished + const result = addon.calculateSync(calculations); + printResult('Sync', result, Date.now() - start); +} + +function runAsync() { + // how many batches should we split the work in to? + const batches = process.argv[3] || 16; + let ended = 0; + let total = 0; + const start = Date.now(); + + function done (err, result) { + total += result; + + // have all the batches finished executing? + if (++ended === batches) { + printResult('Async', total / batches, Date.now() - start); + } + } + + // for each batch of work, request an async Estimate() for + // a portion of the total number of calculations + for (let i = 0; i < batches; i++) { + addon.calculateAsync(calculations / batches, done); + } +} + +runSync(); +runAsync(); diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/async.cc b/src/5-async-work/async_pi_estimate/node-addon-api/async.cc new file mode 100755 index 00000000..3626b0d8 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/async.cc @@ -0,0 +1,36 @@ +#include "async.h" // NOLINT(build/include) +#include +#include "pi_est.h" // NOLINT(build/include) + +class PiWorker : public Napi::AsyncWorker { + public: + PiWorker(Napi::Function& callback, int points) + : Napi::AsyncWorker(callback), points(points), estimate(0) {} + ~PiWorker() {} + + // Executed inside the worker-thread. + // It is not safe to access JS engine data structure + // here, so everything we need for input and output + // should go on `this`. + void Execute() { estimate = Estimate(points); } + + // Executed when the async work is complete + // this function will be run inside the main event loop + // so it is safe to use JS engine data again + void OnOK() { + Callback().Call({Env().Undefined(), Napi::Number::New(Env(), estimate)}); + } + + private: + int points; + double estimate; +}; + +// Asynchronous access to the `Estimate()` function +Napi::Value CalculateAsync(const Napi::CallbackInfo& info) { + int points = info[0].As().Uint32Value(); + Napi::Function callback = info[1].As(); + PiWorker* piWorker = new PiWorker(callback, points); + piWorker->Queue(); + return info.Env().Undefined(); +} diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/async.h b/src/5-async-work/async_pi_estimate/node-addon-api/async.h new file mode 100755 index 00000000..7ab85ce9 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/async.h @@ -0,0 +1,8 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ + +#include + +Napi::Value CalculateAsync(const Napi::CallbackInfo& info); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_ASYNC_H_ diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/binding.gyp b/src/5-async-work/async_pi_estimate/node-addon-api/binding.gyp new file mode 100755 index 00000000..e27a0013 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/binding.gyp @@ -0,0 +1,33 @@ +{ + "targets": [ + { + "target_name": "addon", + "sources": [ + "addon.cc", + "pi_est.cc", + "sync.cc", + "async.cc" + ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'include_dirs': [" + +/* +Estimate the value of Ο€ by using a Monte Carlo method. +Take `points` samples of random x and y values on a +[0,1][0,1] plane. Calculating the length of the diagonal +tells us whether the point lies inside, or outside a +quarter circle running from 0,1 to 1,0. The ratio of the +number of points inside to outside gives us an +approximation of Ο€/4. + +See https://en.wikipedia.org/wiki/File:Pi_30K.gif +for a visualization of how this works. +*/ + +inline int randall(unsigned int* p_seed) { +// windows has thread safe rand() +#ifdef _WIN32 + return rand(); // NOLINT(runtime/threadsafe_fn) +#else + return rand_r(p_seed); +#endif +} + +double Estimate(int points) { + int i = points; + int inside = 0; + unsigned int randseed = 1; + +#ifdef _WIN32 + srand(randseed); +#endif + + // unique seed for each run, for threaded use + unsigned int seed = randall(&randseed); + +#ifdef _WIN32 + srand(seed); +#endif + + while (i-- > 0) { + double x = randall(&seed) / static_cast(RAND_MAX); + double y = randall(&seed) / static_cast(RAND_MAX); + + // x & y and now values between 0 and 1 + // now do a pythagorean diagonal calculation + // `1` represents our 1/4 circle + if ((x * x) + (y * y) <= 1) inside++; + } + + // calculate ratio and multiply by 4 for Ο€ + return (inside / static_cast(points)) * 4; +} diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/pi_est.h b/src/5-async-work/async_pi_estimate/node-addon-api/pi_est.h new file mode 100755 index 00000000..8c35fe86 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/pi_est.h @@ -0,0 +1,6 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ + +double Estimate(int points); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_PI_EST_H_ diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/sync.cc b/src/5-async-work/async_pi_estimate/node-addon-api/sync.cc new file mode 100755 index 00000000..cc08b926 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/sync.cc @@ -0,0 +1,12 @@ +#include "sync.h" // NOLINT(build/include) +#include +#include "pi_est.h" // NOLINT(build/include) + +// Simple synchronous access to the `Estimate()` function +Napi::Value CalculateSync(const Napi::CallbackInfo& info) { + // expect a number as the first argument + int points = info[0].As().Uint32Value(); + double est = Estimate(points); + + return Napi::Number::New(info.Env(), est); +} diff --git a/src/5-async-work/async_pi_estimate/node-addon-api/sync.h b/src/5-async-work/async_pi_estimate/node-addon-api/sync.h new file mode 100755 index 00000000..86b51cc2 --- /dev/null +++ b/src/5-async-work/async_pi_estimate/node-addon-api/sync.h @@ -0,0 +1,8 @@ +#ifndef EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ +#define EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ + +#include + +Napi::Value CalculateSync(const Napi::CallbackInfo& info); + +#endif // EXAMPLES_ASYNC_PI_ESTIMATE_SYNC_H_ diff --git a/src/5-async-work/async_work_promise/napi/binding.c b/src/5-async-work/async_work_promise/napi/binding.c new file mode 100644 index 00000000..703f65a9 --- /dev/null +++ b/src/5-async-work/async_work_promise/napi/binding.c @@ -0,0 +1,170 @@ +#include +#include +#include + +// Limit ourselves to this many primes, starting at 2 +#define PRIME_COUNT 10 +#define CHECK(expr) \ + { \ + if ((expr) == 0) { \ + fprintf(stderr, "%s:%d: failed assertion `%s'\n", __FILE__, __LINE__, #expr); \ + fflush(stderr); \ + abort(); \ + } \ + } + +typedef struct linked_box_s linked_box_t; +struct linked_box_s { + linked_box_t* prev; + int the_prime; +}; + +typedef struct { + napi_async_work work; + napi_deferred deferred; + int data[PRIME_COUNT]; +} AddonData; + +// This function runs on a worker thread. It has no access to the JavaScript. +static void ExecuteWork(napi_env env, void* data) { + AddonData* addon_data = (AddonData*)data; + int idx_inner, idx_outer; + int prime_count = 0; + + // Find the first 1000 prime numbers using an extremely inefficient algorithm. + for (idx_outer = 2; prime_count < PRIME_COUNT; idx_outer++) { + for (idx_inner = 2; idx_inner < idx_outer; idx_inner++) { + if (idx_outer % idx_inner == 0) { + break; + } + } + if (idx_inner < idx_outer) { + continue; + } + + // Save the prime number to the heap. The JavaScript marshaller (CallJs) + // will free this item after having sent it to JavaScript. + addon_data->data[prime_count] = idx_outer; + ++prime_count; + } +} + +// This function runs on the main thread after `ExecuteWork` exits. +static void WorkComplete(napi_env env, napi_status status, void* data) { + if (status != napi_ok) { + return; + } + + AddonData* addon_data = (AddonData*)data; + napi_value array; + CHECK(napi_create_array(env, &array) == napi_ok); + + for (uint32_t idx = 0; idx < PRIME_COUNT; idx++) { + napi_value js_the_prime; + + // Convert the integer to a napi_value. + CHECK(napi_create_int32(env, addon_data->data[idx], &js_the_prime) == napi_ok); + + // Store the js value in the array. + CHECK(napi_set_element(env, array, idx, js_the_prime) == napi_ok); + } + CHECK(napi_resolve_deferred(env, addon_data->deferred, array) == napi_ok); + + // Clean up the work item associated with this run. + CHECK(napi_delete_async_work(env, addon_data->work) == napi_ok); + + // Set both values to NULL so JavaScript can order a new run of the thread. + addon_data->work = NULL; + addon_data->deferred = NULL; +} + +// Create a deferred promise and an async queue work item. +static napi_value StartWork(napi_env env, napi_callback_info info) { + napi_value work_name, promise; + AddonData* addon_data; + + // Retrieve the per-addon data. + CHECK(napi_get_cb_info(env, + info, + NULL, + NULL, + NULL, + (void**)(&addon_data)) == napi_ok); + + // Ensure that no work is currently in progress. + CHECK(addon_data->work == NULL && "Only one work item must exist at a time"); + + // Create a string to describe this asynchronous operation. + CHECK(napi_create_string_utf8(env, + "Node-API Deferred Promise from Async Work Item", + NAPI_AUTO_LENGTH, + &work_name) == napi_ok); + + // Create a deferred promise which we will resolve at the completion of the work. + CHECK(napi_create_promise(env, + &(addon_data->deferred), + &promise) == napi_ok); + + // Create an async work item, passing in the addon data, which will give the + // worker thread access to the above-created deferred promise. + CHECK(napi_create_async_work(env, + NULL, + work_name, + ExecuteWork, + WorkComplete, + addon_data, + &(addon_data->work)) == napi_ok); + + // Queue the work item for execution. + CHECK(napi_queue_async_work(env, addon_data->work) == napi_ok); + + // This causes created `promise` to be returned to JavaScript. + return promise; +} + +// Free the per-addon-instance data. +static void addon_getting_unloaded(napi_env env, void* data, void* hint) { + AddonData* addon_data = (AddonData*)data; + CHECK(addon_data->work == NULL && + "No work item in progress at module unload"); + free(addon_data); +} + +// The commented-out return type and the commented out formal function +// parameters below help us keep in mind the signature of the addon +// initialization function. We write the body as though the return value were as +// commented below and as though there were parameters passed in as commented +// below. +/*napi_value*/ NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) { + + // Define addon-level data associated with this instance of the addon. + AddonData* addon_data = (AddonData*)malloc(sizeof(*addon_data)); + addon_data->work = NULL; + + // Define the properties that will be set on exports. + napi_property_descriptor start_work = { + "startWork", + NULL, + StartWork, + NULL, + NULL, + NULL, + napi_default, + addon_data + }; + + // Decorate exports with the above-defined properties. + CHECK(napi_define_properties(env, exports, 1, &start_work) == napi_ok); + + // Associate the addon data with the exports object, to make sure that when + // the addon gets unloaded our data gets freed. + CHECK(napi_wrap(env, + exports, + addon_data, + addon_getting_unloaded, + NULL, + NULL) == napi_ok); + + // Return the decorated exports object. + return exports; +} diff --git a/src/5-async-work/async_work_promise/napi/binding.gyp b/src/5-async-work/async_work_promise/napi/binding.gyp new file mode 100644 index 00000000..413621ad --- /dev/null +++ b/src/5-async-work/async_work_promise/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.c' ] + } + ] +} diff --git a/src/5-async-work/async_work_promise/napi/index.js b/src/5-async-work/async_work_promise/napi/index.js new file mode 100644 index 00000000..ae4e3773 --- /dev/null +++ b/src/5-async-work/async_work_promise/napi/index.js @@ -0,0 +1,10 @@ +// Use the "bindings" package to locate the native bindings. +const binding = require('bindings')('binding'); + +// Call the function "startWork" which the native bindings library exposes. +// The function returns a promise which will be resolved at the complete of the +// work with an array of worked out primes. This resolution simply prints them out. +binding.startWork() + .then((thePrimes) => { + console.log("Received primes from completed work: " + thePrimes) + }); diff --git a/src/5-async-work/async_work_promise/napi/package.json b/src/5-async-work/async_work_promise/napi/package.json new file mode 100644 index 00000000..4b93c374 --- /dev/null +++ b/src/5-async-work/async_work_promise/napi/package.json @@ -0,0 +1,17 @@ +{ + "name": "async_work_promise", + "version": "0.0.0", + "description": "Calling into JS from the thread pool", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + }, + "scripts": { + "test": "node index.js" + }, + "gypfile": true +} diff --git a/src/5-async-work/async_work_promise/node-addon-api/addon.cc b/src/5-async-work/async_work_promise/node-addon-api/addon.cc new file mode 100644 index 00000000..ccd2d7a3 --- /dev/null +++ b/src/5-async-work/async_work_promise/node-addon-api/addon.cc @@ -0,0 +1,32 @@ +#include "napi.h" +#include "worker.h" + +Napi::Value DoHeavyMath(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (!info[0].IsNumber()) { + Napi::TypeError::New(env, "num1 must be a number") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + uint32_t num_1 = info[0].As().Uint32Value(); + + if (!info[1].IsNumber()) { + Napi::TypeError::New(env, "num2 must be a number") + .ThrowAsJavaScriptException(); + return env.Undefined(); + } + uint32_t num_2 = info[1].As().Uint32Value(); + + DoHeavyMathWorker* worker = new DoHeavyMathWorker(env, num_1, num_2); + worker->Queue(); + return worker->GetPromise(); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "doHeavyMath"), + Napi::Function::New(env, DoHeavyMath)); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/5-async-work/async_work_promise/node-addon-api/binding.gyp b/src/5-async-work/async_work_promise/node-addon-api/binding.gyp new file mode 100644 index 00000000..70c2618d --- /dev/null +++ b/src/5-async-work/async_work_promise/node-addon-api/binding.gyp @@ -0,0 +1,32 @@ +{ + "targets": [{ + "target_name": "addon", + "sources": [ + "addon.cc" + ], + "include_dirs": [ + " +#include +#include + +// Limit ourselves to this many primes, starting at 2 +#define PRIME_COUNT 100000 +#define REPORT_EVERY 1000 + +typedef struct { + napi_async_work work; + napi_threadsafe_function tsfn; +} AddonData; + + +// This function is responsible for converting data coming in from the worker +// thread to napi_value items that can be passed into JavaScript, and for +// calling the JavaScript function. +static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) { + // This parameter is not used. + (void) context; + napi_status status; + + // Retrieve the prime from the item created by the worker thread. + int the_prime = *(int*)data; + + // env and js_cb may both be NULL if Node.js is in its cleanup phase, and + // items are left over from earlier thread-safe calls from the worker thread. + // When env is NULL, we simply skip over the call into Javascript and free the + // items. + if (env != NULL) { + napi_value undefined, js_the_prime; + + // Convert the integer to a napi_value. + status = napi_create_int32(env, the_prime, &js_the_prime); + assert(status == napi_ok); + + // Retrieve the JavaScript `undefined` value so we can use it as the `this` + // value of the JavaScript function call. + status = napi_get_undefined(env, &undefined); + assert(status == napi_ok); + + // Call the JavaScript function and pass it the prime that the secondary + // thread found. + status = napi_call_function(env, + undefined, + js_cb, + 1, + &js_the_prime, + NULL); + assert(status == napi_ok); + } + + // Free the item created by the worker thread. + free(data); +} + +// This function runs on a worker thread. It has no access to the JavaScript +// environment except through the thread-safe function. +static void ExecuteWork(napi_env env, void* data) { + AddonData* addon_data = (AddonData*)data; + int idx_inner, idx_outer; + int prime_count = 0; + napi_status status; + + // We bracket the use of the thread-safe function by this thread by a call to + // napi_acquire_threadsafe_function() here, and by a call to + // napi_release_threadsafe_function() immediately prior to thread exit. + status = napi_acquire_threadsafe_function(addon_data->tsfn); + assert(status == napi_ok); + + // Find the first 1000 prime numbers using an extremely inefficient algorithm. + for (idx_outer = 2; prime_count < PRIME_COUNT; idx_outer++) { + for (idx_inner = 2; idx_inner < idx_outer; idx_inner++) { + if (idx_outer % idx_inner == 0) { + break; + } + } + if (idx_inner < idx_outer) { + continue; + } + + // We found a prime. If it's the tenth since the last time we sent one to + // JavaScript, send it to JavaScript. + if (!(++prime_count % REPORT_EVERY)) { + + // Save the prime number to the heap. The JavaScript marshaller (CallJs) + // will free this item after having sent it to JavaScript. + int* the_prime = malloc(sizeof(*the_prime)); + *the_prime = idx_outer; + + // Initiate the call into JavaScript. The call into JavaScript will not + // have happened when this function returns, but it will be queued. + status = napi_call_threadsafe_function(addon_data->tsfn, + the_prime, + napi_tsfn_blocking); + assert(status == napi_ok); + } + } + + // Indicate that this thread will make no further use of the thread-safe function. + status = napi_release_threadsafe_function(addon_data->tsfn, + napi_tsfn_release); + assert(status == napi_ok); +} + +// This function runs on the main thread after `ExecuteWork` exits. +static void WorkComplete(napi_env env, napi_status status, void* data) { + AddonData* addon_data = (AddonData*)data; + + // Clean up the thread-safe function and the work item associated with this + // run. + status = napi_release_threadsafe_function(addon_data->tsfn, + napi_tsfn_release); + assert(status == napi_ok); + status = napi_delete_async_work(env, addon_data->work); + assert(status == napi_ok); + + // Set both values to NULL so JavaScript can order a new run of the thread. + addon_data->work = NULL; + addon_data->tsfn = NULL; +} + +// Create a thread-safe function and an async queue work item. We pass the +// thread-safe function to the async queue work item so the latter might have a +// chance to call into JavaScript from the worker thread on which the +// ExecuteWork callback runs. +static napi_value StartThread(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_cb, work_name; + AddonData* addon_data; + napi_status status; + + // Retrieve the JavaScript callback we should call with items generated by the + // worker thread, and the per-addon data. + status = napi_get_cb_info(env, + info, + &argc, + &js_cb, + NULL, + (void**)(&addon_data)); + assert(status == napi_ok); + + // Ensure that no work is currently in progress. + assert(addon_data->work == NULL && "Only one work item must exist at a time"); + + // Create a string to describe this asynchronous operation. + status = napi_create_string_utf8(env, + "Node-API Thread-safe Call from Async Work Item", + NAPI_AUTO_LENGTH, + &work_name); + assert(status == napi_ok); + + // Convert the callback retrieved from JavaScript into a thread-safe function + // which we can call from a worker thread. + status = napi_create_threadsafe_function(env, + js_cb, + NULL, + work_name, + 0, + 1, + NULL, + NULL, + NULL, + CallJs, + &(addon_data->tsfn)); + assert(status == napi_ok); + + // Create an async work item, passing in the addon data, which will give the + // worker thread access to the above-created thread-safe function. + status = napi_create_async_work(env, + NULL, + work_name, + ExecuteWork, + WorkComplete, + addon_data, + &(addon_data->work)); + assert(status == napi_ok); + + // Queue the work item for execution. + status = napi_queue_async_work(env, addon_data->work); + assert(status == napi_ok); + + // This causes `undefined` to be returned to JavaScript. + return NULL; +} + +// Free the per-addon-instance data. +static void addon_getting_unloaded(napi_env env, void* data, void* hint) { + AddonData* addon_data = (AddonData*)data; + assert(addon_data->work == NULL && + "No work item in progress at module unload"); + free(addon_data); +} + +// The commented-out return type and the commented out formal function +// parameters below help us keep in mind the signature of the addon +// initialization function. We write the body as though the return value were as +// commented below and as though there were parameters passed in as commented +// below. +/*napi_value*/ NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) { + napi_status status; + // Define addon-level data associated with this instance of the addon. + AddonData* addon_data = (AddonData*)malloc(sizeof(*addon_data)); + addon_data->work = NULL; + + // Define the properties that will be set on exports. + napi_property_descriptor start_work = { + "startThread", + NULL, + StartThread, + NULL, + NULL, + NULL, + napi_default, + addon_data + }; + + // Decorate exports with the above-defined properties. + status = napi_define_properties(env, exports, 1, &start_work); + assert(status == napi_ok); + + // Associate the addon data with the exports object, to make sure that when + // the addon gets unloaded our data gets freed. + status = napi_wrap(env, + exports, + addon_data, + addon_getting_unloaded, + NULL, + NULL); + assert(status == napi_ok); + + // Return the decorated exports object. + return exports; +} diff --git a/src/5-async-work/async_work_thread_safe_function/napi/binding.gyp b/src/5-async-work/async_work_thread_safe_function/napi/binding.gyp new file mode 100644 index 00000000..413621ad --- /dev/null +++ b/src/5-async-work/async_work_thread_safe_function/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'binding', + 'sources': [ 'binding.c' ] + } + ] +} diff --git a/src/5-async-work/async_work_thread_safe_function/napi/index.js b/src/5-async-work/async_work_thread_safe_function/napi/index.js new file mode 100644 index 00000000..85e9d0dc --- /dev/null +++ b/src/5-async-work/async_work_thread_safe_function/napi/index.js @@ -0,0 +1,8 @@ +// Use the "bindings" package to locate the native bindings. +const binding = require('bindings')('binding'); + +// Call the function "startThread" which the native bindings library exposes. +// The function accepts a callback which it will call from the worker thread and +// into which it will pass prime numbers. This callback simply prints them out. +binding.startThread((thePrime) => + console.log("Received prime from secondary thread: " + thePrime)); diff --git a/src/5-async-work/async_work_thread_safe_function/napi/package.json b/src/5-async-work/async_work_thread_safe_function/napi/package.json new file mode 100644 index 00000000..cdcc7e6f --- /dev/null +++ b/src/5-async-work/async_work_thread_safe_function/napi/package.json @@ -0,0 +1,17 @@ +{ + "name": "async_work_thread_safe_function", + "version": "0.0.0", + "description": "Calling into JS from the thread pool", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + }, + "scripts": { + "test": "node index.js" + }, + "gypfile": true +} diff --git a/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/binding.gyp b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/binding.gyp new file mode 100644 index 00000000..f15b5c44 --- /dev/null +++ b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/binding.gyp @@ -0,0 +1,28 @@ +{ + "targets": [ + { + "target_name": "dispatcher", + "sources": [ "src/binding.cc" ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'include_dirs': [" { + console.log('opened'); +}); + +socket.on('message', (message) => { + console.log(`message: ${message}`); +}); + +socket.on('close', () => { + console.log('closed'); +}); diff --git a/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/package.json b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/package.json new file mode 100644 index 00000000..59225bef --- /dev/null +++ b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "call-js-from-async-worker-execute", + "version": "0.0.0", + "description": "Node.js Addons - calls JS from AsyncWorker::Execute", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "node-addon-api": "*", + "bindings": "*" + } +} diff --git a/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/src/binding.cc b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/src/binding.cc new file mode 100644 index 00000000..cd2321c9 --- /dev/null +++ b/src/5-async-work/call-js-from-async-worker-execute/node-addon-api/src/binding.cc @@ -0,0 +1,101 @@ +#include + +#include +#include + +class Dispatcher : public Napi::AsyncWorker { + public: + Dispatcher(Napi::Env env, Napi::Function& callback) + : Napi::AsyncWorker(env), + callback_(callback), + tsfn_{ + Napi::ThreadSafeFunction::New(env, callback, "Dispatcher", 0, 1)} {} + + virtual ~Dispatcher() override { tsfn_.Release(); } + + void Execute() override { + // Since this method executes on a thread that is different from the main + // thread, we can't directly call into JavaScript. To trigger a call into + // JavaScript from this thread, ThreadSafeFunction needs to be used to + // communicate with the main thread, so that the main thread can invoke the + // JavaScript function. + + napi_status status = + tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) { + js_callback.Call({Napi::String::New(env, "open")}); + }); + if (status != napi_ok) { + SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + return; + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) { + js_callback.Call( + {Napi::String::New(env, "message"), Napi::String::New(env, "data1")}); + }); + if (status != napi_ok) { + SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + return; + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) { + js_callback.Call( + {Napi::String::New(env, "message"), Napi::String::New(env, "data2")}); + }); + if (status != napi_ok) { + SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + return; + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) { + js_callback.Call( + {Napi::String::New(env, "message"), Napi::String::New(env, "data3")}); + }); + if (status != napi_ok) { + SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + return; + } + + status = tsfn_.BlockingCall([](Napi::Env env, Napi::Function js_callback) { + js_callback.Call({Napi::String::New(env, "close")}); + }); + if (status != napi_ok) { + SetError("Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + return; + } + } + + void OnError(Napi::Error const& error) override { + callback_.Call({Napi::String::New(Env(), "error"), error.Value()}); + } + + private: + Napi::Function callback_; + Napi::ThreadSafeFunction tsfn_; +}; + +void Dispatch(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 1 || !info[0].IsFunction()) { + Napi::TypeError::New( + env, "The first argument needs to be the callback function.") + .ThrowAsJavaScriptException(); + return; + } + + Napi::Function callback = info[0].As(); + + Dispatcher* dispatcher = new Dispatcher(env, callback); + dispatcher->Queue(); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set("dispatch", Napi::Function::New(env, Dispatch)); + return exports; +} + +NODE_API_MODULE(dispatcher, Init) diff --git a/src/5-async-work/napi-asyncworker-example/node-addon-api/README.md b/src/5-async-work/napi-asyncworker-example/node-addon-api/README.md new file mode 100644 index 00000000..b9827629 --- /dev/null +++ b/src/5-async-work/napi-asyncworker-example/node-addon-api/README.md @@ -0,0 +1,14 @@ +# Node-API AsyncWorker Example + +This is an example project showing how to use the Node.js Node-API AsyncWorker class + +A tutorial describing this project can be found at the [Node-API Resource](https://napi.inspiredware.com/special-topics/asyncworker.html). + +To build and run this program on your system, clone it to your computer and run these two commands inside your clone: + +``` +npm install +npm test +``` + +> You need to have Node 10.5.0 or later installed. \ No newline at end of file diff --git a/src/5-async-work/napi-asyncworker-example/node-addon-api/binding.gyp b/src/5-async-work/napi-asyncworker-example/node-addon-api/binding.gyp new file mode 100644 index 00000000..aeaa9690 --- /dev/null +++ b/src/5-async-work/napi-asyncworker-example/node-addon-api/binding.gyp @@ -0,0 +1,20 @@ +{ + 'targets': [ + { + 'target_name': 'napi-asyncworker-example-native', + 'sources': [ 'src/RunSimpleAsyncWorker.cc', 'src/SimpleAsyncWorker.cc' ], + 'include_dirs': ["(); + Function callback = info[1].As(); + SimpleAsyncWorker* asyncWorker = new SimpleAsyncWorker(callback, runTime); + asyncWorker->Queue(); + std::string msg = + "SimpleAsyncWorker for " + std::to_string(runTime) + " seconds queued."; + return String::New(info.Env(), msg.c_str()); +}; + +Object Init(Env env, Object exports) { + exports["runSimpleAsyncWorker"] = Function::New( + env, runSimpleAsyncWorker, std::string("runSimpleAsyncWorker")); + return exports; +} + +NODE_API_MODULE(addon, Init) \ No newline at end of file diff --git a/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.cc b/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.cc new file mode 100644 index 00000000..afb92470 --- /dev/null +++ b/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.cc @@ -0,0 +1,17 @@ +#include "SimpleAsyncWorker.h" +#include +#include + +SimpleAsyncWorker::SimpleAsyncWorker(Function& callback, int runTime) + : AsyncWorker(callback), runTime(runTime){}; + +void SimpleAsyncWorker::Execute() { + std::this_thread::sleep_for(std::chrono::seconds(runTime)); + if (runTime == 4) SetError("Oops! Failed after 'working' 4 seconds."); +}; + +void SimpleAsyncWorker::OnOK() { + std::string msg = "SimpleAsyncWorker returning after 'working' " + + std::to_string(runTime) + " seconds."; + Callback().Call({Env().Null(), String::New(Env(), msg)}); +}; \ No newline at end of file diff --git a/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.h b/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.h new file mode 100644 index 00000000..eee706a4 --- /dev/null +++ b/src/5-async-work/napi-asyncworker-example/node-addon-api/src/SimpleAsyncWorker.h @@ -0,0 +1,15 @@ +#pragma once +#include +using namespace Napi; + +class SimpleAsyncWorker : public AsyncWorker { + public: + SimpleAsyncWorker(Function& callback, int runTime); + virtual ~SimpleAsyncWorker(){}; + + void Execute(); + void OnOK(); + + private: + int runTime; +}; \ No newline at end of file diff --git a/src/5-async-work/napi-asyncworker-example/node-addon-api/test/Test.js b/src/5-async-work/napi-asyncworker-example/node-addon-api/test/Test.js new file mode 100644 index 00000000..a9ec9c69 --- /dev/null +++ b/src/5-async-work/napi-asyncworker-example/node-addon-api/test/Test.js @@ -0,0 +1,18 @@ +const runWorker = require('../build/Release/napi-asyncworker-example-native'); + +let result = runWorker.runSimpleAsyncWorker(2, AsyncWorkerCompletion); +console.log("runSimpleAsyncWorker returned '"+result+"'."); + +result = runWorker.runSimpleAsyncWorker(4, AsyncWorkerCompletion); +console.log("runSimpleAsyncWorker returned '"+result+"'."); + +result = runWorker.runSimpleAsyncWorker(8, AsyncWorkerCompletion); +console.log("runSimpleAsyncWorker returned '"+result+"'."); + +function AsyncWorkerCompletion (err, result) { + if (err) { + console.log("SimpleAsyncWorker returned an error: ", err); + } else { + console.log("SimpleAsyncWorker returned '"+result+"'."); + } +}; diff --git a/src/6-threadsafe-function/promise-callback-demo/node-addon-api/README.md b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/README.md new file mode 100644 index 00000000..62340ff9 --- /dev/null +++ b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/README.md @@ -0,0 +1,7 @@ +# Node-API Promise Callback Demo + +To build and run this program on your system, clone it to your computer and run these two commands inside your clone: + +``` +npm install +``` diff --git a/src/6-threadsafe-function/promise-callback-demo/node-addon-api/binding.gyp b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/binding.gyp new file mode 100644 index 00000000..03e63b35 --- /dev/null +++ b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/binding.gyp @@ -0,0 +1,20 @@ +{ + 'targets': [ + { + 'target_name': 'promise-callback-demo', + 'sources': [ 'src/promise_callback_demo.cc' ], + 'include_dirs': ["= 10.16.0" + }, + "version": "1.0.0", + "description": "An promise callback demo", + "author": "Node-API Team", + "license": "ISC" +} diff --git a/src/6-threadsafe-function/promise-callback-demo/node-addon-api/src/promise_callback_demo.cc b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/src/promise_callback_demo.cc new file mode 100644 index 00000000..190cc171 --- /dev/null +++ b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/src/promise_callback_demo.cc @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +using namespace Napi; + +class PromiseCallbackDemo; +using Context = PromiseCallbackDemo; +using DataType = std::promise; +void CallJs(Napi::Env env, Function callback, Context* context, DataType* data); +using TSFN = TypedThreadSafeFunction; +using FinalizerDataType = void; + +class PromiseCallbackDemo : public Napi::ObjectWrap { + public: + PromiseCallbackDemo(const Napi::CallbackInfo&); + + static Napi::Function GetClass(Napi::Env); + + std::thread native_thread; + TSFN tsfn; +}; + +void thread_entry(PromiseCallbackDemo* obj) { + for (auto i = 0; i < 10; ++i) { + std::promise promise; + auto future = promise.get_future(); + obj->tsfn.NonBlockingCall(&promise); + + // With exceptions disabled, STL calls will abort the thread, since we + // cannot try/catch, as the calls to `std::make_exception_ptr()` will abort. + try { + auto result = future.get(); + std::cout << "Result from JS, call " << i << ": " << result << "\n"; + } catch (const std::exception& e) { + std::cout << "Error from JS, call " << i << ": " << e.what() << "\n"; + } + } + obj->tsfn.Release(); +} + +void CallJs(Napi::Env env, + Function callback, + Context* context, + DataType* data) { + // Is the JavaScript environment still available to call into, eg. the TSFN is + // not aborted + if (env != nullptr) { + auto jsPromise = callback.Call({}).As(); + auto then = jsPromise.Get("then").As(); + // Attach the resolve handler. + // TODO proper error handling (add failure handler) + then.Call(jsPromise, + {Napi::Function::New(env, + [=](const CallbackInfo& info) { + auto result = + info[0].As().Utf8Value(); + data->set_value(result); + }), + Napi::Function::New(env, [=](const CallbackInfo& info) { + auto result = info[0].As().Utf8Value(); + data->set_exception(std::make_exception_ptr( + std::runtime_error("Error in jsCallback"))); + })}); + + } else { + data->set_exception(std::make_exception_ptr( + std::runtime_error("Environment is shut down"))); + } +} + +PromiseCallbackDemo::PromiseCallbackDemo(const Napi::CallbackInfo& info) + : ObjectWrap(info) { + Napi::Env env = info.Env(); + + // Argument checking + if (info.Length() < 1) { + Napi::TypeError::New(env, "Wrong number of arguments") + .ThrowAsJavaScriptException(); + return; + } + + if (!info[0].IsFunction()) { + Napi::TypeError::New(env, "First argument must be a function") + .ThrowAsJavaScriptException(); + return; + } + + // Since we are storing the std::thread on this ObjectWrap instance, we should + // reference it so it doesn't get garbage collected, and unreference it on + // TSFN finalize. + Ref(); + + tsfn = TSFN::New( + env, + info[0].As(), // JavaScript function called asynchronously + "PromiseCallbackDemo", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + this, // Context + [](Napi::Env, + FinalizerDataType*, + Context* ctx) { // Finalizer used to clean threads up + ctx->Unref(); + ctx->native_thread.join(); + }); + + native_thread = std::thread(thread_entry, this); +} + +Napi::Function PromiseCallbackDemo::GetClass(Napi::Env env) { + return DefineClass(env, "PromiseCallbackDemo", {}); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports["PromiseCallbackDemo"] = PromiseCallbackDemo::GetClass(env); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/src/6-threadsafe-function/promise-callback-demo/node-addon-api/test/index.js b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/test/index.js new file mode 100644 index 00000000..cf30387b --- /dev/null +++ b/src/6-threadsafe-function/promise-callback-demo/node-addon-api/test/index.js @@ -0,0 +1,11 @@ +const { PromiseCallbackDemo } = require('../build/Release/promise-callback-demo'); + +let shouldFail = false; +// Resolve or reject a promise with an ISO date format after 100 milliseconds +function jsCallback() { + return new Promise((resolve, reject) => setTimeout(() => { + ((shouldFail = !shouldFail) ? reject : resolve)(new Date().toISOString()); + }, 100)); +} + +new PromiseCallbackDemo(jsCallback); diff --git a/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.cc b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.cc new file mode 100644 index 00000000..30c7ad47 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.cc @@ -0,0 +1,106 @@ +#include +#include +#include "napi.h" + +constexpr size_t ARRAY_LENGTH = 10; + +// Data structure representing our thread-safe function context. +struct TsfnContext { + TsfnContext(Napi::Env env) : deferred(Napi::Promise::Deferred::New(env)) { + for (size_t i = 0; i < ARRAY_LENGTH; ++i) ints[i] = i; + }; + + // Native Promise returned to JavaScript + Napi::Promise::Deferred deferred; + + // Native thread + std::thread nativeThread; + + // Some data to pass around + int ints[ARRAY_LENGTH]; + + Napi::ThreadSafeFunction tsfn; +}; + +// The thread entry point. This takes as its arguments the specific +// threadsafe-function context created inside the main thread. +void threadEntry(TsfnContext* context); + +// The thread-safe function finalizer callback. This callback executes +// at destruction of thread-safe function, taking as arguments the finalizer +// data and threadsafe-function context. +void FinalizerCallback(Napi::Env env, void* finalizeData, TsfnContext* context); + +// Exported JavaScript function. Creates the thread-safe function and native +// thread. Promise is resolved in the thread-safe function's finalizer. +Napi::Value CreateTSFN(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Construct context data + auto testData = new TsfnContext(env); + + // Create a new ThreadSafeFunction. + testData->tsfn = Napi::ThreadSafeFunction::New( + env, // Environment + info[0].As(), // JS function from caller + "TSFN", // Resource name + 0, // Max queue size (0 = unlimited). + 1, // Initial thread count + testData, // Context, + FinalizerCallback, // Finalizer + (void*)nullptr // Finalizer data + ); + testData->nativeThread = std::thread(threadEntry, testData); + + // Return the deferred's Promise. This Promise is resolved in the thread-safe + // function's finalizer callback. + return testData->deferred.Promise(); +} + +// The thread entry point. This takes as its arguments the specific +// threadsafe-function context created inside the main thread. +void threadEntry(TsfnContext* context) { + // This callback transforms the native addon data (int *data) to JavaScript + // values. It also receives the treadsafe-function's registered callback, and + // may choose to call it. + auto callback = [](Napi::Env env, Napi::Function jsCallback, int* data) { + jsCallback.Call({Napi::Number::New(env, *data)}); + }; + + for (size_t index = 0; index < ARRAY_LENGTH; ++index) { + // Perform a call into JavaScript. + napi_status status = + context->tsfn.BlockingCall(&context->ints[index], callback); + + if (status != napi_ok) { + Napi::Error::Fatal( + "ThreadEntry", + "Napi::ThreadSafeNapi::Function.BlockingCall() failed"); + } + // Sleep for some time. + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + + // Release the thread-safe function. This decrements the internal thread + // count, and will perform finalization since the count will reach 0. + context->tsfn.Release(); +} + +void FinalizerCallback(Napi::Env env, + void* finalizeData, + TsfnContext* context) { + // Join the thread + context->nativeThread.join(); + + // Resolve the Promise previously returned to JS via the CreateTSFN method. + context->deferred.Resolve(Napi::Boolean::New(env, true)); + delete context; +} + +// Addon entry point +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports["createTSFN"] = Napi::Function::New(env, CreateTSFN); + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.js b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.js new file mode 100644 index 00000000..4ee0d938 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/addon.js @@ -0,0 +1,9 @@ +const { createTSFN } = require('bindings')('addon'); + +const callback = (...args) => { + console.log(new Date, ...args); +}; + +void async function() { + console.log(await createTSFN(callback)); +}(); diff --git a/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/binding.gyp b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/binding.gyp new file mode 100644 index 00000000..492c7035 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_counting/node-addon-api/binding.gyp @@ -0,0 +1,21 @@ +{ + 'targets': [{ + 'target_name': 'addon', + 'defines': ['V8_DEPRECATION_WARNINGS=1'], + 'sources': ['addon.cc'], + 'include_dirs': ["= 10.16.0" + } +} diff --git a/src/6-threadsafe-function/thread_safe_function_round_trip/napi/binding.gyp b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/binding.gyp new file mode 100644 index 00000000..d1822682 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/binding.gyp @@ -0,0 +1,8 @@ +{ + 'targets': [ + { + 'target_name': 'round_trip', + 'sources': [ 'round_trip.c' ] + } + ] +} diff --git a/src/6-threadsafe-function/thread_safe_function_round_trip/napi/index.js b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/index.js new file mode 100644 index 00000000..014f7f88 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/index.js @@ -0,0 +1,14 @@ +const bindings = require('bindings')('round_trip'); + +bindings.startThread(item => { + const thePrime = item.prime; + console.log('The prime: ' + thePrime); + + // Answer the call with a 90% probability of returning true somewhere between + // 200 and 400 ms from now. + setTimeout(() => { + const theAnswer = (Math.random() > 0.1); + console.log(thePrime + ': answering with ' + theAnswer); + bindings.registerReturnValue(item, theAnswer); + }, Math.random() * 200 + 200); +}); diff --git a/src/6-threadsafe-function/thread_safe_function_round_trip/napi/package.json b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/package.json new file mode 100644 index 00000000..3f09af52 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/package.json @@ -0,0 +1,17 @@ +{ + "name": "thread_safe_function_round_trip", + "version": "0.0.0", + "description": "Thread-safe Function Example With JavaScript Round Trip", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "engines": { + "node": ">= 10.6.0" + }, + "scripts": { + "test": "node index.js" + }, + "gypfile": true +} diff --git a/src/6-threadsafe-function/thread_safe_function_round_trip/napi/round_trip.c b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/round_trip.c new file mode 100644 index 00000000..4da8f072 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_round_trip/napi/round_trip.c @@ -0,0 +1,426 @@ +#include +#include +#include +#include +#include + +#define CHECK(expr) \ + { \ + if ((expr) == 0) { \ + fprintf(stderr, "%s:%d: failed assertion `%s'\n", __FILE__, __LINE__, #expr); \ + fflush(stderr); \ + abort(); \ + } \ + } + +#define REPORT_EVERY 1000 + +// An item that will be generated from the thread, passed into JavaScript, and +// ultimately marked as resolved when the JavaScript passes it back into the +// addon instance with a return value. +typedef struct ThreadItem { + // This field is read-only once set, so it need not be protected by the mutex. + int the_prime; + + // This field is only accessed from the secondary thread, so it also need not + // be protected by the mutex. + struct ThreadItem* next; + + // These two values must be protected by the mutex. + bool call_has_returned; + bool return_value; +} ThreadItem; + +// The data associated with an instance of the addon. This takes the place of +// global static variables, while allowing multiple instances of the addon to +// co-exist. +typedef struct { + uv_mutex_t check_status_mutex; + uv_thread_t the_thread; + napi_threadsafe_function tsfn; + napi_ref thread_item_constructor; + bool js_accepts; +} AddonData; + +// This function is responsible for converting the native data coming in from +// the secondary thread to JavaScript values, and for calling the JavaScript +// function. It may also be called with `env` and `js_cb` set to `NULL` when +// Node.js is terminating and there are items coming in from the secondary +// thread left to process. In that case, this function does nothing, since it is +// the secondary thread that frees the items. +static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) { + AddonData* addon_data = (AddonData*)context; + napi_value constructor; + + // The semantics of this example are such that, once the JavaScript returns + // `false`, the `ThreadItem` structures can no longer be accessed, because the + // thread terminates and frees them all. Thus, we record the instant when + // JavaScript returns `false` by setting `addon_data->js_accepts` to `false` + // in `RegisterReturnValue` below, and we use the value here to decide whether + // the data coming in from the secondary thread is stale or not. + if (addon_data->js_accepts && !(env == NULL || js_cb == NULL)) { + napi_value undefined, js_thread_item; + // Retrieve the JavaScript `undefined` value. This will serve as the `this` + // value for the function call. + CHECK(napi_get_undefined(env, &undefined) == napi_ok); + + // Retrieve the constructor for the JavaScript class from which the item + // holding the native data will be constructed. + CHECK(napi_get_reference_value(env, + addon_data->thread_item_constructor, + &constructor) == napi_ok); + + // Construct a new instance of the JavaScript class to hold the native item. + CHECK(napi_new_instance(env, + constructor, + 0, + NULL, + &js_thread_item) == napi_ok); + + // Associate the native item with the newly constructed JavaScript object. + // We assume that the JavaScript side will eventually pass this JavaScript + // object back to us via `RegisterReturnValue`, which will allow the + // eventual deallocation of the native data. That's why we do not provide a + // finalizer here. + CHECK(napi_wrap(env, js_thread_item, data, NULL, NULL, NULL) == napi_ok); + + // Call the JavaScript function with the item as wrapped into an instance of + // the JavaScript `ThreadItem` class and the prime. + CHECK(napi_call_function(env, + undefined, + js_cb, + 1, + &js_thread_item, + NULL) == napi_ok); + } +} + +// When the thread is finished we join it to prevent memory leaks. We can safely +// set `addon_data->tsfn` to NULL, because the thread-safe function will be +// cleaned up in the background in response to the secondary thread having +// called `napi_release_threadsafe_function()`. +static void ThreadFinished(napi_env env, void* data, void* context) { + (void) context; + AddonData* addon_data = (AddonData*)data; + CHECK(uv_thread_join(&(addon_data->the_thread)) == 0); + addon_data->tsfn = NULL; +} + +// The secondary thread produces prime numbers using a very inefficient +// algorithm and calls into JavaScript with every REPORT_EVERYth prime number. +// After each call it checks whether any of the previous calls have produced a +// return value, and, if so, whether that return value is `false`. A `false` +// return value indicates that the JavaScript side is no longer interested in +// receiving any values and that the thread and the thread-safe function are to +// be cleaned up. On the JavaScript thread, this is marked in +// `addon_data->js_accepts`. When set to `false`, the JavaScript thread will not +// access the thread items any further, so they can be safely deleted on this +// thread. +static void PrimeThread(void* data) { + AddonData* addon_data = (AddonData*) data; + int idx_outer, idx_inner; + int prime_count = 0; + ThreadItem* first = NULL; + ThreadItem* current = NULL; + ThreadItem* previous = NULL; + ThreadItem* returned = NULL; + + // Check each integer whether it's a prime. + for (idx_outer = 2 ;; idx_outer++) { + + // Check whether `idx_outer` is divisible by anything up to and not + // including itself. + for (idx_inner = 2; + idx_inner < idx_outer && idx_outer % idx_inner != 0; + idx_inner++); + + // If we find a prime, and it is REPORT_EVERY primes away from the previous + // prime we found, then we send it to JavaScript. + if (idx_inner >= idx_outer && (++prime_count % REPORT_EVERY) == 0) { + // Create a new thread item and attach it to the list of outstanding + // `ThreadItem` structures representing calls into JavaScript for which + // no return value has yet been established. + current = memset(malloc(sizeof(*current)), 0, sizeof(*current)); + current->the_prime = idx_outer; + current->call_has_returned = false; + current->return_value = false; + current->next = first; + first = current; + + // Pass the new item into JavaScript. + CHECK(napi_call_threadsafe_function(addon_data->tsfn, + first, + napi_tsfn_blocking) == napi_ok); + } + + // Pass over all outstanding thread items and check whether any of them have + // returned. + for (current = first, previous = NULL, returned = NULL; + current != NULL && returned == NULL; + previous = current, + current = current->next) { + uv_mutex_lock(&(addon_data->check_status_mutex)); + if (current->call_has_returned) { + // Unhook the call that has returned from the list. + if (previous != NULL) { + previous->next = current->next; + } else { + first = current->next; + } + returned = current; + } + uv_mutex_unlock(&(addon_data->check_status_mutex)); + } + + // Process a return value. Free the `ThreadItem` that returned it, and break + // out of the loop if the return value was `false`. + if (returned != NULL) { + // Save the return value to a local variable because we have to check it + // after having freed the structure wherein it is stored. + bool return_value = returned->return_value; + free(returned); + if (!return_value) { + break; + } + } + } + + // Before terminating the thread we free the remaining queue items. `CallJs` + // will be called with pointers to these items, perhaps after this thread has + // already freed them, but that's OK, because on the JavaScript thread + // `addon_data->js_accepts` will certainly have been set to `false`, and so + // `CallJs` will not dereference the stale pointers. + for (current = first; current != NULL;) { + previous = current; + current = current->next; + free(previous); + } + + // Release the thread-safe function. This causes it to be cleaned up in the + // background. + CHECK(napi_release_threadsafe_function(addon_data->tsfn, + napi_tsfn_release) == napi_ok); +} + +// This binding can be called from JavaScript to start the asynchronous prime +// generator. +static napi_value StartThread(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_cb, work_name; + AddonData* addon_data; + + // The binding accepts one parameter - the JavaScript callback function to + // call. + CHECK(napi_get_cb_info(env, + info, + &argc, + &js_cb, + NULL, + (void*)&addon_data) == napi_ok); + + // We do not create a second thread if one is already running. + CHECK(addon_data->tsfn == NULL && "Work already in progress"); + + addon_data->js_accepts = true; + + // This string describes the asynchronous work. + CHECK(napi_create_string_utf8(env, + "Thread-safe Function Round Trip Example", + NAPI_AUTO_LENGTH, + &work_name) == napi_ok); + + // The thread-safe function will be created with an unlimited queue and with + // an initial thread count of 1. The secondary thread will release the + // thread-safe function, decreasing its thread count to 0, thereby setting off + // the process of cleaning up the thread-safe function. + CHECK(napi_create_threadsafe_function(env, + js_cb, + NULL, + work_name, + 0, + 1, + addon_data, + ThreadFinished, + addon_data, + CallJs, + &addon_data->tsfn) == napi_ok); + + // Create the thread that will produce primes and that will call into + // JavaScript using the thread-safe function. + CHECK(uv_thread_create(&(addon_data->the_thread), PrimeThread, addon_data) == 0); + + return NULL; +} + +static bool +is_thread_item (napi_env env, napi_ref constructor_ref, napi_value value) { + bool validate; + napi_value constructor; + CHECK(napi_get_reference_value(env, + constructor_ref, + &constructor) == napi_ok); + CHECK(napi_instanceof(env, value, constructor, &validate) == napi_ok); + return validate; +} + +// We use a separate binding to register a return value for a given call into +// JavaScript, represented by a `ThreadItem` object on both the JavaScript side +// and the native side. This allows the JavaScript side to asynchronously +// determine the return value. +static napi_value RegisterReturnValue(napi_env env, napi_callback_info info) { + // This function accepts two parameters: + // 1. The thread item passed into JavaScript via `CallJs`, and + // 2. The desired return value. + size_t argc = 2; + napi_value argv[2]; + AddonData* addon_data; + bool return_value; + ThreadItem* item; + + // Retrieve the parameters with which this function was called. + CHECK(napi_get_cb_info(env, + info, + &argc, + argv, + NULL, + (void*)&addon_data) == napi_ok); + + // If this function recorded a return value of `false` before, it means that + // the thread and the associated thread-safe function are shutting down. This, + // in turn means that the wrapped `ThreadItem` that was received in the first + // argument may also be stale. Our best strategy is to do nothing and return + // to JavaScript. + if (!addon_data->js_accepts) { + return NULL; + } + + CHECK(argc == 2 && "Exactly two arguments were received"); + + // Make sure the first parameter is an instance of the `ThreadItem` class. + // This type check ensures that there *is* a pointer stored inside the + // JavaScript object, and that the pointer is to a `ThreadItem` structure. + CHECK(is_thread_item(env, addon_data->thread_item_constructor, argv[0])); + + // Retrieve the native data from the item. + CHECK(napi_unwrap(env, argv[0], (void**)&item) == napi_ok); + + // Retrieve the desired return value. + CHECK(napi_get_value_bool(env, argv[1], &return_value) == napi_ok); + + // Set `js_accepts` to false in case the JavaScript callback returned false. + if (addon_data->js_accepts) { + addon_data->js_accepts = return_value; + } + + // Mark the thread item as resolved, and record the JavaScript return value. + uv_mutex_lock(&(addon_data->check_status_mutex)); + item->call_has_returned = true; + item->return_value = return_value; + uv_mutex_unlock(&(addon_data->check_status_mutex)); + + return NULL; +} + +// Constructor for instances of the `ThreadItem` class. This doesn't need to do +// anything since all we want the class for is to be able to type-check +// JavaScript objects that carry within them a pointer to a native `ThreadItem` +// structure. +static napi_value ThreadItemConstructor(napi_env env, napi_callback_info info) { + return NULL; +} + +// Getter for the `prime` property of the `ThreadItem` class. +static napi_value GetPrime(napi_env env, napi_callback_info info) { + napi_value jsthis, prime_property; + AddonData* ad; + CHECK(napi_ok == napi_get_cb_info(env, info, 0, 0, &jsthis, (void*)&ad)); + CHECK(is_thread_item(env, ad->thread_item_constructor, jsthis)); + ThreadItem* item; + CHECK(napi_ok == napi_unwrap(env, jsthis, (void**)&item)); + CHECK(napi_ok == napi_create_int32(env, item->the_prime, &prime_property)); + return prime_property; +} + +static void addon_is_unloading(napi_env env, void* data, void* hint) { + AddonData* addon_data = (AddonData*)data; + uv_mutex_destroy(&(addon_data->check_status_mutex)); + CHECK(napi_delete_reference(env, + addon_data->thread_item_constructor) == napi_ok); + free(data); +} + +// Initialize an instance of this addon. This function may be called multiple +// times if multiple instances of Node.js are running on multiple threads, or if +// there are multiple Node.js contexts running on the same thread. The return +// value and the formal parameters in comments remind us that the function body +// that follows, within which we initialize the addon, has available to it the +// variables named in the formal parameters, and that it must return a +// `napi_value`. +/*napi_value*/ NAPI_MODULE_INIT(/*napi_env env, napi_value exports*/) { + // Create the native data that will be associated with this instance of the + // addon. + AddonData* addon_data = + memset(malloc(sizeof(*addon_data)), 0, sizeof(*addon_data)); + + // Attach the addon data to the exports object to ensure that they are + // destroyed together. + CHECK(napi_wrap(env, + exports, + addon_data, + addon_is_unloading, + NULL, + NULL) == napi_ok); + + // Initialize the various members of the `AddonData` associated with this + // addon instance. + CHECK(uv_mutex_init(&(addon_data->check_status_mutex)) == 0); + + napi_value thread_item_class; + napi_property_descriptor thread_item_properties[] = { + { "prime", 0, 0, GetPrime, 0, 0, napi_enumerable, addon_data } + }; + CHECK(napi_define_class(env, + "ThreadItem", + NAPI_AUTO_LENGTH, + ThreadItemConstructor, + addon_data, + 1, + thread_item_properties, + &thread_item_class) == napi_ok); + CHECK(napi_create_reference(env, + thread_item_class, + 1, + &(addon_data->thread_item_constructor)) == + napi_ok); + + // Expose the two bindings this addon provides. + napi_property_descriptor export_properties[] = { + { + "startThread", + NULL, + StartThread, + NULL, + NULL, + NULL, + napi_default, + addon_data + }, + { + "registerReturnValue", + NULL, + RegisterReturnValue, + NULL, + NULL, + NULL, + napi_default, + addon_data + } + }; + CHECK(napi_define_properties(env, + exports, + sizeof(export_properties) / + sizeof(export_properties[0]), + export_properties) == napi_ok); + + return exports; +} diff --git a/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/binding.gyp b/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/binding.gyp new file mode 100644 index 00000000..7737b22c --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/binding.gyp @@ -0,0 +1,17 @@ +{ + "targets": [ + { + "target_name": "tsfn_object_wrap", + "sources": [ + "tsfn_object_wrap.cc", + ], + "defines": [ + "NAPI_DISABLE_CPP_EXCEPTIONS", + "NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS" + ], + "include_dirs": [ + " +#include +#include + +// A secondary thread increments a value starting from 0 and calls a JS callback +// on the JavaScript main thread. + +// The ObjectWrap subclass whose instances own such a secondary thread. +class TsfnObjectWrap : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env) { + return DefineClass(env, "TsfnObjectWrap", {}); + } + + TsfnObjectWrap(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + // Whenever we construct a new instance of type TsfnObjectWrap, we construct + // a thread-safe function that can be called from a secondary thread and + // which provides a value to the JavaScript main thread. + _tsfn = Napi::ThreadSafeFunction::New( + info.Env(), info[0].As(), "TsfnObjectWrap", 1, 2); + + _thread = std::thread(&TsfnObjectWrap::Thread, std::ref(_tsfn)); + } + + ~TsfnObjectWrap() { + _thread.join(); + } + + private: + // This is the secondary thread. + static void Thread(const Napi::ThreadSafeFunction& tsfn) { + int64_t the_value = 0; + int64_t buffer[3] = {0, 0, 0}; + int idx = 0; + + // Since we're calling the JavaScript main thread in a blocking fashion, + // a buffer of three values is sufficient for synchronizing with the main + // thread without losing a single value and without having to allocate an + // integer on the heap for each call into JavaScript. + while (true) { + buffer[idx] = the_value; + idx = (idx + 1) % 3; + int64_t* value_ref = &buffer[idx]; + napi_status status = tsfn.BlockingCall( + value_ref, [tsfn](Napi::Env env, Napi::Function fn, int64_t* data) { + int64_t native_value = *data; + Napi::Value result = + fn.Call({Napi::Number::New(env, native_value)}); + if (result.IsEmpty()) { + printf("main with %" PRId64 ": result was empty!\n", + native_value); + } else { + printf("main with %" PRId64 ": Done!\n", + result.As().Int64Value()); + } + if (native_value == 10) { + tsfn.Abort(); + } + }); + // We break out of the infinite loop when we're informed that the thread- + // safe function is being torn down. + if (status == napi_closing) { + break; + } + the_value++; + } + } + + Napi::ThreadSafeFunction _tsfn; + std::thread _thread; +}; + +// Boilerplate code to define an add-on that consists of the above class. +Napi::Object TsfnObjectWrapExampleInit(Napi::Env env, Napi::Object exports) { + return TsfnObjectWrap::Init(env); +} + +NODE_API_MODULE(TsfnObjectWrapExample, TsfnObjectWrapExampleInit) diff --git a/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/tsfn_object_wrap.js b/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/tsfn_object_wrap.js new file mode 100644 index 00000000..5e60a6d7 --- /dev/null +++ b/src/6-threadsafe-function/thread_safe_function_with_object_wrap/node-addon-api/tsfn_object_wrap.js @@ -0,0 +1,18 @@ +const TsfnObjectWrap = require('bindings')('tsfn_object_wrap.node') +const x = new TsfnObjectWrap((value) => { + // Do something, anything, with x to keep it in scope, otherwise the instance + // will be collected and the process will exit. + x.someProperty = value; + + console.log('JS1: Called with ' + value); + return -value; +}); + +const y = new TsfnObjectWrap((value) => { + // Do something, anything, with y to keep it in scope, otherwise the instance + // will be collected and the process will exit. + y.someProperty = value; + + console.log('JS2: Called with ' + value); + return -value; +}); diff --git a/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/CMakeLists.txt b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..22573d7d --- /dev/null +++ b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/CMakeLists.txt @@ -0,0 +1,23 @@ +project (example) +include_directories(${CMAKE_JS_INC} node_modules/node-addon-api/) +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_JS_INC}) +file(GLOB SOURCE_FILES "*.cc") +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Include Node-API wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + +# define NAPI_VERSION +add_definitions(-DNAPI_VERSION=6) diff --git a/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/example.cc b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/example.cc new file mode 100644 index 00000000..635bea14 --- /dev/null +++ b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/example.cc @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include + +using namespace Napi; + +class ThreadSafeAsyncIteratorExample + : public ObjectWrap { + public: + ThreadSafeAsyncIteratorExample(const CallbackInfo& info) + : ObjectWrap(info), + _current(info[0].As()), + _last(info[1].As()) {} + + static Object Init(Napi::Env env, Napi::Object exports); + + Napi::Value Iterator(const CallbackInfo& info); + + private: + using Context = ThreadSafeAsyncIteratorExample; + + struct DataType { + std::unique_ptr deferred; + bool done; + std::optional value; + }; + + static void CallJs(Napi::Env env, + Function callback, + Context* context, + DataType* data); + + using TSFN = TypedThreadSafeFunction; + + using FinalizerDataType = void; + + int _current; + int _last; + TSFN _tsfn; + std::thread _thread; + std::unique_ptr _deferred; + + // Thread-safety + std::mutex _mtx; + std::condition_variable _cv; + + void threadEntry(); + + static void FinalizerCallback(Napi::Env env, + void*, + ThreadSafeAsyncIteratorExample* context); +}; + +Object ThreadSafeAsyncIteratorExample::Init(Napi::Env env, + Napi::Object exports) { + Napi::Function func = + DefineClass(env, + "ThreadSafeAsyncIteratorExample", + {InstanceMethod(Napi::Symbol::WellKnown(env, "asyncIterator"), + &ThreadSafeAsyncIteratorExample::Iterator)}); + + exports.Set("ThreadSafeAsyncIteratorExample", func); + return exports; +} + +Napi::Value ThreadSafeAsyncIteratorExample::Iterator(const CallbackInfo& info) { + auto env = info.Env(); + + if (_thread.joinable()) { + Napi::Error::New(env, "Concurrent iterations not implemented.") + .ThrowAsJavaScriptException(); + return Napi::Value(); + } + + _tsfn = + TSFN::New(info.Env(), + "tsfn", + 0, + 1, + this, + std::function(FinalizerCallback)); + + // To prevent premature garbage collection; Unref in TFSN finalizer + Ref(); + + // Create thread + _thread = std::thread(&ThreadSafeAsyncIteratorExample::threadEntry, this); + + // Create iterable + auto iterable = Napi::Object::New(env); + + iterable["next"] = + Function::New(env, [this](const CallbackInfo& info) -> Napi::Value { + std::lock_guard lk(_mtx); + auto env = info.Env(); + if (_deferred) { + Napi::Error::New(env, "Concurrent iterations not implemented.") + .ThrowAsJavaScriptException(); + return Napi::Value(); + } + _deferred = std::make_unique(env); + _cv.notify_all(); + return _deferred->Promise(); + }); + + return iterable; +} + +void ThreadSafeAsyncIteratorExample::threadEntry() { + while (true) { + std::unique_lock lk(_mtx); + _cv.wait(lk, [this] { return this->_deferred != nullptr; }); + auto done = _current > _last; + if (done) { + _tsfn.BlockingCall(new DataType{std::move(this->_deferred), true, {}}); + break; + } else { + std::this_thread::sleep_for( + std::chrono::seconds(1)); // Simulate CPU-intensive work + _tsfn.BlockingCall( + new DataType{std::move(this->_deferred), false, _current++}); + } + } + _tsfn.Release(); +} + +void ThreadSafeAsyncIteratorExample::CallJs(Napi::Env env, + Function callback, + Context* context, + DataType* data) { + if (env != nullptr) { + auto value = Object::New(env); + + if (data->done) { + value["done"] = Boolean::New(env, true); + } else { + value["done"] = Boolean::New(env, false); + value["value"] = Number::New(env, data->value.value()); + } + data->deferred->Resolve(value); + } + + if (data != nullptr) { + delete data; + } +} + +void ThreadSafeAsyncIteratorExample::FinalizerCallback( + Napi::Env env, void*, ThreadSafeAsyncIteratorExample* context) { + context->_thread.join(); + context->Unref(); +} + +Napi::Object Init(Napi::Env env, Object exports) { + ThreadSafeAsyncIteratorExample::Init(env, exports); + return exports; +} + +NODE_API_MODULE(example, Init) diff --git a/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/index.js b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/index.js new file mode 100644 index 00000000..d33bd16d --- /dev/null +++ b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/index.js @@ -0,0 +1,14 @@ +const { ThreadSafeAsyncIteratorExample } = require('bindings')('example'); + +async function main(from, to) { + const iterator = new ThreadSafeAsyncIteratorExample(from, to); + for await (const value of iterator) { + console.log(value); + } +} + +main(0, 5) + .catch(e => { + console.error(e); + process.exit(1); + }); diff --git a/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/package.json b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/package.json new file mode 100644 index 00000000..b470eebf --- /dev/null +++ b/src/6-threadsafe-function/threadsafe-async-iterator/node-addon-api/package.json @@ -0,0 +1,16 @@ +{ + "name": "threadsafe-async-iterator-example", + "version": "0.0.0", + "description": "Async iterator example with threadsafe functions using node-addon-api", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "^1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "test": "node index.js", + "install": "cmake-js compile" + } +} diff --git a/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/CMakeLists.txt b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..3a3bf9a2 --- /dev/null +++ b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/CMakeLists.txt @@ -0,0 +1,23 @@ +project (clock) +include_directories(${CMAKE_JS_INC} node_modules/node-addon-api/) +cmake_minimum_required(VERSION 3.18) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +include_directories(${CMAKE_JS_INC}) +file(GLOB SOURCE_FILES "*.cc") +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Include Node-API wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + +# define NAPI_VERSION +add_definitions(-DNAPI_VERSION=4) diff --git a/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc new file mode 100644 index 00000000..2055ea7b --- /dev/null +++ b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/clock.cc @@ -0,0 +1,97 @@ +#include +#include +#include + +using namespace Napi; + +using Context = Reference; +using DataType = int; +void CallJs(Napi::Env env, Function callback, Context* context, DataType* data); +using TSFN = TypedThreadSafeFunction; +using FinalizerDataType = void; + +std::thread nativeThread; +TSFN tsfn; + +Value Start(const CallbackInfo& info) { + Napi::Env env = info.Env(); + + if (info.Length() < 2) { + throw TypeError::New(env, "Expected two arguments"); + } else if (!info[0].IsFunction()) { + throw TypeError::New(env, "Expected first arg to be function"); + } else if (!info[1].IsNumber()) { + throw TypeError::New(env, "Expected second arg to be number"); + } + + int count = info[1].As().Int32Value(); + + // Create a new context set to the the receiver (ie, `this`) of the function + // call + Context* context = new Reference(Persistent(info.This())); + + // Create a ThreadSafeFunction + tsfn = TSFN::New( + env, + info[0].As(), // JavaScript function called asynchronously + "Resource Name", // Name + 0, // Unlimited queue + 1, // Only one thread will use this initially + context, + [](Napi::Env, + FinalizerDataType*, + Context* ctx) { // Finalizer used to clean threads up + nativeThread.join(); + delete ctx; + }); + + // Create a native thread + nativeThread = std::thread([count] { + for (int i = 0; i < count; i++) { + // Create new data + int* value = new int(clock()); + + // Perform a blocking call + napi_status status = tsfn.BlockingCall(value); + if (status != napi_ok) { + // Handle error + break; + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + // Release the thread-safe function + tsfn.Release(); + }); + + return Boolean::New(env, true); +} + +// Transform native data into JS data, passing it to the provided +// `callback` -- the TSFN's JavaScript function. +void CallJs(Napi::Env env, + Function callback, + Context* context, + DataType* data) { + // Is the JavaScript environment still available to call into, eg. the TSFN is + // not aborted + if (env != nullptr) { + // On Node-API 5+, the `callback` parameter is optional; however, this + // example does ensure a callback is provided. + if (callback != nullptr) { + callback.Call(context->Value(), {Number::New(env, *data)}); + } + } + if (data != nullptr) { + // We're finished with the data. + delete data; + } +} + +Napi::Object Init(Napi::Env env, Object exports) { + exports.Set("start", Function::New(env, Start)); + return exports; +} + +NODE_API_MODULE(clock, Init) diff --git a/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/index.js b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/index.js new file mode 100644 index 00000000..c7bb9738 --- /dev/null +++ b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/index.js @@ -0,0 +1,6 @@ +const { start } = require('bindings')('clock'); + +start.call(new Date(), function (clock) { + const context = this; + console.log(context, clock); +}, 5); diff --git a/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/package.json b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/package.json new file mode 100644 index 00000000..910fd728 --- /dev/null +++ b/src/6-threadsafe-function/typed_threadsafe_function/node-addon-api/package.json @@ -0,0 +1,17 @@ +{ + "name": "example-typedthreadsafefunction", + "version": "0.0.0", + "description": "node-addon-api TypedThreadSafeFunction example", + "main": "index.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0", + "cmake-js": "^8.0.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "test": "node index.js", + "install": "cmake-js compile" + }, + "gypfile": true +} diff --git a/src/7-events/emit_event_from_cpp/node-addon-api/binding.gyp b/src/7-events/emit_event_from_cpp/node-addon-api/binding.gyp new file mode 100644 index 00000000..0a89e5f3 --- /dev/null +++ b/src/7-events/emit_event_from_cpp/node-addon-api/binding.gyp @@ -0,0 +1,30 @@ +{ + "targets": [ + { + "target_name": "emit_from_cpp", + "sources": [ + "src/emit-from-cpp.cc" + ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'include_dirs': [" { + console.log('### START ...') +}) +emitter.on('data', (evt) => { + console.log(evt); +}) + +emitter.on('end', () => { + console.log('### END ###') +}) + +addon.callEmit(emitter.emit.bind(emitter)) diff --git a/src/7-events/emit_event_from_cpp/node-addon-api/package.json b/src/7-events/emit_event_from_cpp/node-addon-api/package.json new file mode 100644 index 00000000..230fae53 --- /dev/null +++ b/src/7-events/emit_event_from_cpp/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "emit-event-from-cpp-example", + "version": "0.0.0", + "description": "Node.js Addons - emit event from C++ to JS", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "node-addon-api": "*", + "bindings": "*" + } +} diff --git a/src/7-events/emit_event_from_cpp/node-addon-api/src/emit-from-cpp.cc b/src/7-events/emit_event_from_cpp/node-addon-api/src/emit-from-cpp.cc new file mode 100644 index 00000000..f96bdad4 --- /dev/null +++ b/src/7-events/emit_event_from_cpp/node-addon-api/src/emit-from-cpp.cc @@ -0,0 +1,27 @@ +#include + +#include +#include +#include + +Napi::Value CallEmit(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Function emit = info[0].As(); + emit.Call({Napi::String::New(env, "start")}); + for (int i = 0; i < 3; i++) { + std::this_thread::sleep_for(std::chrono::seconds(3)); + emit.Call( + {Napi::String::New(env, "data"), Napi::String::New(env, "data ...")}); + } + emit.Call({Napi::String::New(env, "end")}); + return Napi::String::New(env, "OK"); +} + +// Init +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "callEmit"), + Napi::Function::New(env, CallEmit)); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init); diff --git a/src/7-events/inherits_from_event_emitter/node-addon-api/binding.gyp b/src/7-events/inherits_from_event_emitter/node-addon-api/binding.gyp new file mode 100644 index 00000000..bf53ba82 --- /dev/null +++ b/src/7-events/inherits_from_event_emitter/node-addon-api/binding.gyp @@ -0,0 +1,31 @@ +{ + "targets": [ + { + "target_name": "native_emitter", + "sources": [ + "src/binding.cc", + "src/native-emitter.cc" + ], + 'cflags!': [ '-fno-exceptions' ], + 'cflags_cc!': [ '-fno-exceptions' ], + 'include_dirs': [" { + console.log('### START ...') +}) + +emitter.on('data', (evt) => { + console.log(evt) +}) + +emitter.on('end', () => { + console.log('### END ###') +}) + +emitter.callAndEmit() diff --git a/src/7-events/inherits_from_event_emitter/node-addon-api/package.json b/src/7-events/inherits_from_event_emitter/node-addon-api/package.json new file mode 100644 index 00000000..b9524176 --- /dev/null +++ b/src/7-events/inherits_from_event_emitter/node-addon-api/package.json @@ -0,0 +1,15 @@ +{ + "name": "inherits-from-event-emitter-example", + "version": "0.0.0", + "description": "Node.js Addons - inherits from event emitter", + "main": "index.js", + "private": true, + "gypfile": true, + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "node-addon-api": "*", + "bindings": "*" + } +} diff --git a/src/7-events/inherits_from_event_emitter/node-addon-api/src/binding.cc b/src/7-events/inherits_from_event_emitter/node-addon-api/src/binding.cc new file mode 100644 index 00000000..fb5fa9dd --- /dev/null +++ b/src/7-events/inherits_from_event_emitter/node-addon-api/src/binding.cc @@ -0,0 +1,9 @@ +#include +#include "native-emitter.h" + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + NativeEmitter::Init(env, exports); + return exports; +} + +NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.cc b/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.cc new file mode 100644 index 00000000..aeeea22f --- /dev/null +++ b/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.cc @@ -0,0 +1,40 @@ +#include +#include +#include + +#include "native-emitter.h" + +Napi::FunctionReference NativeEmitter::constructor; + +Napi::Object NativeEmitter::Init(Napi::Env env, Napi::Object exports) { + Napi::Function func = + DefineClass(env, + "NativeEmitter", + {InstanceMethod("callAndEmit", &NativeEmitter::CallAndEmit)}); + + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + + exports.Set("NativeEmitter", func); + return exports; +} + +NativeEmitter::NativeEmitter(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + // NOOP +} + +Napi::Value NativeEmitter::CallAndEmit(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Function emit = + info.This().As().Get("emit").As(); + emit.Call(info.This(), {Napi::String::New(env, "start")}); + for (int i = 0; i < 3; i++) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + emit.Call( + info.This(), + {Napi::String::New(env, "data"), Napi::String::New(env, "data ...")}); + } + emit.Call(info.This(), {Napi::String::New(env, "end")}); + return Napi::String::New(env, "OK"); +} diff --git a/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.h b/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.h new file mode 100644 index 00000000..56cdc405 --- /dev/null +++ b/src/7-events/inherits_from_event_emitter/node-addon-api/src/native-emitter.h @@ -0,0 +1,12 @@ +#include + +class NativeEmitter : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object exports); + NativeEmitter(const Napi::CallbackInfo& info); + + private: + static Napi::FunctionReference constructor; + + Napi::Value CallAndEmit(const Napi::CallbackInfo& info); +}; diff --git a/src/8-tooling/build_with_cmake/README.md b/src/8-tooling/build_with_cmake/README.md new file mode 100644 index 00000000..f7d5c18e --- /dev/null +++ b/src/8-tooling/build_with_cmake/README.md @@ -0,0 +1,33 @@ +## Building Node-API Addons Using CMake.js + +### Examples + +The objective of these examples is to demonstrate how to build Node-API addons using [CMake.js](https://github.com/cmake-js/cmake-js#readme). + +These example projects assume that CMake.js has been installed globally: + +``` +npm install -g cmake-js +cmake-js --help +``` + +Then, in each of the `napi` and `node-addon-api` directories, the following commands build and test each addon: + +``` +npm install +npm test +``` + +Complete CMake.js documentation can be found on the [CMake.js GitHub repository](https://github.com/cmake-js/cmake-js#readme). + +### NAPI_VERSION + +When building Node-API addons, it's important to specify to the build system the Node-API version your code is designed to work with. With CMake.js, this information is specified in the `CMakeLists.txt` file: + +``` +add_definitions(-DNAPI_VERSION=3) +``` + +Since Node-API is ABI-stable, your Node-API addon will work, without recompilation, with the Node-API version you specify in `NAPI_VERSION` and all subsequent Node-API versions. + +In the absence of a need for features available only in a specific Node-API version, version 3 is a good choice as it is the version of Node-API that was active when Node-API left experimental status. \ No newline at end of file diff --git a/src/8-tooling/build_with_cmake/napi/CMakeLists.txt b/src/8-tooling/build_with_cmake/napi/CMakeLists.txt new file mode 100644 index 00000000..f87b9bb1 --- /dev/null +++ b/src/8-tooling/build_with_cmake/napi/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.9) +cmake_policy(SET CMP0042 NEW) +set (CMAKE_CXX_STANDARD 11) + +project (build-napi-with-cmake) +include_directories(${CMAKE_JS_INC}) +file(GLOB SOURCE_FILES "hello.c") +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Define NAPI_VERSION +add_definitions(-DNAPI_VERSION=3) diff --git a/src/8-tooling/build_with_cmake/napi/hello.c b/src/8-tooling/build_with_cmake/napi/hello.c new file mode 100644 index 00000000..8ae1eee9 --- /dev/null +++ b/src/8-tooling/build_with_cmake/napi/hello.c @@ -0,0 +1,23 @@ +#include +#include + +static napi_value Method(napi_env env, napi_callback_info info) { + napi_status status; + napi_value world; + status = napi_create_string_utf8(env, "Hello, world!", 13, &world); + assert(status == napi_ok); + return world; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +static napi_value Init(napi_env env, napi_value exports) { + napi_status status; + napi_property_descriptor desc = DECLARE_NAPI_METHOD("hello", Method); + status = napi_define_properties(env, exports, 1, &desc); + assert(status == napi_ok); + return exports; +} + +NAPI_MODULE(hello, Init) diff --git a/src/8-tooling/build_with_cmake/napi/hello.js b/src/8-tooling/build_with_cmake/napi/hello.js new file mode 100644 index 00000000..f3a432a0 --- /dev/null +++ b/src/8-tooling/build_with_cmake/napi/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('build-napi-with-cmake'); + +console.log(addon.hello()); // 'world' diff --git a/src/8-tooling/build_with_cmake/napi/package.json b/src/8-tooling/build_with_cmake/napi/package.json new file mode 100644 index 00000000..21049c10 --- /dev/null +++ b/src/8-tooling/build_with_cmake/napi/package.json @@ -0,0 +1,17 @@ +{ + "name": "build-napi-with-cmake", + "version": "0.0.0", + "description": "Build Node-API native addon with CMake.", + "main": "hello.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0" + }, + "devDependencies": { + "cmake-js": "^8.0.0" + }, + "scripts": { + "install": "cmake-js compile", + "test": "node hello.js" + } +} diff --git a/src/8-tooling/build_with_cmake/node-addon-api/CMakeLists.txt b/src/8-tooling/build_with_cmake/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..456d2312 --- /dev/null +++ b/src/8-tooling/build_with_cmake/node-addon-api/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.9) +cmake_policy(SET CMP0042 NEW) +set (CMAKE_CXX_STANDARD 11) + +project (build-node-addon-api-with-cmake) +include_directories(${CMAKE_JS_INC}) +file(GLOB SOURCE_FILES "hello.cc") +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Include Node-API wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + +# define NAPI_VERSION +add_definitions(-DNAPI_VERSION=3) diff --git a/src/8-tooling/build_with_cmake/node-addon-api/hello.cc b/src/8-tooling/build_with_cmake/node-addon-api/hello.cc new file mode 100644 index 00000000..1c422da3 --- /dev/null +++ b/src/8-tooling/build_with_cmake/node-addon-api/hello.cc @@ -0,0 +1,23 @@ +#include + +static Napi::String Method(const Napi::CallbackInfo& info) { + // Napi::Env is the opaque data structure containing the environment in which + // the request is being run. We will need this env when we want to create any + // new objects inside of the node.js environment + Napi::Env env = info.Env(); + + // Create a C++ level variable + std::string helloWorld = "Hello, world!"; + + // Return a new javascript string that we copy-construct inside of the node.js + // environment + return Napi::String::New(env, helloWorld); +} + +static Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "hello"), + Napi::Function::New(env, Method)); + return exports; +} + +NODE_API_MODULE(hello, Init) diff --git a/src/8-tooling/build_with_cmake/node-addon-api/hello.js b/src/8-tooling/build_with_cmake/node-addon-api/hello.js new file mode 100644 index 00000000..b7529e33 --- /dev/null +++ b/src/8-tooling/build_with_cmake/node-addon-api/hello.js @@ -0,0 +1,3 @@ +const addon = require('bindings')('build-node-addon-api-with-cmake'); + +console.log(addon.hello()); // 'world' diff --git a/src/8-tooling/build_with_cmake/node-addon-api/package.json b/src/8-tooling/build_with_cmake/node-addon-api/package.json new file mode 100644 index 00000000..b0718d20 --- /dev/null +++ b/src/8-tooling/build_with_cmake/node-addon-api/package.json @@ -0,0 +1,18 @@ +{ + "name": "build-node-addon-api-with-cmake", + "version": "0.0.0", + "description": "Build Node-API native addon with CMake and node-addon-api C++ wrapper.", + "main": "hello.js", + "private": true, + "dependencies": { + "bindings": "~1.5.0", + "node-addon-api": "^8.1.0" + }, + "devDependencies": { + "cmake-js": "^8.0.0" + }, + "scripts": { + "install": "cmake-js compile", + "test": "node hello.js" + } +} diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/CMakeLists.txt b/src/8-tooling/typescript_with_addon/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..7f23c6c1 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.9) +set (CMAKE_CXX_STANDARD 11) + +project(typescript_with_addon VERSION 0.0.1) +include_directories(${CMAKE_JS_INC}) +string(APPEND CMAKE_C_FLAGS "-Wall -Wextra -Wno-unused-parameter -Wendif-labels") + +file(GLOB SOURCE_FILES *.cpp *.h *.cc *.c) +add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +# Include Node-API wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) + +# need to pay attention to this part +string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + +# Define NAPI_VERSION +add_definitions(-DNAPI_VERSION=3) + diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/README.md b/src/8-tooling/typescript_with_addon/node-addon-api/README.md new file mode 100644 index 00000000..c5b58212 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/README.md @@ -0,0 +1,19 @@ +## TypeScript calls JavaScript that calls C++ that calls C function + +### Build and run: + +``` +npm install +npm test +``` + +The result: + +``` +This is a TypeScript class constructor. +This is a Javascript function. +This is a C++ function. +This is a C function. +``` + +This is using cmake and cmake-js based on the example [build_with_cmake](../../build_with_cmake) diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/cPart.c b/src/8-tooling/typescript_with_addon/node-addon-api/cPart.c new file mode 100644 index 00000000..37907109 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/cPart.c @@ -0,0 +1,8 @@ +#include +#include "cPart.h" + +int cPart(int value) { + int newValue = value + 30; + printf("I'm a C function and I received %d from C++ and I'm sending back %d \n",value,newValue); + return newValue; +} \ No newline at end of file diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/cPart.h b/src/8-tooling/typescript_with_addon/node-addon-api/cPart.h new file mode 100644 index 00000000..4fe817a5 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/cPart.h @@ -0,0 +1,14 @@ +#ifndef _C_PART_H_ +#define _C_PART_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int cPart(int); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/cppPart.cpp b/src/8-tooling/typescript_with_addon/node-addon-api/cppPart.cpp new file mode 100644 index 00000000..e6c5c449 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/cppPart.cpp @@ -0,0 +1,11 @@ +#include +#include "cPart.h" + +using namespace std; + +int callingCPart(int value) { + int result = cPart(++value); + cout << "I'm a C++ function I'm sending " << value << " to C " + << "and I received " << result << endl; + return result; +} \ No newline at end of file diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/index.ts b/src/8-tooling/typescript_with_addon/node-addon-api/index.ts new file mode 100644 index 00000000..14cd72ad --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/index.ts @@ -0,0 +1,11 @@ +import {javascriptPart} from './jsPart'; + +class TypeScriptPart { + constructor(value:number) { + console.log('I\'m a TypeScript class constructor I\'m sending', + value, + 'to JavaScript and I received', javascriptPart(value)); + } +} + +new TypeScriptPart(10); \ No newline at end of file diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/jsPart.js b/src/8-tooling/typescript_with_addon/node-addon-api/jsPart.js new file mode 100644 index 00000000..4db1151e --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/jsPart.js @@ -0,0 +1,16 @@ +const addon = require('bindings')('typescript_with_addon'); + +function javascriptPart (value) { + const result = addon.cppPartExportedByNapi(++value); + + console.log('I\'m a Javascript function and I\'m sending', + value, + 'to C++ and I received', + result); + + return result; +} + +module.exports = { + javascriptPart +} \ No newline at end of file diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/napiPart.cpp b/src/8-tooling/typescript_with_addon/node-addon-api/napiPart.cpp new file mode 100644 index 00000000..57a107f0 --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/napiPart.cpp @@ -0,0 +1,18 @@ +#include + +extern int callingCPart(int value); + +Napi::Value Method(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + int value = info[0].As().Int32Value(); + Napi::Number num = Napi::Number::New(env, callingCPart(value)); + return num; +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + exports.Set(Napi::String::New(env, "cppPartExportedByNapi"), + Napi::Function::New(env, Method)); + return exports; +} + +NODE_API_MODULE(cppPartExportedByNapi, Init) \ No newline at end of file diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/package.json b/src/8-tooling/typescript_with_addon/node-addon-api/package.json new file mode 100644 index 00000000..f260d42e --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/package.json @@ -0,0 +1,18 @@ +{ + "name": "typescript_with_addon", + "version": "0.0.0", + "description": "TypeScript calling Javascript calling C++ calling C with building with CMake and node-addon-api C++ wrapper.", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^8.1.0" + }, + "scripts": { + "install": "cmake-js compile", + "test": "ts-node index.ts" + }, + "devDependencies": { + "cmake-js": "^8.0.0", + "ts-node": "^10.9.2", + "typescript": "^6.0.2" + } +} diff --git a/src/8-tooling/typescript_with_addon/node-addon-api/tsconfig.json b/src/8-tooling/typescript_with_addon/node-addon-api/tsconfig.json new file mode 100644 index 00000000..746b259d --- /dev/null +++ b/src/8-tooling/typescript_with_addon/node-addon-api/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "commonjs" + } +} + diff --git a/test_all.js b/test_all.js new file mode 100644 index 00000000..a6ad1294 --- /dev/null +++ b/test_all.js @@ -0,0 +1,101 @@ +const fs = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); +const semver = require("semver"); + +const examplesFolder = path.join(__dirname, "src"); + +function getAllExamples(pathToCheck) { + const directoriesToTest = []; + for (const fd of fs.readdirSync(pathToCheck)) { + const absPath = path.join(pathToCheck, fd); + if (fs.existsSync(path.join(absPath, "package.json"))) { + directoriesToTest.push(absPath); + continue; + } + if (fs.statSync(absPath).isDirectory()) { + directoriesToTest.push(...getAllExamples(absPath)); + } + } + return directoriesToTest; +} + +const main = async () => { + const { default: chalk } = await import("chalk"); + const passed = []; + const failedBuilds = []; + const noTest = []; + const failedTests = []; + for (directoryToTest of getAllExamples(examplesFolder)) { + console.log(chalk.green(`testing: ${directoryToTest}`)); + const pkgJson = require(path.join(directoryToTest, "package.json")); + if (pkgJson.engines && pkgJson.engines.node) { + const currentNodeVersion = process.versions.node; + const range = pkgJson.engines.node; + const engineOk = semver.satisfies(currentNodeVersion, range); + if (!engineOk) { + console.warn( + chalk.yellow( + `${directoryToTest} require Node.js ${range}, current is ${currentNodeVersion}, skipping` + ) + ); + continue; + } + } + + let buildCommand = "npx node-gyp rebuild"; + if ("scripts" in pkgJson && "install" in pkgJson.scripts) { + buildCommand = "npm run install"; + } + try { + const stdout = execSync(buildCommand, { cwd: directoryToTest }); + console.log(stdout.toString()); + } catch (err) { + console.log(err); + failedBuilds.push(directoryToTest); + continue; + } + + let testCommand; + if ("scripts" in pkgJson && "start" in pkgJson.scripts) { + testCommand = "npm start"; + } else if ("scripts" in pkgJson && "test" in pkgJson.scripts) { + testCommand = "npm test"; + } else if ("main" in pkgJson) { + testCommand = `node ${pkgJson.main}` + } else { + noTest.push(directoryToTest); + continue; + } + + try { + const stdout = execSync(testCommand, { cwd: directoryToTest }); + console.log(stdout.toString()); + passed.push(directoryToTest); + } catch (err) { + console.log(err); + failedTests.push(directoryToTest); + } + } + + passed.map((dir) => console.log(chalk.green(`passed: ${dir}`))); + + if (noTest.length > 0) { + console.warn(chalk.yellow("no test found:")); + noTest.map((dir) => console.warn(chalk.yellow(` ${dir}`))); + } + + if (failedBuilds.length > 0) { + console.error(chalk.red("failed to build:")); + failedBuilds.map((dir) => console.warn(chalk.red(` ${dir}`))); + } + if (failedTests.length > 0) { + console.error(chalk.red("failed tests:")); + failedTests.map((dir) => console.warn(chalk.red(` ${dir}`))); + } +}; + +main().catch(e => { + console.error(e); + process.exitCode = 1; +});