Repository: neon-bindings/neon Branch: main Commit: 252a5b698538 Files: 280 Total size: 1001.0 KB Directory structure: gitextract_5xdlnvd5/ ├── .cargo/ │ └── config.toml ├── .editorconfig ├── .github/ │ └── workflows/ │ ├── bench.yml │ ├── ci.yml │ └── lint.yml ├── .gitignore ├── .prettierignore ├── AUTHORS.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── bench/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── index.js │ ├── package.json │ └── src/ │ └── lib.rs ├── codecov.yml ├── crates/ │ ├── neon/ │ │ ├── Cargo.toml │ │ └── src/ │ │ ├── context/ │ │ │ ├── internal.rs │ │ │ └── mod.rs │ │ ├── event/ │ │ │ ├── channel.rs │ │ │ ├── mod.rs │ │ │ └── task.rs │ │ ├── executor/ │ │ │ ├── mod.rs │ │ │ └── tokio.rs │ │ ├── handle/ │ │ │ ├── internal.rs │ │ │ ├── mod.rs │ │ │ └── root.rs │ │ ├── lib.rs │ │ ├── lifecycle.rs │ │ ├── macro_internal/ │ │ │ ├── futures.rs │ │ │ └── mod.rs │ │ ├── macros.rs │ │ ├── meta.rs │ │ ├── object/ │ │ │ ├── class.rs │ │ │ ├── mod.rs │ │ │ └── wrap.rs │ │ ├── prelude.rs │ │ ├── reflect.rs │ │ ├── result/ │ │ │ └── mod.rs │ │ ├── sys/ │ │ │ ├── array.rs │ │ │ ├── arraybuffer.rs │ │ │ ├── async_work.rs │ │ │ ├── bindings/ │ │ │ │ ├── functions.rs │ │ │ │ ├── mod.rs │ │ │ │ └── types.rs │ │ │ ├── buffer.rs │ │ │ ├── call.rs │ │ │ ├── convert.rs │ │ │ ├── date.rs │ │ │ ├── debug_send_wrapper.rs │ │ │ ├── error.rs │ │ │ ├── external.rs │ │ │ ├── fun.rs │ │ │ ├── lifecycle.rs │ │ │ ├── mem.rs │ │ │ ├── mod.rs │ │ │ ├── no_panic.rs │ │ │ ├── object.rs │ │ │ ├── primitive.rs │ │ │ ├── promise.rs │ │ │ ├── raw.rs │ │ │ ├── reference.rs │ │ │ ├── scope.rs │ │ │ ├── string.rs │ │ │ ├── tag.rs │ │ │ ├── tsfn.rs │ │ │ └── typedarray.rs │ │ ├── thread/ │ │ │ └── mod.rs │ │ ├── types_docs.rs │ │ └── types_impl/ │ │ ├── bigint.rs │ │ ├── boxed.rs │ │ ├── buffer/ │ │ │ ├── lock.rs │ │ │ ├── mod.rs │ │ │ └── types.rs │ │ ├── date.rs │ │ ├── error.rs │ │ ├── extract/ │ │ │ ├── array.rs │ │ │ ├── boxed.rs │ │ │ ├── buffer.rs │ │ │ ├── container.rs │ │ │ ├── either.rs │ │ │ ├── error.rs │ │ │ ├── json.rs │ │ │ ├── mod.rs │ │ │ ├── private.rs │ │ │ ├── try_from_js.rs │ │ │ ├── try_into_js.rs │ │ │ └── with.rs │ │ ├── function/ │ │ │ ├── mod.rs │ │ │ └── private.rs │ │ ├── mod.rs │ │ ├── private.rs │ │ ├── promise.rs │ │ └── utf8.rs │ └── neon-macros/ │ ├── Cargo.toml │ ├── LICENSE-APACHE │ ├── LICENSE-MIT │ └── src/ │ ├── class/ │ │ ├── meta.rs │ │ └── mod.rs │ ├── export/ │ │ ├── class/ │ │ │ └── meta.rs │ │ ├── class.rs │ │ ├── function/ │ │ │ ├── meta.rs │ │ │ └── mod.rs │ │ ├── global/ │ │ │ ├── meta.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── lib.rs │ └── name.rs ├── doc/ │ ├── MIGRATION_GUIDE_0.10.md │ ├── MIGRATION_GUIDE_1.0.0.md │ └── MIGRATION_GUIDE_NAPI.md ├── package.json ├── pkgs/ │ ├── cargo-cp-artifact/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── bin/ │ │ │ └── cargo-cp-artifact.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── args.js │ │ │ └── index.js │ │ └── test/ │ │ └── args.js │ └── create-neon/ │ ├── .mocharc.json │ ├── README.md │ ├── data/ │ │ ├── templates/ │ │ │ ├── .gitignore.hbs │ │ │ ├── Cargo.toml.hbs │ │ │ ├── README.md.hbs │ │ │ ├── Workspace.toml.hbs │ │ │ ├── ci/ │ │ │ │ └── github/ │ │ │ │ ├── build.yml.hbs │ │ │ │ ├── release.yml.hbs │ │ │ │ ├── setup.yml.hbs │ │ │ │ └── test.yml.hbs │ │ │ ├── lib.rs.hbs │ │ │ ├── manifest/ │ │ │ │ └── base/ │ │ │ │ ├── default.json.hbs │ │ │ │ └── library.json.hbs │ │ │ ├── ts/ │ │ │ │ ├── index.cts.hbs │ │ │ │ ├── index.mts.hbs │ │ │ │ └── load.cts.hbs │ │ │ └── tsconfig.json.hbs │ │ └── versions.json │ ├── dev/ │ │ └── expect.ts │ ├── package.json │ ├── src/ │ │ ├── bin/ │ │ │ └── create-neon.ts │ │ ├── cache/ │ │ │ └── npm.ts │ │ ├── cache.ts │ │ ├── ci/ │ │ │ └── github.ts │ │ ├── ci.ts │ │ ├── create/ │ │ │ ├── app.ts │ │ │ ├── creator.ts │ │ │ └── lib.ts │ │ ├── die.ts │ │ ├── expand/ │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ └── versions.ts │ │ ├── fs.ts │ │ ├── index.ts │ │ ├── print.ts │ │ └── shell.ts │ ├── test/ │ │ └── create-neon.ts │ └── tsconfig.json └── test/ ├── electron/ │ ├── Cargo.toml │ ├── README.md │ ├── index.html │ ├── main.js │ ├── main.test.js │ ├── package.json │ ├── preload.js │ ├── renderer.js │ └── src/ │ └── lib.rs ├── napi/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── lib/ │ │ ├── arrays.js │ │ ├── bigint.js │ │ ├── boxed.js │ │ ├── class.js │ │ ├── coercions.js │ │ ├── container.js │ │ ├── date.js │ │ ├── errors.js │ │ ├── export.js │ │ ├── extract.js │ │ ├── functions.js │ │ ├── futures.js │ │ ├── hello.js │ │ ├── numbers.js │ │ ├── objects.js │ │ ├── strings.js │ │ ├── threads.js │ │ ├── typedarrays.js │ │ ├── types.js │ │ └── workers.js │ ├── package.json │ └── src/ │ ├── js/ │ │ ├── arrays.rs │ │ ├── bigint.rs │ │ ├── boxed.rs │ │ ├── class.rs │ │ ├── coercions.rs │ │ ├── container.rs │ │ ├── date.rs │ │ ├── errors.rs │ │ ├── export.rs │ │ ├── extract.rs │ │ ├── functions.rs │ │ ├── futures.rs │ │ ├── numbers.rs │ │ ├── objects.rs │ │ ├── strings.rs │ │ ├── threads.rs │ │ ├── typedarrays.rs │ │ ├── types.rs │ │ └── workers.rs │ └── lib.rs ├── rust-2024/ │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── package.json │ └── src/ │ └── lib.rs └── ui/ ├── Cargo.toml ├── src/ │ └── lib.rs └── tests/ ├── fail/ │ ├── class-async-borrowed-channel.rs │ ├── class-async-borrowed-channel.stderr │ ├── class-async-context-ref.rs │ ├── class-async-context-ref.stderr │ ├── class-async-fn-borrowed-self.rs │ ├── class-async-fn-borrowed-self.stderr │ ├── class-async-owned-context.rs │ ├── class-async-owned-context.stderr │ ├── class-borrowed-channel-sync.rs │ ├── class-borrowed-channel-sync.stderr │ ├── class-channel-in-sync.rs │ ├── class-channel-in-sync.stderr │ ├── class-constructor-self-receiver.rs │ ├── class-constructor-self-receiver.stderr │ ├── class-constructor-with-self.rs │ ├── class-constructor-with-self.stderr │ ├── class-duplicate-property-names.rs │ ├── class-duplicate-property-names.stderr │ ├── class-immutable-context.rs │ ├── class-immutable-context.stderr │ ├── class-invalid-item-type.rs │ ├── class-invalid-item-type.stderr │ ├── class-invalid-property-name.rs │ ├── class-invalid-property-name.stderr │ ├── class-method-missing-self.rs │ ├── class-method-missing-self.stderr │ ├── class-missing-forced-context.rs │ ├── class-missing-forced-context.stderr │ ├── class-multiple-constructors.rs │ ├── class-multiple-constructors.stderr │ ├── class-multiple-finalizers.rs │ ├── class-multiple-finalizers.stderr │ ├── class-multiple-neon-attrs-const.rs │ ├── class-multiple-neon-attrs-const.stderr │ ├── class-multiple-neon-attrs-method.rs │ ├── class-multiple-neon-attrs-method.stderr │ ├── class-owned-context.rs │ ├── class-owned-context.stderr │ ├── missing-class-clone.rs │ ├── missing-class-clone.stderr │ ├── missing-class-default.rs │ ├── missing-class-default.stderr │ ├── missing-context.rs │ ├── missing-context.stderr │ ├── need-borrowed-context.rs │ ├── need-borrowed-context.stderr │ ├── unexpected-self.rs │ ├── unexpected-self.stderr │ ├── unnecessary-attribute.rs │ ├── unnecessary-attribute.stderr │ ├── unsupported-property.rs │ ├── unsupported-property.stderr │ ├── wrong-context.rs │ └── wrong-context.stderr └── pass/ ├── context-and-this.rs ├── globals.rs └── json.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] # Neon defines mutually exclusive feature flags which prevents using `cargo clippy --all-features` # The following aliases simplify linting the entire workspace neon-check = " check --all --all-targets --features napi-experimental,external-buffers,serde,tokio" neon-clippy = "clippy --all --all-targets --features napi-experimental,external-buffers,serde,tokio -- -A clippy::missing_safety_doc" neon-test = " test --all --features=doc-dependencies,doc-comment,napi-experimental,external-buffers,serde,tokio" neon-doc = " rustdoc -p neon --features=doc-dependencies,napi-experimental,external-buffers,sys,serde,tokio -- --cfg docsrs" ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.rs] indent_size = 4 [*.toml] indent_size = 4 [*.md] trim_trailing_whitespace = false ================================================ FILE: .github/workflows/bench.yml ================================================ name: Benchmarks on: push: branches: - main pull_request: branches: - main jobs: bench: name: regression permissions: checks: write pull-requests: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: bencherdev/bencher@main - name: Use Rust Stable uses: dtolnay/rust-toolchain@stable - name: Use Node.js uses: actions/setup-node@v4 with: node-version: 20.x cache: npm - name: npm install run: npm ci --prefer-offline --no-audit --no-fund - name: Install Benchmark Dependencies working-directory: ./bench run: npm install - name: Build Benchmark working-directory: ./bench run: npm run build - name: Track base branch benchmarks with Bencher if: github.ref_name == 'main' run: bencher run --project neon --token '${{ secrets.BENCHER_API_TOKEN }}' --branch main --testbed ubuntu-latest --adapter json --github-actions '${{ secrets.GITHUB_TOKEN }}' npm run --silent benchmark working-directory: ./bench - name: Track pull request benchmarks with Bencher if: github.ref_name != 'main' run: bencher run --project neon --token '${{ secrets.BENCHER_API_TOKEN }}' --branch "$GITHUB_HEAD_REF" --start-point "$GITHUB_BASE_REF" --start-point-hash '${{ github.event.pull_request.base.sha }}' --start-point-clone-thresholds --start-point-reset --testbed ubuntu-latest --adapter json --github-actions '${{ secrets.GITHUB_TOKEN }}' npm run --silent benchmark working-directory: ./bench ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: # Prevent duplicate runs of this workflow on our own internal PRs. branches: - main - next/* pull_request: types: [opened, synchronize, reopened, labeled] branches: - main - next/* env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "true" jobs: matrix: runs-on: ubuntu-latest outputs: node_version: ${{ steps.set_matrix.outputs.node_version }} rust_toolchain: ${{ steps.set_matrix.outputs.rust_toolchain }} steps: - name: Set Matrix id: set_matrix env: FULL_NODE_VERSIONS: '["18.x", "20.x"]' FULL_RUST_TOOLCHAINS: '["stable", "nightly"]' PARTIAL_NODE_VERSIONS: '["20.x"]' PARTIAL_RUST_TOOLCHAINS: '["stable"]' HAS_FULL_MATRIX_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'full matrix') }} IS_PUSHED: ${{ github.event_name == 'push' }} run: | if [[ "$HAS_FULL_MATRIX_LABEL" == "true" ]] || [[ "$IS_PUSHED" == "true" ]]; then echo "node_version=$FULL_NODE_VERSIONS" >> $GITHUB_OUTPUT echo "rust_toolchain=$FULL_RUST_TOOLCHAINS" >> $GITHUB_OUTPUT else echo "node_version=$PARTIAL_NODE_VERSIONS" >> $GITHUB_OUTPUT echo "rust_toolchain=$PARTIAL_RUST_TOOLCHAINS" >> $GITHUB_OUTPUT fi build: needs: matrix runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node-version: ${{fromJson(needs.matrix.outputs.node_version)}} rust-toolchain: ${{fromJson(needs.matrix.outputs.rust_toolchain)}} steps: - name: Checkout Code uses: actions/checkout@v4 - name: Use Rust ${{ matrix.rust-toolchain }} uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ matrix.rust-toolchain }} components: clippy,rustfmt - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: npm - name: Cache Electron (Linux) if: matrix.os == 'ubuntu-latest' uses: actions/cache@v4 with: key: ${{ runner.os }}-electron-${{ hashFiles('./package-lock.json') }} path: ~/.cache/electron - name: Cache Electron (Windows) if: matrix.os == 'windows-latest' uses: actions/cache@v4 with: key: ${{ runner.os }}-electron-${{ hashFiles('./package-lock.json') }} path: "%LOCALAPPDATA%\\electron\\Cache" - name: Cache Electron (macOS) if: matrix.os == 'macos-latest' uses: actions/cache@v4 with: key: ${{ runner.os }}-electron-${{ hashFiles('./package-lock.json') }} path: ~/Library/Caches/electron - name: Install cargo-llvm-cov if: matrix.os == 'ubuntu-latest' && matrix.rust-toolchain == 'stable' uses: taiki-e/install-action@cargo-llvm-cov - name: Set coverage environment variables if: matrix.os == 'ubuntu-latest' && matrix.rust-toolchain == 'stable' run: cargo llvm-cov show-env | tr -d "'" >> $GITHUB_ENV - name: npm install run: npm ci --prefer-offline --no-audit --no-fund - name: Allow unprivileged X server if: matrix.os == 'ubuntu-latest' run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Test (Linux) if: matrix.os == 'ubuntu-latest' run: xvfb-run --auto-servernum npm test -- --nocapture - name: Test if: matrix.os != 'ubuntu-latest' run: npm test - name: Generate coverage report if: matrix.os == 'ubuntu-latest' && matrix.rust-toolchain == 'stable' run: cargo llvm-cov report --codecov --output-path target/codecov.json - name: Upload coverage to Codecov if: matrix.os == 'ubuntu-latest' && matrix.rust-toolchain == 'stable' uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} slug: neon-bindings/neon files: target/codecov.json ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lints on: push: # Prevent duplicate runs of this workflow on our own internal PRs. branches: - main - next/* pull_request: branches: - main - next/* env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "true" jobs: lint: runs-on: ubuntu-latest strategy: matrix: node-version: [20.x] rust-toolchain: [nightly] steps: - name: Checkout Code uses: actions/checkout@v4 - name: Use Rust ${{ matrix.rust-toolchain }} uses: dtolnay/rust-toolchain@v1 with: toolchain: ${{ matrix.rust-toolchain }} components: clippy,rustfmt - name: Rust Cache uses: Swatinem/rust-cache@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: "npm" - name: Cache Electron uses: actions/cache@v4 with: path: ~/.cache/electron key: ${{ runner.os }}-electron-${{ hashFiles('./package-lock.json') }} - name: npm install run: npm ci --prefer-offline --no-audit --no-fund - name: Prettier Formatting run: npm run prettier:check - name: Rust Formatting run: cargo fmt --all -- --check - name: Rust Clippy run: cargo neon-clippy ================================================ FILE: .gitignore ================================================ # Rust target rls*.log # Created by `trybuild` ui tests wip **/*~ # Node **/node_modules npm-debug.log # JS build **/build **/dist cli/lib test/cli/lib # Neon build **/index.node **/artifacts.json pkgs/create-neon/create-neon-test-project pkgs/create-neon/create-neon-manual-test-project # System **/.DS_Store ================================================ FILE: .prettierignore ================================================ .git/ cli/lib/ test/cli/lib/ node_modules/ **/dist/ target/ artifacts.json ================================================ FILE: AUTHORS.md ================================================ # Authors Neon owes its existence to the contributions of these fine people. * [Reza Akhavan](https://github.com/jedireza) * [andiliu-gh](https://github.com/andiliu-gh) * [Nerijus Arlauskas](https://github.com/Nercury) * [Igor Artamonov](https://github.com/splix) * [Peter Atashian](https://github.com/retep998) * [Alexander Azarov](https://github.com/alaz) * [David Baker](https://github.com/dbkr) * [Sean Billig](https://github.com/sbillig) * [Tim Blair](https://github.com/tblair) * [Max Brunsfeld](https://github.com/maxbrunsfeld) * [Dale Bustad](https://github.com/divmain) * [Eduard-Mihai Burtescu](https://github.com/eddyb) * [Gabriel Castro](https://github.com/GabrielCastro) * [Lin Clark](https://github.com/linclark) * [ComplexSpaces](https://github.com/complexspaces) * [Nathaniel Daniel](https://github.com/adumbidiot) * [John Darrington](https://github.com/DnOberon) * [erics118](https://github.com/erics118) * [Joey Ezechiëls](https://github.com/jjpe) * [Ryan Fitzgerald](https://github.com/rf-) * [Cory Forsyth](https://github.com/bantic) * [Olivier Goffart](https://github.com/ogoffart) * [Dave Herman](https://github.com/dherman) * [Himself65](https://github.com/Himself65) * [Maciej Hirsz](https://github.com/maciejhirsz) * [Amal Hussein](https://github.com/nomadtechie) * [Fedor Indutny](https://github.com/indutny) * [Usagi Ito](https://github.com/usagi) * [j1ngzoue](https://github.com/activeguild) * [Jeroen (jrd-rocks)](https://github.com/jrd-rocks) * [Keegan (mhsjlw)](https://github.com/mhsjlw) * [Florian Keller](https://github.com/ffflorian) * [Aleksey Kladov](https://github.com/matklad) * [Adam Kloboucnik](https://github.com/akloboucnik) * [Renée Kooi](https://github.com/goto-bus-stop) * [Laz](https://github.com/lazops) * [Anton Lazarev](https://github.com/antonok-edm) * [Simon Liang](https://github.com/lhr0909) * [Matthew Little](https://github.com/zone117x) * [Terence Lee](https://github.com/hone) * [Milan Loveless](https://github.com/MilanLoveless) * [Mikuroさいな](https://github.com/MikuroXina) * [MikaelUrankar](https://github.com/MikaelUrankar) * [Darin Morrison](https://github.com/freebroccolo) * [Martin Muñoz](https://github.com/mmun) * [Dominik Nakamura](https://github.com/dnaka91) * [Kayo Phoenix](https://github.com/katyo) * [Mike Piccolo](https://github.com/mfpiccolo) * [Jan Piotrowski](https://github.com/janpio) * [Robbie Pitts](https://github.com/robbiepitts) * [Thiago Pontes](https://github.com/thiagopnts) * [Sean Prashad](https://github.com/SeanPrashad) * [Jordan Rose](https://github.com/jrose-signal) * [Antonio Scandurra](https://github.com/as-cii) * [Edward Shaw](https://github.com/EdShaw) * [Nathan Sobo](https://github.com/nathansobo) * [sockmaster27](https://github.com/sockmaster27) * [André Staltz](https://github.com/staltz) * [Ingvar Stepanyan](https://github.com/RReverser) * [stoically](https://github.com/stoically) * [Andrew Stucki](https://github.com/andrewstucki) * [Martin Svanberg](https://github.com/msvbg) * [A2ZH (theJian)](https://github.com/theJian) * [Alex Touchet](https://github.com/atouchet) * [Corbin Uselton](https://github.com/corbinu) * [K.J. Valencik](https://github.com/kjvalencik) * [Velithris](https://github.com/Zireael-N) * [Roberto Vidal](https://github.com/jrvidal) * [Georg Vienna](https://github.com/geovie) * [Daijiro Wachi](https://github.com/watilde) * [Chi Wang](https://github.com/patr0nus) * [wangcong](https://github.com/king6cong) * [Amila Welihinda](https://github.com/amilajack) * [xyloflake](https://github.com/xyloflake) * [Felix Yan](https://github.com/felixonmars) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at david.herman@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Neon Neon welcomes contribution from everyone. Here are some resources to help you join us! ## Contributions ### Issue reports We welcome issues and aim to keep the barrier low. Just [file a GitHub issue](https://github.com/neon-bindings/neon/issues) like normal---we don't require issue templates or have any particular process. That said, the more information you can provide to help us reproduce your issue, the better! ### Requests For Comments If you would like to propose a design change or new feature for the Neon API or the `neon` command-line tool, we encourage you to [submit an RFC](https://github.com/neon-bindings/rfcs)! The [RFC process](https://github.com/neon-bindings/rfcs#the-process) has a little more overhead than filing an issue, but it's for the goal of allowing the Neon community to have a chance to vet design ideas and reach consensus. And even at that, we've deliberately kept the [RFC template](https://github.com/neon-bindings/rfcs/blob/main/0000-template.md) simple and open-ended. ### Good first bugs Search our issue tracker for anything labeled **[beginner friendly](https://github.com/neon-bindings/neon/issues?q=is%3Aissue+is%3Aopen+label%3A%22beginner+friendly%22)**. **We are here to help you!** Join our Slack and send an at-mention to **@dherman** and we'll happily arrange to help you get going and mentor you as much as you feel would be helpful. ## Conduct We follow the [Contributor Covenant Code of Conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html). The maintainers of Neon personally promise to work actively to uphold that code of conduct. We aim to foster a community that is welcoming, inclusive, empathetic, and kind. If you share those goals and want to have a ton of fun playing with Rust and JS, we hope you will come be a part of our community! ## License Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as described in the [README](README.md), without any additional terms or conditions. ## Communication * Slack: -- get an invite from [the Slackin app](https://rust-bindings-slackin.herokuapp.com) ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "crates/*", "test/*", "bench", ] [profile.release] lto = true ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: LICENSE-MIT ================================================ Copyright (c) 2015 David Herman 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. ================================================ FILE: README.md ================================================ # neon [![Cargo](https://img.shields.io/crates/v/neon.svg)](https://crates.io/crates/neon) [![Test Status](https://github.com/neon-bindings/neon/workflows/CI/badge.svg)](https://github.com/neon-bindings/neon/actions?query=workflow%3A%22CI%22) [![Lint Status](https://github.com/neon-bindings/neon/workflows/Lints/badge.svg)](https://github.com/neon-bindings/neon/actions?query=workflow%3A%22Lints%22) Rust bindings for writing safe and fast Node.js native addons. ## Getting started Once you have the [platform dependencies](https://neon-bindings.com/docs/quick-start) installed, getting started is as simple as: ``` $ npm init neon@latest my-project ``` Then see the [Hello World guide](https://neon-bindings.com/docs/hello-world/) for writing your first Hello World in Neon! ## Docs See our [Neon fundamentals docs](https://neon-bindings.com/docs/intro) and our [API docs](https://docs.rs/neon/latest/neon). ## Neon 1.0.0 Migration Guide The latest version of Neon, 1.0.0, includes several breaking changes in order to fix unsoundness, improve consistency, and add features. **Read the new [migration guide](doc/MIGRATION_GUIDE_1.0.0.md)** to learn how to port your Neon projects to 1.0.0! ## Platform Support ### Operating Systems | Linux | macOS | Windows | | ------ | ----- | ------- | | ✓ | ✓ | ✓ | ### Node.js Neon actively supports all current and [maintenance releases of Node](https://github.com/nodejs/LTS#release-schedule). If you're using a different version of Node and believe it should be supported, let us know. Older Node version support (minimum v10) may require lower Node-API versions. See the Node [version support matrix](https://nodejs.org/api/n-api.html#node-api-version-matrix) for more details. ### Bun (experimental) [Bun](https://bun.sh/) is an alternate JavaScript runtime that targets Node compatibility. In many cases Neon modules will work in bun; however, at the time of this writing, some Node-API functions are [not implemented](https://github.com/oven-sh/bun/issues/158). ### Rust Neon supports Rust stable version 1.65 and higher. We test on the latest stable, beta, and nightly versions of Rust. ## A Taste... ```rust fn make_an_array(mut cx: FunctionContext) -> JsResult { // Create some values: let n = cx.number(9000); let s = cx.string("hello"); let b = cx.boolean(true); // Create a new array: let array = cx.empty_array(); // Push the values into the array: array.set(&mut cx, 0, n)?; array.set(&mut cx, 1, s)?; array.set(&mut cx, 2, b)?; // Return the array: Ok(array) } #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("make_an_array", make_an_array)?; Ok(()) } ``` For more examples, see our [examples repo](https://github.com/neon-bindings/examples) and [integration tests](test). ## Get Involved The Neon community is just getting started and there's tons of fun to be had. Come play! :) The [Neon Community Slack](https://rust-bindings.slack.com) is open to all; use [this invite link](https://join.slack.com/t/rust-bindings/shared_invite/zt-1pl5s83xe-ZvXyrzL8vuUmijU~7yiEcg) to receive an invitation. ### Testing Neon The Neon project is both an [NPM workspace](https://docs.npmjs.com/cli/v8/using-npm/workspaces) and a [Cargo workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The full suite of tests may be executed by installing and testing the NPM workspace. ```sh npm install npm test ``` Individual JavaScript packages may be tested with an `npm` workspace command: ``` npm --workspace=create-neon test ``` Individual Rust crates may be tested with a `cargo` workspace command: ``` cargo test -p neon-build ``` ## License Licensed under either of * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) at your option. ================================================ FILE: RELEASES.md ================================================ # `cargo-cp-artifact` `0.1.9` supports a [breaking change in `cargo`](https://github.com/rust-lang/cargo/issues/13867) that converts artifact names from `kebab-case` to `snake_case`. # Version 1.2.0-alpha.0 ### New Features * [Class macro](https://docs.rs/neon/1/neon/attr.class) * [impl TryFromJs for i32 and u32](https://github.com/neon-bindings/neon/pull/1107) * [impl TryIntoJs for LazyLock](https://github.com/neon-bindings/neon/pull/1111) * [`Deferred::settle()` and `Deferred::try_settle()`](https://github.com/neon-bindings/neon/pull/1120) # Version 1.1.0 ## Continued Commitment to Compatibility Our strong commitment to compatibility since the 1.0 release remains unchanged. New functionality that has not yet stabilized is published under feature flags, so the only breaking changes that we expect to publish are those that affect the unstable features, or, as always, safety bugfixes. ## Version 1.1.1 Hotfix for Node-API versions lower than 5 (https://github.com/neon-bindings/neon/pull/1106). ## Version 1.1.0 ### New Features * [Extractors API](https://docs.rs/neon/1/neon/types/extract/index.html) * [`#[export]` macro](https://docs.rs/neon/1/neon/attr.export.html) * [`npm init neon -- --lib`](https://www.npmjs.com/package/create-neon) * [`JsBox::deref()`](https://docs.rs/neon/1/neon/types/struct.JsBox.html#method.deref) * [`Handle<'_, JsBox>::as_inner()`](https://docs.rs/neon/1/neon/handle/struct.Handle.html#method.as_inner) ### Bugfixes * Fix panic when borrowing empty buffer or typed array (https://github.com/neon-bindings/neon/pull/1058) * Fix build script behavior based on cargo diagnostics format change (https://github.com/neon-bindings/neon/pull/1039) ## Version 1.1.0-alpha.2 ### Breaking Changes (unstable features only) * Convert snake_case to camelCase when exporting functions (https://github.com/neon-bindings/neon/pull/1084) * Remove `FromArgs` impl on `T: TryFromJs` and add `cx.{arg, arg_opt}` (https://github.com/neon-bindings/neon/pull/1096) ### Other * Relax lifetime constraints on `With` (https://github.com/neon-bindings/neon/pull/1086) * Add `JsBox::{deref, as_inner}` to get long-lived reference to JsBox contents (https://github.com/neon-bindings/neon/pull/1087) * Add extractors for TypedArrays (https://github.com/neon-bindings/neon/pull/1089) * Add extractors for common container types (https://github.com/neon-bindings/neon/pull/1091) ## Version 1.1.0-alpha.1 ### Breaking Changes (unstable features only) * `TryIntoJs` and `TryFromJs` take `Cx` instead of generic `Context` (https://github.com/neon-bindings/neon/pull/1062) ### Bugfixes * Fix panic when borrowing empty buffer or typed array (https://github.com/neon-bindings/neon/pull/1058) ### Other * More reliable checking for `Result` types in `#[export]` (https://github.com/neon-bindings/neon/pull/1057) * Allow users to take `Cx` instead of generic `Context` (https://github.com/neon-bindings/neon/pull/1048) * Introduce `With` for `TryIntoJs` (https://github.com/neon-bindings/neon/pull/1059) * Add tokio async runtime support to `#[export]` (https://github.com/neon-bindings/neon/pull/1055) * Add `TryIntoJs` and `TryFromJs` for common container types (https://github.com/neon-bindings/neon/pull/1066) * Allow `Cx` in exported functions (https://github.com/neon-bindings/neon/pull/1068) ## Version 1.1.0-alpha.0 ### Bugfixes * Fix build script behavior based on cargo diagnostics format change (https://github.com/neon-bindings/neon/pull/1039) ### Other * Added Extractors API (https://github.com/neon-bindings/neon/pull/1024) * Added `#[export]` macro (https://github.com/neon-bindings/neon/pull/1025) * Added `npm init neon --lib` (https://github.com/neon-bindings/neon/pull/1014 and https://github.com/neon-bindings/neon/pull/1041) # Version 1.0.0 ## Commitment to Compatibility The release of Neon 1.0 marks our commitment to backwards-compatibility: starting with 1.0.0, Neon users can be confident that future **upgrades to Neon 1.x versions should never require code changes** (with the possible exception of safety bugfixes, which we expect to be rare). We also do not anticipate releasing new major versions often and do not have any plans to do so for now. ## Breaking Changes * Remove the generic parameter from `JsFunction` (https://github.com/neon-bindings/neon/pull/989) * `JsArray::new` takes a `usize` instead of a `u32` (https://github.com/neon-bindings/neon/pull/988) * Made `Context::global` read a key and added `Context::global_object` (https://github.com/neon-bindings/neon/pull/987) * Deprecated feature flags were removed ## Bug fixes * Fix `unhandledRejection` with `JsPromise::to_future` (https://github.com/neon-bindings/neon/pull/1008) * Typo in `cargo-cp-artifact` help (https://github.com/neon-bindings/neon/pull/998) * Typo in README (https://github.com/neon-bindings/neon/pull/1012) ## Other https://github.com/neon-bindings/neon/pull/1010 * Relaxed error behavior on missing Node-API symbols. Neon will panic on first use instead of aborting the process at module load time. * Bumped dependency versions * Changed to edition 2021 * Updated support matrix to Node 18, 20, and 21 # Version 1.0.0-alpha.4 Patch to enable new features flags in docs.rs. # Version 1.0.0-alpha.3 ## Breaking Changes * Removed `Managed` trait ## Improvements * Added `JsBigInt` (https://github.com/neon-bindings/neon/pull/963). * Added UTF-16 functions to `JsString` (https://github.com/neon-bindings/neon/pull/944). * Relaxed `Send` constraints (https://github.com/neon-bindings/neon/pull/979) * Lifecycle support for 32-bit (https://github.com/neon-bindings/neon/pull/977) * Added `sys` feature (https://github.com/neon-bindings/neon/pull/970) ## Bug Fixes * Fix a scope leak in release builds (https://github.com/neon-bindings/neon/pull/952). ## Docs * Examples added for many types ((https://github.com/neon-bindings/neon/pull/942)). ### `cargo-cp-artifact` `0.1.8` fixes sending additional arguments on Windows (https://github.com/neon-bindings/neon/pull/972). # Version 1.0.0-alpha.2 ## Breaking Changes ### `neon::object::This` https://github.com/neon-bindings/neon/pull/918 Trait [`neon::object::This`](https://docs.rs/neon/latest/neon/object/trait.This.html) has been removed. `This` was primarily added for use with the `declare_types!` macro to generate classes. The macro was removed and `This` is no longer needed. Additionally, the `This` argument on `JsFunction` was found to be _invalid_ because it asserted at compile time a type for `this` that could change at runtime. (Note that this was _not_ unsound because the type would be checked by Node-API and result in a `panic`.) ### `JsFunction::this` https://github.com/neon-bindings/neon/pull/918 `JsFunction::this` was changed to perform a downcast and be _fallible_. This is in line with similar APIs (e.g., `Object::get`). Additionally, an infallible version, `JsValue::this_value` was added that does _not_ perform a downcast. ### Added Feature flag for external buffers https://github.com/neon-bindings/neon/pull/937 Electron began using [pointer compression](https://www.electronjs.org/blog/v8-memory-cage) on JavaScript values that is incompatible with external buffers. As a preventative measure, `JsArrayBuffer::external` and `JsBuffer::external` have been placed behind a feature flag that warns of Electron incompatibility. ## Improvements * Lifetimes were relaxed on `execute_scoped` to allow valid code to compile. (https://github.com/neon-bindings/neon/pull/919) * Added a `from_slice` helper on `TypedArray` (https://github.com/neon-bindings/neon/pull/925) * `JsTypedArray` construction and type aliases (https://github.com/neon-bindings/neon/pull/909) ## Bug Fixes * Fixed a panic on VM shutdown when using `Channel` (https://github.com/neon-bindings/neon/pull/934) * Type tags were added to `JsBox` to prevent undefined behavior when multiple native add-ons are used (https://github.com/neon-bindings/neon/pull/907) ## Docs * Significantly improved documentation of `TypedArray` (https://github.com/neon-bindings/neon/pull/909) * Removed unused values in `Channel` docs (https://github.com/neon-bindings/neon/pull/925) ### `cargo-cp-artifact` `0.1.7` includes a fix to unlink `.node` files before copying to address common code signing errors on macOS (https://github.com/neon-bindings/neon/pull/921). # Version 1.0.0-alpha.1 Pre-release of a major milestone for Neon. 1.0. ## Breaking Changes ### Major * Removed the legacy backend; only Node-API is supported going forward (https://github.com/neon-bindings/neon/pull/881) * Removed `neon::result::JsResultExt` in favor of more general `neon::result::ResultExt` (https://github.com/neon-bindings/neon/pull/904) ### Minor * Length APIs (`argument`, `argument_ops`, `len`) use `usize` instead of `i32` (https://github.com/neon-bindings/neon/pull/889) * Deprecate feature flags for accepted RFCs (https://github.com/neon-bindings/neon/pull/872) * `neon::meta::version` returns `semver@1` version instead of `0.9` (https://github.com/neon-bindings/neon/pull/912) ## Features * Add `Object.freeze` and `Object.seal` (https://github.com/neon-bindings/neon/pull/891) * Futures RFC (https://github.com/neon-bindings/neon/pull/872) Implementation (https://github.com/neon-bindings/neon/pull/874) - Await `JoinHandle` from sending an event on a `Channel` - Adapt `JsPromise` to `JsFuture` * API for thread-local data (i.e., instance data) (https://github.com/neon-bindings/neon/pull/902) * Add Object::call_with() convenience method to call a method on an object (https://github.com/neon-bindings/neon/pull/879) ## Bug Fixes * Relax the lifetime constraints on `TypedArray` borrows (https://github.com/neon-bindings/neon/pull/877) * Allowing missing symbols at load time to support [bun](https://bun.sh) (https://github.com/neon-bindings/neon/pull/914) * Prevent a panic when an async event is called after the JavaScript runtime has stopped (https://github.com/neon-bindings/neon/pull/913) * Fix a soundness hole in `JsArrayBuffer::external` and `JsBuffer::external` (https://github.com/neon-bindings/neon/pull/897) ## Docs * Fixed mistake in `Object::get` docs (https://github.com/neon-bindings/neon/pull/903) * Fixed link in README to migration guide (https://github.com/neon-bindings/neon/pull/895) ## Internal * Moved `cargo-cp-artirfact` into the monorepo (https://github.com/neon-bindings/neon/pull/905) * Decreased the size of the Neon build matrix (https://github.com/neon-bindings/neon/pull/893) * Removed scope abstraction from legacy backend (https://github.com/neon-bindings/neon/pull/888) * Improved the monorepo structure of neon (https://github.com/neon-bindings/neon/pull/884) # Version 0.10.1 Fix a soundness hole in `JsArrayBuffer::external` and `JsBuffer::external` (https://github.com/neon-bindings/neon/pull/897). Thanks to [@Cassy343](https://github.com/Cassy343) for finding the [issue](https://github.com/neon-bindings/neon/issues/896)! In previous versions of Neon, it was possible to create a `JsArrayBuffer` or `JsBuffer` that references data without the `'static` lifetime. ```rust pub fn soundness_hole(mut cx: FunctionContext) -> JsResult { let mut data = vec![0u8, 1, 2, 3]; // Creating an external from `&mut [u8]` instead of `Vec` since there is a blanket impl // of `AsMut for &mut T` let buf = JsArrayBuffer::external(&mut cx, data.as_mut_slice()); // `buf` is still holding a reference to `data`! drop(data); Ok(buf) } ``` # Version 0.10 See the [Neon 0.10 Migration Guide](docs/MIGRATION_GUIDE_0.10.md) for more details about new features and breaking changes. ## Features * New [buffer borrowing API](https://github.com/neon-bindings/neon/pull/780) * Added [JoinHandle](https://github.com/neon-bindings/neon/pull/787) for `Channel::send` * [`JsPromise` and `TaskBuilder`](https://github.com/neon-bindings/neon/pull/789) * Handle [panics and exceptions](https://github.com/neon-bindings/neon/pull/808) in Channels and Tasks * [Function call / construct builders](https://github.com/neon-bindings/neon/pull/829) and [simplify low level call](https://github.com/neon-bindings/neon/pull/825) * Create [functions from closures](https://github.com/neon-bindings/neon/pull/811) ## Minor Improvements * [Performance improvements](https://github.com/neon-bindings/neon/pull/815) * [Rename N-API to Node-API](https://github.com/neon-bindings/neon/pull/753) in docs to match Node changes * Remove unused [cslice dependency](https://github.com/neon-bindings/neon/pull/794) * Switch to [`syn-mid`](https://github.com/neon-bindings/neon/pull/814) for faster compile times * Downcast in [`Object::get`](https://github.com/neon-bindings/neon/pull/839) * Added [migration guide](https://github.com/neon-bindings/neon/pull/859) * Added [`Object::get_opt` and `Object::get_value`](https://github.com/neon-bindings/neon/pull/867) ## Fixes * [Safety] Make it harder to store and forge [Throw](https://github.com/neon-bindings/neon/pull/797) * [Soundness] [Make `JsValue` types `!Copy`](https://github.com/neon-bindings/neon/pull/832) * [Soundness] [Tag `Root`](https://github.com/neon-bindings/neon/pull/847) with instance id * `create-neon` no longer [leaves partial project on disk](https://github.com/neon-bindings/neon/pull/840) * Fix legacy backend on [Electron and Windows](https://github.com/neon-bindings/neon/pull/785) * [FreeBSD support](https://github.com/neon-bindings/neon/pull/856) on legacy backend ## Internal Improvements * Replace Electron tests [with Playwright](https://github.com/neon-bindings/neon/pull/835) * Re-organize Neon into an [npm workspace](https://github.com/neon-bindings/neon/pull/852) * [Fix crates.io badge](https://github.com/neon-bindings/neon/pull/781) * [Doc test fixes](https://github.com/neon-bindings/neon/pull/800) * Fix [broken link](https://github.com/neon-bindings/neon/pull/804) in the README # Version 0.9.1 * Expose the `Finalize` trait as `neon::types::Finalize` so that docs are visible * Improved docs and build scripts in `create-neon` to make release builds more discoverable (https://github.com/neon-bindings/neon/pull/771) * Update `nan` to fix an Electron 13 incompatibility (https://github.com/neon-bindings/neon/pull/778) # Version 0.9.0 ## Performance `Channel`, formerly `EventQueue`, are now cloneable. Clones share a backing queue to take advantage of an [optimization](https://github.com/nodejs/node/pull/38506) in Node threadsafe functions. Additionally, when specifying Node API 6 or higher (`napi-6`), calling `cx.channel()` will return a shared queue (https://github.com/neon-bindings/neon/pull/739). The change may cause a performance regression in some pathological use cases (https://github.com/neon-bindings/neon/issues/762). ## Deprecation `EventQueue` and `EventQueueError` have been renamed to `Channel` and `ChannelError` respectively to clarify their function and similarity to Rust channels. The types are available as deprecated aliases (https://github.com/neon-bindings/neon/pull/752). ## Docs * Document error causes for `Channel::try_send` docs (https://github.com/neon-bindings/neon/pull/767) * Document `neon::object` (https://github.com/neon-bindings/neon/pull/740) ## Fixes * Fix usage of a removed API in legacy buffers (https://github.com/neon-bindings/neon/pull/769) # Version 0.8.3 * Fix crash caused by non-thread safety in napi_threadsafefunction on early termination (https://github.com/neon-bindings/neon/pull/744) * Fix memory leak in `Root` (https://github.com/neon-bindings/neon/pull/750) # Version 0.8.2 * More docs improvements * Added a deprecation warning to `neon new` (https://github.com/neon-bindings/neon/pull/722) # Version 0.8.1 * Fix `legacy-backend` for Node 16 (https://github.com/neon-bindings/neon/pull/715) * Various docs improvements # Version 0.8.0 ## Fixes * `as_slice` and `as_mut_slice` properly handle a `null` pointer from an empty buffer (https://github.com/neon-bindings/neon/pull/681) * Global drop queue added to avoid panics on N-API 6+ when dropping a `Root` (https://github.com/neon-bindings/neon/pull/700) ## Features * Added `neon::reflect::eval` (https://github.com/neon-bindings/neon/pull/692) * Added `create-neon` for creating an N-API project (https://github.com/neon-bindings/neon/pull/690) * Added details to the `README.md` generated by `create-neon` (https://github.com/neon-bindings/neon/pull/697) ## Improvements * Switched N-API tests to `cargo-cp-artifact` (https://github.com/neon-bindings/neon/pull/687) * Added `impl Finalize for Option` (https://github.com/neon-bindings/neon/pull/680) * Added a N-API migration guide (https://github.com/neon-bindings/neon/pull/685) ## Housekeeping * Lint fixes (https://github.com/neon-bindings/neon/pull/609) * Lint CI enforcement and `cargo fmt` (https://github.com/neon-bindings/neon/pull/698) # Version 0.7.1 ### Features * Added `JsDate` to N-API backend (https://github.com/neon-bindings/neon/pull/639) * Implement `JsBuffer::unitialized` for N-API backend (https://github.com/neon-bindings/neon/pull/664) ### Fixes * Do not panic if a `Root` is leaked after the event loop has stopped (https://github.com/neon-bindings/neon/pull/677) * Stubs for features that will not be implemented in the N-API backend are removed (https://github.com/neon-bindings/neon/pull/663) * Fix doc URL link (https://github.com/neon-bindings/neon/pull/663) # Version 0.7.0 ## N-API ### Version Selection Neon supports a large number of different Node versions which may have different N-API requirements. Neon now supports selecting the minimum required N-API version required by a module. For example, for N-API Version 4: ```toml neon = { version = "0.7", default-features = false, features = ["napi-4"] } ``` If the Neon module is loaded in an older version of Node that does not support that N-API version, a `panic` message will inform the user. ### Threadsafe Functions A prerelease version of `EventQueue` for calling into the main JavaScript thread from Rust threads can be enabled with the `event-queue-api` feature flag. The API is considered unstable and may change in the future until the [RFC](https://github.com/neon-bindings/rfcs/pull/32) is merged. # Version 0.6.0 The `cx.try_catch(..)` API has been updated to return `T: Sized` instead of `T: Value` (https://github.com/neon-bindings/neon/pull/631). This API is strictly more powerful and allows users to return both JavaScript and Rust values from `try_catch` closures. ## N-API * N-API symbols are now loaded dynamically (https://github.com/neon-bindings/neon/pull/646) * Build process for N-API is greatly simplified by leveraging dynamic loading (https://github.com/neon-bindings/neon/pull/647) # Version 0.5.3 ## Bug Fixes Upgrade `node-gyp` (https://github.com/neon-bindings/neon/pull/623) * Fix Windows Node 15 * Fix Apple M1 ## Features Added `neon::main` macro as a replacement for `register_module!` (https://github.com/neon-bindings/neon/pull/636) ## Known Issues Builds occassionally fail with Windows, Node 15 and npm 7 (https://github.com/neon-bindings/neon/issues/642) # Version 0.5.2 ## CLI Added support for [additional arguments](https://github.com/neon-bindings/neon/pull/633) passed to `cargo build`. Resolves https://github.com/neon-bindings/neon/issues/471. ```sh neon build --release -- --features awesome ``` ## N-API * Improved [arguments performance](https://github.com/neon-bindings/neon/pull/610) * Add [redirect and `NPM_CONFIG_DISTURL`](https://github.com/neon-bindings/neon/pull/620) support # Version 0.5.1 ## Performance * `smallvec` is used for collecting arguments and yields a small performance gain when calling `JsFunction` ## Broader Support Thanks to @staltz, neon now builds for both iOS and Android with [nodejs-mobile](https://github.com/JaneaSystems/nodejs-mobile). # Version 0.5.0 _Re-publish_ Versions `0.4.1` and `0.4.2` included a breaking change in `neon-runtime`. At the time, this was considered acceptable because `neon-runtime` is considered an internal crate and not part of the public API. However, it was discovered, after publishing, that `neon-serde`, a commonly used crate in the `neon` ecosystem, contained a direct dependency on `neon-runtime`. In order to best support users, versions `0.4.1` and `0.4.2` were "yanked" and re-published as `0.5.0`. Additionally, the team is working with the authors of `neon-serde` to remove the dependency on `neon-runtime` to prevent future issues. ## Bug Fixes * Fix stack overflow in `DowncastError` `Display` impl (https://github.com/neon-bindings/neon/pull/606) # Version 0.4.2 _Unpublished / Yanked_ ## Bug Fixes * Fix memory leak and race condition in `EventHandler` # Version 0.4.1 _Unpublished / Yanked_ ## Features ### Try Catch Added the `cx.try_catch` API of [RFC 29](https://github.com/neon-bindings/rfcs/pull/29). This feature is behind the `try-catch-api` feature flag. ## Bug Fixes * Pass `async_context` to `node::MakeCallback` (https://github.com/neon-bindings/neon/pull/498) * Cache bust neon if node version changes (https://github.com/neon-bindings/neon/pull/388) * Fix debug builds in windows (https://github.com/neon-bindings/neon/pull/400) * Fix cross compiling architectures (https://github.com/neon-bindings/neon/pull/491) * Fix neon new hanging on Windows (https://github.com/neon-bindings/neon/pull/537) ## CI Improvements The Neon Project now uses Github Actions thanks to @lhr0909! As part of this change, CI now runs on all of our supported platforms (macOS, Windows, linux) and Node versions. # Version ✨0.4✨ 🎉 ## `EventHandler` API The [`EventHandler` API](https://github.com/neon-bindings/rfcs/blob/main/text/0025-event-handler.md) is a new feature for scheduling work on the javascript main thread from other threads. Big thanks to @geovie for the RFC and implementation. This feature is currently _unstable_ and gated by a `event-handler-api` feature flag. ## Improvements * New project template updated for Rust 2018 ## Bug Fixes * Workaround for nodejs/node-gyp#1933 * Docs build fixed * Temporarily disable static tests which keep breaking CI ## N-API * Context/Isolate threading * Scopes * Strings * Primitive values (numbers, undefined, null, boolean) # Version 0.3.3 Hot fix for `neon build` in projects with many dependencies. # Version 0.3.2 ## Bug fixes and Small Features * Disable node module registration on test build, allowing `cargo test` to be used on neon modules * Added support for alternate `CARGO_TARGET_DIR` locations (e.g., workspaces) * Added macros to `neon::prelude` to improve ergonomics in Rust 2018 * Link `win_delay_hook` when building with `electron-build-env`, fixing Windows Electron * Fixed missing `__cxa_pure_virtual` on Linux * Copy native files into `OUT_DIR` and build there to fix `cargo publish` and follow best practices * Eliminated `mem::uniitialized()` usage, reducing warnings and fixing an instance of undefined behavior ## Potentially Breaking The macOS link arguments were moved from `neon-cli` to `neon-build`. This is more idiomatic, but makes `neon-build` _required_ for macOS builds where it was unnecessary before. Since `neon-build` has been included in the project template since `0.1` this change was not deemed significant enough to warrant a major revision. ## N-API Neon 0.3.2 lays the groundwork for the next major revision. Development of Neon against an ABI stable Node API (N-API) will occur on main. * Added `legacy-runtime` and `n-api` feature flags for toggling neon runtime * Moved the legacy runtime to `nodejs-sys` crate * Stubbed required `n-api` implementation * Added `feature` flag to `neon-cli` to help configuring `n-api` projects # Version 0.3.1 * Build v0.3 project templates by default in the CLI # Version 0.3 ## Breaking Changes * [Removed support for Node 6](https://github.com/neon-bindings/neon/pull/420) ## Bug Fixes * Correctly fail the build if [custom build command fails](https://github.com/neon-bindings/neon/pull/421) * Fix breaking changes with v8 [`GetFunction`](https://github.com/neon-bindings/neon/pull/410) * Moved `nan` from `devDependencies` to `dependencies` in [`neon-runtime`](https://github.com/neon-bindings/neon/pull/367) * Changed neon [crate type](https://github.com/neon-bindings/neon/pull/358) from `dylib` to `cdylib` * Ensure that neon module loading is [not optimized away](https://github.com/neon-bindings/neon/pull/392) ## Improvements * Added support for [`CARGO_BUILD_TARGET` environment variable](https://github.com/neon-bindings/neon/pull/411) # Version ✨0.2✨ 🎉 See the [Neon 0.2 Migration Guide](https://github.com/neon-bindings/neon/wiki/Neon-0.2-Migration-Guide) for documentation on migrating your projects from the Neon 0.1.x series to Neon 0.2, and please [let us know](https://github.com/neon-bindings/neon#get-involved) if you need help! * Release automation (#318) * New `ArrayBuffer` views API -- see [RFC 5](https://github.com/neon-bindings/rfcs/blob/main/text/0005-array-buffer-views.md) (#306) * VM 2.0 -- see [RFC 14](https://github.com/neon-bindings/rfcs/blob/main/text/0014-vm-2.0.md) (#306) * New `JsString` constructor -- see [RFC 21](https://github.com/neon-bindings/rfcs/blob/main/text/0021-string-constructor.md) (#322) * Eliminated `JsInteger`, `JsVariant`, `callee()` -- see [RFC 22](https://github.com/neon-bindings/rfcs/blob/main/text/0022-zero-dot-two.md) (#323) * Renamed `Key` to `PropertyKey` and its method names -- see [RFC 22](https://github.com/neon-bindings/rfcs/blob/main/text/0022-zero-dot-two.md) (#323) * Module reorganization -- see [RFC 20](https://github.com/neon-bindings/rfcs/blob/main/text/0020-module-reorg.md) (#324) * New `JsError` API -- see [RFC 23](https://github.com/neon-bindings/rfcs/blob/main/text/0023-error-subtyping.md) (#325) * Eliminated `ToJsString` API -- see [RFC 22](https://github.com/neon-bindings/rfcs/blob/main/text/0022-zero-dot-two.md) (#326) * Eliminated `NEON_NODE_ABI` env var -- see [RFC 22](https://github.com/neon-bindings/rfcs/blob/main/text/0022-zero-dot-two.md) (#327) * Default to release builds -- see [RFC 22](https://github.com/neon-bindings/rfcs/blob/main/text/0022-zero-dot-two.md) (#328) * Made `Buffer` construction safe by default (#329, #331) * Made `Throw` not implement `std::error::Error` to avoid accidental suppression, thanks to [@kjvalencik](https://github.com/kjvalencik) (#334) * Fixed a bug causing unnecessary rebuilds, thanks to [@kjvalencik](https://github.com/kjvalencik) (#343) * Fixed a soundness bug in the `Task` API, thanks to [@kjvalencik](https://github.com/kjvalencik) (#335) # Version 0.1.23 * Optimization in `Scope` structures, thanks to [@maciejhirsz](https://github.com/maciejhirsz) (#282) * Fixed a memory leak in the `Task` API, thanks to [@kjvalencik](https://github.com/kjvalencik) (#291) * Add support for Node 10, thanks to [@mhsjlw](https://github.com/mhsjlw) and [@nomadtechie](https://github.com/nomadtechie) (#314) # Version 0.1.22 * Reinstate `JsInteger` (although it's deprecated) for now, to be removed in 0.2. (#279) # Version 0.1.21 * Fix a bug that was causing annoying unnecessary rebuilds ([#242](https://github.com/neon-bindings/neon/issues/242)). * New [API for getting the global object](https://api.neon-bindings.com/neon/scope/trait.scope#method.global) ([#249](https://github.com/neon-bindings/neon/issues/249)). # Version 0.1.20 * Background task API ([#214](https://github.com/neon-bindings/neon/pull/214)). * Fixes to Windows builds ([#221](https://github.com/neon-bindings/neon/pull/221), [#227](https://github.com/neon-bindings/neon/pull/227)), thanks to [@hone](https://github.com/hone)'s tenacious troubleshooting. # Version 0.1.19 * TypeScript upgrade fixes ([neon-bindings/neon-cli#62](https://github.com/neon-bindings/neon-cli/pull/62), [neon-bindings/neon-cli#65](https://github.com/neon-bindings/neon-cli/pull/65)). # Version 0.1.18 * CLI bugfix ([neon-bindings/neon-cli#59](https://github.com/neon-bindings/neon-cli/pull/59)). * JsArrayBuffer ([#210](https://github.com/neon-bindings/neon/pull/210)). # Version 0.1.17 * CLI bugfix ([neon-bindings/neon-cli#57](https://github.com/neon-bindings/neon-cli/pull/57)). # Version 0.1.16 * CLI bugfix ([neon-bindings/neon-cli#56](https://github.com/neon-bindings/neon-cli/pull/56)). # Version 0.1.15 (2017-05-21) * Better Electron support in CLI's build process. * Better support for Electron via the artifacts file ([neon-bindings/neon-cli#52](https://github.com/neon-bindings/neon-cli/pull/52)). # Version 0.1.14 (2017-04-02) * Ensure failing tests break the build ([#191](https://github.com/neon-bindings/neon/pull/191)) * Catch Rust panics and convert them to JS exceptions ([#192](https://github.com/neon-bindings/neon/pull/192)) * Implement `Error` for `Throw` ([#201](https://github.com/neon-bindings/neon/pull/191)) * Clean up the CLI and allow `neon build` to optionally take module names ([neon-bindings/neon-cli#48](https://github.com/neon-bindings/neon-cli/pull/48)). # Version 0.1.13 (2017-02-17) * More robust build scripts for neon-runtime, fixing Homebrew node installations (see [#189](https://github.com/neon-bindings/neon/pull/189)) # Version 0.1.12 (2017-02-16) * [Optimized rooting protocol](https://github.com/neon-bindings/neon/commit/cef41584d9978eda2d59866a077cfe7c7d3fa46e) * [Eliminate rustc warnings](https://github.com/neon-bindings/neon/pull/107) * Lots of internal API docs * Windows support! :tada: * [Renamed `neon-sys` to `neon-runtime`](https://github.com/neon-bindings/neon/issues/169) * Depend on `neon-build` as a build dependency (see [neon-bindings/neon-cli#46](https://github.com/neon-bindings/neon-cli/issues/46)). # Version 0.1.11 (2016-08-08) * [Exposed `This` trait](https://github.com/neon-bindings/neon/issues/101) to allow user-level abstractions involving `FunctionCall` * Bump version to match Neon so they can be kept in sync from now on. * Generate a `build.rs` to make Windows work (see [neon-bindings/neon-cli#42](https://github.com/neon-bindings/neon-cli/pull/42) and [neon-bindings/neon-cli#44](https://github.com/neon-bindings/neon-cli/issues/44)). # Version 0.1.10 (2016-05-11) * Added `JsError` API with support for throwing [all](https://github.com/neon-bindings/neon/issues/65) [standard](https://github.com/neon-bindings/neon/issues/66) [error](https://github.com/neon-bindings/neon/issues/67) [types](https://github.com/neon-bindings/neon/issues/74) * [Test harness and CI integration](https://github.com/neon-bindings/neon/issues/80)!! :tada: :tada: :tada: * API to [call JS functions from Rust](https://github.com/neon-bindings/neon/issues/60) * API to [new JS functions from Rust](https://github.com/neon-bindings/neon/issues/61) * Added [generalized `as_slice` and `as_mut_slice` methods](https://github.com/neon-bindings/neon/issues/64) to `CSlice` API. * Fixed a [soundness issue](https://github.com/neon-bindings/neon/issues/64) with Locks. ## Incompatible Changes * The `JsTypeError` type is gone, and replaced by the more general `JsError` type. * `neon::js::error::JsTypeError::throw(msg)` is now `neon::js::error::JsError::throw(neon::js::error::kind::TypeError, msg)` ================================================ FILE: bench/.gitignore ================================================ index.node npm-debug.log* cargo.log cross.log ================================================ FILE: bench/Cargo.toml ================================================ [package] name = "bench" version = "0.1.0" description = "Neon performance regression suite" authors = ["David Herman "] license = "MIT" edition = "2021" exclude = ["index.node"] [lib] crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] neon = { path = "../crates/neon" } ================================================ FILE: bench/README.md ================================================ # bench: Neon performance regression suite ## Building bench To run the build, run: ```sh $ npm run build ``` ## Running the benchmarks To run the benchmarks, run: ```sh $ npm run benchmark ``` ================================================ FILE: bench/index.js ================================================ const { Suite, jsonReport } = require("bench-node"); const addon = require("./index.node"); function median(values) { const sorted = [...values].sort((a, b) => a - b); const n = sorted.length; return n % 2 === 0 ? (sorted[n / 2 - 1] + sorted[n / 2]) / 2 : sorted[Math.floor(n / 2)]; } // A custom reporter for the bencher.dev benchmarking platform. // Format: https://bencher.dev/docs/reference/bencher-metric-format/ // // The reporter provides two measures for each benchmark: // - "throughput": The number of operations per second. // - "latency": The time taken to perform an operation, in ns. // * "value": The median value of all samples. // * "lower_value": The minimum value of all samples. // * "upper_value": The maximum value of all samples. function reportBencherDev(results) { const bmf = Object.create(null); for (const result of results) { bmf[result.name] = { throughput: { value: result.opsSec, }, latency: { value: median(result.histogram.sampleData), lower_value: result.histogram.min, upper_value: result.histogram.max, }, }; } console.log(JSON.stringify(bmf, null, 2)); } const suite = new Suite({ reporter: reportBencherDev }); suite.add("hello-world", () => { addon.hello(); }); suite.add("manually-exported-noop", () => { addon.manualNoop(); }); suite.add("auto-exported-noop", () => { addon.exportNoop(); }); function triple(s, n, b) { return [s, n, b]; } suite.add("JsFunction::call", () => { addon.callCallbackWithCall(triple); }); suite.add("JsFunction::call_with", () => { addon.callCallbackWithCallWith(triple); }); suite.add("JsFunction::bind", () => { addon.callCallbackWithBind(triple); }); suite.run(); ================================================ FILE: bench/package.json ================================================ { "name": "bench", "private": true, "description": "Neon performance regression suite", "main": "index.js", "scripts": { "benchmark": "node --allow-natives-syntax index.js", "cargo-build": "cargo build --message-format=json-render-diagnostics > cargo.log", "postcargo-build": "neon dist < cargo.log", "build": "npm run cargo-build -- --release" }, "author": "David Herman ", "devDependencies": { "@neon-rs/cli": "0.1.82" }, "repository": { "type": "git", "url": "git+https://github.com/neon-bindings/neon.git" }, "bugs": { "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon#readme", "dependencies": { "bench-node": "^0.5.4" } } ================================================ FILE: bench/src/lib.rs ================================================ use neon::prelude::*; #[neon::export] fn export_noop() {} fn manual_noop(mut cx: FunctionContext) -> JsResult { Ok(cx.undefined()) } fn hello(mut cx: FunctionContext) -> JsResult { Ok(cx.string("hello node")) } fn call_callback_with_call(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let s = cx.string("hello node"); let n = cx.number(17.0); let b = cx.boolean(true); let this = cx.null(); let args = vec![s.upcast(), n.upcast(), b.upcast()]; f.call(&mut cx, this, args) } fn call_callback_with_call_with(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; f.call_with(&cx) .this(cx.null()) .arg(cx.string("hello node")) .arg(cx.number(17.0)) .arg(cx.boolean(true)) .apply(&mut cx) } fn call_callback_with_bind(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let this = cx.null(); f.bind(&mut cx) .this(this)? .arg("hello node")? .arg(17.0)? .arg(true)? .call() } #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { // Export all macro-registered exports neon::registered().export(&mut cx)?; cx.export_function("hello", hello)?; cx.export_function("manualNoop", manual_noop)?; cx.export_function("callCallbackWithCall", call_callback_with_call)?; cx.export_function("callCallbackWithCallWith", call_callback_with_call_with)?; cx.export_function("callCallbackWithBind", call_callback_with_bind)?; Ok(()) } ================================================ FILE: codecov.yml ================================================ ignore: - "bench" - "test" coverage: status: project: off patch: off ================================================ FILE: crates/neon/Cargo.toml ================================================ [package] name = "neon" version = "1.1.1" authors = ["Dave Herman "] description = "A safe abstraction layer for Node.js." readme = "../../README.md" homepage = "https://www.neon-bindings.com" repository = "https://github.com/neon-bindings/neon" license = "MIT/Apache-2.0" exclude = ["neon.jpg", "doc/**/*"] edition = "2021" [dev-dependencies] itertools = "0.10.5" semver = "1.0.20" psd = "0.3.4" # used for a doc example anyhow = "1.0.75" # used for a doc example widestring = "1.0.2" # used for a doc example linkify = "0.10.0" # used for a doc example easy-cast = "0.5.2" # used for a doc example [target.'cfg(not(target = "windows"))'.dev-dependencies] # Avoid `clang` as a dependency on windows nodejs-sys = "0.15.0" [dependencies] either = "1.13.0" getrandom = { version = "0.2.11", optional = true } libloading = "0.8.1" linkme = "0.3.33" semver = "1.0.20" smallvec = "1.11.2" once_cell = "1.18.0" neon-macros = { version = "=1.1.1", path = "../neon-macros" } aquamarine = { version = "0.3.2", optional = true } easy-cast = { version = "0.5.2", optional = true } doc-comment = { version = "0.3.3", optional = true } send_wrapper = "0.6.0" serde = { version = "1.0.197", optional = true } serde_json = { version = "1.0.114", optional = true } [dependencies.tokio] version = "1.34.0" default-features = false features = ["sync"] optional = true [features] default = ["napi-8"] # Enable extracting values by serializing to JSON serde = ["dep:serde", "dep:serde_json"] # Enable the creation of external binary buffers. This is disabled by default # since these APIs fail at runtime in environments that enable the V8 memory # cage (such as Electron: https://www.electronjs.org/blog/v8-memory-cage). external-buffers = [] # Experimental Rust Futures API # https://github.com/neon-bindings/rfcs/pull/46 futures = ["dep:tokio"] # Enable low-level system APIs. The `sys` API allows augmenting the Neon API # from external crates. sys = [] # Enable async runtime tokio = ["tokio-rt-multi-thread"] # Shorter alias tokio-rt = ["futures", "tokio/rt"] tokio-rt-multi-thread = ["tokio-rt", "tokio/rt-multi-thread"] # Default N-API version. Prefer to select a minimum required version. # DEPRECATED: This is an alias that should be removed napi-runtime = ["napi-8"] # Select the N-API version # Feature flags to enable the experimental N-API runtime. For now, this feature # is disabled by default. # The Node N-API documentation specifies N-API and Node version requirements # https://nodejs.org/api/n-api.html napi-1 = [] napi-2 = ["napi-1"] napi-3 = ["napi-2"] napi-4 = ["napi-3"] napi-5 = ["napi-4"] napi-6 = ["napi-5"] napi-7 = ["napi-6"] napi-8 = ["napi-7", "getrandom"] napi-latest = ["napi-8"] napi-experimental = ["napi-8"] # Enables the optional dependencies that are only used for generating the API docs. doc-dependencies = ["doc-comment", "aquamarine", "easy-cast"] [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] features = [ "external-buffers", "futures", "napi-experimental", "doc-dependencies", "sys", ] ================================================ FILE: crates/neon/src/context/internal.rs ================================================ use std::{cell::RefCell, ffi::c_void, mem::MaybeUninit}; use crate::{ context::{Cx, ModuleContext}, handle::Handle, result::NeonResult, sys::{self, raw}, types::{private::ValueInternal, JsObject}, }; #[repr(C)] #[derive(Clone, Copy)] pub struct Env(raw::Env); impl From for Env { fn from(env: raw::Env) -> Self { Self(env) } } thread_local! { #[allow(unused)] pub(crate) static IS_RUNNING: RefCell = const { RefCell::new(false) }; } impl Env { pub(crate) fn to_raw(self) -> raw::Env { let Self(ptr) = self; ptr } pub(super) unsafe fn try_catch(self, f: F) -> Result where F: FnOnce() -> Result, { let result = f(); let mut local: MaybeUninit = MaybeUninit::zeroed(); if sys::error::catch_error(self.to_raw(), local.as_mut_ptr()) { Err(local.assume_init()) } else if let Ok(result) = result { Ok(result) } else { panic!("try_catch: unexpected Err(Throw) when VM is not in a throwing state"); } } } pub trait ContextInternal<'cx>: Sized { fn cx(&self) -> &Cx<'cx>; fn cx_mut(&mut self) -> &mut Cx<'cx>; fn env(&self) -> Env { self.cx().env } } fn default_main(mut cx: ModuleContext) -> NeonResult<()> { #[cfg(all(feature = "napi-6", feature = "tokio-rt-multi-thread"))] crate::executor::tokio::init(&mut cx)?; crate::registered().export(&mut cx) } fn init(cx: ModuleContext) -> NeonResult<()> { if crate::macro_internal::MAIN.len() > 1 { panic!("The `neon::main` macro must only be used once"); } if let Some(main) = crate::macro_internal::MAIN.first() { main(cx) } else { default_main(cx) } } #[no_mangle] unsafe extern "C" fn napi_register_module_v1(env: *mut c_void, m: *mut c_void) -> *mut c_void { let env = env.cast(); sys::setup(env); IS_RUNNING.with(|v| { *v.borrow_mut() = true; }); let env = Env(env); let exports = Handle::new_internal(JsObject::from_local(env, m.cast())); let _ = ModuleContext::with(env, exports, init); m } ================================================ FILE: crates/neon/src/context/mod.rs ================================================ //! Provides runtime access to the JavaScript engine. //! //! An _execution context_ represents the current state of a thread of execution in the //! JavaScript engine. Internally, it tracks things like the set of pending function calls, //! whether the engine is currently throwing an exception or not, and whether the engine is //! in the process of shutting down. The context uses this internal state to manage what //! operations are safely available and when. //! //! The [`Context`] trait provides an abstract interface to the JavaScript //! execution context. All interaction with the JavaScript engine in Neon code is mediated //! through instances of this trait. //! //! One particularly useful context type is [`FunctionContext`], which is passed //! to all Neon functions as their initial execution context. //! //! ``` //! # use neon::prelude::*; //! fn hello(mut cx: FunctionContext) -> JsResult { //! Ok(cx.string("hello Neon")) //! } //! ``` //! //! Another important context type is [`ModuleContext`], which is provided //! to a Neon module's [`main`](crate::main) function to enable sharing Neon functions back //! with JavaScript: //! //! ``` //! # use neon::prelude::*; //! # fn hello(_: FunctionContext) -> JsResult { todo!() } //! #[neon::main] //! fn lib(mut cx: ModuleContext) -> NeonResult<()> { //! cx.export_function("hello", hello)?; //! Ok(()) //! } //! ``` //! //! ## Writing Generic Helpers //! //! Depending on the entrypoint, a user may have a [`FunctionContext`], [`ModuleContext`], or //! generic [`Cx`]. While it is possible to write a helper that is generic over the [`Context`] //! trait, it is often simpler to accept a [`Cx`] argument. Due to deref coercion, other contexts //! may be passed into a function that accepts a reference to [`Cx`]. //! //! ``` //! # use neon::prelude::*; //! fn log(cx: &mut Cx, msg: &str) -> NeonResult<()> { //! cx.global::("console")? //! .method(cx, "log")? //! .arg(msg)? //! .exec()?; //! Ok(()) //! } //! //! fn print(mut cx: FunctionContext) -> JsResult { //! let msg = cx.argument::(0)?.value(&mut cx); //! log(&mut cx, &msg)?; //! Ok(cx.undefined()) //! } //! ``` //! //! ## Memory Management //! //! Because contexts represent the engine at a point in time, they are associated with a //! [_lifetime_][lifetime], which limits how long Rust code is allowed to access them. This //! is also used to determine the lifetime of [`Handle`]s, which //! provide safe references to JavaScript memory managed by the engine's garbage collector. //! //! For example, we can //! write a simple string scanner that counts whitespace in a JavaScript string and //! returns a [`JsNumber`]: //! //! ``` //! # use neon::prelude::*; //! fn count_whitespace(mut cx: FunctionContext) -> JsResult { //! let s: Handle = cx.argument(0)?; //! let contents = s.value(&mut cx); //! let count = contents //! .chars() // iterate over the characters //! .filter(|c| c.is_whitespace()) // select the whitespace chars //! .count(); // count the resulting chars //! Ok(cx.number(count as f64)) //! } //! ``` //! //! In this example, `s` is assigned a handle to a string, which ensures that the string //! is _kept alive_ (i.e., prevented from having its storage reclaimed by the JavaScript //! engine's garbage collector) for the duration of the `count_whitespace` function. This //! is how Neon takes advantage of Rust's type system to allow your Rust code to safely //! interact with JavaScript values. //! //! ### Temporary Scopes //! //! Sometimes it can be useful to limit the scope of a handle's lifetime, to allow the //! engine to reclaim memory sooner. This can be important when, for example, an expensive inner loop generates //! temporary JavaScript values that are only needed inside the loop. In these cases, //! the [`execute_scoped`](Context::execute_scoped) and [`compute_scoped`](Context::compute_scoped) //! methods allow you to create temporary contexts in order to allocate temporary //! handles. //! //! For example, to extract the elements of a JavaScript [iterator][iterator] from Rust, //! a Neon function has to work with several temporary handles on each pass through //! the loop: //! //! ``` //! # use neon::prelude::*; //! # fn iterate(mut cx: FunctionContext) -> JsResult { //! let iterator = cx.argument::(0)?; // iterator object //! let next: Handle = // iterator's `next` method //! iterator.prop(&mut cx, "next").get()?; //! let mut numbers: Vec = vec![]; // results vector //! let mut done = false; // loop controller //! //! while !done { //! done = cx.execute_scoped(|mut cx| { // temporary scope //! let obj: Handle = next // temporary object //! .bind(&mut cx) //! .this(iterator)? //! .call()?; //! numbers.push(obj.prop(&mut cx, "value").get()?); // temporary number //! obj.prop(&mut cx, "done").get() // temporary boolean //! })?; //! } //! # Ok(cx.undefined()) //! # } //! ``` //! //! The temporary scope ensures that the temporary values are only kept alive //! during a single pass through the loop, since the temporary context is //! discarded (and all of its handles released) on the inside of the loop. //! //! ## Throwing Exceptions //! //! When a Neon API causes a JavaScript exception to be thrown, it returns an //! [`Err`] result, indicating that the thread associated //! with the context is now throwing. This allows Rust code to perform any //! cleanup before returning, but with an important restriction: //! //! > **While a JavaScript thread is throwing, its context cannot be used.** //! //! Unless otherwise documented, any Neon API that uses a context (as `self` or as //! a parameter) immediately panics if called while the context's thread is throwing. //! //! Typically, Neon code can manage JavaScript exceptions correctly and conveniently //! by using Rust's [question mark (`?`)][question-mark] operator. This ensures that //! Rust code "short-circuits" when an exception is thrown and returns back to //! JavaScript without calling any throwing APIs. //! //! Alternatively, to invoke a Neon API and catch any JavaScript exceptions, use the //! [`Context::try_catch`] method, which catches any thrown //! exception and restores the context to non-throwing state. //! //! ## See also //! //! 1. Ecma International. [Execution contexts](https://tc39.es/ecma262/#sec-execution-contexts), _ECMAScript Language Specification_. //! 2. Madhavan Nagarajan. [What is the Execution Context and Stack in JavaScript?](https://medium.com/@itIsMadhavan/what-is-the-execution-context-stack-in-javascript-e169812e851a) //! 3. Rupesh Mishra. [Execution context, Scope chain and JavaScript internals](https://medium.com/@happymishra66/execution-context-in-javascript-319dd72e8e2c). //! //! [lifetime]: https://doc.rust-lang.org/book/ch10-00-generics.html //! [iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators //! [question-mark]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html pub(crate) mod internal; use std::{ convert::Into, marker::PhantomData, ops::{Deref, DerefMut}, panic::UnwindSafe, }; pub use crate::types::buffer::lock::Lock; use crate::{ event::TaskBuilder, handle::Handle, object::Object, result::{JsResult, NeonResult, Throw}, sys::{ self, raw, scope::{EscapableHandleScope, HandleScope}, }, types::{ boxed::{Finalize, JsBox}, error::JsError, extract::{FromArgs, TryFromJs}, private::ValueInternal, Deferred, JsArray, JsArrayBuffer, JsBoolean, JsBuffer, JsFunction, JsNull, JsNumber, JsObject, JsPromise, JsString, JsUndefined, JsValue, StringResult, Value, }, }; use self::internal::{ContextInternal, Env}; #[cfg(feature = "napi-4")] use crate::event::Channel; #[cfg(feature = "napi-5")] use crate::types::date::{DateError, JsDate}; #[cfg(feature = "napi-6")] use crate::lifecycle::InstanceData; #[doc(hidden)] /// An execution context of a task completion callback. pub type TaskContext<'cx> = Cx<'cx>; #[doc(hidden)] /// An execution context of a scope created by [`Context::execute_scoped()`](Context::execute_scoped). pub type ExecuteContext<'cx> = Cx<'cx>; #[doc(hidden)] /// An execution context of a scope created by [`Context::compute_scoped()`](Context::compute_scoped). pub type ComputeContext<'cx> = Cx<'cx>; #[doc(hidden)] /// A view of the JS engine in the context of a finalize method on garbage collection pub type FinalizeContext<'cx> = Cx<'cx>; /// An execution context constructed from a raw [`Env`](crate::sys::bindings::Env). #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] #[doc(hidden)] pub type SysContext<'cx> = Cx<'cx>; /// Context representing access to the JavaScript runtime pub struct Cx<'cx> { env: Env, _phantom_inner: PhantomData<&'cx ()>, } impl<'cx> Cx<'cx> { /// Creates a context from a raw `Env`. /// /// # Safety /// /// Once a [`Cx`] has been created, it is unsafe to use /// the `Env`. The handle scope for the `Env` must be valid for /// the lifetime `'cx`. #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] pub unsafe fn from_raw(env: sys::Env) -> Self { Self { env: env.into(), _phantom_inner: PhantomData, } } fn new(env: Env) -> Self { Self { env, _phantom_inner: PhantomData, } } pub(crate) fn with_context FnOnce(Cx<'b>) -> T>(env: Env, f: F) -> T { f(Self { env, _phantom_inner: PhantomData, }) } } impl<'cx> ContextInternal<'cx> for Cx<'cx> { fn cx(&self) -> &Cx<'cx> { self } fn cx_mut(&mut self) -> &mut Cx<'cx> { self } } impl<'cx> Context<'cx> for Cx<'cx> {} impl<'cx> From> for Cx<'cx> { fn from(cx: FunctionContext<'cx>) -> Self { cx.cx } } impl<'cx> From> for Cx<'cx> { fn from(cx: ModuleContext<'cx>) -> Self { cx.cx } } #[repr(C)] pub(crate) struct CallbackInfo<'cx> { info: raw::FunctionCallbackInfo, _lifetime: PhantomData<&'cx raw::FunctionCallbackInfo>, } impl CallbackInfo<'_> { pub unsafe fn new(info: raw::FunctionCallbackInfo) -> Self { Self { info, _lifetime: PhantomData, } } fn kind<'b, C: Context<'b>>(&self, cx: &C) -> CallKind { if unsafe { sys::call::is_construct(cx.env().to_raw(), self.info) } { CallKind::Construct } else { CallKind::Call } } pub fn len<'b, C: Context<'b>>(&self, cx: &C) -> usize { unsafe { sys::call::len(cx.env().to_raw(), self.info) } } pub fn argv<'b, C: Context<'b>>(&self, cx: &mut C) -> sys::call::Arguments { unsafe { sys::call::argv(cx.env().to_raw(), self.info) } } pub fn this<'b, C: Context<'b>>(&self, cx: &mut C) -> raw::Local { let env = cx.env(); unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::call::this(env.to_raw(), self.info, &mut local); local } } pub(crate) fn argv_exact<'b, C: Context<'b>, const N: usize>( &self, cx: &mut C, ) -> [Handle<'b, JsValue>; N] { use std::ptr; let mut argv = [JsValue::new_internal(ptr::null_mut()); N]; let mut argc = argv.len(); // # Safety // * Node-API fills empty slots with `undefined` // * `Handle` and `JsValue` are transparent wrappers around a raw pointer unsafe { sys::get_cb_info( cx.env().to_raw(), self.info, &mut argc, argv.as_mut_ptr().cast(), ptr::null_mut(), ptr::null_mut(), ) .unwrap(); } // Empty values will be filled with `undefined` argv } } /// Indicates whether a function was called with `new`. #[derive(Clone, Copy, Debug)] pub enum CallKind { Construct, Call, } /// An _execution context_, which represents the current state of a thread of execution in the JavaScript engine. /// /// All interaction with the JavaScript engine in Neon code is mediated through instances of this trait. /// /// A context has a lifetime `'a`, which ensures the safety of handles managed by the JS garbage collector. All handles created during the lifetime of a context are kept alive for that duration and cannot outlive the context. pub trait Context<'a>: ContextInternal<'a> { /// Lock the JavaScript engine, returning an RAII guard that keeps the lock active as long as the guard is alive. /// /// If this is not the currently active context (for example, if it was used to spawn a scoped context with `execute_scoped` or `compute_scoped`), this method will panic. fn lock<'b>(&'b mut self) -> Lock<'b, Self> where 'a: 'b, { Lock::new(self) } /// Executes a computation in a new memory management scope. /// /// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape. /// /// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks. fn execute_scoped<'b, T, F>(&mut self, f: F) -> T where 'a: 'b, F: FnOnce(Cx<'b>) -> T, { let env = self.env(); let scope = unsafe { HandleScope::new(env.to_raw()) }; let result = f(Cx::new(env)); drop(scope); result } /// Executes a computation in a new memory management scope and computes a single result value that outlives the computation. /// /// Handles created in the new scope are kept alive only for the duration of the computation and cannot escape, with the exception of the result value, which is rooted in the outer context. /// /// This method can be useful for limiting the life of temporary values created during long-running computations, to prevent leaks. fn compute_scoped<'b, V, F>(&mut self, f: F) -> JsResult<'a, V> where 'a: 'b, V: Value, F: FnOnce(Cx<'b>) -> JsResult<'b, V>, { let env = self.env(); let scope = unsafe { EscapableHandleScope::new(env.to_raw()) }; let cx = Cx::new(env); let escapee = unsafe { scope.escape(f(cx)?.to_local()) }; Ok(Handle::new_internal(unsafe { V::from_local(self.env(), escapee) })) } fn try_catch(&mut self, f: F) -> Result> where F: FnOnce(&mut Self) -> NeonResult, { unsafe { self.env() .try_catch(move || f(self)) .map_err(JsValue::new_internal) } } /// Convenience method for creating a `JsBoolean` value. fn boolean(&mut self, b: bool) -> Handle<'a, JsBoolean> { JsBoolean::new(self, b) } /// Convenience method for creating a `JsNumber` value. fn number>(&mut self, x: T) -> Handle<'a, JsNumber> { JsNumber::new(self, x.into()) } /// Convenience method for creating a `JsString` value. /// /// If the string exceeds the limits of the JS engine, this method panics. fn string>(&mut self, s: S) -> Handle<'a, JsString> { JsString::new(self, s) } /// Convenience method for creating a `JsString` value. /// /// If the string exceeds the limits of the JS engine, this method returns an `Err` value. fn try_string>(&mut self, s: S) -> StringResult<'a> { JsString::try_new(self, s) } /// Convenience method for creating a `JsNull` value. fn null(&mut self) -> Handle<'a, JsNull> { JsNull::new(self) } /// Convenience method for creating a `JsUndefined` value. fn undefined(&mut self) -> Handle<'a, JsUndefined> { JsUndefined::new(self) } /// Convenience method for creating an empty `JsObject` value. fn empty_object(&mut self) -> Handle<'a, JsObject> { JsObject::new(self) } /// Convenience method for creating an empty `JsArray` value. fn empty_array(&mut self) -> Handle<'a, JsArray> { JsArray::new(self, 0) } /// Convenience method for creating an empty `JsArrayBuffer` value. fn array_buffer(&mut self, size: usize) -> JsResult<'a, JsArrayBuffer> { JsArrayBuffer::new(self, size) } /// Convenience method for creating an empty `JsBuffer` value. fn buffer(&mut self, size: usize) -> JsResult<'a, JsBuffer> { JsBuffer::new(self, size) } /// Convenience method for creating a `JsDate` value. #[cfg(feature = "napi-5")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] fn date(&mut self, value: impl Into) -> Result, DateError> { JsDate::new(self, value) } /// Convenience method for looking up a global property by name. /// /// Equivalent to: /// /// ``` /// # use neon::prelude::*; /// # fn get_array_global<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { /// # let name = "Array"; /// # let v: Handle = /// { /// let global = cx.global_object(); /// global.prop(cx, name).get() /// } /// # ?; /// # Ok(v) /// # } /// ``` fn global(&mut self, name: &str) -> JsResult<'a, T> { let global = self.global_object(); global.get(self, name) } /// Produces a handle to the JavaScript global object. fn global_object(&mut self) -> Handle<'a, JsObject> { JsObject::build(|out| unsafe { sys::scope::get_global(self.env().to_raw(), out); }) } /// Throws a JS value. fn throw(&mut self, v: Handle) -> NeonResult { unsafe { sys::error::throw(self.env().to_raw(), v.to_local()); Err(Throw::new()) } } /// Creates a direct instance of the [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) class. fn error>(&mut self, msg: S) -> JsResult<'a, JsError> { JsError::error(self, msg) } /// Creates an instance of the [`TypeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError) class. fn type_error>(&mut self, msg: S) -> JsResult<'a, JsError> { JsError::type_error(self, msg) } /// Creates an instance of the [`RangeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError) class. fn range_error>(&mut self, msg: S) -> JsResult<'a, JsError> { JsError::range_error(self, msg) } /// Throws a direct instance of the [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) class. fn throw_error, T>(&mut self, msg: S) -> NeonResult { let err = JsError::error(self, msg)?; self.throw(err) } /// Throws an instance of the [`TypeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError) class. fn throw_type_error, T>(&mut self, msg: S) -> NeonResult { let err = JsError::type_error(self, msg)?; self.throw(err) } /// Throws an instance of the [`RangeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError) class. fn throw_range_error, T>(&mut self, msg: S) -> NeonResult { let err = JsError::range_error(self, msg)?; self.throw(err) } /// Convenience method for wrapping a value in a `JsBox`. /// /// # Example: /// /// ```rust /// # use neon::prelude::*; /// struct Point(usize, usize); /// /// impl Finalize for Point {} /// /// fn my_neon_function(mut cx: FunctionContext) -> JsResult> { /// let point = cx.boxed(Point(0, 1)); /// /// Ok(point) /// } /// ``` fn boxed(&mut self, v: U) -> Handle<'a, JsBox> { JsBox::new(self, v) } #[cfg(feature = "napi-4")] #[deprecated(since = "0.9.0", note = "Please use the channel() method instead")] #[doc(hidden)] fn queue(&mut self) -> Channel { self.channel() } #[cfg(feature = "napi-4")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] /// Returns an unbounded channel for scheduling events to be executed on the JavaScript thread. /// /// When using N-API >= 6,the channel returned by this method is backed by a shared queue. /// To create a channel backed by a _new_ queue see [`Channel`]. fn channel(&mut self) -> Channel { #[cfg(feature = "napi-6")] let channel = InstanceData::channel(self); #[cfg(not(feature = "napi-6"))] let channel = Channel::new(self); channel } /// Creates a [`Deferred`] and [`JsPromise`] pair. The [`Deferred`] handle can be /// used to resolve or reject the [`JsPromise`]. /// /// ``` /// # use neon::prelude::*; /// fn resolve_promise(mut cx: FunctionContext) -> JsResult { /// let (deferred, promise) = cx.promise(); /// let msg = cx.string("Hello, World!"); /// /// deferred.resolve(&mut cx, msg); /// /// Ok(promise) /// } /// ``` fn promise(&mut self) -> (Deferred, Handle<'a, JsPromise>) { JsPromise::new(self) } /// Creates a [`TaskBuilder`] which can be used to schedule the `execute` /// callback to asynchronously execute on the /// [Node worker pool](https://nodejs.org/en/docs/guides/dont-block-the-event-loop/). /// /// ``` /// # use neon::prelude::*; /// fn greet(mut cx: FunctionContext) -> JsResult { /// let name = cx.argument::(0)?.value(&mut cx); /// /// let promise = cx /// .task(move || format!("Hello, {}!", name)) /// .promise(move |mut cx, greeting| Ok(cx.string(greeting))); /// /// Ok(promise) /// } /// ``` fn task<'cx, O, E>(&'cx mut self, execute: E) -> TaskBuilder<'cx, Self, E> where 'a: 'cx, O: Send + 'static, E: FnOnce() -> O + Send + 'static, { TaskBuilder::new(self, execute) } #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] /// Gets the raw `sys::Env` for usage with Node-API. fn to_raw(&self) -> sys::Env { self.env().to_raw() } } /// An execution context of module initialization. pub struct ModuleContext<'cx> { cx: Cx<'cx>, exports: Handle<'cx, JsObject>, } impl<'cx> Deref for ModuleContext<'cx> { type Target = Cx<'cx>; fn deref(&self) -> &Self::Target { self.cx() } } impl<'cx> DerefMut for ModuleContext<'cx> { fn deref_mut(&mut self) -> &mut Self::Target { self.cx_mut() } } impl<'cx> UnwindSafe for ModuleContext<'cx> {} impl<'cx> ModuleContext<'cx> { pub(crate) fn with FnOnce(ModuleContext<'b>) -> T>( env: Env, exports: Handle<'cx, JsObject>, f: F, ) -> T { f(ModuleContext { cx: Cx::new(env), exports, }) } #[cfg(not(feature = "napi-5"))] /// Convenience method for exporting a Neon function from a module. pub fn export_function( &mut self, key: &str, f: fn(FunctionContext) -> JsResult, ) -> NeonResult<()> { let value = JsFunction::new(self, f)?.upcast::(); self.exports.clone().set(self, key, value)?; Ok(()) } #[cfg(feature = "napi-5")] /// Convenience method for exporting a Neon function from a module. pub fn export_function(&mut self, key: &str, f: F) -> NeonResult<()> where F: Fn(FunctionContext) -> JsResult + 'static, V: Value, { let value = JsFunction::new(self, f)?.upcast::(); // Note: Cloning `exports` is necessary to avoid holding a shared reference to // `self` while attempting to use it mutably in `set`. self.exports.clone().set(self, key, value)?; Ok(()) } /// Exports a JavaScript value from a Neon module. pub fn export_value(&mut self, key: &str, val: Handle) -> NeonResult<()> { self.exports.clone().set(self, key, val)?; Ok(()) } /// Produces a handle to a module's exports object. pub fn exports_object(&mut self) -> JsResult<'cx, JsObject> { Ok(self.exports) } } impl<'cx> ContextInternal<'cx> for ModuleContext<'cx> { fn cx(&self) -> &Cx<'cx> { &self.cx } fn cx_mut(&mut self) -> &mut Cx<'cx> { &mut self.cx } } impl<'cx> Context<'cx> for ModuleContext<'cx> {} /// An execution context of a function call. /// /// The type parameter `T` is the type of the `this`-binding. pub struct FunctionContext<'cx> { cx: Cx<'cx>, info: &'cx CallbackInfo<'cx>, arguments: Option, } impl<'cx> Deref for FunctionContext<'cx> { type Target = Cx<'cx>; fn deref(&self) -> &Self::Target { &self.cx } } impl<'cx> DerefMut for FunctionContext<'cx> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cx } } impl<'cx> UnwindSafe for FunctionContext<'cx> {} impl<'cx> FunctionContext<'cx> { /// Indicates whether the function was called with `new`. pub fn kind(&self) -> CallKind { self.info.kind(self) } pub(crate) fn with FnOnce(FunctionContext<'b>) -> U>( env: Env, info: &'cx CallbackInfo<'cx>, f: F, ) -> U { f(FunctionContext { cx: Cx::new(env), info, arguments: None, }) } /// Indicates the number of arguments that were passed to the function. pub fn len(&self) -> usize { self.info.len(self) } /// Indicates if no arguments were passed to the function. pub fn is_empty(&self) -> bool { self.len() == 0 } /// Produces the `i`th argument, or `None` if `i` is greater than or equal to `self.len()`. pub fn argument_opt(&mut self, i: usize) -> Option> { let argv = if let Some(argv) = self.arguments.as_ref() { argv } else { let argv = self.info.argv(self); self.arguments.insert(argv) }; argv.get(i) .map(|v| Handle::new_internal(unsafe { JsValue::from_local(self.env(), v) })) } /// Produces the `i`th argument and casts it to the type `V`, or throws an exception if `i` is greater than or equal to `self.len()` or cannot be cast to `V`. pub fn argument(&mut self, i: usize) -> JsResult<'cx, V> { match self.argument_opt(i) { Some(v) => v.downcast_or_throw(self), None => self.throw_type_error("not enough arguments"), } } /// Produces a handle to the `this`-binding and attempts to downcast as a specific type. /// Equivalent to calling `cx.this_value().downcast_or_throw(&mut cx)`. /// /// Throws an exception if the value is a different type. pub fn this(&mut self) -> JsResult<'cx, T> { self.this_value().downcast_or_throw(self) } /// Produces a handle to the function's [`this`-binding](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this#function_context). pub fn this_value(&mut self) -> Handle<'cx, JsValue> { JsValue::new_internal(self.info.this(self)) } /// Extract Rust data from the JavaScript arguments. /// /// This is frequently more efficient and ergonomic than getting arguments /// individually. See the [`extract`](crate::types::extract) module documentation /// for more examples. /// /// ``` /// # use neon::{prelude::*, types::extract::*}; /// fn add(mut cx: FunctionContext) -> JsResult { /// let (a, b): (f64, f64) = cx.args()?; /// /// Ok(cx.number(a + b)) /// } /// ``` pub fn args(&mut self) -> NeonResult where T: FromArgs<'cx>, { T::from_args(self) } /// Extract a single argument from a unary function. See [`Context::args`] for more details. pub fn arg(&mut self) -> NeonResult where T: TryFromJs<'cx>, { self.args::<(T,)>().map(|(v,)| v) } /// Extract Rust data from the JavaScript arguments. /// /// Similar to [`FunctionContext::args`], but does not throw a JavaScript exception on errors. Useful /// for function overloading. /// /// ``` /// # use neon::{prelude::*, types::extract::*}; /// fn combine(mut cx: FunctionContext) -> JsResult { /// if let Some((a, b)) = cx.args_opt::<(f64, f64)>()? { /// return Ok(cx.number(a + b).upcast()); /// } /// /// let (a, b): (String, String) = cx.args()?; /// /// Ok(cx.string(a + &b).upcast()) /// } /// ``` pub fn args_opt(&mut self) -> NeonResult> where T: FromArgs<'cx>, { T::from_args_opt(self) } /// Extract a single optional argument from a unary function. See [`Context::args_opt`] for more details. pub fn arg_opt(&mut self) -> NeonResult> where T: TryFromJs<'cx>, { self.args_opt::<(T,)>().map(|v| v.map(|(v,)| v)) } pub(crate) fn argv(&mut self) -> [Handle<'cx, JsValue>; N] { self.info.argv_exact(self) } } impl<'cx> ContextInternal<'cx> for FunctionContext<'cx> { fn cx(&self) -> &Cx<'cx> { &self.cx } fn cx_mut(&mut self) -> &mut Cx<'cx> { &mut self.cx } } impl<'cx> Context<'cx> for FunctionContext<'cx> {} ================================================ FILE: crates/neon/src/event/channel.rs ================================================ use std::{ error, fmt, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, }; use crate::{ context::{internal::Env, Context, Cx}, result::{NeonResult, ResultExt, Throw}, sys::{self, tsfn::ThreadsafeFunction}, }; #[cfg(feature = "futures")] use { std::future::Future, std::pin::Pin, std::task::{self, Poll}, tokio::sync::oneshot, }; #[cfg(not(feature = "futures"))] // Synchronous oneshot channel API compatible with `tokio::sync::oneshot` mod oneshot { use std::sync::mpsc; pub(super) mod error { pub use super::mpsc::RecvError; } pub(super) struct Receiver(mpsc::Receiver); impl Receiver { pub(super) fn blocking_recv(self) -> Result { self.0.recv() } } pub(super) fn channel() -> (mpsc::SyncSender, Receiver) { let (tx, rx) = mpsc::sync_channel(1); (tx, Receiver(rx)) } } type Callback = Box; /// Channel for scheduling Rust closures to execute on the JavaScript main thread. /// /// Cloning a `Channel` will create a new channel that shares a backing queue for /// events. /// /// # Example /// /// The following example spawns a standard Rust thread to complete a computation /// and calls back to a JavaScript function asynchronously with the result. /// /// ``` /// # use neon::prelude::*; /// # fn fibonacci(_: f64) -> f64 { todo!() } /// fn async_fibonacci(mut cx: FunctionContext) -> JsResult { /// // These types (`f64`, `Root`, `Channel`) may all be sent /// // across threads. /// let n = cx.argument::(0)?.value(&mut cx); /// let callback = cx.argument::(1)?.root(&mut cx); /// let channel = cx.channel(); /// /// // Spawn a thread to complete the execution. This will _not_ block the /// // JavaScript event loop. /// std::thread::spawn(move || { /// let result = fibonacci(n); /// /// // Send a closure as a task to be executed by the JavaScript event /// // loop. This _will_ block the event loop while executing. /// channel.send(move |mut cx| { /// let callback = callback.into_inner(&mut cx); /// /// callback /// .bind(&mut cx) /// .args(((), result))? /// .exec()?; /// /// Ok(()) /// }); /// }); /// /// Ok(cx.undefined()) /// } /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] pub struct Channel { state: Arc, has_ref: bool, } impl fmt::Debug for Channel { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("Channel") } } impl Channel { /// Creates an unbounded channel for scheduling closures on the JavaScript /// main thread pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Self { Self { state: Arc::new(ChannelState::new(cx)), has_ref: true, } } /// Allow the Node event loop to exit while this `Channel` exists. /// _Idempotent_ pub fn unref<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self { // Already unreferenced if !self.has_ref { return self; } self.has_ref = false; self.state.unref(cx); self } /// Prevent the Node event loop from exiting while this `Channel` exists. (Default) /// _Idempotent_ pub fn reference<'a, C: Context<'a>>(&mut self, cx: &mut C) -> &mut Self { // Already referenced if self.has_ref { return self; } self.has_ref = true; self.state.reference(cx); self } /// Schedules a closure to execute on the JavaScript thread that created this Channel /// Panics if there is a libuv error pub fn send(&self, f: F) -> JoinHandle where T: Send + 'static, F: FnOnce(Cx) -> NeonResult + Send + 'static, { self.try_send(f).unwrap() } /// Schedules a closure to execute on the JavaScript thread that created this Channel /// Returns an `Error` if the task could not be scheduled. /// /// See [`SendError`] for additional details on failure causes. pub fn try_send(&self, f: F) -> Result, SendError> where T: Send + 'static, F: FnOnce(Cx) -> NeonResult + Send + 'static, { let (tx, rx) = oneshot::channel(); let callback = Box::new(move |env| { let env = Env::from(env); // Note: It is sufficient to use `Cx` because // N-API creates a `HandleScope` before calling the callback. Cx::with_context(env, move |cx| { // Error can be ignored; it only means the user didn't join let _ = tx.send(f(cx).map_err(Into::into)); }); }); self.state .tsfn .call(callback, None) .map_err(|_| SendError)?; Ok(JoinHandle { rx }) } /// Returns a boolean indicating if this `Channel` will prevent the Node event /// loop from exiting. pub fn has_ref(&self) -> bool { self.has_ref } } impl Clone for Channel { /// Returns a clone of the Channel instance that shares the internal /// unbounded queue with the original channel. Scheduling callbacks on the /// same queue is faster than using separate channels, but might lead to /// starvation if one of the threads posts significantly more callbacks on /// the channel than the other one. /// /// Cloned and referenced Channel instances might trigger additional /// event-loop tick when dropped. Channel can be wrapped into an Arc and /// shared between different threads/callers to avoid this. fn clone(&self) -> Self { // Not referenced, we can simply clone the fields if !self.has_ref { return Self { state: self.state.clone(), has_ref: false, }; } let state = Arc::clone(&self.state); // Only need to increase the ref count since the tsfn is already referenced state.ref_count.fetch_add(1, Ordering::Relaxed); Self { state, has_ref: true, } } } impl Drop for Channel { fn drop(&mut self) { // Not a referenced event queue if !self.has_ref { return; } // It was only us who kept the `ChannelState` alive. No need to unref // the `tsfn`, because it is going to be dropped once this function // returns. if Arc::strong_count(&self.state) == 1 { return; } // The ChannelState is dropped on a worker thread. We have to `unref` // the tsfn on the UV thread after all pending closures. Note that in // the most of scenarios the optimization in N-API layer would coalesce // `send()` with a user-supplied closure and the unref send here into a // single UV tick. // // If this ever has to be optimized a second `Arc` could be used to wrap // the `state` and it could be cloned in `try_send` and unref'ed on the // UV thread if strong reference count goes to 0. let state = Arc::clone(&self.state); // `Channel::try_send` will only fail if the environment has shutdown. // In that case, the teardown will perform clean-up. let _ = self.try_send(move |mut cx| { state.unref(&mut cx); Ok(()) }); } } /// An owned permission to join on the result of a closure sent to the JavaScript main /// thread with [`Channel::send`]. pub struct JoinHandle { // `Err` is always `Throw`, but `Throw` cannot be sent across threads rx: oneshot::Receiver>, } impl JoinHandle { /// Waits for the associated closure to finish executing /// /// If the closure panics or throws an exception, `Err` is returned /// /// # Panics /// /// This function panics if called within an asynchronous execution context. pub fn join(self) -> Result { Ok(self.rx.blocking_recv()??) } } #[cfg(feature = "futures")] #[cfg_attr(docsrs, doc(cfg(feature = "futures")))] impl Future for JoinHandle { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { match Pin::new(&mut self.rx).poll(cx) { Poll::Ready(result) => { // Flatten `Result, RecvError>` by mapping to // `Result`. This can be simplified by replacing the // closure with a try-block after stabilization. // https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html let get_result = move || Ok(result??); Poll::Ready(get_result()) } Poll::Pending => Poll::Pending, } } } #[derive(Debug)] /// Error returned by [`JoinHandle::join`] indicating the associated closure panicked /// or threw an exception. pub struct JoinError(JoinErrorType); #[derive(Debug)] enum JoinErrorType { Panic, Throw, } impl JoinError { fn as_str(&self) -> &str { match &self.0 { JoinErrorType::Panic => "Closure panicked before returning", JoinErrorType::Throw => "Closure threw an exception", } } } impl fmt::Display for JoinError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.as_str()) } } impl error::Error for JoinError {} impl From for JoinError { fn from(_: oneshot::error::RecvError) -> Self { JoinError(JoinErrorType::Panic) } } // Marker that a `Throw` occurred that can be sent across threads for use in `JoinError` pub(crate) struct SendThrow(()); impl From for JoinError { fn from(_: SendThrow) -> Self { JoinError(JoinErrorType::Throw) } } impl From for SendThrow { fn from(_: Throw) -> SendThrow { SendThrow(()) } } impl ResultExt for Result { fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult { self.or_else(|err| cx.throw_error(err.as_str())) } } /// Error indicating that a closure was unable to be scheduled to execute on the event loop. /// /// The most likely cause of a failure is that Node is shutting down. This may occur if the /// process is forcefully exiting even if the channel is referenced. For example, by calling /// `process.exit()`. // // NOTE: These docs will need to be updated to include `QueueFull` if bounded queues are // implemented. #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] pub struct SendError; impl fmt::Display for SendError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "SendError") } } impl fmt::Debug for SendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } impl error::Error for SendError {} struct ChannelState { tsfn: ThreadsafeFunction, ref_count: AtomicUsize, } impl ChannelState { fn new<'a, C: Context<'a>>(cx: &mut C) -> Self { let tsfn = unsafe { ThreadsafeFunction::new(cx.env().to_raw(), Self::callback) }; Self { tsfn, ref_count: AtomicUsize::new(1), } } fn reference<'a, C: Context<'a>>(&self, cx: &mut C) { // We can use relaxed ordering because `reference()` can only be called // on the Event-Loop thread. if self.ref_count.fetch_add(1, Ordering::Relaxed) != 0 { return; } unsafe { self.tsfn.reference(cx.env().to_raw()); } } fn unref<'a, C: Context<'a>>(&self, cx: &mut C) { // We can use relaxed ordering because `unref()` can only be called // on the Event-Loop thread. if self.ref_count.fetch_sub(1, Ordering::Relaxed) != 1 { return; } unsafe { self.tsfn.unref(cx.env().to_raw()); } } // Monomorphized trampoline funciton for calling the user provided closure fn callback(env: Option, callback: Callback) { if let Some(env) = env { callback(env); } else { crate::context::internal::IS_RUNNING.with(|v| { *v.borrow_mut() = false; }); } } } ================================================ FILE: crates/neon/src/event/mod.rs ================================================ //! Exposes the JavaScript event loop for scheduling asynchronous events. //! //! ## The Event Loop //! //! The [_event loop_][event-loop] is how Node.js provides JavaScript programs //! access to concurrent events such as completion of [file][fs] or //! [network][net] operations, notification of scheduled [timers][timer], or //! receiving of messages from other [processes][process]. //! //! When an asynchronous operation is started from JavaScript, it registers //! a JavaScript callback function to wait for the operation to complete. When //! the operation completes, the callback and the result data are added to an //! internal _event queue_ in the Node.js runtime so that the event can be //! processed in order. //! //! The event loop processes completed events one at a time in the JavaScript //! execution thread by calling the registered callback function with its result //! value as an argument. //! //! ## Creating Custom Events //! //! This module allows Neon programs to create new types of concurrent events //! in Rust and expose them to JavaScript as asynchronous functions. //! //! A common use for custom events is to run expensive or long-lived //! computations in a background thread without blocking the JavaScript //! thread. For example, using the [`psd` crate][psd-crate], a Neon program could //! asynchronously parse (potentially large) [PSD files][psd-file] in a //! background thread: //! //! ``` //! # use neon::prelude::*; //! # #[cfg(not(feature = "napi-6"))] //! # type Channel = (); //! # fn parse(filename: String, callback: Root, channel: Channel) { } //! # #[cfg(feature = "napi-6")] //! fn parse_async(mut cx: FunctionContext) -> JsResult { //! // The types `String`, `Root`, and `Channel` can all be //! // sent across threads. //! let filename = cx.argument::(0)?.value(&mut cx); //! let callback = cx.argument::(1)?.root(&mut cx); //! let channel = cx.channel(); //! //! // Spawn a background thread to complete the execution. The background //! // execution will _not_ block the JavaScript event loop. //! std::thread::spawn(move || { //! // Do the heavy lifting inside the background thread. //! parse(filename, callback, channel); //! }); //! //! Ok(cx.undefined()) //! } //! ``` //! //! (Note that this usage of [`spawn`](std::thread::spawn) makes use of Rust's //! [`move`][move] syntax to transfer ownership of data to the background //! thread.) //! //! Upon completion of its task, the background thread can use the JavaScript //! callback and the channel to notify the main thread of the result: //! //! ``` //! # use neon::prelude::*; //! # #[cfg(not(feature = "napi-6"))] //! # type Channel = (); //! # use psd::Psd; //! # use anyhow::{Context as _, Result}; //! # //! fn psd_from_filename(filename: String) -> Result { //! Psd::from_bytes(&std::fs::read(&filename)?).context("invalid psd file") //! } //! //! # #[cfg(feature = "napi-6")] //! fn parse(filename: String, callback: Root, channel: Channel) { //! let result = psd_from_filename(filename); //! //! // Send a closure as a task to be executed by the JavaScript event //! // loop. This _will_ block the event loop while executing. //! channel.send(move |mut cx| { //! let callback = callback.into_inner(&mut cx); //! //! match result { //! Ok(psd) => { //! // Extract data from the parsed file. //! let obj = cx.empty_object() //! .prop(&mut cx, "width").set(psd.width())? //! .prop("height").set(psd.height())? //! .this(); //! //! callback //! .bind(&mut cx) //! .args(((), obj))? //! .exec()?; //! } //! Err(err) => { //! use neon::types::extract::Error; //! callback //! .bind(&mut cx) //! .arg(Error::from(err))? //! .exec()?; //! } //! } //! //! Ok(()) //! }); //! } //! ``` //! //! ## See also //! //! 1. Panu Pitkamaki. [Event loop from 10,000ft][event-loop]. //! //! [event-loop]: https://bytearcher.com/articles/event-loop-10-000ft/ //! [fs]: https://nodejs.org/dist/latest/docs/api/fs.html //! [net]: https://nodejs.org/dist/latest/docs/api/net.html //! [process]: https://nodejs.org/dist/latest/docs/api/process.html //! [timer]: https://nodejs.org/dist/latest/docs/api/timers.html //! [move]: https://doc.rust-lang.org/std/keyword.move.html //! [psd-crate]: https://crates.io/crates/psd //! [psd-file]: https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ #[cfg(feature = "napi-4")] mod channel; mod task; pub use self::task::TaskBuilder; #[cfg(all(feature = "napi-5", feature = "futures"))] pub(crate) use self::channel::SendThrow; #[cfg(feature = "napi-4")] pub use self::channel::{Channel, JoinError, JoinHandle, SendError}; #[cfg(feature = "napi-4")] #[deprecated(since = "0.9.0", note = "Please use the Channel type instead")] #[doc(hidden)] pub type EventQueue = self::channel::Channel; #[cfg(feature = "napi-4")] #[deprecated(since = "0.9.0", note = "Please use the SendError type instead")] #[doc(hidden)] pub type EventQueueError = self::channel::SendError; ================================================ FILE: crates/neon/src/event/task.rs ================================================ use std::{panic::resume_unwind, thread}; use crate::{ context::{internal::Env, Context, Cx}, handle::Handle, result::{JsResult, NeonResult}, sys::{async_work, raw}, types::{Deferred, JsPromise, Value}, }; /// Node asynchronous task builder /// /// ``` /// # use neon::prelude::*; /// fn greet(mut cx: FunctionContext) -> JsResult { /// let name = cx.argument::(0)?.value(&mut cx); /// /// let promise = cx /// .task(move || format!("Hello, {}!", name)) /// .promise(move |mut cx, greeting| Ok(cx.string(greeting))); /// /// Ok(promise) /// } /// ``` pub struct TaskBuilder<'cx, C, E> { cx: &'cx mut C, execute: E, } impl<'a: 'cx, 'cx, C, O, E> TaskBuilder<'cx, C, E> where C: Context<'a>, O: Send + 'static, E: FnOnce() -> O + Send + 'static, { /// Construct a new task builder from an `execute` callback that can be /// scheduled to execute on the Node worker pool pub fn new(cx: &'cx mut C, execute: E) -> Self { Self { cx, execute } } /// Schedules a task to execute on the Node worker pool, executing the /// `complete` callback on the JavaScript main thread with the result /// of the `execute` callback pub fn and_then(self, complete: F) where F: FnOnce(Cx, O) -> NeonResult<()> + 'static, { let env = self.cx.env(); let execute = self.execute; schedule(env, execute, complete); } /// Schedules a task to execute on the Node worker pool and returns a /// promise that is resolved with the value from the `complete` callback. /// /// The `complete` callback will execute on the JavaScript main thread and /// is passed the return value from `execute`. If the `complete` callback /// throws, the promise will be rejected with the exception pub fn promise(self, complete: F) -> Handle<'a, JsPromise> where V: Value, F: FnOnce(Cx, O) -> JsResult + 'static, { let env = self.cx.env(); let (deferred, promise) = JsPromise::new(self.cx); let execute = self.execute; schedule_promise(env, execute, complete, deferred); promise } } // Schedule a task to execute on the Node worker pool fn schedule(env: Env, input: I, data: D) where I: FnOnce() -> O + Send + 'static, O: Send + 'static, D: FnOnce(Cx, O) -> NeonResult<()> + 'static, { unsafe { async_work::schedule(env.to_raw(), input, execute::, complete::, data); } } fn execute(input: I) -> O where I: FnOnce() -> O + Send + 'static, O: Send + 'static, { input() } fn complete(env: raw::Env, output: thread::Result, callback: D) where O: Send + 'static, D: FnOnce(Cx, O) -> NeonResult<()> + 'static, { let output = output.unwrap_or_else(|panic| { // If a panic was caught while executing the task on the Node Worker // pool, resume panicking on the main JavaScript thread resume_unwind(panic) }); Cx::with_context(env.into(), move |cx| { let _ = callback(cx, output); }); } // Schedule a task to execute on the Node worker pool and settle a `Promise` with the result fn schedule_promise(env: Env, input: I, complete: D, deferred: Deferred) where I: FnOnce() -> O + Send + 'static, O: Send + 'static, D: FnOnce(Cx, O) -> JsResult + 'static, V: Value, { unsafe { async_work::schedule( env.to_raw(), input, execute::, complete_promise::, (complete, deferred), ); } } fn complete_promise( env: raw::Env, output: thread::Result, (complete, deferred): (D, Deferred), ) where O: Send + 'static, D: FnOnce(Cx, O) -> JsResult + 'static, V: Value, { let env = env.into(); Cx::with_context(env, move |cx| { deferred.try_catch_settle(cx, move |cx| { let output = output.unwrap_or_else(|panic| resume_unwind(panic)); complete(cx, output) }) }); } ================================================ FILE: crates/neon/src/executor/mod.rs ================================================ use std::{future::Future, pin::Pin}; use crate::{context::Cx, thread::LocalKey}; #[cfg(feature = "tokio-rt")] pub(crate) mod tokio; type BoxFuture = Pin + Send + 'static>>; pub(crate) static RUNTIME: LocalKey> = LocalKey::new(); pub trait Runtime: Send + Sync + 'static { fn spawn(&self, fut: BoxFuture); } /// Register a [`Future`] executor runtime globally to the addon. /// /// Returns `Ok(())` if a global executor has not been set and `Err(runtime)` if it has. /// /// If the `tokio` feature flag is enabled and the addon does not provide a /// [`#[neon::main]`](crate::main) function, a multithreaded tokio runtime will be /// automatically registered. /// /// **Note**: Each instance of the addon will have its own runtime. It is recommended /// to initialize the async runtime once in a process global and share it across instances. /// /// ``` /// # fn main() { /// # #[cfg(feature = "tokio-rt-multi-thread")] /// # fn example() { /// # use neon::prelude::*; /// use once_cell::sync::OnceCell; /// use tokio::runtime::Runtime; /// /// static RUNTIME: OnceCell = OnceCell::new(); /// /// #[neon::main] /// fn main(mut cx: ModuleContext) -> NeonResult<()> { /// let runtime = RUNTIME /// .get_or_try_init(Runtime::new) /// .or_else(|err| cx.throw_error(err.to_string()))?; /// /// let _ = neon::set_global_executor(&mut cx, runtime); /// /// Ok(()) /// } /// # } /// # } /// ``` pub fn set_global_executor(cx: &mut Cx, runtime: R) -> Result<(), R> where R: Runtime, { if RUNTIME.get(cx).is_some() { return Err(runtime); } RUNTIME.get_or_init(cx, || Box::new(runtime)); Ok(()) } ================================================ FILE: crates/neon/src/executor/tokio.rs ================================================ use std::sync::Arc; use super::{BoxFuture, Runtime}; impl Runtime for tokio::runtime::Runtime { fn spawn(&self, fut: BoxFuture) { spawn(self.handle(), fut); } } impl Runtime for Arc { fn spawn(&self, fut: BoxFuture) { spawn(self.handle(), fut); } } impl Runtime for &'static tokio::runtime::Runtime { fn spawn(&self, fut: BoxFuture) { spawn(self.handle(), fut); } } impl Runtime for tokio::runtime::Handle { fn spawn(&self, fut: BoxFuture) { spawn(self, fut); } } impl Runtime for &'static tokio::runtime::Handle { fn spawn(&self, fut: BoxFuture) { spawn(self, fut); } } fn spawn(handle: &tokio::runtime::Handle, fut: BoxFuture) { #[allow(clippy::let_underscore_future)] let _ = handle.spawn(fut); } #[cfg(feature = "tokio-rt-multi-thread")] pub(crate) fn init(cx: &mut crate::context::ModuleContext) -> crate::result::NeonResult<()> { use once_cell::sync::OnceCell; use tokio::runtime::{Builder, Runtime}; use crate::context::Context; static RUNTIME: OnceCell = OnceCell::new(); super::RUNTIME.get_or_try_init(cx, |cx| { let runtime = RUNTIME .get_or_try_init(|| { #[cfg(feature = "tokio-rt-multi-thread")] let mut builder = Builder::new_multi_thread(); #[cfg(not(feature = "tokio-rt-multi-thread"))] let mut builder = Builder::new_current_thread(); builder.enable_all().build() }) .or_else(|err| cx.throw_error(err.to_string()))?; Ok(Box::new(runtime)) })?; Ok(()) } ================================================ FILE: crates/neon/src/handle/internal.rs ================================================ use std::{fmt::Debug, mem}; use crate::types::Value; pub trait SuperType { fn upcast_internal(v: &T) -> Self; } #[doc(hidden)] /// Trait asserting that `Self` is a transparent wrapper around `Self::Inner` /// with identical representation and may be safely transmuted. /// /// # Safety /// `Self` must be `#[repr(transparent)]` with a field `Self::Inner` pub unsafe trait TransparentNoCopyWrapper: Sized { type Inner: Debug + Copy; // A default implementation cannot be provided because it would create // dependently sized types. This may be supported in a future Rust version. fn into_inner(self) -> Self::Inner; fn wrap_ref(s: &Self::Inner) -> &Self { unsafe { mem::transmute(s) } } fn wrap_mut(s: &mut Self::Inner) -> &mut Self { unsafe { mem::transmute(s) } } } ================================================ FILE: crates/neon/src/handle/mod.rs ================================================ //! References to garbage-collected JavaScript values. //! //! A _handle_ is a safe reference to a JavaScript value that is owned and managed //! by the JavaScript engine's memory management system (the garbage collector). //! //! Neon APIs that accept and return JavaScript values never use raw pointer types //! ([`*T`](pointer)) or reference types ([`&T`](reference)). Instead they use the //! special Neon type [`Handle`], which encapsulates a JavaScript //! [`Value`] and ensures that Rust only maintains access to //! the value while it is guaranteed to be valid. //! //! ## Working with Handles //! //! The `Handle` type automatically dereferences to `T` (via the standard //! [`Deref`] trait), so you can call `T`'s methods on a value of //! type `Handle`. For example, we can call //! [`JsNumber::value()`](crate::types::JsNumber::value) on a `Handle`: //! //! ``` //! # use neon::prelude::*; //! # fn run(mut cx: FunctionContext) -> JsResult { //! let n: Handle = cx.argument(0)?; //! let v = n.value(&mut cx); // JsNumber::value() //! # Ok(cx.undefined()) //! # } //! ``` //! //! ## Example //! //! This Neon function takes an object as its argument, extracts an object property, //! `homeAddress`, and then extracts a string property, `zipCode` from that second //! object. Each JavaScript value in the calculation is stored locally in a `Handle`. //! //! ``` //! # use neon::prelude::*; //! # use neon::export; //! #[export] //! fn customer_zip_code<'cx>(cx: &mut FunctionContext<'cx>, customer: Handle<'cx, JsObject>) -> JsResult<'cx, JsString> { //! let home_address: Handle = customer.prop(cx, "homeAddress").get()?; //! let zip_code: Handle = home_address.prop(cx, "zipCode").get()?; //! Ok(zip_code) //! } //! ``` pub(crate) mod internal; pub(crate) mod root; use std::{ error::Error, fmt::{self, Debug, Display}, marker::PhantomData, mem, ops::{Deref, DerefMut}, }; pub use self::root::Root; use crate::{ context::Context, handle::internal::{SuperType, TransparentNoCopyWrapper}, result::{JsResult, ResultExt}, sys, types::Value, }; /// A handle to a JavaScript value that is owned by the JavaScript engine. #[derive(Debug)] #[repr(transparent)] pub struct Handle<'a, V: Value + 'a> { // Contains the actual `Copy` JavaScript value data. It will be wrapped in // in a `!Copy` type when dereferencing. Only `V` should be visible to the user. value: ::Inner, phantom: PhantomData<&'a V>, } impl<'a, V: Value> Clone for Handle<'a, V> { fn clone(&self) -> Self { *self } } impl<'a, V: Value> Copy for Handle<'a, V> {} impl<'a, V: Value + 'a> Handle<'a, V> { pub(crate) fn new_internal(value: V) -> Handle<'a, V> { Handle { value: value.into_inner(), phantom: PhantomData, } } } /// An error representing a failed downcast. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct DowncastError { phantom_from: PhantomData, phantom_to: PhantomData, } impl Debug for DowncastError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "DowncastError") } } impl DowncastError { fn new() -> Self { DowncastError { phantom_from: PhantomData, phantom_to: PhantomData, } } } impl Display for DowncastError { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "failed to downcast {} to {}", F::name(), T::name()) } } impl Error for DowncastError {} /// The result of a call to [`Handle::downcast()`](Handle::downcast). pub type DowncastResult<'a, F, T> = Result, DowncastError>; impl<'a, F: Value, T: Value> ResultExt> for DowncastResult<'a, F, T> { fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, T> { match self { Ok(v) => Ok(v), Err(e) => cx.throw_type_error(e.to_string()), } } } impl<'a, T: Value> Handle<'a, T> { /// Safely upcast a handle to a supertype. /// /// This method does not require an execution context because it only copies a handle. pub fn upcast>(&self) -> Handle<'a, U> { Handle::new_internal(SuperType::upcast_internal(self.deref())) } /// Tests whether this value is an instance of the given type. /// /// # Example: /// /// ```no_run /// # use neon::prelude::*; /// # fn my_neon_function(mut cx: FunctionContext) -> JsResult { /// let v: Handle = cx.number(17).upcast(); /// v.is_a::(&mut cx); // false /// v.is_a::(&mut cx); // true /// v.is_a::(&mut cx); // true /// # Ok(cx.undefined()) /// # } /// ``` pub fn is_a<'b, U: Value, C: Context<'b>>(&self, cx: &mut C) -> bool { U::is_typeof(cx.cx_mut(), self.deref()) } /// Attempts to downcast a handle to another type, which may fail. A failure /// to downcast **does not** throw a JavaScript exception, so it's OK to /// continue interacting with the JS engine if this method produces an `Err` /// result. pub fn downcast<'b, U: Value, C: Context<'b>>(&self, cx: &mut C) -> DowncastResult<'a, T, U> { match U::downcast(cx.cx_mut(), self.deref()) { Some(v) => Ok(Handle::new_internal(v)), None => Err(DowncastError::new()), } } /// Attempts to downcast a handle to another type, raising a JavaScript `TypeError` /// exception on failure. This method is a convenient shorthand, equivalent to /// `self.downcast::().or_throw::(cx)`. pub fn downcast_or_throw<'b, U: Value, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'a, U> { self.downcast(cx).or_throw(cx) } pub fn strict_equals<'b, U: Value, C: Context<'b>>( &self, cx: &mut C, other: Handle<'b, U>, ) -> bool { unsafe { sys::mem::strict_equals(cx.env().to_raw(), self.to_local(), other.to_local()) } } } impl<'a, V: Value> Deref for Handle<'a, V> { type Target = V; fn deref(&self) -> &V { unsafe { mem::transmute(&self.value) } } } impl<'a, V: Value> DerefMut for Handle<'a, V> { fn deref_mut(&mut self) -> &mut V { unsafe { mem::transmute(&mut self.value) } } } ================================================ FILE: crates/neon/src/handle/root.rs ================================================ use std::{ffi::c_void, marker::PhantomData}; use crate::{ context::Context, handle::Handle, object::Object, sys::{raw, reference}, types::boxed::Finalize, }; #[cfg(feature = "napi-6")] use { crate::{ lifecycle::{DropData, InstanceData, InstanceId}, sys::tsfn::ThreadsafeFunction, }, std::sync::Arc, }; #[cfg(not(feature = "napi-6"))] use std::thread::{self, ThreadId}; #[cfg(not(feature = "napi-6"))] type InstanceId = ThreadId; #[repr(transparent)] #[derive(Clone)] pub(crate) struct NapiRef(*mut c_void); impl NapiRef { /// # Safety /// Must only be used from the same module context that created the reference pub(crate) unsafe fn unref(self, env: raw::Env) { reference::unreference(env, self.0.cast()); } } // # Safety // `NapiRef` are reference counted types that allow references to JavaScript objects // to outlive a `Context` (`napi_env`). Since access is serialized by obtaining a // `Context`, they are both `Send` and `Sync`. // https://nodejs.org/api/n-api.html#n_api_references_to_objects_with_a_lifespan_longer_than_that_of_the_native_method unsafe impl Send for NapiRef {} unsafe impl Sync for NapiRef {} /// A thread-safe handle that holds a reference to a JavaScript object and /// prevents it from being garbage collected. /// /// A `Root` may be sent across threads, but the referenced object may /// only be accessed on the JavaScript thread that created it. pub struct Root { // `Option` is used to skip `Drop` when `Root::drop` or `Root::into_inner` is used. // It will *always* be `Some` when a user is interacting with `Root`. internal: Option, instance_id: InstanceId, #[cfg(feature = "napi-6")] drop_queue: Arc>, _phantom: PhantomData, } impl std::fmt::Debug for Root { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Root<{}>", std::any::type_name::()) } } // `Root` are intended to be `Send` and `Sync` // Safety: `Root` contains two types. A `NapiRef` which is `Send` and `Sync` and a // `PhantomData` that does not impact the safety. unsafe impl Send for Root {} unsafe impl Sync for Root {} #[cfg(feature = "napi-6")] fn instance_id<'a, C: Context<'a>>(cx: &mut C) -> InstanceId { InstanceData::id(cx) } #[cfg(not(feature = "napi-6"))] fn instance_id<'a, C: Context<'a>>(_: &mut C) -> InstanceId { thread::current().id() } impl Root { /// Create a reference to a JavaScript object. The object will not be /// garbage collected until the `Root` is dropped. A `Root` may only /// be dropped on the JavaScript thread that created it. /// /// The caller _should_ ensure `Root::into_inner` or `Root::drop` is called /// to properly dispose of the `Root`. If the value is dropped without /// calling one of these methods: /// * N-API < 6, Neon will `panic` to notify of the leak /// * N-API >= 6, Neon will drop from a global queue at a runtime cost pub fn new<'a, C: Context<'a>>(cx: &mut C, value: &T) -> Self { let env = cx.env().to_raw(); let internal = unsafe { reference::new(env, value.to_local()) }; Self { internal: Some(NapiRef(internal as *mut _)), instance_id: instance_id(cx), #[cfg(feature = "napi-6")] drop_queue: InstanceData::drop_queue(cx), _phantom: PhantomData, } } /// Clone a reference to the contained JavaScript object. This method can /// be considered identical to the following: /// ``` /// # use neon::prelude::*; /// # fn my_neon_function(mut cx: FunctionContext) -> JsResult { /// # let root = cx.argument::(0)?.root(&mut cx); /// let inner = root.into_inner(&mut cx); /// let cloned = inner.root(&mut cx); /// let root = inner.root(&mut cx); /// # Ok(cx.undefined()) /// # } /// ``` pub fn clone<'a, C: Context<'a>>(&self, cx: &mut C) -> Self { let env = cx.env(); let internal = self.as_napi_ref(cx).0 as *mut _; unsafe { reference::reference(env.to_raw(), internal); }; Self { internal: self.internal.clone(), instance_id: instance_id(cx), #[cfg(feature = "napi-6")] drop_queue: Arc::clone(&self.drop_queue), _phantom: PhantomData, } } /// Safely drop a `Root` without returning the referenced JavaScript /// object. pub fn drop<'a, C: Context<'a>>(self, cx: &mut C) { let env = cx.env().to_raw(); unsafe { self.into_napi_ref(cx).unref(env); } } /// Return the referenced JavaScript object and allow it to be garbage collected. /// /// # Panics /// /// This method panics if it is called from a different JavaScript thread than the /// one in which the handle was created. pub fn into_inner<'a, C: Context<'a>>(self, cx: &mut C) -> Handle<'a, T> { let env = cx.env(); let internal = self.into_napi_ref(cx); let local = unsafe { reference::get(env.to_raw(), internal.0.cast()) }; unsafe { internal.unref(env.to_raw()); } Handle::new_internal(unsafe { T::from_local(env, local) }) } /// Access the inner JavaScript object without consuming the `Root` /// This method aliases the reference without changing the reference count. It /// can be used in place of a clone immediately followed by a call to `into_inner`. /// /// # Panics /// /// This method panics if it is called from a different JavaScript thread than the /// one in which the handle was created. pub fn to_inner<'a, C: Context<'a>>(&self, cx: &mut C) -> Handle<'a, T> { let env = cx.env(); let local = unsafe { reference::get(env.to_raw(), self.as_napi_ref(cx).0 as *mut _) }; Handle::new_internal(unsafe { T::from_local(env, local) }) } fn as_napi_ref<'a, C: Context<'a>>(&self, cx: &mut C) -> &NapiRef { if self.instance_id != instance_id(cx) { panic!("Attempted to dereference a `neon::handle::Root` from the wrong module "); } self.internal .as_ref() // `unwrap` will not `panic` because `internal` will always be `Some` // until the `Root` is consumed. .unwrap() } fn into_napi_ref<'a, C: Context<'a>>(mut self, cx: &mut C) -> NapiRef { let reference = self.as_napi_ref(cx).clone(); // This uses `as_napi_ref` instead of `Option::take` for the instance id safety check self.internal = None; reference } } // Allows putting `Root` directly in a container that implements `Finalize` // For example, `Vec>` or `JsBox`. impl Finalize for Root { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { self.drop(cx); } } impl Drop for Root { #[cfg(not(feature = "napi-6"))] fn drop(&mut self) { // If `None`, the `NapiRef` has already been manually dropped if self.internal.is_none() { return; } // Destructors are called during stack unwinding, prevent a double // panic and instead prefer to leak. if std::thread::panicking() { eprintln!("Warning: neon::handle::Root leaked during a panic"); return; } // Only panic if the event loop is still running if let Ok(true) = crate::context::internal::IS_RUNNING.try_with(|v| *v.borrow()) { panic!("Must call `into_inner` or `drop` on `neon::handle::Root`"); } } #[cfg(feature = "napi-6")] fn drop(&mut self) { // If `None`, the `NapiRef` has already been manually dropped if let Some(internal) = self.internal.take() { let _ = self.drop_queue.call(DropData::Ref(internal), None); } } } ================================================ FILE: crates/neon/src/lib.rs ================================================ //! The [Neon][neon] crate provides bindings for writing [Node.js addons][addons] //! (i.e., dynamically-loaded binary modules) with a safe and fast Rust API. //! //! ## Getting Started //! //! You can conveniently bootstrap a new Neon project with the Neon project //! generator. You don't need to install anything special on your machine as //! long as you have a [supported version of Node and Rust][supported] on //! your system. //! //! To start a new project, open a terminal in the directory where you would //! like to place the project, and run at the command prompt: //! //! ```text //! % npm init neon my-project //! ... answer the user prompts ... //! ✨ Created Neon project `my-project`. Happy 🦀 hacking! ✨ //! ``` //! //! where `my-project` can be any name you like for the project. This will //! run the Neon project generator, prompting you with a few questions and //! placing a simple but working Neon project in a subdirectory called //! `my-project` (or whatever name you chose). //! //! You can then install and build the project by changing into the project //! directory and running the standard Node installation command: //! //! ```text //! % cd my-project //! % npm install //! % node //! > require(".").hello() //! 'hello node' //! ``` //! //! You can look in the project's generated `README.md` for more details on //! the project structure. //! //! ## Example //! //! The generated `src/lib.rs` contains a function annotated with the //! [`#[neon::main]`](main) attribute, marking it as the module's main entry //! point to be executed when the module is loaded. This function can have //! any name but is conventionally called `main`: //! //! ```no_run //! # mod example { //! # use neon::prelude::*; //! # fn hello(mut cx: FunctionContext) -> JsResult { //! # Ok(cx.string("hello node")) //! # } //! #[neon::main] //! fn main(mut cx: ModuleContext) -> NeonResult<()> { //! cx.export_function("hello", hello)?; //! Ok(()) //! } //! # } //! ``` //! //! The example code generated by `npm init neon` exports a single //! function via [`ModuleContext::export_function`](context::ModuleContext::export_function). //! The `hello` function is defined just above `main` in `src/lib.rs`: //! //! ``` //! # use neon::prelude::*; //! # //! fn hello(mut cx: FunctionContext) -> JsResult { //! Ok(cx.string("hello node")) //! } //! ``` //! //! The `hello` function takes a [`FunctionContext`](context::FunctionContext) and //! returns a JavaScript string. Because all Neon functions can potentially throw a //! JavaScript exception, the return type is wrapped in a [`JsResult`](result::JsResult). //! //! [neon]: https://www.neon-bindings.com/ //! [addons]: https://nodejs.org/api/addons.html //! [supported]: https://github.com/neon-bindings/neon#platform-support #![cfg_attr(docsrs, feature(doc_cfg))] pub mod context; pub mod event; pub mod handle; mod macros; pub mod meta; pub mod object; pub mod prelude; pub mod reflect; pub mod result; #[cfg(not(feature = "sys"))] mod sys; #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] #[cfg(feature = "napi-6")] pub mod thread; // To use the #[aquamarine] attribute on the top-level neon::types module docs, we have to // use this hack so we can keep the module docs in a separate file. // See: https://github.com/mersinvald/aquamarine/issues/5#issuecomment-1168816499 mod types_docs; mod types_impl; #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] pub mod sys; #[cfg(all(feature = "napi-6", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-6", feature = "futures"))))] pub use executor::set_global_executor; pub use types_docs::exports as types; #[doc(hidden)] pub mod macro_internal; pub use crate::macros::*; use crate::{context::ModuleContext, handle::Handle, result::NeonResult, types::JsValue}; #[cfg(feature = "napi-6")] mod lifecycle; #[cfg(all(feature = "napi-6", feature = "futures"))] mod executor; #[cfg(feature = "napi-8")] static MODULE_TAG: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { let mut lower = [0; std::mem::size_of::()]; // Generating a random module tag at runtime allows Neon builds to be reproducible. A few // alternatives considered: // * Generating a random value at build time; this reduces runtime dependencies but, breaks // reproducible builds // * A static random value; this solves the previous issues, but does not protect against ABI // differences across Neon and Rust versions // * Calculating a variable from the environment (e.g. Rust version); this theoretically works // but, is complicated and error prone. This could be a future optimization. getrandom::getrandom(&mut lower).expect("Failed to generate a Neon module type tag"); // We only use 64-bits of the available 128-bits. The rest is reserved for future versioning and // expansion of implementation. let lower = u64::from_ne_bytes(lower); // Note: `upper` must be non-zero or `napi_check_object_type_tag` will always return false // https://github.com/nodejs/node/blob/5fad0b93667ffc6e4def52996b9529ac99b26319/src/js_native_api_v8.cc#L2455 crate::sys::TypeTag { lower, upper: 1 } }); /// Values exported with [`neon::export`](export) pub struct Exports(()); impl Exports { /// Export all values exported with [`neon::export`](export) /// /// ``` /// # fn main() { /// # use neon::prelude::*; /// #[neon::main] /// fn main(mut cx: ModuleContext) -> NeonResult<()> { /// neon::registered().export(&mut cx)?; /// Ok(()) /// } /// # } /// ``` /// /// For more control, iterate over exports. /// /// ``` /// # fn main() { /// # use neon::prelude::*; /// #[neon::main] /// fn main(mut cx: ModuleContext) -> NeonResult<()> { /// for create in neon::registered() { /// let (name, value) = create(&mut cx)?; /// /// cx.export_value(name, value)?; /// } /// /// Ok(()) /// } /// # } /// ``` pub fn export(self, cx: &mut ModuleContext) -> NeonResult<()> { for create in self { let (name, value) = create(cx)?; cx.export_value(name, value)?; } Ok(()) } } impl IntoIterator for Exports { type Item = <::IntoIter as IntoIterator>::Item; type IntoIter = std::slice::Iter< 'static, for<'cx> fn(&mut ModuleContext<'cx>) -> NeonResult<(&'static str, Handle<'cx, JsValue>)>, >; fn into_iter(self) -> Self::IntoIter { crate::macro_internal::EXPORTS.into_iter() } } /// Access values exported with [`neon::export`](export) pub fn registered() -> Exports { Exports(()) } #[test] fn feature_matrix() { use std::{env, process::Command}; // N.B.: Only versions that are used are included in order to keep the set // of permutations as small as possible. const NODE_API_VERSIONS: &[&str] = &["napi-1", "napi-4", "napi-5", "napi-6", "napi-8"]; const FEATURES: &[&str] = &["external-buffers", "futures", "serde", "tokio", "tokio-rt"]; let cargo = env::var_os("CARGO").unwrap_or_else(|| "cargo".into()); for features in itertools::Itertools::powerset(FEATURES.iter()) { for version in NODE_API_VERSIONS.iter().map(|f| f.to_string()) { let features = features.iter().fold(version, |f, s| f + "," + s); let status = Command::new(&cargo) .args(["check", "-p", "neon", "--no-default-features", "--features"]) .arg(features) .spawn() .unwrap() .wait() .unwrap(); assert!(status.success()); } } } ================================================ FILE: crates/neon/src/lifecycle.rs ================================================ //! # Environment life cycle APIs //! //! These APIs map to the life cycle of a specific "Agent" or self-contained //! environment. If a Neon module is loaded multiple times (Web Workers, worker //! threads), these API will be handle data associated with a specific instance. //! //! See the [N-API Lifecycle][npai-docs] documentation for more details. //! //! [napi-docs]: https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis use std::{ any::Any, marker::PhantomData, sync::{ atomic::{AtomicU32, Ordering}, Arc, }, }; use crate::{ context::Context, event::Channel, handle::root::NapiRef, sys::{lifecycle, raw::Env, tsfn::ThreadsafeFunction}, types::promise::NodeApiDeferred, }; #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr(transparent)] /// Uniquely identifies an instance of the module /// /// _Note_: Since `InstanceData` is created lazily, the order of `id` may not /// reflect the order that instances were created. pub(crate) struct InstanceId(u32); impl InstanceId { fn next() -> Self { static NEXT_ID: AtomicU32 = AtomicU32::new(0); let next = NEXT_ID.fetch_add(1, Ordering::SeqCst).checked_add(1); match next { Some(id) => Self(id), None => panic!("u32 overflow ocurred in Lifecycle InstanceId"), } } } /// `InstanceData` holds Neon data associated with a particular instance of a /// native module. If a module is loaded multiple times (e.g., worker threads), this /// data will be unique per instance. pub(crate) struct InstanceData { id: InstanceId, /// Used to free `Root` in the same JavaScript environment that created it /// /// _Design Note_: An `Arc` ensures the `ThreadsafeFunction` outlives the unloading /// of a module. Since it is unlikely that modules will be re-loaded frequently, this /// could be replaced with a leaked `&'static ThreadsafeFunction`. However, /// given the cost of FFI, this optimization is omitted until the cost of an /// `Arc` is demonstrated as significant. drop_queue: Arc>, /// Shared `Channel` that is cloned to be returned by the `cx.channel()` method shared_channel: Channel, /// Table of user-defined instance-local cells. locals: LocalTable, } #[derive(Default)] pub(crate) struct LocalTable { cells: Vec, } pub(crate) type LocalCellValue = Box; #[derive(Default)] pub(crate) enum LocalCell { #[default] /// Uninitialized state. Uninit, /// Intermediate "dirty" state representing the middle of a `get_or_try_init` transaction. Trying, /// Fully initialized state. Init(LocalCellValue), } impl LocalCell { /// Establish the initial state at the beginning of the initialization protocol. /// This method ensures that re-entrant initialization always panics (i.e. when /// an existing `get_or_try_init` is in progress). fn pre_init(&mut self, f: F) where F: FnOnce() -> LocalCell, { match self { LocalCell::Uninit => { *self = f(); } LocalCell::Trying => panic!("attempt to reinitialize Local during initialization"), LocalCell::Init(_) => {} } } pub(crate) fn get<'cx, 'a, C>(cx: &'a mut C, id: usize) -> Option<&'a mut LocalCellValue> where C: Context<'cx>, { let cell = InstanceData::locals(cx).get(id); match cell { LocalCell::Init(ref mut b) => Some(b), _ => None, } } pub(crate) fn get_or_init<'cx, 'a, C, F>( cx: &'a mut C, id: usize, f: F, ) -> &'a mut LocalCellValue where C: Context<'cx>, F: FnOnce() -> LocalCellValue, { InstanceData::locals(cx) .get(id) .pre_init(|| LocalCell::Init(f())); LocalCell::get(cx, id).unwrap() } pub(crate) fn get_or_try_init<'cx, 'a, C, E, F>( cx: &'a mut C, id: usize, f: F, ) -> Result<&'a mut LocalCellValue, E> where C: Context<'cx>, F: FnOnce(&mut C) -> Result, { // Kick off a new transaction and drop it before getting the result. { let mut tx = TryInitTransaction::new(cx, id); tx.run(|cx| f(cx))?; } // If we're here, the transaction has succeeded, so get the result. Ok(LocalCell::get(cx, id).unwrap()) } } impl LocalTable { pub(crate) fn get(&mut self, index: usize) -> &mut LocalCell { if index >= self.cells.len() { self.cells.resize_with(index + 1, Default::default); } &mut self.cells[index] } } /// An RAII implementation of `LocalCell::get_or_try_init`, which ensures that /// the state of a cell is properly managed through all possible control paths. /// As soon as the transaction begins, the cell is labelled as being in a dirty /// state (`LocalCell::Trying`), so that any additional re-entrant attempts to /// initialize the cell will fail fast. The `Drop` implementation ensures that /// after the transaction, the cell goes back to a clean state of either /// `LocalCell::Uninit` if it fails or `LocalCell::Init` if it succeeds. struct TryInitTransaction<'cx, 'a, C: Context<'cx>> { cx: &'a mut C, id: usize, _lifetime: PhantomData<&'cx ()>, } impl<'cx, 'a, C: Context<'cx>> TryInitTransaction<'cx, 'a, C> { fn new(cx: &'a mut C, id: usize) -> Self { let mut tx = Self { cx, id, _lifetime: PhantomData, }; tx.cell().pre_init(|| LocalCell::Trying); tx } /// _Post-condition:_ If this method returns an `Ok` result, the cell is in the /// `LocalCell::Init` state. fn run(&mut self, f: F) -> Result<(), E> where F: FnOnce(&mut C) -> Result, { if self.is_trying() { let value = f(self.cx)?; *self.cell() = LocalCell::Init(value); } Ok(()) } fn cell(&mut self) -> &mut LocalCell { InstanceData::locals(self.cx).get(self.id) } #[allow(clippy::wrong_self_convention)] fn is_trying(&mut self) -> bool { matches!(self.cell(), LocalCell::Trying) } } impl<'cx, 'a, C: Context<'cx>> Drop for TryInitTransaction<'cx, 'a, C> { fn drop(&mut self) { if self.is_trying() { *self.cell() = LocalCell::Uninit; } } } /// Wrapper for raw Node-API values to be dropped on the main thread pub(crate) enum DropData { Deferred(NodeApiDeferred), Ref(NapiRef), } impl DropData { /// Drop a value on the main thread fn drop(env: Option, data: Self) { if let Some(env) = env { unsafe { match data { DropData::Deferred(data) => data.leaked(env), DropData::Ref(data) => data.unref(env), } } } } } impl InstanceData { /// Return the data associated with this module instance, lazily initializing if /// necessary. /// /// # Safety /// No additional locking (e.g., `Mutex`) is necessary because holding a /// `Context` reference ensures serialized access. pub(crate) fn get<'cx, C: Context<'cx>>(cx: &mut C) -> &mut InstanceData { let env = cx.env().to_raw(); let data = unsafe { lifecycle::get_instance_data::(env).as_mut() }; if let Some(data) = data { return data; } let drop_queue = unsafe { let queue = ThreadsafeFunction::new(env, DropData::drop); queue.unref(env); queue }; let shared_channel = { let mut channel = Channel::new(cx); channel.unref(cx); channel }; let data = InstanceData { id: InstanceId::next(), drop_queue: Arc::new(drop_queue), shared_channel, locals: LocalTable::default(), }; unsafe { &mut *lifecycle::set_instance_data(env, data) } } /// Helper to return a reference to the `drop_queue` field of `InstanceData` pub(crate) fn drop_queue<'cx, C: Context<'cx>>( cx: &mut C, ) -> Arc> { Arc::clone(&InstanceData::get(cx).drop_queue) } /// Clones the shared channel and references it since new channels should start /// referenced, but the shared channel is unreferenced. pub(crate) fn channel<'cx, C: Context<'cx>>(cx: &mut C) -> Channel { let mut channel = InstanceData::get(cx).shared_channel.clone(); channel.reference(cx); channel } /// Unique identifier for this instance of the module pub(crate) fn id<'cx, C: Context<'cx>>(cx: &mut C) -> InstanceId { InstanceData::get(cx).id } /// Helper to return a reference to the `locals` field of `InstanceData`. pub(crate) fn locals<'cx, C: Context<'cx>>(cx: &mut C) -> &mut LocalTable { &mut InstanceData::get(cx).locals } } ================================================ FILE: crates/neon/src/macro_internal/futures.rs ================================================ use std::future::Future; use crate::{ context::{Context, Cx, TaskContext}, result::JsResult, types::JsValue, }; pub fn spawn<'cx, F, S>(cx: &mut Cx<'cx>, fut: F, settle: S) -> JsResult<'cx, JsValue> where F: Future + Send + 'static, F::Output: Send, S: FnOnce(TaskContext, F::Output) -> JsResult + Send + 'static, { let rt = match crate::executor::RUNTIME.get(cx) { Some(rt) => rt, None => return cx.throw_error("must initialize with neon::set_global_executor"), }; let ch = cx.channel(); let (d, promise) = cx.promise(); rt.spawn(Box::pin(async move { let res = fut.await; let _ = d.try_settle_with(&ch, move |cx| settle(cx, res)); })); Ok(promise.upcast()) } ================================================ FILE: crates/neon/src/macro_internal/mod.rs ================================================ //! Internals needed by macros. These have to be exported for the macros to work use std::marker::PhantomData; pub use linkme; use crate::{ context::{Context, Cx, ModuleContext}, handle::Handle, result::{JsResult, NeonResult}, types::{extract::TryIntoJs, JsValue}, }; #[cfg(feature = "serde")] use crate::types::extract::Json; #[cfg(all(feature = "napi-6", feature = "futures"))] pub use self::futures::*; pub mod object { pub use crate::object::wrap::{unwrap, wrap}; } #[cfg(all(feature = "napi-6", feature = "futures"))] mod futures; type Export<'cx> = (&'static str, Handle<'cx, JsValue>); #[linkme::distributed_slice] pub static EXPORTS: [for<'cx> fn(&mut ModuleContext<'cx>) -> NeonResult>]; #[linkme::distributed_slice] pub static MAIN: [for<'cx> fn(ModuleContext<'cx>) -> NeonResult<()>]; // Wrapper for the value type and return type tags pub struct NeonMarker(PhantomData, PhantomData); // Markers to determine the type of a value #[cfg(feature = "serde")] pub struct NeonJsonTag; pub struct NeonValueTag; pub struct NeonResultTag; pub trait ToNeonMarker { type Return; fn to_neon_marker(&self) -> NeonMarker; } // Specialized implementation for `Result` impl ToNeonMarker for Result { type Return = NeonResultTag; fn to_neon_marker(&self) -> NeonMarker { NeonMarker(PhantomData, PhantomData) } } // Default implementation that takes lower precedence due to autoref impl ToNeonMarker for &T { type Return = NeonValueTag; fn to_neon_marker(&self) -> NeonMarker { NeonMarker(PhantomData, PhantomData) } } impl NeonMarker { pub fn neon_into_js<'cx, T>(self, cx: &mut Cx<'cx>, v: T) -> JsResult<'cx, JsValue> where T: TryIntoJs<'cx>, { v.try_into_js(cx).map(|v| v.upcast()) } } #[cfg(feature = "serde")] impl NeonMarker { pub fn neon_into_js<'cx, T>(self, cx: &mut Cx<'cx>, v: T) -> JsResult<'cx, JsValue> where Json: TryIntoJs<'cx>, { Json(v).try_into_js(cx).map(|v| v.upcast()) } } #[cfg(feature = "serde")] impl NeonMarker { pub fn neon_into_js<'cx, T, E>( self, cx: &mut Cx<'cx>, res: Result, ) -> JsResult<'cx, JsValue> where Result, E>: TryIntoJs<'cx>, { res.map(Json).try_into_js(cx).map(|v| v.upcast()) } } impl NeonMarker { pub fn into_neon_result(self, _cx: &mut Cx, v: T) -> NeonResult { Ok(v) } } impl NeonMarker { pub fn into_neon_result<'cx, T, E>(self, cx: &mut Cx<'cx>, res: Result) -> NeonResult where E: TryIntoJs<'cx>, { match res { Ok(v) => Ok(v), Err(err) => err.try_into_js(cx).and_then(|err| cx.throw(err)), } } } #[cfg(feature = "napi-6")] pub use crate::object::class::new_class_metadata; #[cfg(feature = "napi-6")] pub use crate::types_impl::extract::private::Sealed; #[cfg(feature = "napi-6")] pub use crate::object::wrap::WrapError; #[cfg(feature = "napi-6")] pub use crate::object::class::ClassInternal; #[cfg(feature = "napi-6")] pub use crate::object::class::{ClassMetadata, RootClassMetadata}; #[cfg(feature = "napi-6")] pub fn internal_constructor<'cx, T: crate::object::Class>( cx: &mut Cx<'cx>, ) -> NeonResult> { let class_instance = T::local(cx)?; Ok(class_instance.internal_constructor()) } ================================================ FILE: crates/neon/src/macros.rs ================================================ //! Helper module to add documentation to macros prior to re-exporting. #[cfg(feature = "napi-6")] /// Create a Neon class from a Rust datatype /// /// The `#[neon::class]` attribute can be applied to an `impl` block to create a JavaScript /// class that wraps a Rust struct (or enum). The `impl` block specifies a constructor method named /// `new` to create instances of the struct, which Neon automatically attaches to instances of the /// JavaScript class during object construction. /// /// Typically, Neon classes are exported from their addon, which can be done with the /// [`#[neon::export(class)]`](crate::export) attribute. /// /// ## Example /// /// ``` /// # use neon::prelude::*; /// # use neon::{context::Context, types::Finalize}; /// #[derive(Clone)] /// pub struct User { /// username: String, /// first_name: String, /// last_name: String, /// } /// /// #[neon::export(class)] /// impl User { /// pub fn new(username: String, first_name: String, last_name: String) -> Self { /// Self { username, first_name, last_name } /// } /// /// pub fn to_string(&self) -> String { /// format!("[object User:{}]", self.username) /// } /// } /// ``` /// /// ## Constructor /// /// Classes must have exactly one constructor method named `new`. The constructor takes /// the class parameters and returns `Self`. Constructor arguments can be any type that /// implements [`TryFromJs`](crate::types::extract::TryFromJs). /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct Person { /// name: String, /// age: u32, /// } /// /// #[neon::class] /// impl Person { /// pub fn new(name: String, age: u32) -> Self { /// Self { name, age } /// } /// } /// ``` /// /// ## Methods /// /// Class methods can have either `&self` or `&mut self` as their first parameter. /// Methods can take any type that implements [`TryFromJs`](crate::types::extract::TryFromJs) /// and return any type that implements [`TryIntoJs`](crate::types::extract::TryIntoJs). /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct Counter { /// value: i32, /// } /// /// #[neon::class] /// impl Counter { /// pub fn new(initial: i32) -> Self { /// Self { value: initial } /// } /// /// pub fn increment(&mut self) -> i32 { /// self.value += 1; /// self.value /// } /// /// pub fn get(&self) -> i32 { /// self.value /// } /// } /// ``` /// /// ### Reference Parameters /// /// Methods can accept class instances by reference (`&T`) or mutable reference (`&mut T`) /// to avoid cloning when passing instances between methods. The type must implement /// [`TryFromJsRef`](crate::types::extract::TryFromJsRef) for immutable references or /// [`TryFromJsRefMut`](crate::types::extract::TryFromJsRefMut) for mutable references. /// The `#[neon::class]` macro automatically implements these traits for all class types. /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// #[derive(Clone)] /// pub struct Point { /// x: f64, /// y: f64, /// } /// /// #[neon::class] /// impl Point { /// pub fn new(x: f64, y: f64) -> Self { /// Self { x, y } /// } /// /// // Accept another Point by immutable reference (no clone) /// pub fn distance(&self, other: &Self) -> f64 { /// let dx = self.x - other.x; /// let dy = self.y - other.y; /// (dx * dx + dy * dy).sqrt() /// } /// /// // Accept another Point by mutable reference (no clone) /// pub fn swap_coordinates(&mut self, other: &mut Self) { /// std::mem::swap(&mut self.x, &mut other.x); /// std::mem::swap(&mut self.y, &mut other.y); /// } /// /// // Accept another Point by value (clones the instance) /// pub fn midpoint(&self, other: Self) -> Self { /// Self { /// x: (self.x + other.x) / 2.0, /// y: (self.y + other.y) / 2.0, /// } /// } /// } /// ``` /// /// From JavaScript: /// ```js /// const p1 = new Point(0, 0); /// const p2 = new Point(3, 4); /// /// console.log(p1.distance(p2)); // 5 (no cloning) /// /// p1.swapCoordinates(p2); // Mutates both points /// console.log(p1.x); // 3 /// console.log(p2.x); // 0 /// /// const mid = p1.midpoint(p2); // Clones p2 /// ``` /// /// **When to use references:** /// - Use `&T` when you only need to read from the instance /// - Use `&mut T` when you need to mutate the instance /// - Use `T` (by value) when the semantics require taking ownership /// /// Note that reference parameters still use [`RefCell`](std::cell::RefCell) internally, /// so runtime borrow checking applies. Attempting to borrow the same instance both mutably /// and immutably (or multiple times mutably) will panic. /// /// ## Finalizer /// /// Classes can implement a `finalize` method to perform cleanup when the /// JavaScript object is garbage collected. The `finalize` method takes /// ownership of the class instance and is called when the object is no longer /// reachable from JavaScript. /// /// ``` /// # use neon::prelude::*; /// pub struct Logger { /// name: String, /// } /// /// #[neon::class] /// impl Logger { /// pub fn new(name: String) -> Self { /// Self { name } /// } /// /// pub fn finalize<'cx, C: Context<'cx>>(self, _cx: &mut C) { /// println!("Logger {} is being finalized", self.name); /// } /// } /// ``` /// /// ## Mutability and Borrow Checking /// /// Neon classes use [`RefCell`](std::cell::RefCell) internally to allow mutation through /// `&mut self` methods while maintaining JavaScript's shared ownership semantics. This means /// that borrow checking happens at runtime, not compile time, and violating Rust's borrowing /// rules will cause a panic. /// /// **Important:** You cannot call a method that requires `&mut self` while another method /// is borrowing the instance (even with `&self`). This includes: /// - Reentrancy from JavaScript callbacks /// - Nested method calls on the same instance /// /// For complex scenarios involving callbacks or shared mutable state across threads, /// consider using additional interior mutability types like /// [`Arc>`](std::sync::Arc) for the specific fields that need it. /// /// ### Method Attributes /// /// Methods support the same attributes as [`#[neon::export]`](crate::export) functions, including /// `json`, `task`, `async`, `context`, `this`, and `name`, and may be fallible by returning /// `Result` types. /// /// #### JSON Methods /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct DataProcessor; /// /// #[neon::class] /// impl DataProcessor { /// pub fn new() -> Self { /// Self /// } /// /// #[neon(json)] /// pub fn process_data(&self, items: Vec) -> Vec { /// items.into_iter().map(|s| s.to_uppercase()).collect() /// } /// } /// ``` /// /// #### Async Methods /// /// Methods declared with `async fn` are automatically detected and exported as async. The /// macro automatically clones the instance before calling the method. Because Rust's `async fn` /// captures `self` into the Future (which must be `'static`), the method must take `self` by /// value (not `&self` or `&mut self`) and the struct must implement `Clone`. Any shared mutable /// state should use types like [`Arc>`](std::sync::Arc) for thread-safe interior /// mutability. /// /// ``` /// # #[cfg(all(feature = "napi-6", feature = "futures"))] /// # { /// # use neon::prelude::*; /// # use neon::types::Finalize; /// # use std::sync::{Arc, Mutex}; /// # #[derive(Clone)] /// # pub struct AsyncWorker { /// # counter: Arc>, /// # } /// #[neon::class] /// impl AsyncWorker { /// pub fn new() -> Self { /// Self { /// counter: Arc::new(Mutex::new(0)), /// } /// } /// /// // Must take `self` by value; the macro clones the instance automatically /// pub async fn fetch_data(self, url: String) -> String { /// // Simulate async work /// let mut count = self.counter.lock().unwrap(); /// *count += 1; /// format!("Data from {} (request #{})", url, count) /// } /// } /// # } /// ``` /// /// ##### Synchronous Setup /// /// For more control over async behavior, use `#[neon(async)]` with a method that /// returns a [`Future`](std::future::Future). This allows synchronous setup on /// the JavaScript main thread. With this approach, the method takes `&self` and you /// are responsible for cloning any data needed to make the returned Future `'static`. /// /// ``` /// # #[cfg(all(feature = "napi-6", feature = "futures"))] /// # { /// # use neon::prelude::*; /// # use neon::types::Finalize; /// # use std::future::Future; /// # #[derive(Clone)] /// # pub struct AsyncWorker { /// # value: String, /// # } /// #[neon::class] /// impl AsyncWorker { /// # pub fn new() -> Self { Self { value: String::new() } } /// #[neon(async)] /// pub fn process_data(&self, data: String) -> impl Future + 'static { /// println!("Setup on main thread"); /// // Clone any instance data you need for the Future /// let value = self.value.clone(); /// async move { /// format!("{}: {}", value, data.to_uppercase()) /// } /// } /// } /// # } /// ``` /// /// #### Task Methods /// /// Methods can be executed on Node's worker pool using the `task` attribute. The macro /// automatically clones the instance before moving it to the worker thread, so the struct /// must implement `Clone`. Like `async fn` methods, task methods must take `self` by value /// (not `&self` or `&mut self`) to make it clear that they operate on a clone. /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// #[derive(Clone)] /// pub struct CpuWorker { /// multiplier: u32, /// } /// /// #[neon::class] /// impl CpuWorker { /// pub fn new(multiplier: u32) -> Self { /// Self { multiplier } /// } /// /// // Must take `self` by value; the macro clones the instance automatically /// #[neon(task)] /// pub fn heavy_computation(self, iterations: u32) -> u32 { /// (0..iterations).map(|i| i * self.multiplier).sum() /// } /// } /// ``` /// /// #### Method Naming /// /// Like [`#[neon::export]`](crate::export) functions, method names are converted from `snake_case` /// to `camelCase`. Custom names can be specified with the `name` attribute: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct Label { /// data: String, /// } /// /// #[neon::class] /// impl Label { /// pub fn new() -> Self { /// Self { data: String::new() } /// } /// /// #[neon(name = "trimStart")] /// pub fn trim_leading(&self) -> String { /// self.data.trim_start().to_string() /// } /// } /// ``` /// /// #### Fallible Methods /// /// Methods can return `Result` types to throw JavaScript exceptions, just like /// `#[neon::export]` functions. /// /// ``` /// # use neon::prelude::*; /// pub struct User { /// name: String, /// } /// /// #[neon::class] /// impl User { /// pub fn new(name: String) -> Self { /// Self { name } /// } /// /// pub fn get_name(&self) -> String { /// self.name.clone() /// } /// /// pub fn set_name(&mut self, name: String) -> Result<(), &'static str> { /// if name.is_empty() { /// return Err("Name cannot be empty"); /// } /// /// self.name = name; /// /// Ok(()) /// } /// } /// ``` /// /// ### Constructor Attributes /// /// Constructor methods support the `json` and `context` attributes and may be fallible as well. /// /// ``` /// # use neon::prelude::*; /// # use neon::types::extract::Json; /// pub struct Argv { /// pub args: Vec, /// } /// /// #[neon::class] /// impl Argv { /// // context attribute is inferred automatically /// #[neon(json)] /// pub fn new(cx: &mut Cx, args: Option>) -> NeonResult { /// let args = if let Some(args) = args { args } else { /// let Json(args): Json> = cx /// .global::("process")? /// .prop(cx, "argv") /// .get()?; /// args /// }; /// Ok(Self { args } ) /// } /// /// pub fn len(&self) -> u32 { /// self.args.len() as u32 /// } /// /// pub fn get(&self, index: u32) -> Option { /// self.args.get(index as usize).cloned() /// } /// } /// ``` /// /// ## Const Properties /// /// Classes can expose Rust constants as static, immutable properties on the JavaScript class: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct MathConstants; /// /// #[neon::class] /// impl MathConstants { /// const PI: f64 = 3.14159; /// const VERSION: u32 = 1; /// /// #[neon(name = "maxValue")] /// const MAX_VALUE: f64 = f64::MAX; /// /// #[neon(json)] /// const DEFAULT_SETTINGS: &'static [&'static str] = &["feature1", "feature2"]; /// /// pub fn new() -> Self { /// Self /// } /// } /// ``` /// /// From JavaScript: /// ```js /// console.log(MathConstants.PI); // 3.14159 /// console.log(MathConstants.maxValue); // 1.7976931348623157e+308 /// console.log(MathConstants.DEFAULT_SETTINGS); // ["feature1", "feature2"] /// ``` /// /// Const properties support the same attributes as globals: `name` for custom naming /// and `json` for automatic JSON serialization. Properties are immutable from JavaScript. /// /// ## Context and This Parameters /// /// Methods can access the JavaScript runtime context and the JavaScript object wrapper: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct Interactive { /// data: String, /// } /// /// #[neon::class] /// impl Interactive { /// pub fn new(data: String) -> Self { /// Self { data } /// } /// /// // Method with context parameter /// pub fn create_object<'cx>( /// &self, /// cx: &mut FunctionContext<'cx>, /// ) -> JsResult<'cx, JsObject> { /// let obj = cx.empty_object(); /// let value = cx.string(&self.data); /// obj.set(cx, "data", value)?; /// Ok(obj) /// } /// /// // Method with this parameter (access to JS object) /// pub fn inspect_this(&self, this: Handle) -> String { /// format!("JS object available: {}", self.data) /// } /// } /// ``` /// /// ## Working with Class Instances /// /// Methods can accept and return instances of the same class directly. When a class instance /// is passed as a parameter or returned from a method, it is automatically cloned from (or into) /// the internal [`RefCell`](std::cell::RefCell) storage, so the struct must implement `Clone`. /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// #[derive(Clone)] /// pub struct Point { /// x: f64, /// y: f64, /// } /// /// #[neon::class] /// impl Point { /// pub fn new(x: f64, y: f64) -> Self { /// Self { x, y } /// } /// /// pub fn distance(&self, other: Self) -> f64 { /// let dx = self.x - other.x; /// let dy = self.y - other.y; /// (dx * dx + dy * dy).sqrt() /// } /// /// pub fn midpoint(&self, other: Self) -> Self { /// Self { /// x: (self.x + other.x) / 2.0, /// y: (self.y + other.y) / 2.0, /// } /// } /// } /// ``` /// /// From JavaScript, you can call these methods with other instances of the same class: /// ```js /// const p1 = new Point(0, 0); /// const p2 = new Point(3, 4); /// console.log(p1.distance(p2)); // 5 /// const midpoint = p1.midpoint(p2); // Point { x: 1.5, y: 2 } /// ``` /// /// ## Export Shorthand /// /// Use [`#[neon::export(class)]`](crate::export) to combine class definition with /// automatic module export: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct AutoExported { /// value: u32, /// } /// /// // Combines #[neon::class] with automatic export /// #[neon::export(class)] /// impl AutoExported { /// pub fn new(value: u32) -> Self { /// Self { value } /// } /// } /// ``` /// /// Like other exports, classes can be exported with custom names: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// pub struct InternalPoint { /// x: f64, /// y: f64, /// } /// /// // Export as "Point" instead of "InternalPoint" /// #[neon::export(class(name = "Point"))] /// impl InternalPoint { /// pub fn new(x: f64, y: f64) -> Self { /// Self { x, y } /// } /// /// pub fn distance_from_origin(&self) -> f64 { /// (self.x * self.x + self.y * self.y).sqrt() /// } /// } /// ``` /// /// It's also possible to distinguish between the class name and export name: /// /// ``` /// # use neon::prelude::*; /// pub struct RustPoint { /// x: f64, /// y: f64, /// } /// /// // Export as "Point" but with Point.name === "NeonPoint" /// #[neon::export(class(name = "NeonPoint"), name = "Point")] /// impl RustPoint { /// pub fn new(x: f64, y: f64) -> Self { /// Self { x, y } /// } /// } /// ``` /// /// ## Error Handling /// /// Methods can return [`Result`] types to throw JavaScript exceptions, just like /// [`#[neon::export]`](crate::export) functions: /// /// ``` /// # use neon::prelude::*; /// # use neon::types::{Finalize, extract::Error}; /// pub struct FileReader; /// /// #[neon::class] /// impl FileReader { /// pub fn new() -> Self { /// Self /// } /// /// pub fn read_file(&self, path: String) -> Result { /// std::fs::read_to_string(path).map_err(Error::from) /// } /// } /// ``` /// /// ## `Class` Trait /// /// The `#[neon::class]` macro automatically implements the [`Class`](crate::object::Class) /// trait for the struct. This trait can be used to access the constructor function at runtime. /// /// ``` /// # use neon::prelude::*; /// # use neon::types::Finalize; /// use neon::object::Class; /// # #[derive(Clone)] /// # pub struct Point { /// # x: f64, /// # y: f64, /// # } /// # /// # #[neon::class] /// # impl Point { /// # pub fn new(x: f64, y: f64) -> Self { /// # Self { x, y } /// # } /// # } /// /// # fn init_statics<'cx>(cx: &mut FunctionContext<'cx>) -> JsResult<'cx, JsUndefined> { /// let constructor = Point::constructor(cx)?; /// constructor /// .prop(cx, "ORIGIN") /// .set(Point::new(0.0, 0.0))?; /// # Ok(cx.undefined()) /// # } /// ``` pub use neon_macros::class; /// Marks a function as the main entry point for initialization in /// a Neon module. /// /// This attribute should only be used _once_ in a module and will /// be called each time the module is initialized in a context. /// /// If a `main` function is not provided, all registered exports will be exported. If /// the `tokio` feature flag is enabled, a multithreaded tokio runtime will also be /// registered globally. /// /// ``` /// # use neon::prelude::*; /// # fn main() { /// #[neon::main] /// fn main(mut cx: ModuleContext) -> NeonResult<()> { /// // Export all registered exports /// neon::registered().export(&mut cx)?; /// /// let version = cx.string("1.0.0"); /// /// cx.export_value("version", version)?; /// /// Ok(()) /// } /// # } /// ``` pub use neon_macros::main; /// Register an item to be exported by the Neon addon /// /// ## Exporting constants and statics /// /// ``` /// #[neon::export] /// static GREETING: &str = "Hello, Neon!"; /// /// #[neon::export] /// const ANSWER: u8 = 42; /// ``` /// /// ### Renaming an export /// /// By default, items will be exported with their Rust name. Exports may /// be renamed by providing the `name` attribute. /// /// ``` /// #[neon::export(name = "myGreeting")] /// static GREETING: &str = "Hello, Neon!"; /// ``` /// /// ### JSON exports /// /// Complex values may be exported by automatically serializing to JSON and /// parsing in JavaScript. Any type that implements `serde::Serialize` may be used. /// /// ``` /// #[neon::export(json)] /// static MESSAGES: &[&str] = &["hello", "goodbye"]; /// ``` /// /// ## Exporting functions /// /// Functions may take any type that implements [`TryFromJs`](crate::types::extract::TryFromJs) as /// an argument and return any type that implements [`TryIntoJs`](crate::types::extract::TryIntoJs). /// /// ``` /// #[neon::export] /// fn add(a: f64, b: f64) -> f64 { /// a + b /// } /// ``` /// /// ### Naming exported functions /// /// Conventionally, Rust uses `snake_case` for function identifiers and JavaScript uses `camelCase`. /// By default, Neon will attempt to convert function names to camel case. For example: /// /// ```rust /// #[neon::export] /// fn add_one(n: f64) -> f64 { /// n + 1.0 /// } /// ``` /// /// The `add_one` function will be exported as `addOne` in JavaScript. /// /// ```js /// import { addOne } from "."; /// ``` /// /// [Similar to globals](#renaming-an-export), exported functions can be overridden with the `name` /// attribute. /// /// ```rust /// #[neon::export(name = "addOneSync")] /// fn add_one(n: f64) -> f64 { /// n + 1.0 /// } /// ``` /// Neon uses the following rules when converting `snake_case` to `camelCase`: /// /// * All _leading_ and _trailing_ underscores (`_`) are preserved /// * Characters _immediately_ following a _non-leading_ underscore are converted to uppercase /// * If the identifier contains an _unexpected_ character, **no** conversion is performed and /// the identifier is used _unchanged_. Unexpected characters include: /// - Uppercase characters /// - Duplicate _interior_ (non-leading, non-trailing underscores) /// /// ### Exporting a function that uses JSON /// /// The [`Json`](crate::types::extract::Json) wrapper allows ergonomically handling complex /// types that implement `serde::Deserialize` and `serde::Serialize`. /// /// ``` /// # use neon::types::extract::Json; /// #[neon::export] /// fn sort(Json(mut items): Json>) -> Json> { /// items.sort(); /// Json(items) /// } /// ``` /// /// As a convenience, macro uses may add the `json` attribute to automatically /// wrap arguments and return values with `Json`. /// /// ``` /// #[neon::export(json)] /// fn sort(mut items: Vec) -> Vec { /// items.sort(); /// items /// } /// ``` /// /// ### Tasks /// /// Neon provides an API for spawning tasks to execute asynchronously on Node's worker /// pool. JavaScript may await a promise for completion of the task. /// /// ``` /// # use neon::prelude::*; /// #[neon::export] /// fn add<'cx>(cx: &mut FunctionContext<'cx>, a: f64, b: f64) -> JsResult<'cx, JsPromise> { /// let promise = cx /// .task(move || a + b) /// .promise(|mut cx, res| Ok(cx.number(res))); /// /// Ok(promise) /// } /// ``` /// /// As a convenience, macro users may indicate that a function should be executed /// asynchronously on the worker pool by adding the `task` attribute. /// /// ``` /// #[neon::export(task)] /// fn add(a: f64, b: f64) -> f64 { /// a + b /// } /// ``` /// /// ### Async Functions /// /// The [`export`] macro can export `async fn`, converting to a JavaScript `Promise`, if a global /// future executor is registered. See [`neon::set_global_executor`](crate::set_global_executor) for /// more details. /// /// ``` /// # #[cfg(all(feature = "napi-6", feature = "futures"))] /// # { /// #[neon::export] /// async fn add(a: f64, b: f64) -> f64 { /// a + b /// } /// # } /// ``` /// /// #### Synchronous Setup /// /// To implement a function that appears asynchronous to JavaScript, but needs to perform /// some synchronous setup on the JavaScript main thread, a normal (i.e., non-`async`) Rust /// function that returns a [`Future`](std::future::Future) can be annotated with /// `#[neon::export(async)]`. /// /// ``` /// # #[cfg(all(feature = "napi-6", feature = "futures"))] /// # { /// # use std::future::Future; /// # use neon::prelude::*; /// #[neon::export(async)] /// fn add(a: f64, b: f64) -> impl Future { /// println!("Hello from the JavaScript main thread!"); /// /// async move { /// a + b /// } /// } /// # } /// ``` /// /// If work needs to be performed on the JavaScript main thread _after_ the asynchronous operation, /// the [`With`](crate::types::extract::With) extractor can be used to execute a closure before returning. /// /// ``` /// # #[cfg(all(feature = "napi-6", feature = "futures"))] /// # { /// # use neon::types::extract::{self, TryIntoJs}; /// #[neon::export] /// async fn add(a: f64, b: f64) -> impl for<'cx> TryIntoJs<'cx> { /// let sum = a + b; /// /// extract::with(move |cx| { /// println!("Hello from the JavaScript main thread!"); /// /// sum.try_into_js(cx) /// }) /// } /// # } /// ``` /// /// ### Error Handling /// /// If an exported function returns a [`Result`], a JavaScript exception will be thrown /// with the [`Err`]. Any error type that implements [`TryIntoJs`](crate::types::extract::TryIntoJs) /// may be used. /// /// ``` /// #[neon::export] /// fn throw(msg: String) -> Result<(), String> { /// Err(msg) /// } /// ``` /// /// The [`Error`](crate::types::extract::Error) type is provided for ergonomic error conversions /// from most error types using the `?` operator. /// /// ``` /// use neon::types::extract::Error; /// /// #[neon::export] /// fn read_file(path: String) -> Result { /// let contents = std::fs::read_to_string(path)?; /// Ok(contents) /// } /// ``` /// /// ### Interact with the JavaScript runtime /// /// More complex functions may need to interact directly with the JavaScript runtime, /// for example with [`Context`](crate::context::Context) or handles to JavaScript values. /// /// Functions may optionally include a [`Cx`](crate::context::Cx) or /// [`FunctionContext`](crate::context::FunctionContext) argument. Note that unlike functions /// created with [`JsFunction::new`](crate::types::JsFunction), exported function receive a borrowed /// context and may require explicit lifetimes. /// /// ``` /// # use neon::prelude::*; /// #[neon::export] /// fn add<'cx>( /// cx: &mut Cx<'cx>, /// a: Handle, /// b: Handle, /// ) -> JsResult<'cx, JsNumber> { /// let a = a.value(cx); /// let b = b.value(cx); /// /// Ok(cx.number(a + b)) /// } /// ``` /// /// ### Classes /// /// The `#[neon::export(class)]` attribute may be used on an `impl` block to /// combine class definition with automatic export. See the documentation for /// [`#[neon::class]`](crate::class) for more details. /// /// ### Advanced /// /// The following attributes are for advanced configuration and may not be /// necessary for most users. /// /// #### `context` /// /// The `#[neon::export]` uses a heuristic to determine if the first argument /// to a function is a _context_ argument. /// /// * In a function executed on the JavaScript main thread, it looks for `&mut Cx` /// or `&mut FunctionContext` to determine if the [`Context`](crate::context::Context) /// should be passed. /// * In a function executed on another thread, it looks for [`Channel`](crate::event::Channel). /// /// If the type has been renamed when importing, the `context` attribute can be /// added to force it to be passed. /// /// ``` /// use neon::event::Channel as Ch; /// use neon::context::FunctionContext as FnCtx; /// /// #[neon::export(context)] /// fn add(_cx: &mut FnCtx, a: f64, b: f64) -> f64 { /// a + b /// } /// /// #[neon::export(context)] /// async fn div(_ch: Ch, a: f64, b: f64) -> f64 { /// a / b /// } /// ``` /// /// #### `this` /// /// The `#[neon::export]` uses a heuristic to determine if an argument to this function is /// referring to [`this`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this). /// /// 1. If the first argument is a [context](#context), use the 0th argument, otherwise use the 1st. /// 2. If the argument binding is named `this` /// 3. Or if it is a tuple struct pattern with an element named `this` /// /// ``` /// use neon::types::extract::Boxed; /// /// #[neon::export] /// fn buffer_clone(this: Vec) -> Vec { /// this /// } /// /// #[neon::export] /// fn box_to_string(Boxed(this): Boxed) -> String { /// this /// } /// ``` /// /// If the function uses a variable name other than `this`, the `this` attribute may /// be added. /// /// ``` /// #[neon::export(this)] /// fn buffer_clone(me: Vec) -> Vec { /// me /// } /// ``` pub use neon_macros::export; ================================================ FILE: crates/neon/src/meta.rs ================================================ //! Metadata about the Neon version and build. use semver::Version; /// The Neon version. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// The Neon major version. pub const MAJOR: &str = env!("CARGO_PKG_VERSION_MAJOR"); /// The Neon minor version. pub const MINOR: &str = env!("CARGO_PKG_VERSION_MINOR"); /// The Neon patch version. pub const PATCH: &str = env!("CARGO_PKG_VERSION_PATCH"); /// Produces a `semver::Version` data structure representing the Neon version. pub fn version() -> Version { Version { major: MAJOR.parse().unwrap(), minor: MINOR.parse().unwrap(), patch: PATCH.parse().unwrap(), pre: Default::default(), build: Default::default(), } } ================================================ FILE: crates/neon/src/object/class.rs ================================================ use crate::{ context::Cx, handle::{Handle, Root}, object::Object, result::{JsResult, NeonResult}, types::JsFunction, }; #[doc(hidden)] pub trait ClassInternal { fn local<'cx>(cx: &mut Cx<'cx>) -> NeonResult>; fn create<'cx>(cx: &mut Cx<'cx>) -> NeonResult>; } /// A trait defining a Neon class. /// /// **This should not be implemented directly.** Instead, use the [`#[neon::class]`](crate::class) /// attribute macro to define a class, which will automatically implement this trait. pub trait Class: ClassInternal { /// The class name. fn name() -> String; /// The constructor function for the class. fn constructor<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction>; } #[doc(hidden)] pub struct ClassMetadata<'cx> { external_constructor: Handle<'cx, JsFunction>, internal_constructor: Handle<'cx, JsFunction>, } pub fn new_class_metadata<'cx>( external: Handle<'cx, JsFunction>, internal: Handle<'cx, JsFunction>, ) -> ClassMetadata<'cx> { ClassMetadata { external_constructor: external, internal_constructor: internal, } } impl<'cx> ClassMetadata<'cx> { pub fn constructor(&self) -> Handle<'cx, JsFunction> { self.external_constructor } pub(crate) fn internal_constructor(&self) -> Handle<'cx, JsFunction> { self.internal_constructor } #[doc(hidden)] pub fn root<'cx2>(&self, cx: &mut Cx<'cx2>) -> RootClassMetadata { RootClassMetadata { external_constructor: self.external_constructor.root(cx), internal_constructor: self.internal_constructor.root(cx), } } } #[doc(hidden)] pub struct RootClassMetadata { pub external_constructor: Root, pub internal_constructor: Root, } // Since it's just a pair of Root which are both Send, we can mark it as such. unsafe impl Send for RootClassMetadata {} impl RootClassMetadata { pub fn to_inner<'a, 'cx: 'a>(&'a self, cx: &'a mut Cx<'cx>) -> ClassMetadata<'cx> { ClassMetadata { external_constructor: self.external_constructor.to_inner(cx), internal_constructor: self.internal_constructor.to_inner(cx), } } } ================================================ FILE: crates/neon/src/object/mod.rs ================================================ //! Traits for working with JavaScript objects. //! //! This module defines the [`Object`] trait, which is implemented //! by all object types in the [JavaScript type hierarchy][hierarchy]. This //! trait provides key operations in the semantics of JavaScript objects, //! such as getting and setting an object's properties. //! //! ## Property Keys //! //! Object properties are accessed by a _property key_, which in JavaScript //! can be a string or [symbol][symbol]. (Neon does not yet have support for //! symbols.) For convenience, the [`PropertyKey`] trait allows //! Neon programs to use various Rust string types, as well as numeric types, //! as keys when accessing object properties, converting the keys to strings //! as necessary: //! //! ``` //! # use neon::prelude::*; //! fn set_and_check<'cx>( //! cx: &mut Cx<'cx>, //! obj: Handle<'cx, JsObject> //! ) -> JsResult<'cx, JsValue> { //! // set property "17" with integer shorthand //! obj.prop(cx, 17).set("hello")?; //! // get property "17" with string shorthand //! // returns the same value ("hello!") //! obj.prop(cx, "17").get() //! } //! ``` //! //! [hierarchy]: crate::types#the-javascript-type-hierarchy //! [symbol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol use smallvec::smallvec; use crate::{ context::{internal::ContextInternal, Context, Cx}, handle::{Handle, Root}, result::{NeonResult, Throw}, sys::{self, raw}, types::{ build, extract::{TryFromJs, TryIntoJs}, function::{BindOptions, CallOptions}, private::ValueInternal, utf8::Utf8, JsFunction, JsUndefined, JsValue, Value, }, }; #[cfg(feature = "napi-6")] use crate::{result::JsResult, types::JsArray}; #[cfg(feature = "napi-6")] pub use self::class::Class; #[doc(hidden)] pub use self::wrap::{unwrap, wrap}; #[cfg(feature = "napi-6")] pub(crate) mod class; pub(crate) mod wrap; /// A property key in a JavaScript object. pub trait PropertyKey: Copy { unsafe fn get_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut raw::Local, obj: raw::Local, ) -> bool; unsafe fn set_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut bool, obj: raw::Local, val: raw::Local, ) -> bool; } impl PropertyKey for u32 { unsafe fn get_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut raw::Local, obj: raw::Local, ) -> bool { sys::object::get_index(out, cx.env().to_raw(), obj, self) } unsafe fn set_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut bool, obj: raw::Local, val: raw::Local, ) -> bool { sys::object::set_index(out, cx.env().to_raw(), obj, self, val) } } impl<'a, K: Value> PropertyKey for Handle<'a, K> { unsafe fn get_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut raw::Local, obj: raw::Local, ) -> bool { let env = cx.env().to_raw(); sys::object::get(out, env, obj, self.to_local()) } unsafe fn set_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut bool, obj: raw::Local, val: raw::Local, ) -> bool { let env = cx.env().to_raw(); sys::object::set(out, env, obj, self.to_local(), val) } } impl PropertyKey for &str { unsafe fn get_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut raw::Local, obj: raw::Local, ) -> bool { let (ptr, len) = Utf8::from(self).into_small_unwrap().lower(); let env = cx.env().to_raw(); sys::object::get_string(env, out, obj, ptr, len) } unsafe fn set_from<'c, C: Context<'c>>( self, cx: &mut C, out: &mut bool, obj: raw::Local, val: raw::Local, ) -> bool { let (ptr, len) = Utf8::from(self).into_small_unwrap().lower(); let env = cx.env().to_raw(); sys::object::set_string(env, out, obj, ptr, len, val) } } /// A builder for accessing an object property. /// /// The builder methods make it convenient to get and set properties /// as well as to bind and call methods. /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// # let obj: Handle = cx.argument(0)?; /// let x: f64 = obj /// .prop(&mut cx, "x") /// .get()?; /// /// obj.prop(&mut cx, "y") /// .set(x)?; /// /// let s: String = obj.method(&mut cx, "toString")?.call()?; /// # Ok(cx.string(s)) /// # } /// ``` pub struct PropOptions<'a, 'cx, O, K> where 'cx: 'a, O: Object, K: PropertyKey, { pub(crate) cx: &'a mut Cx<'cx>, pub(crate) this: Handle<'cx, O>, pub(crate) key: K, } impl<'a, 'cx, O, K> PropOptions<'a, 'cx, O, K> where 'cx: 'a, O: Object, K: PropertyKey, { /// Returns the original object from which the property was accessed. pub fn this(&self) -> Handle<'cx, O> { self.this } /// Updates the property key. /// /// This method is useful for chaining multiple property assignments: /// /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// let obj = cx.empty_object() /// .prop(&mut cx, "x") /// .set(1)? /// .prop("y") /// .set(2)? /// .prop("color") /// .set("blue")? /// .this(); /// # Ok(obj) /// # } /// ``` pub fn prop(&mut self, key: K) -> &mut Self { self.key = key; self } /// Gets the property from the object and attempts to convert it to a Rust value. /// /// May throw an exception either during accessing the property or converting the /// result type. pub fn get>(&mut self) -> NeonResult { let v = self.this.get_value(self.cx, self.key)?; R::from_js(self.cx, v) } /// Sets the property on the object to a value converted from Rust. /// /// May throw an exception either during converting the value or setting the property. pub fn set>(&mut self, v: V) -> NeonResult<&mut Self> { let v = v.try_into_js(self.cx)?; self.this.set(self.cx, self.key, v)?; Ok(self) } /// Sets the property on the object to a value computed from a closure. /// /// May throw an exception either during converting the value or setting the property. pub fn set_with(&mut self, f: F) -> NeonResult<&mut Self> where R: TryIntoJs<'cx>, F: FnOnce(&mut Cx<'cx>) -> R, { let v = f(self.cx).try_into_js(self.cx)?; self.this.set(self.cx, self.key, v)?; Ok(self) } /// Gets the property from the object as a method and binds `this` to the object. /// /// May throw an exception when accessing the property. /// /// Defers checking that the method is callable until call time. pub fn bind(&'a mut self) -> NeonResult> { let callee: Handle = self.this.get(self.cx, self.key)?; let this = Some(self.this.upcast()); Ok(BindOptions { cx: self.cx, callee, this, args: smallvec![], }) } } /// The trait of all object types. pub trait Object: Value { /// Create a [`PropOptions`] for accessing a property. /// /// # Safety /// /// Because `cx` is a mutable reference, Neon guarantees it /// is the context with the shortest possible lifetime, so /// replacing the lifetime `'self` with `'cx` cannot extend /// the lifetime of the property beyond the lifetime of the /// object. fn prop<'a, 'cx: 'a, K: PropertyKey>( &self, cx: &'a mut Cx<'cx>, key: K, ) -> PropOptions<'a, 'cx, Self, K> { let this: Handle<'_, Self> = Handle::new_internal(unsafe { ValueInternal::from_local(cx.env(), self.to_local()) }); PropOptions { cx, this, key } } /// Gets a property from the object as a method and binds `this` to the object. /// /// May throw an exception either from accessing the property. /// /// Defers checking that the method is callable until call time. fn method<'a, 'cx: 'a, K: PropertyKey>( &self, cx: &'a mut Cx<'cx>, key: K, ) -> NeonResult> { let callee: Handle = self.prop(cx, key).get()?; let this = Some(self.as_value(cx)); Ok(BindOptions { cx, callee, this, args: smallvec![], }) } #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get_opt<'a, V: Value, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, key: K, ) -> NeonResult>> { let v = self.get_value(cx, key)?; if v.is_a::(cx) { return Ok(None); } v.downcast_or_throw(cx).map(Some) } #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get_value<'a, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, key: K, ) -> NeonResult> { build(cx.env(), |out| unsafe { key.get_from(cx, out, self.to_local()) }) } #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get<'a, V: Value, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, key: K, ) -> NeonResult> { self.get_value(cx, key)?.downcast_or_throw(cx) } #[cfg(feature = "napi-6")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] fn get_own_property_names<'a, C: Context<'a>>(&self, cx: &mut C) -> JsResult<'a, JsArray> { let env = cx.env(); build(cx.env(), |out| unsafe { sys::object::get_own_property_names(out, env.to_raw(), self.to_local()) }) } #[cfg(feature = "napi-8")] fn freeze<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> { let env = cx.env().to_raw(); let obj = self.to_local(); unsafe { match sys::object::freeze(env, obj) { Ok(()) => Ok(self), Err(sys::Status::PendingException) => Err(Throw::new()), _ => cx.throw_type_error("object cannot be frozen"), } } } #[cfg(feature = "napi-8")] fn seal<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult<&Self> { let env = cx.env().to_raw(); let obj = self.to_local(); unsafe { match sys::object::seal(env, obj) { Ok(()) => Ok(self), Err(sys::Status::PendingException) => Err(Throw::new()), _ => cx.throw_type_error("object cannot be sealed"), } } } #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn set<'a, C: Context<'a>, K: PropertyKey, W: Value>( &self, cx: &mut C, key: K, val: Handle, ) -> NeonResult { let mut result = false; unsafe { if key.set_from(cx, &mut result, self.to_local(), val.to_local()) { Ok(result) } else { Err(Throw::new()) } } } fn root<'a, C: Context<'a>>(&self, cx: &mut C) -> Root { Root::new(cx, self) } #[deprecated(since = "TBD", note = "use `Object::method()` instead")] fn call_method_with<'a, C, K>(&self, cx: &mut C, method: K) -> NeonResult> where C: Context<'a>, K: PropertyKey, { let mut options = self.get::(cx, method)?.call_with(cx); options.this(JsValue::new_internal(self.to_local())); Ok(options) } } ================================================ FILE: crates/neon/src/object/wrap.rs ================================================ use std::{any::Any, error, ffi::c_void, fmt, mem::MaybeUninit, ptr}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::Handle, object::Object, result::{JsResult, NeonResult, ResultExt, Throw}, sys, types::{extract::TryIntoJs, Finalize, Value}, }; type BoxAny = Box; #[derive(Debug)] pub struct WrapError(WrapErrorType); impl WrapError { fn object_expected() -> Self { Self(WrapErrorType::ObjectExpected) } fn already_wrapped() -> Self { Self(WrapErrorType::AlreadyWrapped) } #[cfg(feature = "napi-8")] fn not_wrapped() -> Self { Self(WrapErrorType::NotWrapped) } fn wrong_type(expected: &'static str) -> Self { Self(WrapErrorType::WrongType(expected)) } #[cfg(feature = "napi-8")] fn foreign_type() -> Self { Self(WrapErrorType::ForeignType) } } impl fmt::Display for WrapError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } } impl error::Error for WrapError {} impl crate::types::extract::private::Sealed for WrapError {} impl<'cx> TryIntoJs<'cx> for WrapError { type Value = crate::types::JsError; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { match self.0 { WrapErrorType::ObjectExpected => cx.type_error("object expected"), _ => cx.type_error(self.to_string()), } } } impl ResultExt for Result { fn or_throw<'cx, C>(self, cx: &mut C) -> NeonResult where C: Context<'cx>, { match self { Ok(v) => Ok(v), Err(WrapError(WrapErrorType::ObjectExpected)) => cx.throw_type_error("object expected"), Err(err) => cx.throw_type_error(err.to_string()), } } } #[derive(Debug)] enum WrapErrorType { ObjectExpected, AlreadyWrapped, #[cfg(feature = "napi-8")] NotWrapped, WrongType(&'static str), #[cfg(feature = "napi-8")] ForeignType, } fn ref_cell_target_type_name(s: &str) -> Option { if let Some(start) = s.find('<') { let s = &s[start + 1..]; if let Some(end) = s.find('>') { return Some(s[0..end].to_string()); } } None } impl fmt::Display for WrapErrorType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::ObjectExpected => write!(f, "object expected"), Self::AlreadyWrapped => write!(f, "non-class instance expected"), #[cfg(feature = "napi-8")] Self::NotWrapped => write!(f, "class instance expected"), Self::WrongType(expected) => { let target_type_name = ref_cell_target_type_name(expected).unwrap_or(expected.to_string()); write!(f, "expected instance of {}", target_type_name) } #[cfg(feature = "napi-8")] Self::ForeignType => write!(f, "Neon object expected"), } } } pub fn wrap(cx: &mut Cx, o: Handle, v: T) -> NeonResult> where T: Finalize + 'static, V: Object, { let env = cx.env().to_raw(); let o = o.to_local(); let v = Box::into_raw(Box::new(Box::new(v) as BoxAny)); // # Safety // The `finalize` function will be called when the JavaScript object is garbage // collected. The `data` pointer is guaranteed to be the same pointer passed when // wrapping. unsafe extern "C" fn finalize(env: sys::Env, data: *mut c_void, _hint: *mut c_void) where T: Finalize + 'static, { let data = Box::from_raw(data.cast::()); let data = *data.downcast::().unwrap(); let env = Env::from(env); Cx::with_context(env, move |mut cx| data.finalize(&mut cx)); } // # Safety // The `env` value was obtained from a valid `Cx` and the `o` handle has // already been verified to be an object. unsafe { match sys::wrap( env, o, v.cast(), Some(finalize::), ptr::null_mut(), ptr::null_mut(), ) { Err(sys::Status::InvalidArg) => { // Wrap failed, we can safely free the value let _ = Box::from_raw(v); return Ok(Err(WrapError::already_wrapped())); } Err(sys::Status::PendingException) => { // Wrap failed, we can safely free the value let _ = Box::from_raw(v); return Err(Throw::new()); } // If an unexpected error occurs, we cannot safely free the value // because `finalize` may be called later. res => res.unwrap(), } #[cfg(feature = "napi-8")] match sys::type_tag_object(env, o, &*crate::MODULE_TAG) { Err(sys::Status::InvalidArg) => { sys::remove_wrap(env, o, ptr::null_mut()).unwrap(); // Unwrap succeeded, we can safely free the value let _ = Box::from_raw(v); return Ok(Err(WrapError::foreign_type())); } res => res.unwrap(), } } Ok(Ok(())) } pub fn unwrap<'cx, T, V>(cx: &mut Cx, o: Handle<'cx, V>) -> NeonResult> where T: Finalize + 'static, V: Value, { let env = cx.env().to_raw(); let o = o.to_local(); #[cfg(feature = "napi-8")] // # Safety // The `env` value was obtained from a valid `Cx`. unsafe { let mut is_tagged = false; match sys::check_object_type_tag(env, o, &*crate::MODULE_TAG, &mut is_tagged) { Err(sys::Status::PendingException) => return Err(Throw::new()), Err(sys::Status::ObjectExpected) => return Ok(Err(WrapError::object_expected())), res => res.unwrap(), } if !is_tagged { return Ok(Err(WrapError::not_wrapped())); } } // # Safety // The `env` value was obtained from a valid `Cx`. let data = unsafe { let mut data = MaybeUninit::<*mut BoxAny>::uninit(); match sys::unwrap(env, o, data.as_mut_ptr().cast()) { Err(sys::Status::PendingException) => return Err(Throw::new()), Err(sys::Status::ObjectExpected) => return Ok(Err(WrapError::object_expected())), res => res.unwrap(), } // # Safety // Since `unwrap` was successful, we know this is a valid pointer. On Node-API // versions 8 and higher, we are also guaranteed it is a `BoxAny`. &*data.assume_init() }; match data.downcast_ref() { Some(result) => Ok(Ok(result)), None => Ok(Err(WrapError::wrong_type(std::any::type_name::()))), } } ================================================ FILE: crates/neon/src/prelude.rs ================================================ //! Convenience module for the most common Neon imports. #[doc(no_inline)] pub use crate::{ context::{CallKind, Context, Cx, FunctionContext, ModuleContext}, handle::{Handle, Root}, object::Object, result::{JsResult, NeonResult, ResultExt as NeonResultExt}, types::{ boxed::{Finalize, JsBox}, JsArray, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBoolean, JsBuffer, JsError, JsFloat32Array, JsFloat64Array, JsFunction, JsInt16Array, JsInt32Array, JsInt8Array, JsNull, JsNumber, JsObject, JsPromise, JsString, JsTypedArray, JsUint16Array, JsUint32Array, JsUint8Array, JsUndefined, JsValue, Value, }, }; #[doc(hidden)] pub use crate::context::{ComputeContext, ExecuteContext, TaskContext}; #[cfg(feature = "napi-4")] #[doc(no_inline)] pub use crate::event::{Channel, SendError}; #[cfg(feature = "napi-4")] #[doc(no_inline)] #[allow(deprecated)] pub use crate::event::{EventQueue, EventQueueError}; ================================================ FILE: crates/neon/src/reflect.rs ================================================ //! Exposes JavaScript's reflection API to Rust. use crate::{ context::Context, handle::Handle, result::JsResult, types::{build, private::ValueInternal, JsString, JsValue}, }; pub fn eval<'a, 'b, C: Context<'a>>( cx: &mut C, script: Handle<'b, JsString>, ) -> JsResult<'a, JsValue> { let env = cx.env().to_raw(); build(cx.env(), |out| unsafe { crate::sys::string::run_script(out, env, script.to_local()) }) } ================================================ FILE: crates/neon/src/result/mod.rs ================================================ //! Represents JavaScript exceptions as a Rust [`Result`](std::result) type. //! //! Most interactions with the JavaScript engine can throw a JavaScript exception. Neon APIs //! that can throw an exception are called _throwing APIs_ and return the type //! [`NeonResult`] (or its shorthand [`JsResult`]). //! //! When a throwing API triggers a JavaScript exception, it returns an [Err] //! result. This indicates that the thread associated with the [`Context`] //! is now throwing, and allows Rust code to perform any cleanup. See the //! [`neon::context`](crate::context) module documentation for more about //! [contexts and exceptions](crate::context#throwing-exceptions). //! //! Typically, Neon code can manage JavaScript exceptions correctly and conveniently by //! using Rust's [question mark (`?`)][question-mark] operator. This ensures that Rust code //! "short-circuits" when an exception is thrown and returns back to JavaScript without //! calling any throwing APIs. //! //! ## Example //! //! Neon functions typically use [`JsResult`] for their return type. This //! example defines a function that extracts a property called `"message"` from an object, //! throwing an exception if the argument is not of the right type or extracting the property //! fails: //! //! ``` //! # use neon::prelude::*; //! fn get_message(mut cx: FunctionContext) -> JsResult { //! let obj: Handle = cx.argument(0)?; //! obj.prop(&mut cx, "message").get() //! } //! ``` //! //! [question-mark]: https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/the-question-mark-operator-for-easier-error-handling.html use std::{ fmt::{Display, Formatter, Result as FmtResult}, marker::PhantomData, }; use crate::{context::Context, handle::Handle, types::Value}; /// A [unit type][unit] indicating that the JavaScript thread is throwing an exception. /// /// `Throw` deliberately does not implement [`std::error::Error`]. It's /// not recommended to chain JavaScript exceptions with other kinds of Rust errors, /// since throwing means that the JavaScript thread is unavailable until the exception /// is handled. /// /// [unit]: https://doc.rust-lang.org/book/ch05-01-defining-structs.html#unit-like-structs-without-any-fields #[derive(Debug)] pub struct Throw(PhantomData<*mut ()>); // *mut is !Send + !Sync, making it harder to accidentally store impl Throw { #[cfg(feature = "sys")] /// Creates a `Throw` struct representing a JavaScript exception /// state. /// /// # Safety /// /// `Throw` should *only* be constructed when the JavaScript VM is in a /// throwing state. I.e., when [`Status::PendingException`](crate::sys::bindings::Status::PendingException) /// is returned. pub unsafe fn new() -> Self { Self(PhantomData) } #[cfg(not(feature = "sys"))] pub(crate) unsafe fn new() -> Self { Self(PhantomData) } } impl Display for Throw { fn fmt(&self, fmt: &mut Formatter) -> FmtResult { fmt.write_str("JavaScript Error") } } /// The result type for throwing APIs. pub type NeonResult = Result; /// Shorthand for a [`NeonResult`] that produces JavaScript values. pub type JsResult<'b, T> = NeonResult>; /// Extension trait for converting Rust [`Result`] values /// into [`NeonResult`] values by throwing JavaScript exceptions. pub trait ResultExt { fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult; } impl<'a, 'b, T, E> ResultExt> for Result, Handle<'b, E>> where T: Value, E: Value, { fn or_throw<'cx, C: Context<'cx>>(self, cx: &mut C) -> JsResult<'a, T> { self.or_else(|err| cx.throw(err)) } } ================================================ FILE: crates/neon/src/sys/array.rs ================================================ //! Facilities for working with Array `napi_value`s. use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(out: &mut Local, env: Env, length: usize) { napi::create_array_with_length(env, length, out as *mut _).unwrap(); } /// Gets the length of a `napi_value` containing a JavaScript Array. /// /// # Panics /// This function panics if `array` is not an Array, or if a previous n-api call caused a pending /// exception. pub unsafe fn len(env: Env, array: Local) -> u32 { let mut len = 0; napi::get_array_length(env, array, &mut len as *mut _).unwrap(); len } ================================================ FILE: crates/neon/src/sys/arraybuffer.rs ================================================ #[cfg(feature = "external-buffers")] use std::os::raw::c_void; use std::{mem::MaybeUninit, ptr::null_mut, slice}; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(env: Env, len: usize) -> Result { let mut buf = MaybeUninit::uninit(); let status = napi::create_arraybuffer(env, len, null_mut(), buf.as_mut_ptr()); match status { Err(err @ napi::Status::PendingException) => return Err(err), status => status.unwrap(), }; Ok(buf.assume_init()) } #[cfg(feature = "external-buffers")] pub unsafe fn new_external(env: Env, data: T) -> Local where T: AsMut<[u8]> + Send, { // Safety: Boxing could move the data; must box before grabbing a raw pointer let mut data = Box::new(data); let buf = data.as_mut().as_mut(); let length = buf.len(); let mut result = MaybeUninit::uninit(); napi::create_external_arraybuffer( env, buf.as_mut_ptr() as *mut _, length, Some(drop_external::), Box::into_raw(data) as *mut _, result.as_mut_ptr(), ) .unwrap(); result.assume_init() } #[cfg(feature = "external-buffers")] unsafe extern "C" fn drop_external(_env: Env, _data: *mut c_void, hint: *mut c_void) { drop(Box::::from_raw(hint as *mut _)); } /// # Safety /// * Caller must ensure `env` and `buf` are valid /// * The lifetime `'a` does not exceed the lifetime of `Env` or `buf` pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { let mut data = MaybeUninit::uninit(); let mut size = 0usize; napi::get_arraybuffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _).unwrap(); if size == 0 { return &mut []; } slice::from_raw_parts_mut(data.assume_init().cast(), size) } /// # Safety /// * Caller must ensure `env` and `buf` are valid pub unsafe fn size(env: Env, buf: Local) -> usize { let mut data = MaybeUninit::uninit(); let mut size = 0usize; napi::get_arraybuffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _).unwrap(); size } ================================================ FILE: crates/neon/src/sys/async_work.rs ================================================ //! Rust wrappers for Node-API simple asynchronous operations //! //! Unlike `napi_async_work` which threads a single mutable pointer to a data //! struct to both the `execute` and `complete` callbacks, the wrapper follows //! a more idiomatic Rust ownership pattern by passing the output of `execute` //! into the input of `complete`. //! //! See: [Async operations in Node-API](https://nodejs.org/api/n-api.html#n_api_simple_asynchronous_operations) use std::{ ffi::c_void, mem, panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, ptr, thread, }; use super::{ bindings as napi, debug_send_wrapper::DebugSendWrapper, no_panic::FailureBoundary, raw::Env, }; const BOUNDARY: FailureBoundary = FailureBoundary { both: "A panic and exception occurred while executing a `neon::event::TaskBuilder` task", exception: "An exception occurred while executing a `neon::event::TaskBuilder` task", panic: "A panic occurred while executing a `neon::event::TaskBuilder` task", }; type Execute = fn(input: I) -> O; type Complete = fn(env: Env, output: thread::Result, data: D); /// Schedule work to execute on the libuv thread pool /// /// # Safety /// * `env` must be a valid `napi_env` for the current thread /// * The `thread::Result::Err` must only be used for resuming unwind if /// `execute` is not unwind safe pub unsafe fn schedule( env: Env, input: I, execute: Execute, complete: Complete, data: D, ) where I: Send + 'static, O: Send + 'static, D: 'static, { let mut data = Box::new(Data { state: State::Input(input), execute, complete, data: DebugSendWrapper::new(data), // Work is initialized as a null pointer, but set by `create_async_work` // `data` must not be used until this value has been set. work: ptr::null_mut(), }); // Store a pointer to `work` before ownership is transferred to `Box::into_raw` let work = &mut data.work as *mut _; // Create the `async_work` napi::create_async_work( env, ptr::null_mut(), super::string(env, "neon_async_work"), Some(call_execute::), Some(call_complete::), Box::into_raw(data).cast(), work, ) .unwrap(); // Queue the work match napi::queue_async_work(env, *work) { Ok(()) => {} status => { // If queueing failed, delete the work to prevent a leak let _ = napi::delete_async_work(env, *work); status.unwrap() } } } /// A pointer to data is passed to the `execute` and `complete` callbacks struct Data { state: State, execute: Execute, complete: Complete, data: DebugSendWrapper, work: napi::AsyncWork, } /// State of the task that is transitioned by `execute` and `complete` enum State { /// Initial data input passed to `execute` Input(I), /// Transient state while `execute` is running Executing, /// Return data of `execute` passed to `complete` Output(thread::Result), } impl State { /// Return the input if `State::Input`, replacing with `State::Executing` fn take_execute_input(&mut self) -> Option { match mem::replace(self, Self::Executing) { Self::Input(input) => Some(input), _ => None, } } /// Return the output if `State::Output`, replacing with `State::Executing` fn into_output(self) -> Option> { match self { Self::Output(output) => Some(output), _ => None, } } } /// Callback executed on the libuv thread pool /// /// # Safety /// * `Env` should not be used because it could attempt to call JavaScript /// * `data` is expected to be a pointer to `Data` unsafe extern "C" fn call_execute(_: Env, data: *mut c_void) { let data = &mut *data.cast::>(); // This is unwind safe because unwinding will resume on the other side let output = catch_unwind(AssertUnwindSafe(|| { // `unwrap` is ok because `call_execute` should be called exactly once // after initialization let input = data.state.take_execute_input().unwrap(); (data.execute)(input) })); data.state = State::Output(output); } /// Callback executed on the JavaScript main thread /// /// # Safety /// * `data` is expected to be a pointer to `Data` unsafe extern "C" fn call_complete(env: Env, status: napi::Status, data: *mut c_void) { let Data { state, complete, data, work, .. } = *Box::>::from_raw(data.cast()); debug_assert_eq!(napi::delete_async_work(env, work), Ok(())); BOUNDARY.catch_failure(env, None, move |env| { // `unwrap` is okay because `call_complete` should be called exactly once // if and only if `call_execute` has completed successfully let output = state.into_output().unwrap(); // The event looped has stopped if we do not have an Env let env = if let Some(env) = env { env } else { // Resume panicking if necessary if let Err(panic) = output { resume_unwind(panic); } return ptr::null_mut(); }; match status { napi::Status::Ok => complete(env, output, data.take()), napi::Status::Cancelled => {} _ => assert_eq!(status, napi::Status::Ok), } ptr::null_mut() }); } ================================================ FILE: crates/neon/src/sys/bindings/functions.rs ================================================ #![allow(clippy::too_many_arguments)] mod napi1 { use super::super::types::*; use std::os::raw::{c_char, c_void}; generate!( #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] extern "C" { fn get_undefined(env: Env, result: *mut Value) -> Status; fn get_null(env: Env, result: *mut Value) -> Status; fn get_global(env: Env, result: *mut Value) -> Status; fn get_boolean(env: Env, value: bool, result: *mut Value) -> Status; fn create_double(env: Env, value: f64, result: *mut Value) -> Status; fn create_object(env: Env, result: *mut Value) -> Status; fn get_value_bool(env: Env, value: Value, result: *mut bool) -> Status; fn get_value_double(env: Env, value: Value, result: *mut f64) -> Status; fn get_value_uint32(env: Env, value: Value, result: *mut u32) -> Status; fn get_value_int32(env: Env, value: Value, result: *mut i32) -> Status; fn create_array_with_length(env: Env, length: usize, result: *mut Value) -> Status; fn get_array_length(env: Env, value: Value, result: *mut u32) -> Status; fn get_new_target(env: Env, cbinfo: CallbackInfo, result: *mut Value) -> Status; fn coerce_to_string(env: Env, value: Value, result: *mut Value) -> Status; fn throw(env: Env, error: Value) -> Status; fn create_error(env: Env, code: Value, msg: Value, result: *mut Value) -> Status; fn get_and_clear_last_exception(env: Env, result: *mut Value) -> Status; fn is_exception_pending(env: Env, result: *mut bool) -> Status; fn get_value_external(env: Env, value: Value, result: *mut *mut c_void) -> Status; fn typeof_value(env: Env, value: Value, result: *mut ValueType) -> Status; fn close_escapable_handle_scope(env: Env, scope: EscapableHandleScope) -> Status; fn open_escapable_handle_scope(env: Env, result: *mut EscapableHandleScope) -> Status; fn open_handle_scope(env: Env, result: *mut HandleScope) -> Status; fn close_handle_scope(env: Env, scope: HandleScope) -> Status; fn is_arraybuffer(env: Env, value: Value, result: *mut bool) -> Status; fn is_typedarray(env: Env, value: Value, result: *mut bool) -> Status; fn is_buffer(env: Env, value: Value, result: *mut bool) -> Status; fn is_error(env: Env, value: Value, result: *mut bool) -> Status; fn is_array(env: Env, value: Value, result: *mut bool) -> Status; fn is_promise(env: Env, value: Value, result: *mut bool) -> Status; fn get_value_string_utf8( env: Env, value: Value, buf: *mut c_char, bufsize: usize, result: *mut usize, ) -> Status; // The `buf` argument is defined as a `char16_t` which _should_ be a `u16` on most // platforms. When generating bindings with `rust-bindgen` it unconditionally defines // it as `u16` as well. fn get_value_string_utf16( env: Env, value: Value, buf: *mut u16, bufsize: usize, result: *mut usize, ) -> Status; fn create_type_error(env: Env, code: Value, msg: Value, result: *mut Value) -> Status; fn create_range_error(env: Env, code: Value, msg: Value, result: *mut Value) -> Status; fn create_string_utf8( env: Env, str: *const c_char, length: usize, result: *mut Value, ) -> Status; fn create_arraybuffer( env: Env, byte_length: usize, data: *mut *mut c_void, result: *mut Value, ) -> Status; fn get_arraybuffer_info( env: Env, arraybuffer: Value, data: *mut *mut c_void, byte_length: *mut usize, ) -> Status; fn create_typedarray( env: Env, type_: TypedArrayType, length: usize, arraybuffer: Value, byte_offset: usize, result: *mut Value, ) -> Status; fn get_typedarray_info( env: Env, typedarray: Value, typ: *mut TypedArrayType, length: *mut usize, data: *mut *mut c_void, buf: *mut Value, offset: *mut usize, ) -> Status; fn create_buffer( env: Env, length: usize, data: *mut *mut c_void, result: *mut Value, ) -> Status; fn get_buffer_info( env: Env, value: Value, data: *mut *mut c_void, length: *mut usize, ) -> Status; fn get_cb_info( env: Env, cbinfo: CallbackInfo, argc: *mut usize, argv: *mut Value, this_arg: *mut Value, data: *mut *mut c_void, ) -> Status; fn create_external( env: Env, data: *mut c_void, finalize_cb: Finalize, finalize_hint: *mut c_void, result: *mut Value, ) -> Status; fn new_instance( env: Env, constructor: Value, argc: usize, argv: *const Value, result: *mut Value, ) -> Status; fn call_function( env: Env, recv: Value, func: Value, argc: usize, argv: *const Value, result: *mut Value, ) -> Status; fn create_function( env: Env, utf8name: *const c_char, length: usize, cb: Callback, data: *mut c_void, result: *mut Value, ) -> Status; fn set_property(env: Env, object: Value, key: Value, value: Value) -> Status; fn get_property(env: Env, object: Value, key: Value, result: *mut Value) -> Status; fn set_element(env: Env, object: Value, index: u32, value: Value) -> Status; fn get_element(env: Env, object: Value, index: u32, result: *mut Value) -> Status; fn escape_handle( env: Env, scope: EscapableHandleScope, escapee: Value, result: *mut Value, ) -> Status; fn create_reference( env: Env, value: Value, initial_ref_count: u32, result: *mut Ref, ) -> Status; fn reference_ref(env: Env, reference: Ref, result: *mut u32) -> Status; fn reference_unref(env: Env, reference: Ref, result: *mut u32) -> Status; fn delete_reference(env: Env, reference: Ref) -> Status; fn get_reference_value(env: Env, reference: Ref, result: *mut Value) -> Status; fn strict_equals(env: Env, lhs: Value, rhs: Value, result: *mut bool) -> Status; #[cfg(any(feature = "sys", feature = "external-buffers"))] fn create_external_arraybuffer( env: Env, data: *mut c_void, length: usize, finalize_cb: Finalize, finalize_hint: *mut c_void, result: *mut Value, ) -> Status; #[cfg(any(feature = "sys", feature = "external-buffers"))] fn create_external_buffer( env: Env, length: usize, data: *mut c_void, finalize_cb: Finalize, finalize_hint: *mut c_void, result: *mut Value, ) -> Status; fn run_script(env: Env, script: Value, result: *mut Value) -> Status; fn create_async_work( env: Env, async_resource: Value, async_resource_name: Value, execute: AsyncExecuteCallback, complete: AsyncCompleteCallback, data: *mut c_void, result: *mut AsyncWork, ) -> Status; fn delete_async_work(env: Env, work: AsyncWork) -> Status; fn queue_async_work(env: Env, work: AsyncWork) -> Status; fn create_promise(env: Env, deferred: *mut Deferred, promise: *mut Value) -> Status; fn resolve_deferred(env: Env, deferred: Deferred, resolution: Value) -> Status; fn reject_deferred(env: Env, deferred: Deferred, rejection: Value) -> Status; fn fatal_error( location: *const c_char, location_len: usize, message: *const c_char, message_len: usize, ); fn wrap( env: Env, js_object: Value, native_object: *mut c_void, finalize_cb: Finalize, finalize_hint: *mut c_void, result: *mut Ref, ) -> Status; fn unwrap(env: Env, js_object: Value, result: *mut *mut c_void) -> Status; #[cfg(feature = "napi-8")] fn remove_wrap(env: Env, js_object: Value, result: *mut *mut c_void) -> Status; } ); } #[cfg(feature = "napi-4")] mod napi4 { use super::super::types::*; use std::os::raw::c_void; generate!( #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] extern "C" { fn create_threadsafe_function( env: Env, func: Value, async_resource: Value, async_resource_name: Value, max_queue_size: usize, initial_thread_count: usize, thread_finalize_data: *mut c_void, thread_finalize_cb: Finalize, context: *mut c_void, call_js_cb: ThreadsafeFunctionCallJs, result: *mut ThreadsafeFunction, ) -> Status; fn call_threadsafe_function( func: ThreadsafeFunction, data: *mut c_void, is_blocking: ThreadsafeFunctionCallMode, ) -> Status; fn release_threadsafe_function( func: ThreadsafeFunction, mode: ThreadsafeFunctionReleaseMode, ) -> Status; fn ref_threadsafe_function(env: Env, func: ThreadsafeFunction) -> Status; fn unref_threadsafe_function(env: Env, func: ThreadsafeFunction) -> Status; } ); } #[cfg(feature = "napi-5")] mod napi5 { use super::super::types::*; use std::ffi::c_void; generate!( #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] extern "C" { fn create_date(env: Env, value: f64, result: *mut Value) -> Status; fn get_date_value(env: Env, value: Value, result: *mut f64) -> Status; fn is_date(env: Env, value: Value, result: *mut bool) -> Status; fn add_finalizer( env: Env, js_object: Value, native_object: *mut c_void, finalize_cb: Finalize, finalize_hint: *mut c_void, result: Ref, ) -> Status; } ); } #[cfg(feature = "napi-6")] mod napi6 { use super::super::types::*; use std::os::raw::c_void; generate!( #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] extern "C" { fn get_all_property_names( env: Env, object: Value, key_mode: KeyCollectionMode, key_filter: KeyFilter, key_conversion: KeyConversion, result: *mut Value, ) -> Status; fn set_instance_data( env: Env, data: *mut c_void, finalize_cb: Finalize, finalize_hint: *mut c_void, ) -> Status; fn get_instance_data(env: Env, data: *mut *mut c_void) -> Status; fn create_bigint_int64(env: Env, value: i64, result: *mut Value) -> Status; fn create_bigint_uint64(env: Env, value: u64, result: *mut Value) -> Status; fn create_bigint_words( env: Env, sign_bit: i32, word_count: usize, words: *const u64, result: *mut Value, ) -> Status; fn get_value_bigint_int64( env: Env, value: Value, result: *mut i64, lossless: *mut bool, ) -> Status; fn get_value_bigint_uint64( env: Env, value: Value, result: *mut u64, lossless: *mut bool, ) -> Status; fn get_value_bigint_words( env: Env, value: Value, sign_bit: *mut i64, word_count: *mut usize, words: *mut u64, ) -> Status; } ); } #[cfg(feature = "napi-8")] mod napi8 { use super::super::types::*; generate!( #[cfg_attr(docsrs, doc(cfg(feature = "napi-8")))] extern "C" { fn object_freeze(env: Env, object: Value) -> Status; fn object_seal(env: Env, object: Value) -> Status; fn type_tag_object(env: Env, object: Value, tag: *const TypeTag) -> Status; fn check_object_type_tag( env: Env, object: Value, tag: *const TypeTag, result: *mut bool, ) -> Status; } ); } pub use napi1::*; #[cfg(feature = "napi-4")] pub use napi4::*; #[cfg(feature = "napi-5")] pub use napi5::*; #[cfg(feature = "napi-6")] pub use napi6::*; #[cfg(feature = "napi-8")] pub use napi8::*; use super::{Env, Status}; // This symbol is loaded separately because it is a prerequisite unsafe fn get_version(host: &libloading::Library, env: Env) -> Result { let get_version = host.get:: Status>(b"napi_get_version")?; let mut version = 0; assert_eq!(get_version(env, &mut version as *mut _), Status::Ok,); Ok(version) } pub(crate) unsafe fn load(env: Env) -> Result<(), libloading::Error> { #[cfg(not(windows))] let host = libloading::os::unix::Library::this().into(); #[cfg(windows)] let host = libloading::os::windows::Library::this()?.into(); // This never fail since `get_version` is in N-API Version 1 and the module will fail // with `Error: Module did not self-register` if N-API does not exist. let actual_version = get_version(&host, env).expect("Failed to find N-API version"); let expected_version = match () { _ if cfg!(feature = "napi-8") => 8, _ if cfg!(feature = "napi-7") => 7, _ if cfg!(feature = "napi-6") => 6, _ if cfg!(feature = "napi-5") => 5, _ if cfg!(feature = "napi-4") => 4, _ if cfg!(feature = "napi-3") => 3, _ if cfg!(feature = "napi-2") => 2, _ => 1, }; if actual_version < expected_version { eprintln!("Minimum required Node-API version {expected_version}, found {actual_version}.\n\nSee the Node-API support matrix for more details: https://nodejs.org/api/n-api.html#node-api-version-matrix"); } napi1::load(&host); #[cfg(feature = "napi-4")] napi4::load(&host); #[cfg(feature = "napi-5")] napi5::load(&host); #[cfg(feature = "napi-6")] napi6::load(&host); #[cfg(feature = "napi-8")] napi8::load(&host); Ok(()) } ================================================ FILE: crates/neon/src/sys/bindings/mod.rs ================================================ //! # FFI bindings to Node-API symbols //! //! Rust types generated from [Node-API](https://nodejs.org/api/n-api.html). // These types are manually copied from bindings generated from `bindgen`. To // update, use the following approach: // // * Run a debug build of Neon at least once to install `nodejs-sys` // * Open the generated bindings at `target/debug/build/nodejs-sys-*/out/bindings.rs` // * Copy the types needed into `types.rs` and `functions.rs` // * Modify to match Rust naming conventions: // - Remove `napi_` prefixes // - Use `PascalCase` for types // - Rename types that match a reserved word /// Constructs the name of a N-API symbol as a string from a function identifier /// E.g., `get_undefined` becomes `"napi_get_undefined"` macro_rules! napi_name { // Explicitly replace identifiers that have been renamed from the N-API // symbol because they would match a reserved word. (typeof_value) => { "napi_typeof" }; // Default case: Stringify the identifier and prefix with `napi_` ($name:ident) => { concat!("napi_", stringify!($name)) }; } /// Generate dynamic bindings to N-API symbols from definitions in an /// block `extern "C"`. /// /// * A single global mutable struct holds references to the N-API functions /// * The global `Napi` struct is initialized with stubs that panic if called /// * A `load` function is generated that loads the N-API symbols from the /// host process and replaces the global struct with real implementations /// * `load` should be called exactly once before using any N-API functions /// * Wrapper functions are generated to delegate to fields in the `Napi` struct /// /// Sample input: /// /// ```ignore /// extern "C" { /// fn get_undefined(env: Env, result: *mut Value) -> Status; /// /* Additional functions may be included */ /// } /// ``` /// /// Generated output: /// /// ```ignore /// // Each field is a pointer to a N-API function /// struct Napi { /// get_undefined: unsafe extern "C" fn(env: Env, result: *mut Value) -> Status, /// /* ... repeat for each N-API function */ /// } /// /// // Defines a panic function that is called if symbols have not been loaded /// #[inline(never)] /// fn panic_load() -> T { /// panic!("Must load N-API bindings") /// } /// /// // Mutable global instance of the Napi struct /// // Initialized with stubs of N-API methods that panic /// static mut NAPI: Napi = { /// // Stubs are defined in a block to prevent naming conflicts with wrappers /// unsafe extern "C" fn get_undefined(_: Env, _: *mut Value) -> Status { /// panic_load() /// } /// /* ... repeat for each N-API function */ /// /// Napi { /// get_undefined, /// /* ... repeat for each N-API function */ /// } /// }; /// /// // Load N-API symbols from the host process /// // # Safety: Must only be called once /// pub(super) unsafe fn load( /// host: &libloading::Library, /// actual_napi_version: u32, /// expected_napi_version: u32, /// ) -> Result<(), libloading::Error> { /// assert!( /// actual_napi_version >= expected_napi_version, /// "Minimum required N-API version {}, found {}.", /// expected_napi_version, /// actual_napi_version, /// ); /// /// NAPI = Napi { /// // Load each N-API symbol /// get_undefined: *host.get("napi_get_undefined".as_bytes())?, /// /* ... repeat for each N-API function */ /// }; /// /// Ok(()) /// } /// /// // Each N-API function has wrapper for easy calling. These calls are optimized /// // to a single pointer dereference. /// #[inline] /// pub(crate) unsafe fn get_undefined(env: Env, result: *mut Value) -> Status { /// (NAPI.get_undefined)(env, result) /// } /// ``` macro_rules! generate { (#[$extern_attr:meta] extern "C" { $($(#[$attr:meta])? fn $name:ident($($param:ident: $ptype:ty$(,)?)*)$( -> $rtype:ty)?;)+ }) => { struct Napi { $( $name: unsafe extern "C" fn( $($param: $ptype,)* )$( -> $rtype)*, )* } #[inline(never)] fn panic_load() -> T { panic!("Node-API symbol has not been loaded") } static mut NAPI: Napi = { $( unsafe extern "C" fn $name($(_: $ptype,)*)$( -> $rtype)* { panic_load() } )* Napi { $( $name, )* } }; pub(super) unsafe fn load(host: &libloading::Library) { let print_warn = |err| eprintln!("WARN: {}", err); NAPI = Napi { $( $name: match host.get(napi_name!($name).as_bytes()) { Ok(f) => *f, // Node compatible runtimes may not have full coverage of Node-API // (e.g., bun). Instead of failing to start, warn on start and // panic when the API is called. // https://github.com/Jarred-Sumner/bun/issues/158 Err(err) => { print_warn(err); NAPI.$name }, }, )* }; } $( #[$extern_attr] $(#[$attr])? #[inline] #[doc = concat!( "[`", napi_name!($name), "`](https://nodejs.org/api/n-api.html#", napi_name!($name), ")", )] pub unsafe fn $name($($param: $ptype,)*)$( -> ::core::result::Result<(), $rtype>)* { #[allow(unused)] let r = (NAPI.$name)($($param,)*); $(match r { <$rtype>::Ok => Ok(()), status => Err(status) })* } )* }; } pub use self::{functions::*, types::*}; mod functions; mod types; ================================================ FILE: crates/neon/src/sys/bindings/types.rs ================================================ use std::ffi::c_void; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct Env__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_env`](https://nodejs.org/api/n-api.html#napi_env) pub type Env = *mut Env__; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct Value__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_value`](https://nodejs.org/api/n-api.html#napi_value) pub type Value = *mut Value__; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct CallbackInfo__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_callback_info`](https://nodejs.org/api/n-api.html#napi_callback_info) pub type CallbackInfo = *mut CallbackInfo__; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct EscapableHandleScope__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_escapable_handle_scope`](https://nodejs.org/api/n-api.html#napi_escapable_handle_scope) pub type EscapableHandleScope = *mut EscapableHandleScope__; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct HandleScope__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_handle_scope`](https://nodejs.org/api/n-api.html#napi_handle_scope) pub type HandleScope = *mut HandleScope__; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct Ref__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_ref`](https://nodejs.org/api/n-api.html#napi_ref) pub type Ref = *mut Ref__; #[cfg(feature = "napi-4")] #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct ThreadsafeFunction__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] #[cfg(feature = "napi-4")] /// [`napi_threadsafe_function`](https://nodejs.org/api/n-api.html#napi_threadsafe_function) pub type ThreadsafeFunction = *mut ThreadsafeFunction__; #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_callback`](https://nodejs.org/api/n-api.html#napi_callback) pub type Callback = Option Value>; #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_finalize`](https://nodejs.org/api/n-api.html#napi_finalize) pub type Finalize = Option; #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] #[cfg(feature = "napi-4")] /// [`napi_threadsafe_function_call_js`](https://nodejs.org/api/n-api.html#napi_threadsafe_function_call_js) pub type ThreadsafeFunctionCallJs = Option< unsafe extern "C" fn(env: Env, js_callback: Value, context: *mut c_void, data: *mut c_void), >; #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_status`](https://nodejs.org/api/n-api.html#napi_status) pub enum Status { Ok = 0, InvalidArg = 1, ObjectExpected = 2, StringExpected = 3, NameExpected = 4, FunctionExpected = 5, NumberExpected = 6, BooleanExpected = 7, ArrayExpected = 8, GenericFailure = 9, PendingException = 10, Cancelled = 11, EscapeCalledTwice = 12, HandleScopeMismatch = 13, CallbackScopeMismatch = 14, QueueFull = 15, Closing = 16, BigintExpected = 17, DateExpected = 18, ArraybufferExpected = 19, DetachableArraybufferExpected = 20, WouldDeadlock = 21, } #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] /// [`napi_valuetype`](https://nodejs.org/api/n-api.html#napi_valuetype) pub enum ValueType { Undefined = 0, Null = 1, Boolean = 2, Number = 3, String = 4, Symbol = 5, Object = 6, Function = 7, External = 8, BigInt = 9, } #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_typedarray_type`](https://nodejs.org/api/n-api.html#napi_typedarray_type) pub enum TypedArrayType { I8 = 0, U8 = 1, U8Clamped = 2, I16 = 3, U16 = 4, I32 = 5, U32 = 6, F32 = 7, F64 = 8, I64 = 9, U64 = 10, } #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] #[cfg(feature = "napi-6")] /// [`napi_key_collection_mode`](https://nodejs.org/api/n-api.html#napi_key_collection_mode) pub enum KeyCollectionMode { IncludePrototypes = 0, OwnOnly = 1, } #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] #[cfg(feature = "napi-6")] /// [`napi_key_conversion`](https://nodejs.org/api/n-api.html#napi_key_conversion) pub enum KeyConversion { KeepNumbers = 0, NumbersToStrings = 1, } #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] #[cfg(feature = "napi-4")] #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] /// [`napi_threadsafe_function_call_mode`](https://nodejs.org/api/n-api.html#napi_threadsafe_function_call_mode) pub enum ThreadsafeFunctionCallMode { NonBlocking = 0, Blocking = 1, } #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] #[cfg(feature = "napi-4")] #[allow(dead_code)] #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] /// [`napi_threadsafe_function_release_mode`](https://nodejs.org/api/n-api.html#napi_threadsafe_function_release_mode) pub enum ThreadsafeFunctionReleaseMode { Release = 0, Abort = 1, } #[repr(transparent)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] #[cfg(feature = "napi-6")] /// [`napi_key_filter`](https://nodejs.org/api/n-api.html#napi_key_filter) pub struct KeyFilter(pub ::std::os::raw::c_uint); #[allow(dead_code)] #[cfg(feature = "napi-6")] impl KeyFilter { pub const ALL_PROPERTIES: KeyFilter = KeyFilter(0); pub const WRITABLE: KeyFilter = KeyFilter(1); pub const CONFIGURABLE: KeyFilter = KeyFilter(4); pub const SKIP_STRINGS: KeyFilter = KeyFilter(8); pub const SKIP_SYMBOLS: KeyFilter = KeyFilter(16); } #[cfg(feature = "napi-6")] impl std::ops::BitOr for KeyFilter { type Output = Self; #[inline] fn bitor(self, other: Self) -> Self { KeyFilter(self.0 | other.0) } } #[cfg(feature = "napi-6")] impl std::ops::BitOrAssign for KeyFilter { #[inline] fn bitor_assign(&mut self, rhs: KeyFilter) { self.0 |= rhs.0; } } #[cfg(feature = "napi-6")] impl std::ops::BitAnd for KeyFilter { type Output = Self; #[inline] fn bitand(self, other: Self) -> Self { KeyFilter(self.0 & other.0) } } #[cfg(feature = "napi-6")] impl std::ops::BitAndAssign for KeyFilter { #[inline] fn bitand_assign(&mut self, rhs: KeyFilter) { self.0 &= rhs.0; } } #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct AsyncWork__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_async_work`](https://nodejs.org/api/n-api.html#napi_async_work) pub type AsyncWork = *mut AsyncWork__; #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_async_execute_callback`](https://nodejs.org/api/n-api.html#napi_async_execute_callback) pub type AsyncExecuteCallback = Option; #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_async_complete_callback`](https://nodejs.org/api/n-api.html#napi_async_complete_callback) pub type AsyncCompleteCallback = Option; #[repr(C)] #[derive(Debug, Copy, Clone)] #[doc(hidden)] pub struct Deferred__ { _unused: [u8; 0], } #[cfg_attr(docsrs, doc(cfg(feature = "napi-1")))] /// [`napi_deferred`](https://nodejs.org/api/n-api.html#napi_deferred) pub type Deferred = *mut Deferred__; #[cfg_attr(docsrs, doc(cfg(feature = "napi-8")))] #[cfg(feature = "napi-8")] #[repr(C)] #[derive(Debug, Copy, Clone)] /// [`napi_type_tag`](https://nodejs.org/api/n-api.html#napi_type_tag) pub struct TypeTag { pub lower: u64, pub upper: u64, } ================================================ FILE: crates/neon/src/sys/buffer.rs ================================================ #[cfg(feature = "external-buffers")] use std::os::raw::c_void; use std::{mem::MaybeUninit, slice}; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(env: Env, len: usize) -> Result { let (buf, bytes) = uninitialized(env, len)?; std::ptr::write_bytes(bytes, 0, len); Ok(buf) } pub unsafe fn uninitialized(env: Env, len: usize) -> Result<(Local, *mut u8), napi::Status> { let mut buf = MaybeUninit::uninit(); let mut bytes = MaybeUninit::uninit(); let status = napi::create_buffer(env, len, bytes.as_mut_ptr(), buf.as_mut_ptr()); match status { Err(err @ napi::Status::PendingException) => return Err(err), status => status.unwrap(), }; Ok((buf.assume_init(), bytes.assume_init().cast())) } #[cfg(feature = "external-buffers")] pub unsafe fn new_external(env: Env, data: T) -> Local where T: AsMut<[u8]> + Send, { // Safety: Boxing could move the data; must box before grabbing a raw pointer let mut data = Box::new(data); let buf = data.as_mut().as_mut(); let length = buf.len(); let mut result = MaybeUninit::uninit(); napi::create_external_buffer( env, length, buf.as_mut_ptr() as *mut _, Some(drop_external::), Box::into_raw(data) as *mut _, result.as_mut_ptr(), ) .unwrap(); result.assume_init() } #[cfg(feature = "external-buffers")] unsafe extern "C" fn drop_external(_env: Env, _data: *mut c_void, hint: *mut c_void) { drop(Box::::from_raw(hint as *mut _)); } /// # Safety /// * Caller must ensure `env` and `buf` are valid /// * The lifetime `'a` does not exceed the lifetime of `Env` or `buf` pub unsafe fn as_mut_slice<'a>(env: Env, buf: Local) -> &'a mut [u8] { let mut data = MaybeUninit::uninit(); let mut size = 0usize; napi::get_buffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _).unwrap(); if size == 0 { return &mut []; } slice::from_raw_parts_mut(data.assume_init().cast(), size) } /// # Safety /// * Caller must ensure `env` and `buf` are valid pub unsafe fn size(env: Env, buf: Local) -> usize { let mut data = MaybeUninit::uninit(); let mut size = 0usize; napi::get_buffer_info(env, buf, data.as_mut_ptr(), &mut size as *mut _).unwrap(); size } ================================================ FILE: crates/neon/src/sys/call.rs ================================================ use std::{mem::MaybeUninit, ptr::null_mut}; use smallvec::SmallVec; use super::{ bindings as napi, raw::{Env, FunctionCallbackInfo, Local}, }; // Number of arguments to allocate on the stack. This should be large enough // to cover most use cases without being wasteful. // // * If the number is too large, too much space is allocated and then filled // with `undefined`. // * If the number is too small, getting arguments frequently takes two tries // and requires heap allocation. const ARGV_SIZE: usize = 4; #[repr(transparent)] /// List of JavaScript arguments to a function // `Arguments` is intended to be a small abstraction to hide the usage of // `SmallVec` allowing changes to `ARGV_SIZE` in a single location pub struct Arguments(SmallVec<[Local; ARGV_SIZE]>); impl Arguments { #[inline] /// Get an argument at a specific position pub fn get(&self, i: usize) -> Option { self.0.get(i).cloned() } } pub unsafe fn is_construct(env: Env, info: FunctionCallbackInfo) -> bool { let mut target: MaybeUninit = MaybeUninit::zeroed(); napi::get_new_target(env, info, target.as_mut_ptr()).unwrap(); // get_new_target is guaranteed to assign to target, so it's initialized. let target: Local = target.assume_init(); // By the get_new_target contract, target will either be NULL if the current // function was called without `new`, or a valid napi_value handle if the current // function was called with `new`. !target.is_null() } pub unsafe fn this(env: Env, info: FunctionCallbackInfo, out: &mut Local) { napi::get_cb_info(env, info, null_mut(), null_mut(), out as *mut _, null_mut()).unwrap(); } /// Gets the number of arguments passed to the function. // TODO: Remove this when `FunctionContext` is refactored to get call info upfront. pub unsafe fn len(env: Env, info: FunctionCallbackInfo) -> usize { let mut argc = 0usize; napi::get_cb_info( env, info, &mut argc as *mut _, null_mut(), null_mut(), null_mut(), ) .unwrap(); argc } /// Returns the function arguments for a call pub unsafe fn argv(env: Env, info: FunctionCallbackInfo) -> Arguments { // Allocate space on the stack for up to `ARGV_SIZE` values let mut argv = MaybeUninit::<[Local; ARGV_SIZE]>::uninit(); // Starts as the size allocated; after `get_cb_info` it is the number of arguments let mut argc = ARGV_SIZE; napi::get_cb_info( env, info, &mut argc as *mut _, argv.as_mut_ptr().cast(), null_mut(), null_mut(), ) .unwrap(); // We did not allocate enough space; allocate on the heap and try again let argv = if argc > ARGV_SIZE { // We know exactly how much space to reserve let mut argv = Vec::with_capacity(argc); napi::get_cb_info( env, info, &mut argc as *mut _, argv.as_mut_ptr(), null_mut(), null_mut(), ) .unwrap(); // Set the size of `argv` to the number of initialized elements argv.set_len(argc); SmallVec::from_vec(argv) // There were `ARGV_SIZE` or fewer arguments, use the stack allocated space } else { SmallVec::from_buf_and_len(argv.assume_init(), argc) }; Arguments(argv) } ================================================ FILE: crates/neon/src/sys/convert.rs ================================================ use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn to_string(out: &mut Local, env: Env, value: Local) -> bool { let status = napi::coerce_to_string(env, value, out as *mut _); status.is_ok() } ================================================ FILE: crates/neon/src/sys/date.rs ================================================ use std::mem::MaybeUninit; use super::{ bindings as napi, raw::{Env, Local}, }; /// Create a new date object /// /// # Safety /// /// `env` is a raw pointer. Please ensure it points to a napi_env that is valid for the current context. pub unsafe fn new_date(env: Env, value: f64) -> Local { let mut local = MaybeUninit::zeroed(); napi::create_date(env, value, local.as_mut_ptr()).unwrap(); local.assume_init() } /// Get the value of a date object /// /// # Safety /// /// `env` is a raw pointer. Please ensure it points to a napi_env that is valid for the current context. /// `Local` must be an NAPI value associated with the given `Env` pub unsafe fn value(env: Env, p: Local) -> f64 { let mut value = 0.0; napi::get_date_value(env, p, &mut value as *mut _).unwrap(); value } ================================================ FILE: crates/neon/src/sys/debug_send_wrapper.rs ================================================ //! Wrapper that ensures types are always used from the same thread //! in debug builds. It is a zero-cost in release builds. pub(super) use wrapper::DebugSendWrapper; #[cfg(debug_assertions)] mod wrapper { use std::ops::Deref; #[repr(transparent)] pub struct DebugSendWrapper(send_wrapper::SendWrapper); impl DebugSendWrapper { pub fn new(value: T) -> Self { Self(send_wrapper::SendWrapper::new(value)) } pub fn take(self) -> T { self.0.take() } } impl Deref for DebugSendWrapper { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } } #[cfg(not(debug_assertions))] mod wrapper { use std::ops::Deref; #[repr(transparent)] pub struct DebugSendWrapper(T); impl DebugSendWrapper { pub fn new(value: T) -> Self { Self(value) } pub fn take(self) -> T { self.0 } } impl Deref for DebugSendWrapper { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } } } ================================================ FILE: crates/neon/src/sys/error.rs ================================================ use std::{mem::MaybeUninit, panic::Location, ptr}; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn is_throwing(env: Env) -> bool { let mut b: MaybeUninit = MaybeUninit::zeroed(); napi::is_exception_pending(env, b.as_mut_ptr()).unwrap(); b.assume_init() } pub unsafe fn catch_error(env: Env, error: *mut Local) -> bool { if !is_throwing(env) { return false; } napi::get_and_clear_last_exception(env, error).unwrap(); true } pub unsafe fn clear_exception(env: Env) { let mut result = MaybeUninit::uninit(); napi::is_exception_pending(env, result.as_mut_ptr()).unwrap(); if !result.assume_init() { return; } let mut result = MaybeUninit::uninit(); napi::get_and_clear_last_exception(env, result.as_mut_ptr()).unwrap(); } pub unsafe fn throw(env: Env, val: Local) { napi::throw(env, val).unwrap(); } pub unsafe fn new_error(env: Env, out: &mut Local, msg: Local) { let mut result = MaybeUninit::uninit(); napi::create_error(env, ptr::null_mut(), msg, result.as_mut_ptr()).unwrap(); *out = result.assume_init(); } pub unsafe fn new_type_error(env: Env, out: &mut Local, msg: Local) { let mut result = MaybeUninit::uninit(); napi::create_type_error(env, ptr::null_mut(), msg, result.as_mut_ptr()).unwrap(); *out = result.assume_init(); } pub unsafe fn new_range_error(env: Env, out: &mut Local, msg: Local) { let mut result = MaybeUninit::uninit(); napi::create_range_error(env, ptr::null_mut(), msg, result.as_mut_ptr()).unwrap(); *out = result.assume_init(); } pub unsafe fn throw_error_from_utf8(env: Env, msg: *const u8, len: i32) { let mut out = MaybeUninit::uninit(); napi::create_string_utf8(env, msg as *const _, len as usize, out.as_mut_ptr()).unwrap(); let mut err = MaybeUninit::uninit(); napi::create_error(env, ptr::null_mut(), out.assume_init(), err.as_mut_ptr()).unwrap(); throw(env, err.assume_init()); } #[track_caller] pub(super) unsafe fn fatal_error(message: &str) -> ! { let location = Location::caller().to_string(); napi::fatal_error( location.as_ptr().cast(), location.len(), message.as_ptr().cast(), message.len(), ); unreachable!("Expected napi_fatal_error to exit the process") } ================================================ FILE: crates/neon/src/sys/external.rs ================================================ use std::mem::MaybeUninit; use super::{ bindings as napi, debug_send_wrapper::DebugSendWrapper, raw::{Env, Local}, }; /// `finalize_external` is invoked immediately before a `napi_external` is garbage collected extern "C" fn finalize_external( env: Env, // Raw pointer to a `Box` stored by a `napi_external` data: *mut std::ffi::c_void, // Pointer to a Rust `fn` stored in the `hint` parameter of a `napi_external` called // with the contents of `data` immediately before the value is garbage collected. hint: *mut std::ffi::c_void, ) { unsafe { let data = Box::>::from_raw(data as *mut _); let finalizer: fn(Env, T) = std::mem::transmute(hint as *const ()); finalizer(env, data.take()); } } /// Returns a pointer to data stored in a `napi_external` /// Safety: `deref` must only be called with `napi_external` created by that /// module. Calling `deref` with an external created by another native module, /// even another neon module, is undefined behavior. /// pub unsafe fn deref(env: Env, local: Local) -> Option<*const T> { let mut result = MaybeUninit::uninit(); napi::typeof_value(env, local, result.as_mut_ptr()).unwrap(); let result = result.assume_init(); // Ensure we have an external if result != napi::ValueType::External { return None; } // As a future improvement, this could be done with a dynamic symbol check instead of // relying on the Node-API version compatibility at compile time. #[cfg(feature = "napi-8")] // Check the external came from this module if !super::tag::check_object_type_tag(env, local, &crate::MODULE_TAG) { return None; } let mut result = MaybeUninit::uninit(); napi::get_value_external(env, local, result.as_mut_ptr()).unwrap(); let v = result.assume_init(); let v = &**v.cast_const().cast::>() as *const T; Some(v) } /// Creates a `napi_external` from a Rust type pub unsafe fn create(env: Env, v: T, finalizer: fn(Env, T)) -> Local { let v = Box::new(DebugSendWrapper::new(v)); let mut result = MaybeUninit::uninit(); let status = napi::create_external( env, Box::into_raw(v) as *mut _, Some(finalize_external::), // Casting to `*const ()` is required to ensure the correct layout // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html finalizer as *const () as *mut _, result.as_mut_ptr(), ); // `napi_create_external` will only fail if the VM is in a throwing state // or shutting down. status.unwrap(); let external = result.assume_init(); #[cfg(feature = "napi-8")] // Tag the object as coming from this module super::tag::type_tag_object(env, external, &crate::MODULE_TAG); external } ================================================ FILE: crates/neon/src/sys/fun.rs ================================================ //! Facilities for working with JS functions. use std::{mem::MaybeUninit, os::raw::c_void, ptr}; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(env: Env, name: &str, callback: F) -> Result where F: Fn(Env, napi::CallbackInfo) -> Local + 'static, { let mut out = MaybeUninit::uninit(); let data = Box::into_raw(Box::new(callback)); let status = napi::create_function( env, name.as_ptr().cast(), name.len(), Some(call_boxed::), data.cast(), out.as_mut_ptr(), ); match status { Err(err @ napi::Status::PendingException) => { drop(Box::from_raw(data)); return Err(err); } status => status.unwrap(), }; let out = out.assume_init(); #[cfg(feature = "napi-5")] { unsafe extern "C" fn drop_function( _env: Env, _finalize_data: *mut c_void, finalize_hint: *mut c_void, ) { drop(Box::from_raw(finalize_hint.cast::())); } let status = napi::add_finalizer( env, out, ptr::null_mut(), Some(drop_function::), data.cast(), ptr::null_mut(), ); // If adding the finalizer fails the closure will leak, but it would // be unsafe to drop it because there's no guarantee V8 won't use the // pointer. status.unwrap(); } Ok(out) } // C ABI compatible function for invoking a boxed closure from the data field // of a Node-API JavaScript function unsafe extern "C" fn call_boxed(env: Env, info: napi::CallbackInfo) -> Local where F: Fn(Env, napi::CallbackInfo) -> Local + 'static, { let mut data = MaybeUninit::uninit(); napi::get_cb_info( env, info, ptr::null_mut(), ptr::null_mut(), ptr::null_mut(), data.as_mut_ptr(), ) .unwrap(); let callback = &*data.assume_init().cast::(); callback(env, info) } pub unsafe fn construct( out: &mut Local, env: Env, fun: Local, argc: usize, argv: *const c_void, ) -> bool { let status = napi::new_instance(env, fun, argc, argv as *const _, out as *mut _); status.is_ok() } ================================================ FILE: crates/neon/src/sys/lifecycle.rs ================================================ //! # Environment life cycle APIs //! //! These APIs map to the life cycle of a specific "Agent" or self-contained //! environment. If a Neon module is loaded multiple times (Web Workers, worker //! threads), these API will be handle data associated with a specific instance. //! //! See the [N-API Lifecycle][npai-docs] documentation for more details. //! //! [napi-docs]: https://nodejs.org/api/n-api.html#n_api_environment_life_cycle_apis use std::{mem::MaybeUninit, os::raw::c_void, ptr}; use super::{bindings as napi, raw::Env}; /// # Safety /// `env` must point to a valid `napi_env` for this thread pub unsafe fn set_instance_data(env: Env, data: T) -> *mut T { let data = Box::into_raw(Box::new(data)); napi::set_instance_data(env, data.cast(), Some(drop_box::), ptr::null_mut()).unwrap(); data } /// # Safety /// * `T` must be the same type used in `set_instance_data` /// * Caller must ensure reference does not outlive `Env` /// * Return value may be `null` /// * `env` must point to a valid `napi_env` for this thread pub unsafe fn get_instance_data(env: Env) -> *mut T { let mut data = MaybeUninit::uninit(); napi::get_instance_data(env, data.as_mut_ptr()).unwrap(); data.assume_init().cast() } unsafe extern "C" fn drop_box(_env: Env, data: *mut c_void, _hint: *mut c_void) { drop(Box::::from_raw(data.cast())); } ================================================ FILE: crates/neon/src/sys/mem.rs ================================================ use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn strict_equals(env: Env, lhs: Local, rhs: Local) -> bool { let mut result = false; napi::strict_equals(env, lhs, rhs, &mut result as *mut _).unwrap(); result } ================================================ FILE: crates/neon/src/sys/mod.rs ================================================ //! Raw bindings to [Node-API][node-api] //! //! [Node-API][node-api] is Node.js's API for building native addons. Neon is //! predominantly a safe wrapper for Node-API and most users should prefer the //! the high level abstractions [outside](crate) of the sys module. //! //! However, directly using Node-API can be a useful tool for accessing low //! level functionality not exposed by Neon or experimenting with extensions //! to Neon without needing to fork the project. //! //! [node-api]: https://nodejs.org/api/n-api.html //! //! ## Initialization //! //! Before any Node-API functions may be used, [`setup`] must be called at //! least once. //! //! ```rust,no_run //! # #[cfg(feature = "sys")] //! # { //! # let env = std::ptr::null_mut().cast(); //! unsafe { neon::sys::setup(env); } //! # } //! ``` //! **Note**: It is unnecessary to call [`setup`] if //! [`#[neon::main]`](crate::main) is used to initialize the addon. //! //! ## Safety //! //! The following are guidelines for ensuring safe usage of Node-API in Neon //! but, do not represent a comprehensive set of safety rules. If possible, //! users should avoid calling Neon methods or holding references to structures //! created by Neon when calling Node-API functions directly. //! //! ### Env //! //! Neon ensures safety by carefully restricting access to [`Env`] //! by wrapping it in a [`Context`](crate::context::Context). Usages of `Env` //! should follow Neon's borrowing rules of `Context`. //! //! It is unsound to use an `Env` if Rust's borrowing rules would prevent usage //! of the in scope `Context`. //! //! ### Values //! //! Neon [value types](crate::types) encapsulate references to //! [JavaScript values](bindings::Value) with a _known type_. It is unsound to //! construct a Neon value with a [`Value`] of the incorrect type. //! //! ## Example //! //! ```rust,no_run //! # #[cfg(feature = "sys")] //! # { //! # let env = std::ptr::null_mut().cast(); //! use neon::{context::Cx, prelude::*, sys::bindings}; //! //! let cx = unsafe { //! neon::sys::setup(env); //! //! Cx::from_raw(env) //! }; //! //! let raw_string: bindings::Value = cx.string("Hello, World!").to_raw(); //! let js_string = unsafe { JsString::from_raw(&cx, raw_string) }; //! # } //! ``` use std::{mem::MaybeUninit, sync::Once}; // Bindings are re-exported here to minimize changes. // Follow-up issue: https://github.com/neon-bindings/neon/issues/982 pub(crate) use bindings::*; pub(crate) mod array; pub(crate) mod arraybuffer; pub(crate) mod async_work; pub(crate) mod buffer; pub(crate) mod call; pub(crate) mod convert; pub(crate) mod error; pub(crate) mod external; pub(crate) mod fun; pub(crate) mod mem; pub(crate) mod no_panic; pub(crate) mod object; pub(crate) mod primitive; pub(crate) mod promise; pub(crate) mod raw; pub(crate) mod reference; pub(crate) mod scope; pub(crate) mod string; pub(crate) mod tag; pub(crate) mod typedarray; pub mod bindings; #[cfg(feature = "napi-4")] pub(crate) mod tsfn; #[cfg(feature = "napi-5")] pub(crate) mod date; mod debug_send_wrapper; #[cfg(feature = "napi-6")] pub(crate) mod lifecycle; /// Create a JavaScript `String`, panicking if unsuccessful /// /// # Safety /// * `env` is a `napi_env` valid for the current thread /// * The returned value does not outlive `env` unsafe fn string(env: Env, s: impl AsRef) -> raw::Local { let s = s.as_ref(); let mut result = MaybeUninit::uninit(); create_string_utf8( env, s.as_bytes().as_ptr() as *const _, s.len(), result.as_mut_ptr(), ) .unwrap(); result.assume_init() } static SETUP: Once = Once::new(); /// Loads Node-API symbols from the host process. /// /// Must be called at least once before using any functions in bindings or /// they will panic. /// /// # Safety /// `env` must be a valid `napi_env` for the current thread pub unsafe fn setup(env: Env) { SETUP.call_once(|| load(env).expect("Failed to load N-API symbols")); } ================================================ FILE: crates/neon/src/sys/no_panic.rs ================================================ //! Utilities that _will_ not panic for use in contexts where unwinding would be //! undefined behavior. //! //! The following helpers do not panic and instead use `napi_fatal_error` //! to crash the process in a controlled way, making them safe for use in FFI //! callbacks. //! //! `#[track_caller]` is used on these helpers to ensure `fatal_error` reports //! the calling location instead of the helpers defined here. use std::{ any::Any, ffi::c_void, mem::MaybeUninit, panic::{catch_unwind, AssertUnwindSafe}, ptr, }; use super::{ bindings as napi, debug_send_wrapper::DebugSendWrapper, error::fatal_error, raw::{Env, Local}, }; type Panic = Box; const UNKNOWN_PANIC_MESSAGE: &str = "Unknown panic"; /// `FailureBoundary`] acts as boundary between Rust and FFI code, protecting /// a critical section of code from unhandled failure. It will catch both Rust /// panics and JavaScript exceptions. Attempts to handle failures are executed /// in order of ascending severity: /// /// 1. Reject a `Promise` if a `Deferred` was provided /// 2. Emit a fatal exception /// 3. Abort the process with a message and location /// /// This process will be aborted if any step unrecoverably fails. For example, /// if a `napi::Env` is unavailable, it is impossible to reject a `Promise` or /// emit a fatal exception. pub struct FailureBoundary { pub both: &'static str, pub exception: &'static str, pub panic: &'static str, } impl FailureBoundary { #[track_caller] pub unsafe fn catch_failure(&self, env: Env, deferred: Option, f: F) where F: FnOnce(Option) -> Local, { // Make `env = None` if unable to call into JS #[allow(clippy::unnecessary_lazy_evaluations)] let env = can_call_into_js(env).then(|| env); // Run the user supplied callback, catching panics // This is unwind safe because control is never yielded back to the caller let panic = catch_unwind(AssertUnwindSafe(move || f(env))); // Unwrap the `Env` let env = if let Some(env) = env { env } else { // If there was a panic and we don't have an `Env`, crash the process if let Err(panic) = panic { let msg = panic_msg(&panic).unwrap_or(UNKNOWN_PANIC_MESSAGE); fatal_error(msg); } // If we don't have an `Env`, we can't catch an exception, nothing more to try return; }; // Check and catch a thrown exception let exception = catch_exception(env); // Create an error message or return if there wasn't a panic or exception let msg = match (exception, panic.as_ref()) { // Exception and a panic (Some(_), Err(_)) => self.both, // Exception, but not a panic (Some(err), Ok(_)) => { // Reject the promise without wrapping if let Some(deferred) = deferred { reject_deferred(env, deferred, err); return; } self.exception } // Panic, but not an exception (None, Err(_)) => self.panic, // No errors occurred! We're done! (None, Ok(value)) => { if let Some(deferred) = deferred { resolve_deferred(env, deferred, *value); } return; } }; // Reject the promise if let Some(deferred) = deferred { let error = create_error(env, msg, exception, panic.err()); reject_deferred(env, deferred, error); return; } let error = create_error(env, msg, exception, panic.err()); // Trigger a fatal exception fatal_exception(env, error); } } // HACK: Force `NAPI_PREAMBLE` to run without executing any JavaScript to tell if it's // possible to call into JS. // // `NAPI_PREAMBLE` is a macro that checks if it is possible to call into JS. // https://github.com/nodejs/node/blob/5fad0b93667ffc6e4def52996b9529ac99b26319/src/js_native_api_v8.h#L211-L218 // // `napi_throw` starts by using `NAPI_PREAMBLE` and then a `CHECK_ARGS` on the `napi_value`. Since // we already know `env` is non-null, we expect the `null` value to cause a `napi_invalid_arg` error. // https://github.com/nodejs/node/blob/5fad0b93667ffc6e4def52996b9529ac99b26319/src/js_native_api_v8.cc#L1925-L1926 fn can_call_into_js(env: Env) -> bool { !env.is_null() && unsafe { napi::throw(env, ptr::null_mut()) == Err(napi::Status::InvalidArg) } } // We cannot use `napi_fatal_exception` because of this bug; instead, cause an // unhandled rejection which has similar behavior on recent versions of Node. // https://github.com/nodejs/node/issues/33771 unsafe fn fatal_exception(env: Env, error: Local) { let mut deferred = MaybeUninit::uninit(); let mut promise = MaybeUninit::uninit(); let deferred = match napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()) { Ok(()) => deferred.assume_init(), _ => fatal_error("Failed to create a promise"), }; if napi::reject_deferred(env, deferred, error) != Ok(()) { fatal_error("Failed to reject a promise"); } } #[track_caller] unsafe fn create_error( env: Env, msg: &str, exception: Option, panic: Option, ) -> Local { // Construct the `uncaughtException` Error object let error = error_from_message(env, msg); // Add the exception to the error if let Some(exception) = exception { set_property(env, error, "cause", exception); }; // Add the panic to the error if let Some(panic) = panic { set_property(env, error, "panic", error_from_panic(env, panic)); } error } #[track_caller] unsafe fn resolve_deferred(env: Env, deferred: napi::Deferred, value: Local) { if napi::resolve_deferred(env, deferred, value) != Ok(()) { fatal_error("Failed to resolve promise"); } } #[track_caller] unsafe fn reject_deferred(env: Env, deferred: napi::Deferred, value: Local) { if napi::reject_deferred(env, deferred, value) != Ok(()) { fatal_error("Failed to reject promise"); } } #[track_caller] unsafe fn catch_exception(env: Env) -> Option { if !is_exception_pending(env) { return None; } let mut error = MaybeUninit::uninit(); if napi::get_and_clear_last_exception(env, error.as_mut_ptr()) != Ok(()) { fatal_error("Failed to get and clear the last exception"); } Some(error.assume_init()) } #[track_caller] unsafe fn error_from_message(env: Env, msg: &str) -> Local { let msg = create_string(env, msg); let mut err = MaybeUninit::uninit(); let status = napi::create_error(env, ptr::null_mut(), msg, err.as_mut_ptr()); match status { Ok(()) => err.assume_init(), Err(_) => fatal_error("Failed to create an Error"), } } #[track_caller] unsafe fn error_from_panic(env: Env, panic: Panic) -> Local { if let Some(msg) = panic_msg(&panic) { error_from_message(env, msg) } else { let error = error_from_message(env, UNKNOWN_PANIC_MESSAGE); let panic = external_from_panic(env, panic); set_property(env, error, "cause", panic); error } } #[track_caller] unsafe fn set_property(env: Env, object: Local, key: &str, value: Local) { let key = create_string(env, key); if napi::set_property(env, object, key, value).is_err() { fatal_error("Failed to set an object property"); } } #[track_caller] unsafe fn panic_msg(panic: &Panic) -> Option<&str> { if let Some(msg) = panic.downcast_ref::<&str>() { Some(msg) } else if let Some(msg) = panic.downcast_ref::() { Some(msg) } else { None } } unsafe fn external_from_panic(env: Env, panic: Panic) -> Local { let fail = || fatal_error("Failed to create a neon::types::JsBox from a panic"); let mut result = MaybeUninit::uninit(); if napi::create_external( env, Box::into_raw(Box::new(DebugSendWrapper::new(panic))).cast(), Some(finalize_panic), ptr::null_mut(), result.as_mut_ptr(), ) .is_err() { fail(); } let external = result.assume_init(); #[cfg(feature = "napi-8")] if napi::type_tag_object(env, external, &*crate::MODULE_TAG).is_err() { fail(); } external } extern "C" fn finalize_panic(_env: Env, data: *mut c_void, _hint: *mut c_void) { unsafe { drop(Box::from_raw(data.cast::())); } } #[track_caller] unsafe fn create_string(env: Env, msg: &str) -> Local { let mut string = MaybeUninit::uninit(); if napi::create_string_utf8(env, msg.as_ptr().cast(), msg.len(), string.as_mut_ptr()).is_err() { fatal_error("Failed to create a String"); } string.assume_init() } unsafe fn is_exception_pending(env: Env) -> bool { let mut throwing = false; if napi::is_exception_pending(env, &mut throwing).is_err() { fatal_error("Failed to check if an exception is pending"); } throwing } ================================================ FILE: crates/neon/src/sys/object.rs ================================================ use std::mem::MaybeUninit; use super::{ bindings as napi, raw::{Env, Local}, }; /// Mutates the `out` argument to refer to a `napi_value` containing a newly created JavaScript Object. pub unsafe fn new(out: &mut Local, env: Env) { napi::create_object(env, out as *mut _).unwrap(); } #[cfg(feature = "napi-8")] pub unsafe fn freeze(env: Env, obj: Local) -> Result<(), napi::Status> { let status = napi::object_freeze(env, obj); debug_assert!(matches!( status, Ok(()) | Err(napi::Status::PendingException | napi::Status::GenericFailure) )); status } #[cfg(feature = "napi-8")] pub unsafe fn seal(env: Env, obj: Local) -> Result<(), napi::Status> { napi::object_seal(env, obj) } #[cfg(feature = "napi-6")] /// Mutates the `out` argument to refer to a `napi_value` containing the own property names of the /// `object` as a JavaScript Array. pub unsafe fn get_own_property_names(out: &mut Local, env: Env, object: Local) -> bool { let mut property_names = MaybeUninit::uninit(); match napi::get_all_property_names( env, object, napi::KeyCollectionMode::OwnOnly, napi::KeyFilter::ALL_PROPERTIES | napi::KeyFilter::SKIP_SYMBOLS, napi::KeyConversion::NumbersToStrings, property_names.as_mut_ptr(), ) { Err(napi::Status::PendingException) => return false, status => status.unwrap(), } *out = property_names.assume_init(); true } /// Mutate the `out` argument to refer to the value at `index` in the given `object`. Returns `false` if the value couldn't be retrieved. pub unsafe fn get_index(out: &mut Local, env: Env, object: Local, index: u32) -> bool { let status = napi::get_element(env, object, index, out as *mut _); status.is_ok() } /// Sets the key value of a `napi_value` at the `index` provided. Returns `true` if the set /// succeeded. /// /// The `out` parameter and the return value contain the same information for historical reasons, /// see [discussion]. /// /// [discussion]: https://github.com/neon-bindings/neon/pull/458#discussion_r344827965 pub unsafe fn set_index(out: &mut bool, env: Env, object: Local, index: u32, val: Local) -> bool { let status = napi::set_element(env, object, index, val); *out = status.is_ok(); *out } /// Mutate the `out` argument to refer to the value at a named `key` in the given `object`. Returns `false` if the value couldn't be retrieved. pub unsafe fn get_string( env: Env, out: &mut Local, object: Local, key: *const u8, len: i32, ) -> bool { let mut key_val = MaybeUninit::uninit(); // Not using `crate::string::new()` because it requires a _reference_ to a Local, // while we only have uninitialized memory. match napi::create_string_utf8(env, key as *const _, len as usize, key_val.as_mut_ptr()) { Err(napi::Status::PendingException) => return false, status => status.unwrap(), } // Not using napi_get_named_property() because the `key` may not be null terminated. match napi::get_property(env, object, key_val.assume_init(), out as *mut _) { Err(napi::Status::PendingException) => return false, status => status.unwrap(), } true } /// Sets the key value of a `napi_value` at a named key. Returns `true` if the set succeeded. /// /// The `out` parameter and the return value contain the same information for historical reasons, /// see [discussion]. /// /// [discussion]: https://github.com/neon-bindings/neon/pull/458#discussion_r344827965 pub unsafe fn set_string( env: Env, out: &mut bool, object: Local, key: *const u8, len: i32, val: Local, ) -> bool { let mut key_val = MaybeUninit::uninit(); *out = true; match napi::create_string_utf8(env, key as *const _, len as usize, key_val.as_mut_ptr()) { Err(napi::Status::PendingException) => { *out = false; return false; } status => status.unwrap(), } match napi::set_property(env, object, key_val.assume_init(), val) { Err(napi::Status::PendingException) => { *out = false; return false; } status => status.unwrap(), } true } /// Mutates `out` to refer to the value of the property of `object` named by the `key` value. /// Returns false if the value couldn't be retrieved. pub unsafe fn get(out: &mut Local, env: Env, object: Local, key: Local) -> bool { let status = napi::get_property(env, object, key, out as *mut _); status.is_ok() } /// Sets the property value of an `napi_value` object, named by another `value` `key`. Returns `true` if the set succeeded. /// /// The `out` parameter and the return value contain the same information for historical reasons, /// see [discussion]. /// /// [discussion]: https://github.com/neon-bindings/neon/pull/458#discussion_r344827965 pub unsafe fn set(out: &mut bool, env: Env, object: Local, key: Local, val: Local) -> bool { let status = napi::set_property(env, object, key, val); *out = status.is_ok(); *out } ================================================ FILE: crates/neon/src/sys/primitive.rs ================================================ use super::{ bindings as napi, raw::{Env, Local}, }; /// Mutates the `out` argument provided to refer to the global `undefined` object. pub unsafe fn undefined(out: &mut Local, env: Env) { napi::get_undefined(env, out as *mut Local).unwrap(); } /// Mutates the `out` argument provided to refer to the global `null` object. pub unsafe fn null(out: &mut Local, env: Env) { napi::get_null(env, out as *mut Local).unwrap(); } /// Mutates the `out` argument provided to refer to one of the global `true` or `false` objects. pub unsafe fn boolean(out: &mut Local, env: Env, b: bool) { napi::get_boolean(env, b, out as *mut Local).unwrap(); } /// Get the boolean value out of a `Local` object. If the `Local` object does not contain a /// boolean, this function panics. pub unsafe fn boolean_value(env: Env, p: Local) -> bool { let mut value = false; assert_eq!( napi::get_value_bool(env, p, &mut value as *mut bool), Ok(()) ); value } /// Mutates the `out` argument provided to refer to a newly created `Local` containing a /// JavaScript number. pub unsafe fn number(out: &mut Local, env: Env, v: f64) { napi::create_double(env, v, out as *mut Local).unwrap(); } /// Gets the underlying value of an `Local` object containing a JavaScript number. Panics if /// the given `Local` is not a number. pub unsafe fn number_value(env: Env, p: Local) -> f64 { let mut value = 0.0; napi::get_value_double(env, p, &mut value as *mut f64).unwrap(); value } ================================================ FILE: crates/neon/src/sys/promise.rs ================================================ //! JavaScript Promise and Deferred handle //! //! See: [Promises in Node-API](https://nodejs.org/api/n-api.html#n_api_promises) use std::mem::MaybeUninit; use super::{bindings as napi, raw::Env}; /// Create a `Promise` and a `napi::Deferred` handle for resolving it /// /// # Safety /// * `env` is a valid `napi_env` for the current thread /// * The returned `napi::Value` does not outlive `env` pub unsafe fn create(env: Env) -> (napi::Deferred, napi::Value) { let mut deferred = MaybeUninit::uninit(); let mut promise = MaybeUninit::uninit(); napi::create_promise(env, deferred.as_mut_ptr(), promise.as_mut_ptr()).unwrap(); (deferred.assume_init(), promise.assume_init()) } /// Resolve a promise from a `napi::Deferred` handle /// /// # Safety /// * `env` is a valid `napi_env` for the current thread /// * `resolution` is a valid `napi::Value` pub unsafe fn resolve(env: Env, deferred: napi::Deferred, resolution: napi::Value) { napi::resolve_deferred(env, deferred, resolution).unwrap(); } /// Rejects a promise from a `napi::Deferred` handle /// /// # Safety /// * `env` is a valid `napi_env` for the current thread /// * `rejection` is a valid `napi::Value` pub unsafe fn reject(env: Env, deferred: napi::Deferred, rejection: napi::Value) { napi::reject_deferred(env, deferred, rejection).unwrap(); } #[cfg(feature = "napi-6")] /// Rejects a promise from a `napi::Deferred` handle with a string message /// /// # Safety /// * `env` is a valid `napi_env` for the current thread pub unsafe fn reject_err_message(env: Env, deferred: napi::Deferred, msg: impl AsRef) { let msg = super::string(env, msg); let mut err = MaybeUninit::uninit(); napi::create_error(env, std::ptr::null_mut(), msg, err.as_mut_ptr()).unwrap(); reject(env, deferred, err.assume_init()); } ================================================ FILE: crates/neon/src/sys/raw.rs ================================================ use super::bindings as napi; pub type Local = napi::Value; pub type FunctionCallbackInfo = napi::CallbackInfo; pub type Env = napi::Env; ================================================ FILE: crates/neon/src/sys/reference.rs ================================================ use std::mem::MaybeUninit; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(env: Env, value: Local) -> napi::Ref { let mut result = MaybeUninit::uninit(); napi::create_reference(env, value, 1, result.as_mut_ptr()).unwrap(); result.assume_init() } /// # Safety /// Must only be used from the same module context that created the reference pub unsafe fn reference(env: Env, value: napi::Ref) -> usize { let mut result = MaybeUninit::uninit(); napi::reference_ref(env, value, result.as_mut_ptr()).unwrap(); result.assume_init() as usize } /// # Safety /// Must only be used from the same module context that created the reference pub unsafe fn unreference(env: Env, value: napi::Ref) { let mut result = MaybeUninit::uninit(); napi::reference_unref(env, value, result.as_mut_ptr()).unwrap(); if result.assume_init() == 0 { napi::delete_reference(env, value).unwrap(); } } /// # Safety /// Must only be used from the same module context that created the reference pub unsafe fn get(env: Env, value: napi::Ref) -> Local { let mut result = MaybeUninit::uninit(); napi::get_reference_value(env, value, result.as_mut_ptr()).unwrap(); result.assume_init() } ================================================ FILE: crates/neon/src/sys/scope.rs ================================================ use std::mem::MaybeUninit; use super::{ bindings as napi, raw::{Env, Local}, }; pub(crate) struct HandleScope { env: Env, scope: napi::HandleScope, } impl HandleScope { pub(crate) unsafe fn new(env: Env) -> Self { let mut scope = MaybeUninit::uninit(); napi::open_handle_scope(env, scope.as_mut_ptr()).unwrap(); Self { env, scope: scope.assume_init(), } } } impl Drop for HandleScope { fn drop(&mut self) { unsafe { let _status = napi::close_handle_scope(self.env, self.scope); debug_assert_eq!(_status, Ok(()),); } } } pub(crate) struct EscapableHandleScope { env: Env, scope: napi::EscapableHandleScope, } impl EscapableHandleScope { pub(crate) unsafe fn new(env: Env) -> Self { let mut scope = MaybeUninit::uninit(); napi::open_escapable_handle_scope(env, scope.as_mut_ptr()).unwrap(); Self { env, scope: scope.assume_init(), } } pub(crate) unsafe fn escape(&self, value: napi::Value) -> napi::Value { let mut escapee = MaybeUninit::uninit(); napi::escape_handle(self.env, self.scope, value, escapee.as_mut_ptr()).unwrap(); escapee.assume_init() } } impl Drop for EscapableHandleScope { fn drop(&mut self) { unsafe { let _status = napi::close_escapable_handle_scope(self.env, self.scope); debug_assert_eq!(_status, Ok(()),); } } } pub unsafe fn get_global(env: Env, out: &mut Local) { super::get_global(env, out as *mut _).unwrap(); } ================================================ FILE: crates/neon/src/sys/string.rs ================================================ use std::{mem::MaybeUninit, ptr}; use super::{ bindings as napi, raw::{Env, Local}, }; pub unsafe fn new(out: &mut Local, env: Env, data: *const u8, len: i32) -> bool { let status = napi::create_string_utf8(env, data as *const _, len as usize, out); status.is_ok() } pub unsafe fn utf8_len(env: Env, value: Local) -> usize { let mut len = MaybeUninit::uninit(); napi::get_value_string_utf8(env, value, ptr::null_mut(), 0, len.as_mut_ptr()).unwrap(); len.assume_init() } pub unsafe fn data(env: Env, out: *mut u8, len: usize, value: Local) -> usize { let mut read = MaybeUninit::uninit(); napi::get_value_string_utf8(env, value, out as *mut _, len, read.as_mut_ptr()).unwrap(); read.assume_init() } pub unsafe fn utf16_len(env: Env, value: Local) -> usize { let mut len = MaybeUninit::uninit(); napi::get_value_string_utf16(env, value, ptr::null_mut(), 0, len.as_mut_ptr()).unwrap(); len.assume_init() } pub unsafe fn data_utf16(env: Env, out: *mut u16, len: usize, value: Local) -> usize { let mut read = MaybeUninit::uninit(); napi::get_value_string_utf16(env, value, out, len, read.as_mut_ptr()).unwrap(); read.assume_init() } pub unsafe fn run_script(out: &mut Local, env: Env, value: Local) -> bool { let status = napi::run_script(env, value, out as *mut _); status == Ok(()) } ================================================ FILE: crates/neon/src/sys/tag.rs ================================================ use super::{ bindings as napi, raw::{Env, Local}, }; /// Return true if an `napi_value` `val` has the expected value type. unsafe fn is_type(env: Env, val: Local, expect: napi::ValueType) -> bool { let mut actual = napi::ValueType::Undefined; napi::typeof_value(env, val, &mut actual as *mut _).unwrap(); actual == expect } pub unsafe fn is_undefined(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Undefined) } pub unsafe fn is_null(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Null) } /// Is `val` a JavaScript number? pub unsafe fn is_number(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Number) } /// Is `val` a JavaScript boolean? pub unsafe fn is_boolean(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Boolean) } /// Is `val` a JavaScript string? pub unsafe fn is_string(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::String) } pub unsafe fn is_object(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Object) } pub unsafe fn is_array(env: Env, val: Local) -> bool { let mut result = false; napi::is_array(env, val, &mut result as *mut _).unwrap(); result } pub unsafe fn is_function(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::Function) } pub unsafe fn is_error(env: Env, val: Local) -> bool { let mut result = false; napi::is_error(env, val, &mut result as *mut _).unwrap(); result } /// Is `val` a Node.js Buffer instance? pub unsafe fn is_buffer(env: Env, val: Local) -> bool { let mut result = false; napi::is_buffer(env, val, &mut result as *mut _).unwrap(); result } /// Is `val` an ArrayBuffer instance? pub unsafe fn is_arraybuffer(env: Env, val: Local) -> bool { let mut result = false; napi::is_arraybuffer(env, val, &mut result as *mut _).unwrap(); result } /// Is `val` a TypedArray instance? pub unsafe fn is_typedarray(env: Env, val: Local) -> bool { let mut result = false; napi::is_typedarray(env, val, &mut result as *mut _).unwrap(); result } #[cfg(feature = "napi-5")] pub unsafe fn is_date(env: Env, val: Local) -> bool { let mut result = false; napi::is_date(env, val, &mut result as *mut _).unwrap(); result } /// Is `val` a Promise? /// /// # Safety /// * `env` is a valid `napi_env` for the current thread pub unsafe fn is_promise(env: Env, val: Local) -> bool { let mut result = false; napi::is_promise(env, val, &mut result as *mut _).unwrap(); result } #[cfg(feature = "napi-8")] pub unsafe fn type_tag_object(env: Env, object: Local, tag: &super::TypeTag) { napi::type_tag_object(env, object, tag as *const _).unwrap(); } #[cfg(feature = "napi-8")] pub unsafe fn check_object_type_tag(env: Env, object: Local, tag: &super::TypeTag) -> bool { let mut result = false; napi::check_object_type_tag(env, object, tag as *const _, &mut result as *mut _).unwrap(); result } #[cfg(feature = "napi-6")] pub unsafe fn is_bigint(env: Env, val: Local) -> bool { is_type(env, val, napi::ValueType::BigInt) } ================================================ FILE: crates/neon/src/sys/tsfn.rs ================================================ //! Idiomatic Rust wrappers for N-API threadsafe functions use std::{ ffi::c_void, mem::MaybeUninit, ptr, sync::{Arc, Mutex}, }; use super::{bindings as napi, no_panic::FailureBoundary, raw::Env}; const BOUNDARY: FailureBoundary = FailureBoundary { both: "A panic and exception occurred while executing a `neon::event::Channel::send` callback", exception: "An exception occurred while executing a `neon::event::Channel::send` callback", panic: "A panic occurred while executing a `neon::event::Channel::send` callback", }; #[derive(Debug)] struct Tsfn(napi::ThreadsafeFunction); unsafe impl Send for Tsfn {} unsafe impl Sync for Tsfn {} #[derive(Debug)] /// Threadsafe Function encapsulate a Rust function pointer and N-API threadsafe /// function for scheduling tasks to execute on a JavaScript thread. pub struct ThreadsafeFunction { tsfn: Tsfn, is_finalized: Arc>, callback: fn(Option, T), } #[derive(Debug)] struct Callback { callback: fn(Option, T), data: T, } /// Error returned when scheduling a threadsafe function with some data pub struct CallError; impl ThreadsafeFunction { /// Creates a new unbounded N-API Threadsafe Function /// Safety: `Env` must be valid for the current thread pub unsafe fn new(env: Env, callback: fn(Option, T)) -> Self { Self::with_capacity(env, 0, callback) } /// Creates a bounded N-API Threadsafe Function /// Safety: `Env` must be valid for the current thread pub unsafe fn with_capacity( env: Env, max_queue_size: usize, callback: fn(Option, T), ) -> Self { let mut result = MaybeUninit::uninit(); let is_finalized = Arc::new(Mutex::new(false)); assert_eq!( napi::create_threadsafe_function( env, std::ptr::null_mut(), std::ptr::null_mut(), super::string(env, "neon threadsafe function"), max_queue_size, // Always set the reference count to 1. Prefer using // Rust `Arc` to maintain the struct. 1, Arc::into_raw(is_finalized.clone()) as *mut _, Some(Self::finalize), std::ptr::null_mut(), Some(Self::callback), result.as_mut_ptr(), ), Ok(()), ); Self { tsfn: Tsfn(result.assume_init()), is_finalized, callback, } } /// Schedule a threadsafe function to be executed with some data pub fn call( &self, data: T, is_blocking: Option, ) -> Result<(), CallError> { let is_blocking = is_blocking.unwrap_or(napi::ThreadsafeFunctionCallMode::Blocking); let callback = Box::into_raw(Box::new(Callback { callback: self.callback, data, })); // Hold the lock before entering `call_threadsafe_function` so that // `finalize_cb` would never complete. let mut is_finalized = self.is_finalized.lock().unwrap(); let status = { if *is_finalized { Err(napi::Status::Closing) } else { unsafe { napi::call_threadsafe_function(self.tsfn.0, callback as *mut _, is_blocking) } } }; match status { Ok(()) => Ok(()), Err(status) => { // Prevent further calls to `call_threadsafe_function` if status == napi::Status::Closing { *is_finalized = true; } // If the call failed, the callback won't execute let _ = unsafe { Box::from_raw(callback) }; Err(CallError) } } } /// References a threadsafe function to prevent exiting the event loop until it has been dropped. (Default) /// Safety: `Env` must be valid for the current thread pub unsafe fn reference(&self, env: Env) { napi::ref_threadsafe_function(env, self.tsfn.0).unwrap(); } /// Unreferences a threadsafe function to allow exiting the event loop before it has been dropped. /// Safety: `Env` must be valid for the current thread pub unsafe fn unref(&self, env: Env) { napi::unref_threadsafe_function(env, self.tsfn.0).unwrap(); } // Provides a C ABI wrapper for a napi callback notifying us about tsfn // being finalized. unsafe extern "C" fn finalize(_env: Env, data: *mut c_void, _hint: *mut c_void) { let is_finalized = Arc::from_raw(data as *mut Mutex); *is_finalized.lock().unwrap() = true; } // Provides a C ABI wrapper for invoking the user supplied function pointer // On panic or exception, creates a fatal exception of the form: // Error(msg: string) { // // Exception thrown // cause?: Error, // // Panic occurred // panic?: Error(msg: string) { // // Opaque panic type if it wasn't a string // cause?: JsBox // } // } unsafe extern "C" fn callback( env: Env, _js_callback: napi::Value, _context: *mut c_void, data: *mut c_void, ) { let Callback { callback, data } = *Box::from_raw(data as *mut Callback); BOUNDARY.catch_failure(env, None, move |env| { callback(env, data); ptr::null_mut() }); } } impl Drop for ThreadsafeFunction { fn drop(&mut self) { let is_finalized = self.is_finalized.lock().unwrap(); // tsfn was already finalized by `Environment::CleanupHandles()` in Node.js if *is_finalized { return; } unsafe { debug_assert_eq!( napi::release_threadsafe_function( self.tsfn.0, napi::ThreadsafeFunctionReleaseMode::Release, ), Ok(()) ); }; } } ================================================ FILE: crates/neon/src/sys/typedarray.rs ================================================ use std::{ffi::c_void, mem::MaybeUninit}; use super::{ bindings::{self as napi, TypedArrayType}, raw::{Env, Local}, }; #[derive(Debug)] /// Information describing a JavaScript [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) pub struct TypedArrayInfo { pub typ: TypedArrayType, pub length: usize, pub data: *mut c_void, pub buf: Local, pub offset: usize, } /// Get [information](TypedArrayInfo) describing a JavaScript `TypedArray` /// /// # Safety /// * `env` must be valid `napi_env` for the current scope /// * `value` must be a handle pointing to a `TypedArray` pub unsafe fn info(env: Env, value: Local) -> TypedArrayInfo { let mut info = MaybeUninit::::zeroed(); let ptr = info.as_mut_ptr(); assert_eq!( napi::get_typedarray_info( env, value, &mut (*ptr).typ, &mut (*ptr).length, &mut (*ptr).data, &mut (*ptr).buf, &mut (*ptr).offset, ), Ok(()), ); info.assume_init() } pub unsafe fn new( env: Env, typ: TypedArrayType, buffer: Local, offset: usize, len: usize, ) -> Result { let mut array = MaybeUninit::uninit(); let status = napi::create_typedarray(env, typ, len, buffer, offset, array.as_mut_ptr()); match status { Err(err @ napi::Status::PendingException) => return Err(err), status => status.unwrap(), }; Ok(array.assume_init()) } ================================================ FILE: crates/neon/src/thread/mod.rs ================================================ //! Thread-local storage for JavaScript threads. //! //! At runtime, an instance of a Node.js addon can contain its own local storage, //! which can then be shared and accessed as needed from Rust in a Neon module. This can //! be useful for setting up long-lived state that needs to be shared between calls //! of an addon's APIs. //! //! For example, an addon may wish to track the [thread ID][threadId] of each of its //! instances: //! //! ``` //! # use neon::prelude::*; //! # use neon::thread::LocalKey; //! static THREAD_ID: LocalKey = LocalKey::new(); //! //! pub fn thread_id(cx: &mut Cx) -> NeonResult { //! THREAD_ID.get_or_try_init(cx, |cx| { //! let require: Handle = cx.global("require")?; //! let worker: Handle = require //! .bind(cx) //! .arg("node:worker_threads")? //! .call()?; //! let thread_id: f64 = worker.prop(cx, "threadId").get()?; //! Ok(thread_id as u32) //! }).cloned() //! } //! ``` //! //! ### The Addon Lifecycle //! //! For some use cases, a single shared global constant stored in a `static` variable //! might be sufficient: //! //! ``` //! static MY_CONSTANT: &'static str = "hello Neon"; //! ``` //! //! This variable will be allocated when the addon is first loaded into the Node.js //! process. This works fine for single-threaded applications, or global thread-safe //! data. //! //! However, since the addition of [worker threads][workers] in Node v10, //! modules can be instantiated multiple times in a single Node process. So even //! though the dynamically-loaded binary library (i.e., the Rust implementation of //! the addon) is only loaded once in the running process, its [`#[main]`](crate::main) //! function can be executed multiple times with distinct module objects, one per application //! thread: //! //! ![The Node.js addon lifecycle, described in detail below.][lifecycle] //! //! This means that any thread-local data needs to be initialized separately for each //! instance of the addon. This module provides a simple container type, [`LocalKey`], //! for allocating and initializing thread-local data. (Technically, this data is stored in the //! addon's [module instance][environment], which is equivalent to being thread-local.) //! //! A common example is when an addon needs to maintain a reference to a JavaScript value. A //! reference can be [rooted](crate::handle::Root) and stored in a static, but references cannot //! be used across separate threads. By placing the reference in thread-local storage, an //! addon can ensure that each thread stores its own distinct reference: //! //! ``` //! # use neon::prelude::*; //! # use neon::thread::LocalKey; //! # fn initialize_my_datatype<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> { unimplemented!() } //! static MY_CONSTRUCTOR: LocalKey> = LocalKey::new(); //! //! pub fn my_constructor<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> { //! let constructor = MY_CONSTRUCTOR.get_or_try_init(cx, |cx| { //! let constructor: Handle = initialize_my_datatype(cx)?; //! Ok(constructor.root(cx)) //! })?; //! Ok(constructor.to_inner(cx)) //! } //! ``` //! //! Notice that if this code were implemented without a `LocalKey`, it would panic whenever //! one thread stores an instance of the constructor and a different thread attempts to //! access it with the call to [`to_inner()`](crate::handle::Root::to_inner). //! //! ### When to Use Thread-Local Storage //! //! Single-threaded applications don't generally need to worry about thread-local data. //! There are two cases where Neon apps should consider storing static data in a //! `LocalKey` storage cell: //! //! - **Multi-threaded applications:** If your Node application uses the `Worker` //! API, you'll want to store any static data that might get access from multiple //! threads in thread-local data. //! - **Libraries:** If your addon is part of a library that could be used by multiple //! applications, you'll want to store static data in thread-local data in case the //! addon ends up instantiated by multiple threads in some future application. //! //! ### Why Not Use Standard TLS? //! //! Since the JavaScript engine may not tie JavaScript threads 1:1 to system threads, //! it is recommended to use this module instead of the Rust standard thread-local storage //! when associating data with a JavaScript thread. //! //! [environment]: https://nodejs.org/api/n-api.html#environment-life-cycle-apis //! [lifecycle]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/lifecycle.png //! [workers]: https://nodejs.org/api/worker_threads.html //! [threadId]: https://nodejs.org/api/worker_threads.html#workerthreadid use std::any::Any; use std::marker::PhantomData; use std::sync::atomic::{AtomicUsize, Ordering}; use once_cell::sync::OnceCell; use crate::context::Context; use crate::lifecycle::LocalCell; static COUNTER: AtomicUsize = AtomicUsize::new(0); fn next_id() -> usize { COUNTER.fetch_add(1, Ordering::SeqCst) } /// A JavaScript thread-local container that owns its contents, similar to /// [`std::thread::LocalKey`] but tied to a JavaScript thread rather /// than a system thread. /// /// ### Initialization and Destruction /// /// Initialization is dynamically performed on the first call to one of the `init` methods /// of `LocalKey`, and values that implement [`Drop`] get destructed when /// the JavaScript thread exits, i.e. when a worker thread terminates or the main thread /// terminates on process exit. #[derive(Default)] pub struct LocalKey { _type: PhantomData, id: OnceCell, } impl LocalKey { /// Creates a new local value. This method is `const`, so it can be assigned to /// static variables. pub const fn new() -> Self { Self { _type: PhantomData, id: OnceCell::new(), } } fn id(&self) -> usize { *self.id.get_or_init(next_id) } } impl LocalKey { /// Gets the current value of the cell. Returns `None` if the cell has not /// yet been initialized. pub fn get<'cx, 'a, C>(&self, cx: &'a mut C) -> Option<&'cx T> where C: Context<'cx>, { // Unwrap safety: The type bound LocalKey and the fact that every LocalKey has a unique // id guarantees that the cell is only ever assigned instances of type T. let r: Option<&T> = LocalCell::get(cx, self.id()).map(|value| value.downcast_ref().unwrap()); // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to // move or change for the duration of the context. unsafe { std::mem::transmute::, Option<&'cx T>>(r) } } /// Gets the current value of the cell, initializing it with the result of /// calling `f` if it has not yet been initialized. pub fn get_or_init<'cx, 'a, C, F>(&self, cx: &'a mut C, f: F) -> &'cx T where C: Context<'cx>, F: FnOnce() -> T, { // Unwrap safety: The type bound LocalKey and the fact that every LocalKey has a unique // id guarantees that the cell is only ever assigned instances of type T. let r: &T = LocalCell::get_or_init(cx, self.id(), || Box::new(f())) .downcast_ref() .unwrap(); // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to // move or change for the duration of the context. unsafe { std::mem::transmute::<&'a T, &'cx T>(r) } } /// Gets the current value of the cell, initializing it with the result of /// calling `f` if it has not yet been initialized. Returns `Err` if the /// callback triggers a JavaScript exception. /// /// # Panics /// /// During the execution of `f`, calling any methods on this `LocalKey` that /// attempt to initialize it will panic. pub fn get_or_try_init<'cx, 'a, C, E, F>(&self, cx: &'a mut C, f: F) -> Result<&'cx T, E> where C: Context<'cx>, F: FnOnce(&mut C) -> Result, { // Unwrap safety: The type bound LocalKey and the fact that every LocalKey has a unique // id guarantees that the cell is only ever assigned instances of type T. let r: &T = LocalCell::get_or_try_init(cx, self.id(), |cx| Ok(Box::new(f(cx)?)))? .downcast_ref() .unwrap(); // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to // move or change for the duration of the context. Ok(unsafe { std::mem::transmute::<&'a T, &'cx T>(r) }) } } impl LocalKey { /// Gets the current value of the cell, initializing it with the default value /// if it has not yet been initialized. pub fn get_or_init_default<'cx, 'a, C>(&self, cx: &'a mut C) -> &'cx T where C: Context<'cx>, { self.get_or_init(cx, Default::default) } } ================================================ FILE: crates/neon/src/types_docs.rs ================================================ #[cfg_attr(feature = "aquamarine", aquamarine::aquamarine)] /// Representations of JavaScript's core builtin types. /// /// ## Modeling JavaScript Types /// /// All JavaScript values in Neon implement the abstract [`Value`](crate::types::Value) /// trait, which is the most generic way to work with JavaScript values. Neon provides a /// number of types that implement this trait, each representing a particular /// type of JavaScript value. /// /// By convention, JavaScript types in Neon have the prefix `Js` in their name, /// such as [`JsNumber`](crate::types::JsNumber) (for the JavaScript `number` /// type) or [`JsFunction`](crate::types::JsFunction) (for the JavaScript /// `function` type). /// /// ### Handles and Casts /// /// Access to JavaScript values in Neon works through [handles](crate::handle), /// which ensure the safe interoperation between Rust and the JavaScript garbage /// collector. This means, for example, a Rust variable that stores a JavaScript string /// will have the type `Handle` rather than [`JsString`](crate::types::JsString). /// /// Neon types model the JavaScript type hierarchy through the use of *casts*. /// The [`Handle::upcast()`](crate::handle::Handle::upcast) method safely converts /// a handle to a JavaScript value of one type into a handle to a value of its /// supertype. For example, it's safe to treat a [`JsArray`](crate::types::JsArray) /// as a [`JsObject`](crate::types::JsObject), so you can do an "upcast" and it will /// never fail: /// /// ``` /// # use neon::prelude::*; /// fn as_object(array: Handle) -> Handle { /// let object: Handle = array.upcast(); /// object /// } /// ``` /// /// Unlike upcasts, the [`Handle::downcast()`](crate::handle::Handle::downcast) method /// requires a runtime check to test a value's type at runtime, so it can fail with /// a [`DowncastError`](crate::handle::DowncastError): /// /// ``` /// # use neon::prelude::*; /// fn as_array<'a>( /// cx: &mut impl Context<'a>, /// object: Handle<'a, JsObject> /// ) -> JsResult<'a, JsArray> { /// object.downcast(cx).or_throw(cx) /// } /// ``` /// /// ### The JavaScript Type Hierarchy /// /// The top of the JavaScript type hierarchy is modeled with the Neon type /// [`JsValue`](crate::types::JsValue). A [handle](crate::handle) to a `JsValue` /// can refer to any JavaScript value. (For TypeScript programmers, this can be /// thought of as similar to TypeScript's [`unknown`][unknown] type.) /// /// From there, the type hierarchy divides into _object types_ and _primitive /// types_: /// /// ```mermaid /// flowchart LR /// JsValue(JsValue) /// JsValue-->JsObject(JsObject) /// click JsValue "./struct.JsValue.html" "JsValue" /// click JsObject "./struct.JsObject.html" "JsObject" /// subgraph primitives [Primitive Types] /// JsBoolean(JsBoolean) /// JsNumber(JsNumber) /// JsString(JsString) /// JsNull(JsNull) /// JsUndefined(JsUndefined) /// click JsBoolean "./struct.JsBoolean.html" "JsBoolean" /// click JsNumber "./struct.JsNumber.html" "JsNumber" /// click JsString "./struct.JsString.html" "JsString" /// click JsNull "./struct.JsNull.html" "JsNull" /// click JsUndefined "./struct.JsUndefined.html" "JsUndefined" /// end /// JsValue-->primitives /// ``` /// /// The top of the object type hierarchy is [`JsObject`](crate::types::JsObject). A /// handle to a `JsObject` can refer to any JavaScript object. /// /// The primitive types are the built-in JavaScript datatypes that are not object /// types: [`JsBoolean`](crate::types::JsBoolean), [`JsNumber`](crate::types::JsNumber), /// [`JsString`](crate::types::JsString), [`JsNull`](crate::types::JsNull), and /// [`JsUndefined`](crate::types::JsUndefined). /// /// #### Object Types /// /// The object type hierarchy further divides into a variety of different subtypes: /// /// ```mermaid /// flowchart LR /// JsObject(JsObject) /// click JsObject "./struct.JsObject.html" "JsObject" /// subgraph objects [Standard Object Types] /// JsFunction(JsFunction) /// JsArray(JsArray) /// JsDate(JsDate) /// JsError(JsError) /// click JsFunction "./struct.JsFunction.html" "JsFunction" /// click JsArray "./struct.JsArray.html" "JsArray" /// click JsDate "./struct.JsDate.html" "JsDate" /// click JsError "./struct.JsError.html" "JsError" /// end /// subgraph typedarrays [Typed Arrays] /// JsBuffer(JsBuffer) /// JsArrayBuffer(JsArrayBuffer) /// JsTypedArray("JsTypedArray<T>") /// click JsBuffer "./struct.JsBuffer.html" "JsBuffer" /// click JsArrayBuffer "./struct.JsArrayBuffer.html" "JsArrayBuffer" /// click JsTypedArray "./struct.JsTypedArray.html" "JsTypedArray" /// end /// subgraph custom [Custom Types] /// JsBox(JsBox) /// click JsBox "./struct.JsBox.html" "JsBox" /// end /// JsObject-->objects /// JsObject-->typedarrays /// JsObject-->custom /// ``` /// /// These include several categories of object types: /// - **Standard object types:** [`JsFunction`](crate::types::JsFunction), /// [`JsArray`](crate::types::JsArray), [`JsDate`](crate::types::JsDate), and /// [`JsError`](crate::types::JsError). /// - **Typed arrays:** [`JsBuffer`](crate::types::JsBuffer), /// [`JsArrayBuffer`](crate::types::JsArrayBuffer), and /// [`JsTypedArray`](crate::types::JsTypedArray). /// - **Custom types:** [`JsBox`](crate::types::JsBox), a special Neon type that allows /// the creation of custom objects that own Rust data structures. /// /// All object types implement the [`Object`](crate::object::Object) trait, which /// allows getting and setting properties of an object. /// /// [unknown]: https://mariusschulz.com/blog/the-unknown-type-in-typescript#the-unknown-type pub mod exports { pub use crate::types_impl::*; } ================================================ FILE: crates/neon/src/types_impl/bigint.rs ================================================ //! Types for working with [`JsBigInt`]. use std::{error, fmt, mem::MaybeUninit}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, result::{NeonResult, ResultExt}, sys::{self, raw}, types::{private, JsBigInt, Value}, }; #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Indicates if a `JsBigInt` is positive or negative pub enum Sign { Positive, Negative, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Indicates a lossless conversion from a [`JsBigInt`] to a Rust integer /// could not be performed. /// /// Failures include: /// * Negative sign on an unsigned int /// * Overflow of an int /// * Underflow of a signed int pub struct RangeError(T); impl RangeError { /// Get the lossy value read from a `BigInt`. It may be truncated, /// sign extended or wrapped. pub fn into_inner(self) -> T { self.0 } } impl fmt::Display for RangeError where T: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Loss of precision reading BigInt ({})", self.0) } } impl error::Error for RangeError where T: fmt::Display + fmt::Debug {} impl ResultExt for Result> where E: fmt::Display, { fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult { self.or_else(|err| cx.throw_range_error(err.to_string())) } } impl JsBigInt { pub const POSITIVE: Sign = Sign::Positive; pub const NEGATIVE: Sign = Sign::Negative; /// Creates a `BigInt` from an [`i64`]. /// /// # Example /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let value: Handle = JsBigInt::from_i64(&mut cx, 42); /// # Ok(value) /// # } /// ``` pub fn from_i64<'cx, C>(cx: &mut C, n: i64) -> Handle<'cx, Self> where C: Context<'cx>, { let mut v = MaybeUninit::uninit(); let v = unsafe { sys::create_bigint_int64(cx.env().to_raw(), n, v.as_mut_ptr()).unwrap(); v.assume_init() }; Handle::new_internal(Self(v)) } /// Creates a `BigInt` from a [`u64`]. /// /// # Example /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let value: Handle = JsBigInt::from_u64(&mut cx, 42); /// # Ok(value) /// # } /// ``` pub fn from_u64<'cx, C>(cx: &mut C, n: u64) -> Handle<'cx, Self> where C: Context<'cx>, { let mut v = MaybeUninit::uninit(); let v = unsafe { sys::create_bigint_uint64(cx.env().to_raw(), n, v.as_mut_ptr()).unwrap(); v.assume_init() }; Handle::new_internal(Self(v)) } // Internal helper for creating a _signed_ `BigInt` from a [`u128`] magnitude fn from_u128_sign<'cx, C>(cx: &mut C, sign: Sign, n: u128) -> Handle<'cx, Self> where C: Context<'cx>, { let n = n.to_le(); let digits = [n as u64, (n >> 64) as u64]; Self::from_digits_le(cx, sign, &digits) } /// Creates a `BigInt` from an [`i128`]. /// /// # Example /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let value: Handle = JsBigInt::from_i128(&mut cx, 42); /// # Ok(value) /// # } /// ``` pub fn from_i128<'cx, C>(cx: &mut C, n: i128) -> Handle<'cx, Self> where C: Context<'cx>, { if n >= 0 { return Self::from_u128(cx, n as u128); } // Get the magnitude from a two's compliment negative let n = u128::MAX - (n as u128) + 1; Self::from_u128_sign(cx, Self::NEGATIVE, n) } /// Creates a `BigInt` from a [`u128`]. /// /// # Example /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let value: Handle = JsBigInt::from_u128(&mut cx, 42); /// # Ok(value) /// # } /// ``` pub fn from_u128<'cx, C>(cx: &mut C, n: u128) -> Handle<'cx, Self> where C: Context<'cx>, { Self::from_u128_sign(cx, Self::POSITIVE, n) } /// Creates a `BigInt` from a signed magnitude. The `BigInt` is calculated as:\ /// `Sign * (digit[0] x (2⁶⁴)⁰ + digit[0] x (2⁶⁴)¹ + digit[0] x (2⁶⁴)² ...)` /// /// # Example /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// # fn example(mut cx: FunctionContext) -> JsResult { /// // Creates a `BigInt` equal to `2n ** 128n` /// let value: Handle = JsBigInt::from_digits_le( /// &mut cx, /// JsBigInt::POSITIVE, /// &[0, 0, 1], /// ); /// # Ok(value) /// # } /// ``` // // XXX: It's unclear if individual digits are expected to be little endian or native. // The current code assumes _native_. Neon modules are currently broken on big-endian // platforms. If this is fixed in the future, unit tests will determine if this // assumption is accurate. pub fn from_digits_le<'cx, C>(cx: &mut C, sign: Sign, digits: &[u64]) -> Handle<'cx, Self> where C: Context<'cx>, { let sign_bit = match sign { Sign::Positive => 0, Sign::Negative => 1, }; let mut v = MaybeUninit::uninit(); let v = unsafe { sys::create_bigint_words( cx.env().to_raw(), sign_bit, digits.len(), digits.as_ptr(), v.as_mut_ptr(), ) .unwrap(); v.assume_init() }; Handle::new_internal(Self(v)) } /// Reads an `i64` from a `BigInt`. /// /// Fails on overflow and underflow. /// /// # Example /// /// See [`JsBigInt`]. pub fn to_i64<'cx, C>(&self, cx: &mut C) -> Result> where C: Context<'cx>, { let mut n = 0; let mut lossless = false; unsafe { sys::get_value_bigint_int64(cx.env().to_raw(), self.0, &mut n, &mut lossless).unwrap(); } if lossless { Ok(n) } else { Err(RangeError(n)) } } /// Reads a `u64` from a `BigInt`. /// /// Fails on overflow or a negative sign. pub fn to_u64<'cx, C>(&self, cx: &mut C) -> Result> where C: Context<'cx>, { let mut n = 0; let mut lossless = false; unsafe { sys::get_value_bigint_uint64(cx.env().to_raw(), self.0, &mut n, &mut lossless).unwrap(); } if lossless { Ok(n) } else { Err(RangeError(n)) } } /// Reads an `i128` from a `BigInt`. /// /// Fails on overflow and underflow. pub fn to_i128<'cx, C>(&self, cx: &mut C) -> Result> where C: Context<'cx>, { let mut digits = [0; 2]; let (sign, num_digits) = self.read_digits_le(cx, &mut digits); // Cast digits into a `u128` magnitude let n = (digits[0] as u128) | ((digits[1] as u128) << 64); let n = u128::from_le(n); // Verify that the magnitude leaves room for the sign bit let n = match sign { Sign::Positive => { if n > (i128::MAX as u128) { return Err(RangeError(i128::MAX)); } else { n as i128 } } Sign::Negative => { if n > (i128::MAX as u128) + 1 { return Err(RangeError(i128::MIN)); } else { (n as i128).wrapping_neg() } } }; // Leading zeroes are truncated and never returned. If there are additional // digits, the number is out of range. if num_digits > digits.len() { Err(RangeError(n)) } else { Ok(n) } } /// Reads a `u128` from a `BigInt`. /// /// Fails on overflow or a negative sign. pub fn to_u128<'cx, C>(&self, cx: &mut C) -> Result> where C: Context<'cx>, { let mut digits = [0; 2]; let (sign, num_digits) = self.read_digits_le(cx, &mut digits); // Cast digits into a `u128` magnitude let n = (digits[0] as u128) | ((digits[1] as u128) << 64); let n = u128::from_le(n); // Leading zeroes are truncated and never returned. If there are additional // digits, the number is out of range. if matches!(sign, Sign::Negative) || num_digits > digits.len() { Err(RangeError(n)) } else { Ok(n) } } /// Gets a signed magnitude pair from a `BigInt`. /// /// The `BigInt` is calculated as:\ /// `Sign * (digit[0] x (2⁶⁴)⁰ + digit[0] x (2⁶⁴)¹ + digit[0] x (2⁶⁴)² ...)` pub fn to_digits_le<'cx, C>(&self, cx: &mut C) -> (Sign, Vec) where C: Context<'cx>, { let mut v = vec![0; self.len(cx)]; let (sign, len) = self.read_digits_le(cx, &mut v); // It shouldn't be possible for the number of digits to change. If it // it does, it's a correctness issue and not a soundness bug. debug_assert_eq!(v.len(), len); (sign, v) } /// Gets the sign from a `BigInt` and reads digits into a buffer. /// The returned `usize` is the total number of digits in the `BigInt`. /// /// # Example /// /// Read a `u256` from a `BigInt`. /// /// ``` /// # use std::error::Error; /// # use neon::{prelude::*, types::JsBigInt}; /// fn bigint_to_u256(cx: &mut FunctionContext, n: Handle) -> NeonResult<[u64; 4]> { /// let mut digits = [0; 4]; /// let (sign, num_digits) = n.read_digits_le(cx, &mut digits); /// /// if sign == JsBigInt::NEGATIVE { /// return cx.throw_error("Underflow reading u256 from BigInt"); /// } /// /// if num_digits > digits.len() { /// return cx.throw_error("Overflow reading u256 from BigInt"); /// } /// /// Ok(digits) /// } /// ``` pub fn read_digits_le<'cx, C>(&self, cx: &mut C, digits: &mut [u64]) -> (Sign, usize) where C: Context<'cx>, { let mut sign_bit = 0; let mut word_count = digits.len(); unsafe { sys::get_value_bigint_words( cx.env().to_raw(), self.0, &mut sign_bit, &mut word_count, digits.as_mut_ptr(), ) .unwrap(); } let sign = if sign_bit == 0 { Sign::Positive } else { Sign::Negative }; (sign, word_count) } /// Gets the number of `u64` digits in a `BigInt` pub fn len<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { // Get the length by reading into an empty slice and ignoring the sign self.read_digits_le(cx, &mut []).1 } } impl Value for JsBigInt {} unsafe impl TransparentNoCopyWrapper for JsBigInt { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl private::ValueInternal for JsBigInt { fn name() -> &'static str { "BigInt" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_bigint(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { Self(h) } } ================================================ FILE: crates/neon/src/types_impl/boxed.rs ================================================ use std::{ any::{self, Any}, ops::Deref, }; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, sys::{external, raw}, types::{boxed::private::JsBoxInner, private::ValueInternal, Value}, }; type BoxAny = Box; mod private { pub struct JsBoxInner { pub(super) local: crate::sys::raw::Local, // Cached raw pointer to the data contained in the `JsBox`. This value is // required to implement `Deref` for `JsBox`. Unlike most `Js` types, `JsBox` // is not a transparent wrapper around a `napi_value` and cannot implement `This`. // // Safety: `JsBox` cannot verify the lifetime. Store a raw pointer to force // uses to be marked unsafe. In practice, it can be treated as `'static` but // should only be exposed as part of a `Handle` tied to a `Context` lifetime. // Safety: The value must not move on the heap; we must never give a mutable // reference to the data until the `JsBox` is no longer accessible. pub(super) raw_data: *const T, } } /// A JavaScript smart pointer object that owns Rust data. /// /// The type `JsBox` provides shared ownership of a value of type `T`, /// allocated in the heap. The data is owned by the JavaScript engine and the /// lifetime is managed by the JavaScript garbage collector. /// /// Shared references in Rust disallow mutation by default, and `JsBox` is no /// exception: you cannot generally obtain a mutable reference to something /// inside a `JsBox`. If you need to mutate through a `JsBox`, use /// [`Cell`](https://doc.rust-lang.org/std/cell/struct.Cell.html), /// [`RefCell`](https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html), /// or one of the other types that provide /// [interior mutability](https://doc.rust-lang.org/book/ch15-05-interior-mutability.html). /// /// Values contained by a `JsBox` must implement the `Finalize` trait. `Finalize::finalize` /// will execute with the value in a `JsBox` immediately before the `JsBox` is garbage /// collected. If no additional finalization is necessary, an emply implementation may /// be provided. /// /// /// ## `Deref` behavior /// /// `JsBox` automatically dereferences to `T` (via the `Deref` trait), so /// you can call `T`'s method on a value of type `JsBox`. /// /// ```rust /// # use neon::prelude::*; /// # fn my_neon_function(mut cx: FunctionContext) -> JsResult { /// let vec: Handle>> = cx.boxed(vec![1, 2, 3]); /// /// println!("Length: {}", vec.len()); /// # Ok(cx.undefined()) /// # } /// ``` /// /// ## Examples /// /// Passing some immutable data between Rust and JavaScript. /// /// ```rust /// # use neon::prelude::*; /// # use std::path::{Path, PathBuf}; /// fn create_path(mut cx: FunctionContext) -> JsResult> { /// let path = cx.argument::(0)?.value(&mut cx); /// let path = Path::new(&path).to_path_buf(); /// /// Ok(cx.boxed(path)) /// } /// /// fn print_path(mut cx: FunctionContext) -> JsResult { /// let path = cx.argument::>(0)?; /// /// println!("{}", path.display()); /// /// Ok(cx.undefined()) /// } /// ``` /// /// Passing a user defined struct wrapped in a `RefCell` for mutability. This /// pattern is useful for creating classes in JavaScript. /// /// ```rust /// # use neon::prelude::*; /// # use std::cell::RefCell; /// /// type BoxedPerson = JsBox>; /// /// struct Person { /// name: String, /// } /// /// impl Finalize for Person {} /// /// impl Person { /// pub fn new(name: String) -> Self { /// Person { name } /// } /// /// pub fn set_name(&mut self, name: String) { /// self.name = name; /// } /// /// pub fn greet(&self) -> String { /// format!("Hello, {}!", self.name) /// } /// } /// /// fn person_new(mut cx: FunctionContext) -> JsResult { /// let name = cx.argument::(0)?.value(&mut cx); /// let person = RefCell::new(Person::new(name)); /// /// Ok(cx.boxed(person)) /// } /// /// fn person_set_name(mut cx: FunctionContext) -> JsResult { /// let person = cx.argument::(0)?; /// let mut person = person.borrow_mut(); /// let name = cx.argument::(1)?.value(&mut cx); /// /// person.set_name(name); /// /// Ok(cx.undefined()) /// } /// /// fn person_greet(mut cx: FunctionContext) -> JsResult { /// let person = cx.argument::(0)?; /// let person = person.borrow(); /// let greeting = person.greet(); /// /// Ok(cx.string(greeting)) /// } #[repr(transparent)] pub struct JsBox(JsBoxInner); impl std::fmt::Debug for JsBoxInner { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "JsBox<{}>", std::any::type_name::()) } } impl std::fmt::Debug for JsBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Debug::fmt(&self.0, f) } } // Attempt to use a `napi_value` as a `napi_external` to unwrap a `BoxAny> /// Safety: `local` must be a `napi_value` that is valid for the lifetime `'a`. unsafe fn maybe_external_deref<'a>(env: Env, local: raw::Local) -> Option<&'a BoxAny> { external::deref::(env.to_raw(), local).map(|v| &*v) } // Custom `Clone` implementation since `T` might not be `Clone` impl Clone for JsBoxInner { fn clone(&self) -> Self { *self } } impl Object for JsBox {} impl Copy for JsBoxInner {} impl Value for JsBox {} unsafe impl TransparentNoCopyWrapper for JsBox { type Inner = JsBoxInner; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsBox { fn name() -> &'static str { any::type_name::() } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { let data = unsafe { maybe_external_deref(cx.env(), other.to_local()) }; data.map(|v| v.is::()).unwrap_or(false) } fn downcast(cx: &mut Cx, other: &Other) -> Option { let local = other.to_local(); let data = unsafe { maybe_external_deref(cx.env(), local) }; // Attempt to downcast the `Option<&BoxAny>` to `Option<*const T>` data.and_then(|v| v.downcast_ref()) .map(|raw_data| Self(JsBoxInner { local, raw_data })) } fn to_local(&self) -> raw::Local { self.0.local } unsafe fn from_local(env: Env, local: raw::Local) -> Self { let raw_data = unsafe { maybe_external_deref(env, local) } .expect("Failed to unwrap napi_external as Box") .downcast_ref() .expect("Failed to downcast Any"); Self(JsBoxInner { local, raw_data }) } } /// Values contained by a `JsBox` must be `Finalize + 'static` /// /// ### `Finalize` /// /// The `sys::prelude::Finalize` trait provides a `finalize` method that will be called /// immediately before the `JsBox` is garbage collected. /// /// ### `'static' /// /// The lifetime of a `JsBox` is managed by the JavaScript garbage collector. Since Rust /// is unable to verify the lifetime of the contents, references must be valid for the /// entire duration of the program. This does not mean that the `JsBox` will be valid /// until the application terminates, only that its lifetime is indefinite. impl JsBox { /// Constructs a new `JsBox` containing `value`. pub fn new<'cx, C: Context<'cx>>(cx: &mut C, value: T) -> Handle<'cx, JsBox> { // This function will execute immediately before the `JsBox` is garbage collected. // It unwraps the `napi_external`, downcasts the `BoxAny` and moves the type // out of the `Box`. Lastly, it calls the trait method `Finalize::finalize` of the // contained value `T`. fn finalizer(env: raw::Env, data: BoxAny) { let data = *data.downcast::().unwrap(); let env = Env::from(env); Cx::with_context(env, move |mut cx| data.finalize(&mut cx)); } Self::create_external(cx, value, finalizer::) } } impl JsBox { pub(crate) fn manually_finalize<'cx>(cx: &mut Cx<'cx>, value: T) -> Handle<'cx, JsBox> { fn finalizer(_env: raw::Env, _data: BoxAny) {} Self::create_external(cx, value, finalizer) } fn create_external<'cx, C: Context<'cx>>( cx: &mut C, value: T, finalizer: fn(raw::Env, BoxAny), ) -> Handle<'cx, JsBox> { let v = Box::new(value) as BoxAny; // Since this value was just constructed, we know it is `T` let raw_data = &*v as *const dyn Any as *const T; let local = unsafe { external::create(cx.env().to_raw(), v, finalizer) }; Handle::new_internal(JsBox(JsBoxInner { local, raw_data })) } } impl JsBox { /// Gets a reference to the inner value of a [`JsBox`]. This method is similar to /// [dereferencing](JsBox::deref) a `JsBox` (e.g., `&*boxed`), but the lifetime /// is _safely_ extended to `'cx`. /// /// See also [`Handle::as_inner`]. // N.B.: This would be cleaner with https://github.com/rust-lang/rust/issues/44874 pub fn deref<'cx>(v: &Handle<'cx, Self>) -> &'cx T { v.as_inner() } } impl<'cx, T: 'static> Handle<'cx, JsBox> { /// Gets a reference to the inner value of a [`JsBox`]. This method is similar to /// [dereferencing](JsBox::deref) a `JsBox` (e.g., `&*boxed`), but the lifetime /// is _safely_ extended to `'cx`. /// /// See also [`JsBox::deref`]. pub fn as_inner(&self) -> &'cx T { // # Safety // JS values associated with an in-scope `Context` *cannot* be garbage collected. // This value is guaranteed to live at least as long as `'cx`. unsafe { &*self.0.raw_data } } } impl Deref for JsBox { type Target = T; fn deref(&self) -> &Self::Target { // # Safety // `T` will live at least as long as `JsBox` because it may not be garbage // collected while in scope and only immutable references can be obtained. unsafe { &*self.0.raw_data } } } /// A trait for finalizing values owned by the main JavaScript thread. /// /// [`Finalize::finalize`] is executed on the main JavaScript thread /// immediately before garbage collection. /// /// Values contained by a `JsBox` must implement `Finalize`. /// /// ## Examples /// /// `Finalize` provides a default implementation that does not perform any finalization. /// /// ```rust /// # use neon::prelude::*; /// struct Point(f64, f64); /// /// impl Finalize for Point {} /// ``` /// /// A `finalize` method may be specified for performing clean-up operations before dropping /// the contained value. /// /// ```rust /// # use neon::prelude::*; /// struct Point(f64, f64); /// /// impl Finalize for Point { /// fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { /// cx.global_object() /// .method(cx.cx_mut(), "emit").unwrap() /// .args(("gc_point", self.0, self.1)).unwrap() /// .exec().unwrap(); /// } /// } /// ``` pub trait Finalize: Sized { fn finalize<'a, C: Context<'a>>(self, _: &mut C) {} } // Primitives impl Finalize for bool {} impl Finalize for char {} impl Finalize for i8 {} impl Finalize for i16 {} impl Finalize for i32 {} impl Finalize for i64 {} impl Finalize for isize {} impl Finalize for u8 {} impl Finalize for u16 {} impl Finalize for u32 {} impl Finalize for u64 {} impl Finalize for usize {} impl Finalize for f32 {} impl Finalize for f64 {} // Common types impl Finalize for String {} impl Finalize for std::path::PathBuf {} // Tuples macro_rules! finalize_tuple_impls { ($( $name:ident )+) => { impl<$($name: Finalize),+> Finalize for ($($name,)+) { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { #![allow(non_snake_case)] let ($($name,)+) = self; ($($name.finalize(cx),)+); } } }; } impl Finalize for () {} finalize_tuple_impls! { T0 } finalize_tuple_impls! { T0 T1 } finalize_tuple_impls! { T0 T1 T2 } finalize_tuple_impls! { T0 T1 T2 T3 } finalize_tuple_impls! { T0 T1 T2 T3 T4 } finalize_tuple_impls! { T0 T1 T2 T3 T4 T5 } finalize_tuple_impls! { T0 T1 T2 T3 T4 T5 T6 } finalize_tuple_impls! { T0 T1 T2 T3 T4 T5 T6 T7 } // Collections impl Finalize for Vec { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { for item in self { item.finalize(cx); } } } // Smart pointers and other wrappers impl Finalize for std::boxed::Box { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { (*self).finalize(cx); } } impl Finalize for Option { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { if let Some(v) = self { v.finalize(cx); } } } impl Finalize for std::rc::Rc { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { if let Ok(v) = std::rc::Rc::try_unwrap(self) { v.finalize(cx); } } } impl Finalize for std::sync::Arc { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { if let Ok(v) = std::sync::Arc::try_unwrap(self) { v.finalize(cx); } } } impl Finalize for std::sync::Mutex { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { if let Ok(v) = self.into_inner() { v.finalize(cx); } } } impl Finalize for std::sync::RwLock { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { if let Ok(v) = self.into_inner() { v.finalize(cx); } } } impl Finalize for std::cell::Cell { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { self.into_inner().finalize(cx); } } impl Finalize for std::cell::RefCell { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { self.into_inner().finalize(cx); } } ================================================ FILE: crates/neon/src/types_impl/buffer/lock.rs ================================================ use std::{cell::RefCell, ops::Range}; use crate::{ context::Context, types::buffer::{BorrowError, Ref, RefMut}, }; #[derive(Debug)] /// A temporary lock of an execution context. /// /// While a lock is alive, no JavaScript code can be executed in the execution context. /// /// Values that support the `Borrow` trait may be dynamically borrowed by passing a /// [`Lock`]. pub struct Lock<'cx, C> { pub(super) cx: &'cx C, pub(super) ledger: RefCell, } impl<'a: 'cx, 'cx, C> Lock<'cx, C> where C: Context<'a>, { /// Constructs a new [`Lock`] and locks the VM. See also [`Context::lock`]. pub fn new(cx: &'cx mut C) -> Lock<'cx, C> { Lock { cx, ledger: Default::default(), } } } #[derive(Debug, Default)] // Bookkeeping for dynamically check borrowing rules // // Ranges are open on the end: `[start, end)` pub(super) struct Ledger { // Mutable borrows. Should never overlap with other borrows. pub(super) owned: Vec>, // Immutable borrows. May overlap or contain duplicates. pub(super) shared: Vec>, } impl Ledger { // Convert a slice of arbitrary type and size to a range of bytes addresses // // Alignment does not matter because we are only interested in bytes. pub(super) fn slice_to_range(data: &[T]) -> Range<*const u8> { let Range { start, end } = data.as_ptr_range(); (start.cast())..(end.cast()) } // Dynamically check a slice conforms to borrow rules before returning by // using interior mutability of the ledger. pub(super) fn try_borrow<'a, T>( ledger: &'a RefCell, data: &'a [T], ) -> Result, BorrowError> { if !data.is_empty() { ledger.borrow_mut().try_add_borrow(data)?; } Ok(Ref { ledger, data }) } // Dynamically check a mutable slice conforms to borrow rules before returning by // using interior mutability of the ledger. pub(super) fn try_borrow_mut<'a, T>( ledger: &'a RefCell, data: &'a mut [T], ) -> Result, BorrowError> { if !data.is_empty() { ledger.borrow_mut().try_add_borrow_mut(data)?; } Ok(RefMut { ledger, data }) } // Try to add an immutable borrow to the ledger fn try_add_borrow(&mut self, data: &[T]) -> Result<(), BorrowError> { let range = Self::slice_to_range(data); // Check if the borrow overlaps with any active mutable borrow check_overlap(&self.owned, &range)?; // Record a record of the immutable borrow self.shared.push(range); Ok(()) } // Try to add a mutable borrow to the ledger fn try_add_borrow_mut(&mut self, data: &mut [T]) -> Result<(), BorrowError> { let range = Self::slice_to_range(data); // Check if the borrow overlaps with any active mutable borrow check_overlap(&self.owned, &range)?; // Check if the borrow overlaps with any active immutable borrow check_overlap(&self.shared, &range)?; // Record a record of the mutable borrow self.owned.push(range); Ok(()) } } fn is_disjoint(a: &Range<*const u8>, b: &Range<*const u8>) -> bool { b.start >= a.end || a.start >= b.end } fn check_overlap( existing: &[Range<*const u8>], range: &Range<*const u8>, ) -> Result<(), BorrowError> { if existing.iter().all(|i| is_disjoint(i, range)) { Ok(()) } else { Err(BorrowError::new()) } } #[cfg(test)] mod tests { use std::cell::RefCell; use std::error::Error; use std::mem; use std::slice; use super::{BorrowError, Ledger}; // Super unsafe, but we only use it for testing `Ledger` fn unsafe_aliased_slice(data: &mut [T]) -> &'static mut [T] { unsafe { slice::from_raw_parts_mut(data.as_mut_ptr(), data.len()) } } #[test] fn test_overlapping_immutable_borrows() -> Result<(), Box> { let ledger = RefCell::new(Ledger::default()); let data = [0u8; 128]; Ledger::try_borrow(&ledger, &data[0..10])?; Ledger::try_borrow(&ledger, &data[0..100])?; Ledger::try_borrow(&ledger, &data[20..])?; Ok(()) } #[test] fn test_nonoverlapping_borrows() -> Result<(), Box> { let ledger = RefCell::new(Ledger::default()); let mut data = [0; 16]; let (a, b) = data.split_at_mut(4); let _a = Ledger::try_borrow_mut(&ledger, a)?; let _b = Ledger::try_borrow(&ledger, b)?; Ok(()) } #[test] fn test_overlapping_borrows() -> Result<(), Box> { let ledger = RefCell::new(Ledger::default()); let mut data = [0; 16]; let a = unsafe_aliased_slice(&mut data[4..8]); let b = unsafe_aliased_slice(&mut data[6..12]); let ab = Ledger::try_borrow(&ledger, a)?; // Should fail because it overlaps assert_eq!( Ledger::try_borrow_mut(&ledger, b).unwrap_err(), BorrowError::new(), ); // Drop the first borrow mem::drop(ab); // Should succeed because previous borrow was dropped let bb = Ledger::try_borrow_mut(&ledger, b)?; // Should fail because it overlaps assert_eq!( Ledger::try_borrow(&ledger, a).unwrap_err(), BorrowError::new(), ); // Drop the second borrow mem::drop(bb); // Should succeed because previous borrow was dropped let _ab = Ledger::try_borrow(&ledger, a)?; Ok(()) } } ================================================ FILE: crates/neon/src/types_impl/buffer/mod.rs ================================================ //! Types and traits for working with binary buffers. use std::{ cell::RefCell, error::Error, fmt::{self, Debug, Display}, marker::PhantomData, ops::{Deref, DerefMut}, }; use crate::{ context::Context, handle::Handle, result::{JsResult, NeonResult, ResultExt}, types::{ buffer::lock::{Ledger, Lock}, JsArrayBuffer, JsTypedArray, Value, }, }; pub(crate) mod lock; pub(super) mod types; pub use types::Binary; /// A trait allowing Rust to borrow binary data from the memory buffer of JavaScript /// [typed arrays][typed-arrays]. /// /// This trait provides both statically and dynamically checked borrowing. As usual /// in Rust, mutable borrows are guaranteed not to overlap with other borrows. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// use neon::types::buffer::TypedArray; /// /// fn double(mut cx: FunctionContext) -> JsResult { /// let mut array: Handle = cx.argument(0)?; /// /// for elem in array.as_mut_slice(&mut cx).iter_mut() { /// *elem *= 2; /// } /// /// Ok(cx.undefined()) /// } /// ``` /// /// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays pub trait TypedArray: Value { type Item: Binary; /// Statically checked immutable borrow of binary data. /// /// This may not be used if a mutable borrow is in scope. For the dynamically /// checked variant see [`TypedArray::try_borrow`]. fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] where C: Context<'cx>; /// Statically checked mutable borrow of binary data. /// /// This may not be used if any other borrow is in scope. For the dynamically /// checked variant see [`TypedArray::try_borrow_mut`]. fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] where C: Context<'cx>; /// Dynamically checked immutable borrow of binary data, returning an error if the /// the borrow would overlap with a mutable borrow. /// /// The borrow lasts until [`Ref`] exits scope. /// /// This is the dynamically checked version of [`TypedArray::as_slice`]. fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> where C: Context<'cx>; /// Dynamically checked mutable borrow of binary data, returning an error if the /// the borrow would overlap with an active borrow. /// /// The borrow lasts until [`RefMut`] exits scope. /// /// This is the dynamically checked version of [`TypedArray::as_mut_slice`]. fn try_borrow_mut<'cx, 'a, C>( &mut self, lock: &'a Lock, ) -> Result, BorrowError> where C: Context<'cx>; /// Returns the size, in bytes, of the allocated binary data. fn size<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>; /// Constructs an instance from a slice by copying its contents. fn from_slice<'cx, C>(cx: &mut C, slice: &[Self::Item]) -> JsResult<'cx, Self> where C: Context<'cx>; } #[derive(Debug)] /// Wraps binary data immutably borrowed from a JavaScript value. pub struct Ref<'a, T> { data: &'a [T], ledger: &'a RefCell, } #[derive(Debug)] /// Wraps binary data mutably borrowed from a JavaScript value. pub struct RefMut<'a, T> { data: &'a mut [T], ledger: &'a RefCell, } impl<'a, T> Deref for Ref<'a, T> { type Target = [T]; fn deref(&self) -> &Self::Target { self.data } } impl<'a, T> Deref for RefMut<'a, T> { type Target = [T]; fn deref(&self) -> &Self::Target { self.data } } impl<'a, T> DerefMut for RefMut<'a, T> { fn deref_mut(&mut self) -> &mut Self::Target { self.data } } impl<'a, T> Drop for Ref<'a, T> { fn drop(&mut self) { if self.is_empty() { return; } let mut ledger = self.ledger.borrow_mut(); let range = Ledger::slice_to_range(self.data); let i = ledger.shared.iter().rposition(|r| r == &range).unwrap(); ledger.shared.remove(i); } } impl<'a, T> Drop for RefMut<'a, T> { fn drop(&mut self) { if self.is_empty() { return; } let mut ledger = self.ledger.borrow_mut(); let range = Ledger::slice_to_range(self.data); let i = ledger.owned.iter().rposition(|r| r == &range).unwrap(); ledger.owned.remove(i); } } #[derive(Eq, PartialEq)] /// An error returned by [`TypedArray::try_borrow`] or [`TypedArray::try_borrow_mut`] indicating /// that a mutable borrow would overlap with another borrow. /// /// [`BorrowError`] may be converted to an exception with [`ResultExt::or_throw`]. pub struct BorrowError { _private: (), } impl BorrowError { fn new() -> Self { BorrowError { _private: () } } } impl Error for BorrowError {} impl Display for BorrowError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Display::fmt("Borrow overlaps with an active mutable borrow", f) } } impl Debug for BorrowError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BorrowError").finish() } } impl ResultExt for Result { fn or_throw<'a, C: Context<'a>>(self, cx: &mut C) -> NeonResult { self.or_else(|_| cx.throw_error("BorrowError")) } } /// Represents a typed region of an [`ArrayBuffer`](crate::types::JsArrayBuffer). /// /// A `Region` can be created via the /// [`Handle::region()`](crate::handle::Handle::region) or /// [`JsTypedArray::region()`](crate::types::JsTypedArray::region) methods. /// /// A region is **not** checked for validity until it is converted to /// a typed array via [`to_typed_array()`](Region::to_typed_array) or /// [`JsTypedArray::from_region()`](crate::types::JsTypedArray::from_region). /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn f(mut cx: FunctionContext) -> JsResult { /// // Allocate a 16-byte ArrayBuffer and a uint32 array of length 2 (i.e., 8 bytes) /// // starting at byte offset 4 of the buffer: /// // /// // 0 4 8 12 16 /// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// // buf: | | | | | | | | | | | | | | | | | /// // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// // ^ ^ /// // | | /// // +-------+-------+ /// // arr: | | | /// // +-------+-------+ /// // 0 1 2 /// let buf = cx.array_buffer(16)?; /// let arr = JsUint32Array::from_region(&mut cx, &buf.region(4, 2))?; /// # Ok(arr) /// # } /// ``` #[derive(Clone, Copy)] pub struct Region<'cx, T: Binary> { buffer: Handle<'cx, JsArrayBuffer>, offset: usize, len: usize, phantom: PhantomData, } impl<'cx, T> Region<'cx, T> where T: Binary, JsTypedArray: Value, { /// Returns the handle to the region's buffer. pub fn buffer(&self) -> Handle<'cx, JsArrayBuffer> { self.buffer } /// Returns the starting byte offset of the region. pub fn offset(&self) -> usize { self.offset } /// Returns the number of elements of type `T` in the region. #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize { self.len } /// Returns the size of the region in bytes, which is equal to /// `(self.len() * size_of::())`. pub fn size(&self) -> usize { self.len * std::mem::size_of::() } /// Constructs a typed array for this buffer region. /// /// The resulting typed array has `self.len()` elements and a size of /// `self.size()` bytes. /// /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. pub fn to_typed_array<'c, C>(&self, cx: &mut C) -> JsResult<'c, JsTypedArray> where C: Context<'c>, { JsTypedArray::from_region(cx, self) } } mod private { use super::Binary; use crate::sys::raw; use std::fmt::{Debug, Formatter}; use std::marker::PhantomData; pub trait Sealed {} #[derive(Clone)] pub struct JsTypedArrayInner { pub(super) local: raw::Local, pub(super) buffer: raw::Local, pub(super) _type: PhantomData, } impl Debug for JsTypedArrayInner { fn fmt(&self, f: &mut Formatter) -> Result<(), std::fmt::Error> { f.write_str("JsTypedArrayInner { ")?; f.write_str("local: ")?; self.local.fmt(f)?; f.write_str(", buffer: ")?; self.buffer.fmt(f)?; f.write_str(", _type: PhantomData")?; f.write_str(" }")?; Ok(()) } } impl Copy for JsTypedArrayInner {} } ================================================ FILE: crates/neon/src/types_impl/buffer/types.rs ================================================ use std::{marker::PhantomData, slice}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, result::{JsResult, Throw}, sys::{self, raw, typedarray::TypedArrayInfo, TypedArrayType}, types_impl::{ buffer::{ lock::{Ledger, Lock}, private::{self, JsTypedArrayInner}, BorrowError, Ref, RefMut, Region, TypedArray, }, private::ValueInternal, Value, }, }; #[cfg(feature = "doc-comment")] use doc_comment::doc_comment; #[cfg(not(feature = "doc-comment"))] macro_rules! doc_comment { {$comment:expr, $decl:item} => { $decl }; } /// The type of Node /// [`Buffer`](https://nodejs.org/api/buffer.html) /// objects. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// use neon::types::buffer::TypedArray; /// /// fn make_sequence(mut cx: FunctionContext) -> JsResult { /// let len = cx.argument::(0)?.value(&mut cx); /// let mut buffer = cx.buffer(len as usize)?; /// /// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { /// *elem = i as u8; /// } /// /// Ok(buffer) /// } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsBuffer(raw::Local); impl JsBuffer { /// Constructs a new `Buffer` object, safely zero-filled. /// /// **See also:** [`Context::buffer`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { unsafe { let result = sys::buffer::new(cx.env().to_raw(), len); if let Ok(buf) = result { Ok(Handle::new_internal(Self(buf))) } else { Err(Throw::new()) } } } /// Constructs a `JsBuffer` from a slice by copying its contents. /// /// This method is defined on `JsBuffer` as a convenience and delegates to /// [`TypedArray::from_slice`][TypedArray::from_slice]. pub fn from_slice<'cx, C>(cx: &mut C, slice: &[u8]) -> JsResult<'cx, Self> where C: Context<'cx>, { ::from_slice(cx, slice) } /// Constructs a new `Buffer` object with uninitialized memory pub unsafe fn uninitialized<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { let result = sys::buffer::uninitialized(cx.env().to_raw(), len); if let Ok((buf, _)) = result { Ok(Handle::new_internal(Self(buf))) } else { Err(Throw::new()) } } #[cfg(feature = "external-buffers")] #[cfg_attr(docsrs, doc(cfg(feature = "external-buffers")))] /// Construct a new `Buffer` from bytes allocated by Rust. /// /// # Compatibility Note /// /// Some Node environments are built using V8's _sandboxed pointers_ functionality, which /// [disallows the use of external buffers](https://www.electronjs.org/blog/v8-memory-cage). /// In those environments, calling the underlying /// [runtime function](https://nodejs.org/api/n-api.html#napi_create_external_buffer) /// used by this method results in an immediate termination of the Node VM. /// /// As a result, this API is disabled by default. If you are confident that your code will /// only be used in environments that disable sandboxed pointers, you can make use of this /// method by enabling the **`external-buffers`** feature flag. pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, Self> where C: Context<'a>, T: AsMut<[u8]> + Send + 'static, { let env = cx.env().to_raw(); let value = unsafe { sys::buffer::new_external(env, data) }; Handle::new_internal(Self(value)) } } unsafe impl TransparentNoCopyWrapper for JsBuffer { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsBuffer { fn name() -> &'static str { "Buffer" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_buffer(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { Self(h) } } impl Value for JsBuffer {} impl Object for JsBuffer {} impl private::Sealed for JsBuffer {} impl TypedArray for JsBuffer { type Item = u8; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] where C: Context<'cx>, { // # Safety // Only the `Context` with the *most* narrow scope is accessible because `compute_scoped` // and `execute_scope` take an exclusive reference to `Context`. A handle is always // associated with a `Context` and the value will not be garbage collected while that // `Context` is in scope. This means that the referenced data is valid *at least* as long // as `Context`, even if the `Handle` is dropped. unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_local()) } } fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] where C: Context<'cx>, { // # Safety // See `as_slice` unsafe { sys::buffer::as_mut_slice(cx.env().to_raw(), self.to_local()) } } fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> where C: Context<'cx>, { // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow(&lock.ledger, unsafe { sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_local()) }) } fn try_borrow_mut<'cx, 'a, C>( &mut self, lock: &'a Lock, ) -> Result, BorrowError> where C: Context<'cx>, { // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow_mut(&lock.ledger, unsafe { sys::buffer::as_mut_slice(lock.cx.env().to_raw(), self.to_local()) }) } fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { unsafe { sys::buffer::size(cx.env().to_raw(), self.to_local()) } } fn from_slice<'cx, C>(cx: &mut C, slice: &[u8]) -> JsResult<'cx, Self> where C: Context<'cx>, { let mut buffer = cx.buffer(slice.len())?; let target = buffer.as_mut_slice(cx); target.copy_from_slice(slice); Ok(buffer) } } /// The type of JavaScript /// [`ArrayBuffer`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) /// objects. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// use neon::types::buffer::TypedArray; /// /// fn make_sequence(mut cx: FunctionContext) -> JsResult { /// let len = cx.argument::(0)?.value(&mut cx); /// let mut buffer = cx.array_buffer(len as usize)?; /// /// for (i, elem) in buffer.as_mut_slice(&mut cx).iter_mut().enumerate() { /// *elem = i as u8; /// } /// /// Ok(buffer) /// } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsArrayBuffer(raw::Local); impl JsArrayBuffer { /// Constructs a new `JsArrayBuffer` object, safely zero-filled. /// /// **See also:** [`Context::array_buffer`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> JsResult<'a, Self> { unsafe { let result = sys::arraybuffer::new(cx.env().to_raw(), len); if let Ok(buf) = result { Ok(Handle::new_internal(Self(buf))) } else { Err(Throw::new()) } } } /// Constructs a `JsArrayBuffer` from a slice by copying its contents. /// /// This method is defined on `JsArrayBuffer` as a convenience and delegates to /// [`TypedArray::from_slice`][TypedArray::from_slice]. pub fn from_slice<'cx, C>(cx: &mut C, slice: &[u8]) -> JsResult<'cx, Self> where C: Context<'cx>, { ::from_slice(cx, slice) } #[cfg(feature = "external-buffers")] #[cfg_attr(docsrs, doc(cfg(feature = "external-buffers")))] /// Construct a new `JsArrayBuffer` from bytes allocated by Rust. /// /// # Compatibility Note /// /// Some Node environments are built using V8's _sandboxed pointers_ functionality, which /// [disallows the use of external buffers](https://www.electronjs.org/blog/v8-memory-cage). /// In those environments, calling the underlying /// [runtime function](https://nodejs.org/api/n-api.html#napi_create_external_arraybuffer) /// used by this method results in an immediate termination of the Node VM. /// /// As a result, this API is disabled by default. If you are confident that your code will /// only be used in environments that disable sandboxed pointers, you can make use of this /// method by enabling the **`external-buffers`** feature flag. pub fn external<'a, C, T>(cx: &mut C, data: T) -> Handle<'a, Self> where C: Context<'a>, T: AsMut<[u8]> + Send + 'static, { let env = cx.env().to_raw(); let value = unsafe { sys::arraybuffer::new_external(env, data) }; Handle::new_internal(Self(value)) } /// Returns a region of this buffer. /// /// See also: [`Handle::region()`](Handle::region) for a more /// ergonomic form of this method. pub fn region<'cx, T: Binary>( buffer: &Handle<'cx, JsArrayBuffer>, offset: usize, len: usize, ) -> Region<'cx, T> { buffer.region(offset, len) } } impl<'cx> Handle<'cx, JsArrayBuffer> { /// Returns a [`Region`] representing a typed /// region of this buffer, starting at `offset` and containing `len` elements /// of type `T`. /// /// The region is **not** checked for validity by this method. Regions are only /// validated when they are converted to typed arrays. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn f(mut cx: FunctionContext) -> JsResult { /// let buf: Handle = cx.argument(0)?; /// let region = buf.region::(64, 8); /// println!("offset={}, len={}, size={}", region.offset(), region.len(), region.size()); /// # Ok(cx.undefined()) /// # } /// ``` /// /// See the [`Region`] documentation for more information. pub fn region(&self, offset: usize, len: usize) -> Region<'cx, T> { Region { buffer: *self, offset, len, phantom: PhantomData, } } } unsafe impl TransparentNoCopyWrapper for JsArrayBuffer { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsArrayBuffer { fn name() -> &'static str { "JsArrayBuffer" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_arraybuffer(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { Self(h) } } impl Value for JsArrayBuffer {} impl Object for JsArrayBuffer {} impl private::Sealed for JsArrayBuffer {} impl TypedArray for JsArrayBuffer { type Item = u8; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] where C: Context<'cx>, { unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_local()) } } fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] where C: Context<'cx>, { unsafe { sys::arraybuffer::as_mut_slice(cx.env().to_raw(), self.to_local()) } } fn try_borrow<'cx, 'a, C>(&self, lock: &'a Lock) -> Result, BorrowError> where C: Context<'cx>, { // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow(&lock.ledger, unsafe { sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_local()) }) } fn try_borrow_mut<'cx, 'a, C>( &mut self, lock: &'a Lock, ) -> Result, BorrowError> where C: Context<'cx>, { // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow_mut(&lock.ledger, unsafe { sys::arraybuffer::as_mut_slice(lock.cx.env().to_raw(), self.to_local()) }) } fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { unsafe { sys::arraybuffer::size(cx.env().to_raw(), self.to_local()) } } fn from_slice<'cx, C>(cx: &mut C, slice: &[u8]) -> JsResult<'cx, Self> where C: Context<'cx>, { let len = slice.len(); let mut buffer = JsArrayBuffer::new(cx, len)?; let target = buffer.as_mut_slice(cx); target.copy_from_slice(slice); Ok(buffer) } } /// A marker trait for all possible element types of binary buffers. /// /// This trait can only be implemented within the Neon library. pub trait Binary: private::Sealed + Copy { /// The internal Node-API enum value for this binary type. const TYPE_TAG: TypedArrayType; } /// The family of JavaScript [typed array][typed-arrays] types. /// /// ## Typed Arrays /// /// JavaScript's [typed arrays][typed-arrays] are objects that allow efficiently reading /// and writing raw binary data in memory. In Neon, the generic type `JsTypedArray` /// represents a JavaScript typed array with element type `T`. For example, a JavaScript /// [`Uint32Array`][Uint32Array] represents a compact array of 32-bit unsigned integers, /// and is represented in Neon as a `JsTypedArray`. /// /// Neon also offers a set of convenience shorthands for concrete instances of /// `JsTypedArray`, named after their corresponding JavaScript type. For example, /// `JsTypedArray` can also be referred to as [`JsUint32Array`][JsUint32Array]. /// /// The following table shows the complete set of typed array types, with both their /// JavaScript and Neon types: /// /// | Rust Type | Convenience Type | JavaScript Type | /// | ------------------------------ | -------------------------------------- | ---------------------------------- | /// | `JsTypedArray<`[`u8`][u8]`>` | [`JsUint8Array`][JsUint8Array] | [`Uint8Array`][Uint8Array] | /// | `JsTypedArray<`[`i8`][i8]`>` | [`JsInt8Array`][JsInt8Array] | [`Int8Array`][Int8Array] | /// | `JsTypedArray<`[`u16`][u16]`>` | [`JsUint16Array`][JsUint16Array] | [`Uint16Array`][Uint16Array] | /// | `JsTypedArray<`[`i16`][i16]`>` | [`JsInt16Array`][JsInt16Array] | [`Int16Array`][Int16Array] | /// | `JsTypedArray<`[`u32`][u32]`>` | [`JsUint32Array`][JsUint32Array] | [`Uint32Array`][Uint32Array] | /// | `JsTypedArray<`[`i32`][i32]`>` | [`JsInt32Array`][JsInt32Array] | [`Int32Array`][Int32Array] | /// | `JsTypedArray<`[`u64`][u64]`>` | [`JsBigUint64Array`][JsBigUint64Array] | [`BigUint64Array`][BigUint64Array] | /// | `JsTypedArray<`[`i64`][i64]`>` | [`JsBigInt64Array`][JsBigInt64Array] | [`BigInt64Array`][BigInt64Array] | /// | `JsTypedArray<`[`f32`][f32]`>` | [`JsFloat32Array`][JsFloat32Array] | [`Float32Array`][Float32Array] | /// | `JsTypedArray<`[`f64`][f64]`>` | [`JsFloat64Array`][JsFloat64Array] | [`Float64Array`][Float64Array] | /// /// ### Example: Creating an integer array /// /// This example creates a typed array of unsigned 32-bit integers with a user-specified /// length: /// /// ``` /// # use neon::prelude::*; /// fn create_int_array(mut cx: FunctionContext) -> JsResult> { /// let len = cx.argument::(0)?.value(&mut cx) as usize; /// JsTypedArray::new(&mut cx, len) /// } /// ``` /// /// ## Buffers /// /// Typed arrays are managed with the [`ArrayBuffer`][ArrayBuffer] type, which controls /// the storage of the underlying data buffer, and several typed views for managing access /// to the buffer. Neon provides access to the `ArrayBuffer` class with the /// [`JsArrayBuffer`](crate::types::JsArrayBuffer) type. /// /// Node also provides a [`Buffer`][Buffer] type, which is built on top of `ArrayBuffer` /// and provides additional functionality. Neon provides access to the `Buffer` class /// with the [`JsBuffer`](crate::types::JsBuffer) type. /// /// Many of Node's I/O APIs work with these types, and they can also be used for /// compact in-memory data structures, which can be shared efficiently between /// JavaScript and Rust without copying. /// /// [u8]: std::primitive::u8 /// [i8]: std::primitive::i8 /// [u16]: std::primitive::u16 /// [i16]: std::primitive::i16 /// [u32]: std::primitive::u32 /// [i32]: std::primitive::i32 /// [u64]: std::primitive::u64 /// [i64]: std::primitive::i64 /// [f32]: std::primitive::f32 /// [f64]: std::primitive::f64 /// [JsUint8Array]: crate::types::JsUint8Array /// [JsInt8Array]: crate::types::JsInt8Array /// [JsUint16Array]: crate::types::JsUint16Array /// [JsInt16Array]: crate::types::JsInt16Array /// [JsUint32Array]: crate::types::JsUint32Array /// [JsInt32Array]: crate::types::JsInt32Array /// [JsBigUint64Array]: crate::types::JsBigUint64Array /// [JsBigInt64Array]: crate::types::JsBigInt64Array /// [JsFloat32Array]: crate::types::JsFloat32Array /// [JsFloat64Array]: crate::types::JsFloat64Array /// [Uint8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array /// [Int8Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array /// [Uint16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array /// [Int16Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array /// [Uint32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array /// [Int32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array /// [BigUint64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array /// [BigInt64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array /// [Float32Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array /// [Float64Array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array /// [typed-arrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays /// [ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer /// [Buffer]: https://nodejs.org/api/buffer.html #[derive(Debug)] #[repr(transparent)] pub struct JsTypedArray(JsTypedArrayInner); impl private::Sealed for JsTypedArray {} unsafe impl TransparentNoCopyWrapper for JsTypedArray { type Inner = JsTypedArrayInner; fn into_inner(self) -> Self::Inner { self.0 } } impl TypedArray for JsTypedArray where T: Binary, Self: Value, { type Item = T; fn as_slice<'cx, 'a, C>(&self, cx: &'a C) -> &'a [Self::Item] where C: Context<'cx>, { unsafe { let env = cx.env().to_raw(); let value = self.to_local(); let info = sys::typedarray::info(env, value); slice_from_info(info) } } fn as_mut_slice<'cx, 'a, C>(&mut self, cx: &'a mut C) -> &'a mut [Self::Item] where C: Context<'cx>, { unsafe { let env = cx.env().to_raw(); let value = self.to_local(); let info = sys::typedarray::info(env, value); slice_from_info_mut(info) } } fn try_borrow<'cx, 'b, C>( &self, lock: &'b Lock<'b, C>, ) -> Result, BorrowError> where C: Context<'cx>, { unsafe { let env = lock.cx.env().to_raw(); let value = self.to_local(); let info = sys::typedarray::info(env, value); // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow(&lock.ledger, slice_from_info(info)) } } fn try_borrow_mut<'cx, 'a, C>( &mut self, lock: &'a Lock<'a, C>, ) -> Result, BorrowError> where C: Context<'cx>, { unsafe { let env = lock.cx.env().to_raw(); let value = self.to_local(); let info = sys::typedarray::info(env, value); // The borrowed data must be guarded by `Ledger` before returning Ledger::try_borrow_mut(&lock.ledger, slice_from_info_mut(info)) } } fn size<'cx, C: Context<'cx>>(&self, cx: &mut C) -> usize { self.len(cx) * std::mem::size_of::() } fn from_slice<'cx, C>(cx: &mut C, slice: &[T]) -> JsResult<'cx, Self> where C: Context<'cx>, { let _elt_size = std::mem::size_of::(); let size = std::mem::size_of_val(slice); let buffer = cx.array_buffer(size)?; let mut array = Self::from_buffer(cx, buffer)?; let target = array.as_mut_slice(cx); target.copy_from_slice(slice); Ok(array) } } impl JsTypedArray where JsTypedArray: Value, { /// Constructs an instance from a slice by copying its contents. /// /// This method is defined on `JsTypedArray` as a convenience and delegates to /// [`TypedArray::from_slice`][TypedArray::from_slice]. pub fn from_slice<'cx, C>(cx: &mut C, slice: &[T]) -> JsResult<'cx, Self> where C: Context<'cx>, { as TypedArray>::from_slice(cx, slice) } } impl JsTypedArray where T: Binary, Self: Value, { /// Constructs a typed array that views `buffer`. /// /// The resulting typed array has `(buffer.size() / size_of::())` elements. pub fn from_buffer<'cx, 'b: 'cx, C>( cx: &mut C, buffer: Handle<'b, JsArrayBuffer>, ) -> JsResult<'cx, Self> where C: Context<'cx>, { let size = buffer.size(cx); let elt_size = std::mem::size_of::(); let len = size / elt_size; if (len * elt_size) != size { return cx.throw_range_error(format!( "byte length of typed array should be a multiple of {elt_size}" )); } Self::from_region(cx, &buffer.region(0, len)) } /// Constructs a typed array for the specified buffer region. /// /// The resulting typed array has `region.len()` elements and a size of /// `region.size()` bytes. /// /// Throws an exception if the region is invalid, for example if the starting /// offset is not properly aligned, or the length goes beyond the end of the /// buffer. pub fn from_region<'c, 'r, C>(cx: &mut C, region: &Region<'r, T>) -> JsResult<'c, Self> where C: Context<'c>, { let &Region { buffer, offset, len, .. } = region; let arr = unsafe { sys::typedarray::new( cx.env().to_raw(), T::TYPE_TAG, buffer.to_local(), offset, len, ) .map_err(|_| Throw::new())? }; Ok(Handle::new_internal(Self(JsTypedArrayInner { local: arr, buffer: buffer.to_local(), _type: PhantomData, }))) } /// Returns information about the backing buffer region for this typed array. pub fn region<'cx, C>(&self, cx: &mut C) -> Region<'cx, T> where C: Context<'cx>, { let env = cx.env(); let info = unsafe { sys::typedarray::info(env.to_raw(), self.to_local()) }; Region { buffer: Handle::new_internal(unsafe { JsArrayBuffer::from_local(cx.env(), info.buf) }), offset: info.offset, len: info.length, phantom: PhantomData, } } /// Constructs a new typed array of length `len`. /// /// The resulting typed array has a newly allocated storage buffer of /// size `(len * size_of::())` bytes. pub fn new<'cx, C>(cx: &mut C, len: usize) -> JsResult<'cx, Self> where C: Context<'cx>, { let buffer = cx.array_buffer(len * std::mem::size_of::())?; Self::from_region(cx, &buffer.region(0, len)) } /// Returns the [`JsArrayBuffer`](JsArrayBuffer) that owns the underlying storage buffer /// for this typed array. /// /// Note that the typed array might only reference a region of the buffer; use the /// [`offset()`](JsTypedArray::offset) and /// [`size()`](crate::types::buffer::TypedArray::size) methods to /// determine the region. pub fn buffer<'cx, C>(&self, cx: &mut C) -> Handle<'cx, JsArrayBuffer> where C: Context<'cx>, { Handle::new_internal(unsafe { JsArrayBuffer::from_local(cx.env(), self.0.buffer) }) } /// Returns the offset (in bytes) of the typed array from the start of its /// [`JsArrayBuffer`](JsArrayBuffer). pub fn offset<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_local()) }; info.offset } /// Returns the length of the typed array, i.e. the number of elements. /// /// Note that, depending on the element size, this is not necessarily the same as /// [`size()`](crate::types::buffer::TypedArray::size). In particular: /// /// ```ignore /// self.size() == self.len() * size_of::() /// ``` #[allow(clippy::len_without_is_empty)] pub fn len<'cx, C>(&self, cx: &mut C) -> usize where C: Context<'cx>, { let info = unsafe { sys::typedarray::info(cx.env().to_raw(), self.to_local()) }; info.length } } unsafe fn slice_from_info<'a, T>(info: TypedArrayInfo) -> &'a [T] { if info.length == 0 { &[] } else { slice::from_raw_parts(info.data.cast(), info.length) } } unsafe fn slice_from_info_mut<'a, T>(info: TypedArrayInfo) -> &'a mut [T] { if info.length == 0 { &mut [] } else { slice::from_raw_parts_mut(info.data.cast(), info.length) } } macro_rules! impl_typed_array { ($typ:ident, $etyp:ty, $($pattern:pat_param)|+, $tag:ident, $alias:ident, $two:expr$(,)?) => { impl private::Sealed for $etyp {} impl Binary for $etyp { const TYPE_TAG: TypedArrayType = TypedArrayType::$tag; } impl Value for JsTypedArray<$etyp> {} impl Object for JsTypedArray<$etyp> {} impl ValueInternal for JsTypedArray<$etyp> { fn name() -> &'static str { stringify!($typ) } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { let env = cx.env().to_raw(); let other = other.to_local(); if unsafe { !sys::tag::is_typedarray(env, other) } { return false; } let info = unsafe { sys::typedarray::info(env, other) }; matches!(info.typ, $($pattern)|+) } fn to_local(&self) -> raw::Local { self.0.local } unsafe fn from_local(env: Env, local: raw::Local) -> Self { // Safety: Recomputing this information ensures that the lifetime of the // buffer handle matches the lifetime of the typed array handle. let info = unsafe { sys::typedarray::info(env.to_raw(), local) }; Self(JsTypedArrayInner { local, buffer: info.buf, _type: PhantomData, }) } } doc_comment! { concat!( "The type of JavaScript [`", stringify!($typ), "`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/", stringify!($typ), ") objects. # Example ``` # use neon::prelude::*; use neon::types::buffer::TypedArray; fn double(mut cx: FunctionContext) -> JsResult { let mut array: Handle<", stringify!($alias), "> = cx.argument(0)?; for elem in array.as_mut_slice(&mut cx).iter_mut() { *elem *= ", stringify!($two), "; } Ok(cx.undefined()) } ```", ), pub type $alias = JsTypedArray<$etyp>; } }; } impl_typed_array!(Int8Array, i8, TypedArrayType::I8, I8, JsInt8Array, 2); impl_typed_array!( Uint8Array, u8, TypedArrayType::U8 | TypedArrayType::U8Clamped, U8, JsUint8Array, 2, ); impl_typed_array!(Int16Array, i16, TypedArrayType::I16, I16, JsInt16Array, 2); impl_typed_array!(Uint16Array, u16, TypedArrayType::U16, U16, JsUint16Array, 2); impl_typed_array!(Int32Array, i32, TypedArrayType::I32, I32, JsInt32Array, 2); impl_typed_array!(Uint32Array, u32, TypedArrayType::U32, U32, JsUint32Array, 2); impl_typed_array!( Float32Array, f32, TypedArrayType::F32, F32, JsFloat32Array, 2.0, ); impl_typed_array!( Float64Array, f64, TypedArrayType::F64, F64, JsFloat64Array, 2.0, ); impl_typed_array!( BigInt64Array, i64, TypedArrayType::I64, I64, JsBigInt64Array, 2, ); impl_typed_array!( BigUint64Array, u64, TypedArrayType::U64, U64, JsBigUint64Array, 2, ); ================================================ FILE: crates/neon/src/types_impl/date.rs ================================================ use std::{ error::Error, fmt::{self, Debug}, }; use super::{private::ValueInternal, Value}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, result::{JsResult, ResultExt}, sys::{self, raw}, }; /// The type of JavaScript /// [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) /// objects. /// /// # Example /// /// The following shows an example of converting Rust /// [`SystemTime`](std::time::SystemTime) timestamps to JavaScript `Date` objects. /// /// ``` /// # use neon::prelude::*; /// use easy_cast::Cast; // for safe numeric conversions /// use neon::types::JsDate; /// use std::{error::Error, fs::File, time::SystemTime}; /// /// /// Return the "modified" timestamp for the file at the given path. /// fn last_modified(path: &str) -> Result> { /// Ok(File::open(&path)? /// .metadata()? /// .modified()? /// .duration_since(SystemTime::UNIX_EPOCH)? /// .as_millis() /// .try_cast()?) /// } /// /// fn modified(mut cx: FunctionContext) -> JsResult { /// let path: Handle = cx.argument(0)?; /// /// last_modified(&path.value(&mut cx)) /// .and_then(|n| Ok(cx.date(n)?)) /// .or_else(|err| cx.throw_error(err.to_string())) /// } /// ``` #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] #[derive(Debug)] #[repr(transparent)] pub struct JsDate(raw::Local); impl Value for JsDate {} unsafe impl TransparentNoCopyWrapper for JsDate { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } /// An error produced when constructing a date with an invalid value. #[derive(Debug)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] pub struct DateError(DateErrorKind); impl DateError { pub fn kind(&self) -> DateErrorKind { self.0 } } impl fmt::Display for DateError { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str(self.0.as_str()) } } impl Error for DateError {} /// The error kinds corresponding to `DateError` #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] pub enum DateErrorKind { /// Produced for an initialization value greater than /// [`JsDate::MAX_VALUE`](JsDate::MAX_VALUE). Overflow, /// Produced for an initialization value lesser than /// [`JsDate::MIN_VALUE`](JsDate::MIN_VALUE). Underflow, } impl DateErrorKind { fn as_str(&self) -> &'static str { match *self { DateErrorKind::Overflow => "Date overflow", DateErrorKind::Underflow => "Date underflow", } } } impl<'a, T: Value> ResultExt> for Result, DateError> { /// Creates an `Error` on error fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, T> { self.or_else(|e| cx.throw_range_error(e.0.as_str())) } } impl JsDate { /// The smallest possible `Date` value, /// [defined by ECMAScript](https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.3.3). pub const MIN_VALUE: f64 = -8.64e15; /// The largest possible `Date` value, /// [defined by ECMAScript](https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.3.2). pub const MAX_VALUE: f64 = 8.64e15; /// Creates a new `Date`. It errors when `value` is outside the range of valid JavaScript /// `Date` values. When `value` is `NaN`, the operation will succeed but with an /// invalid `Date`. pub fn new<'a, C: Context<'a>, T: Into>( cx: &mut C, value: T, ) -> Result, DateError> { let env = cx.env().to_raw(); let time = value.into(); if time > JsDate::MAX_VALUE { return Err(DateError(DateErrorKind::Overflow)); } else if time < JsDate::MIN_VALUE { return Err(DateError(DateErrorKind::Underflow)); } let local = unsafe { sys::date::new_date(env, time) }; let date = Handle::new_internal(JsDate(local)); Ok(date) } /// Creates a new `Date` with lossy conversion for out of bounds `Date` values. /// Out of bounds values will be treated as `NaN`. pub fn new_lossy<'a, C: Context<'a>, V: Into>(cx: &mut C, value: V) -> Handle<'a, JsDate> { let env = cx.env().to_raw(); let local = unsafe { sys::date::new_date(env, value.into()) }; Handle::new_internal(JsDate(local)) } /// Gets the `Date`'s value. An invalid `Date` will return [`std::f64::NAN`]. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> f64 { let env = cx.env().to_raw(); unsafe { sys::date::value(env, self.to_local()) } } /// Checks if the `Date`'s value is valid. A `Date` is valid if its value is /// between [`JsDate::MIN_VALUE`] and [`JsDate::MAX_VALUE`] or if it is `NaN`. pub fn is_valid<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { let value = self.value(cx); (JsDate::MIN_VALUE..=JsDate::MAX_VALUE).contains(&value) } } impl ValueInternal for JsDate { fn name() -> &'static str { "object" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_date(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsDate(h) } } impl Object for JsDate {} ================================================ FILE: crates/neon/src/types_impl/error.rs ================================================ //! Types and traits representing JavaScript error values. use std::panic::{catch_unwind, UnwindSafe}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, result::{NeonResult, Throw}, sys::{self, raw}, types::{build, private::ValueInternal, utf8::Utf8, Value}, }; /// The type of JavaScript /// [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) /// objects. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // Create a type error: /// let err = cx.type_error("expected a number, found a string")?; /// /// // Add some custom diagnostic properties to the error: /// err.prop(&mut cx, "expected").set("number")?; /// err.prop(&mut cx, "found").set("string")?; /// /// // Throw the error: /// cx.throw(err)?; /// # Ok(cx.undefined()) /// # } /// ``` #[repr(transparent)] #[derive(Debug)] pub struct JsError(raw::Local); unsafe impl TransparentNoCopyWrapper for JsError { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsError { fn name() -> &'static str { "Error" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_error(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsError(h) } } impl Value for JsError {} impl Object for JsError {} impl JsError { /// Creates a direct instance of the [`Error`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error) class. /// /// **See also:** [`Context::error`] pub fn error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, ) -> NeonResult> { let msg = cx.string(msg.as_ref()); build(cx.env(), |out| unsafe { sys::error::new_error(cx.env().to_raw(), out, msg.to_local()); true }) } /// Creates an instance of the [`TypeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError) class. /// /// **See also:** [`Context::type_error`] pub fn type_error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, ) -> NeonResult> { let msg = cx.string(msg.as_ref()); build(cx.env(), |out| unsafe { sys::error::new_type_error(cx.env().to_raw(), out, msg.to_local()); true }) } /// Creates an instance of the [`RangeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError) class. /// /// **See also:** [`Context::range_error`] pub fn range_error<'a, C: Context<'a>, S: AsRef>( cx: &mut C, msg: S, ) -> NeonResult> { let msg = cx.string(msg.as_ref()); build(cx.env(), |out| unsafe { sys::error::new_range_error(cx.env().to_raw(), out, msg.to_local()); true }) } } pub(crate) fn convert_panics NeonResult>( env: Env, f: F, ) -> NeonResult { match catch_unwind(f) { Ok(result) => result, Err(panic) => { let msg = if let Some(string) = panic.downcast_ref::() { format!("internal error in Neon module: {string}") } else if let Some(str) = panic.downcast_ref::<&str>() { format!("internal error in Neon module: {str}") } else { "internal error in Neon module".to_string() }; let (data, len) = Utf8::from(&msg[..]).truncate().lower(); unsafe { sys::error::clear_exception(env.to_raw()); sys::error::throw_error_from_utf8(env.to_raw(), data, len); Err(Throw::new()) } } } } ================================================ FILE: crates/neon/src/types_impl/extract/array.rs ================================================ use std::{error, fmt, mem::MaybeUninit}; use crate::{ context::{internal::ContextInternal, Context, Cx}, handle::Handle, result::{JsResult, NeonResult, Throw}, sys, types::{ extract::{private, TryFromJs, TryIntoJs, TypeExpected}, private::ValueInternal, JsArray, JsValue, }, }; /// Extracts a [JavaScript array](JsArray) into a Rust collection or converts a collection to a JS array. /// /// Any collection that implements [`FromIterator`] and [`IntoIterator`] can be extracted. Extraction /// fails with [`ArrayError`] if the value is not an array or an element fails to be extracted. /// /// # Example /// /// ``` /// # use std::collections::HashSet; /// # use neon::types::extract::Array; /// #[neon::export] /// fn list_of_strings(Array(arr): Array>) -> Array> { /// Array(arr) /// } /// /// #[neon::export] /// fn double(Array(arr): Array>) -> Array> { /// Array(arr.into_iter().map(|x| x * 2.0)) /// } /// /// #[neon::export] /// fn dedupe(set: Array>) -> Array> { /// set /// } /// ``` /// /// **Note**: Only native JS arrays are accepted. For typed arrays use [`Uint8Array`](super::Uint8Array), /// [`Float64Array`](super::Float64Array), etc. pub struct Array(pub T); impl private::Sealed for Array {} impl<'cx, T> TryFromJs<'cx> for Array where T: FromIterator, T: IntoIterator, T::Item: TryFromJs<'cx>, { type Error = ArrayError<>::Error>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let env = cx.env().to_raw(); let v = v.to_local(); let len = unsafe { let mut len = 0; match sys::get_array_length(env, v, &mut len) { Err(sys::Status::PendingException) => return Err(Throw::new()), Err(sys::Status::ArrayExpected) => return Ok(Err(ArrayError::array())), res => res.unwrap(), } len }; (0..len) .map(|i| { let item = unsafe { let mut item = MaybeUninit::uninit(); match sys::get_element(env, v, i, item.as_mut_ptr()) { Err(sys::Status::PendingException) => return Err(Throw::new()), res => res.unwrap(), } Handle::new_internal(JsValue::from_local(cx.env(), item.assume_init())) }; match T::Item::try_from_js(cx, item) { Ok(Ok(item)) => Ok(Ok(item)), Ok(Err(err)) => Ok(Err(ArrayError::item(err))), Err(err) => Err(err), } }) .collect::, Throw>>() .map(|v| v.map(Array)) } } impl<'cx, T> TryIntoJs<'cx> for Array where T: IntoIterator, T::Item: TryIntoJs<'cx>, { type Value = JsArray; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { let iter = self.0.into_iter(); let env = cx.env().to_raw(); let (len, _) = iter.size_hint(); let arr = unsafe { let mut arr = MaybeUninit::uninit(); match sys::create_array_with_length(env, len, arr.as_mut_ptr()) { Err(sys::Status::PendingException) => return Err(Throw::new()), res => res.unwrap(), } arr.assume_init() }; for (i, item) in iter.enumerate() { let item = item.try_into_js(cx)?.to_local(); let Ok(i) = u32::try_from(i) else { return cx.throw_error("Exceeded maximum length of an array"); }; unsafe { match sys::set_element(env, arr, i, item) { Err(sys::Status::PendingException) => return Err(Throw::new()), res => res.unwrap(), } } } unsafe { Ok(Handle::new_internal(JsArray::from_local(cx.env(), arr))) } } } /// Error when extracting an [`Array`] #[derive(Debug)] pub enum ArrayError { /// Value was not a JavaScript array. Array(TypeExpected), /// An element failed to convert to `T::Item`. Item(E), } impl ArrayError { fn array() -> Self { Self::Array(TypeExpected::::new()) } fn item(err: E) -> Self { Self::Item(err) } } impl fmt::Display for ArrayError where E: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ArrayError::Array(err) => write!(f, "{}", err), ArrayError::Item(err) => write!(f, "{}", err), } } } impl error::Error for ArrayError where E: error::Error {} impl<'cx, E> TryIntoJs<'cx> for ArrayError where E: TryIntoJs<'cx>, { type Value = JsValue; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { match self { ArrayError::Array(err) => err.try_into_js(cx).map(|v| v.upcast()), ArrayError::Item(err) => err.try_into_js(cx).map(|v| v.upcast()), } } } impl<'cx, E> private::Sealed for ArrayError where E: TryIntoJs<'cx> {} ================================================ FILE: crates/neon/src/types_impl/extract/boxed.rs ================================================ use crate::{ context::{Context, Cx}, handle::Handle, result::{JsResult, NeonResult}, types::{ extract::{private, TryFromJs, TryIntoJs, TypeExpected}, Finalize, JsBox, JsValue, }, }; /// Wrapper to extract `T` from a [`JsBox`](JsBox) or create a [`JsBox`] /// from a `T`. /// /// [`Boxed`] is especially useful for exporting async functions and tasks. /// /// ``` /// # use std::sync::Arc; /// # use neon::{prelude::*, types::extract::Boxed}; /// struct Greeter { /// greeting: String, /// } /// /// impl Finalize for Greeter {} /// /// impl Greeter { /// fn new(greeting: String) -> Self { /// Self { greeting } /// } /// /// fn greet(&self, name: &str) -> String { /// format!("{}, {name}!", self.greeting) /// } /// } /// /// #[neon::export] /// fn create_greeter(greeting: String) -> Boxed> { /// Boxed(Arc::new(Greeter::new(greeting))) /// } /// /// #[neon::export(task)] /// fn greet(Boxed(greeter): Boxed>, name: String) -> String { /// greeter.greet(&name) /// } /// ``` pub struct Boxed(pub T); impl<'cx, T> TryFromJs<'cx> for Boxed where T: Clone + 'static, { type Error = TypeExpected>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::, _>(cx) { Ok(v) => Ok(Ok(Self(T::clone(&v)))), Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T> TryIntoJs<'cx> for Boxed where T: Finalize + 'static, { type Value = JsBox; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.boxed(self.0)) } } impl private::Sealed for Boxed {} ================================================ FILE: crates/neon/src/types_impl/extract/buffer.rs ================================================ use crate::{ context::Cx, handle::Handle, result::{JsResult, NeonResult}, types::{ buffer::{Binary, TypedArray}, extract::{private, TryFromJs, TryIntoJs, TypeExpected}, JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBuffer, JsFloat32Array, JsFloat64Array, JsInt16Array, JsInt32Array, JsInt8Array, JsTypedArray, JsUint16Array, JsUint32Array, JsUint8Array, JsValue, Value, }, }; /// Wrapper for converting between bytes and [`JsArrayBuffer`](JsArrayBuffer) pub struct ArrayBuffer(pub B); impl<'cx, B> TryFromJs<'cx> for ArrayBuffer where for<'b> B: From<&'b [u8]>, { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let v = match v.downcast::(cx) { Ok(v) => v, Err(_) => return Ok(Err(Self::Error::new())), }; Ok(Ok(ArrayBuffer(B::from(v.as_slice(cx))))) } } impl<'cx, B> TryIntoJs<'cx> for ArrayBuffer where B: AsRef<[u8]>, { type Value = JsArrayBuffer; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsArrayBuffer::from_slice(cx, self.0.as_ref()) } } impl private::Sealed for ArrayBuffer {} /// Wrapper for converting between bytes and [`JsBuffer`](JsBuffer) pub struct Buffer(pub B); impl<'cx, B> TryFromJs<'cx> for Buffer where for<'b> B: From<&'b [u8]>, { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let v = match v.downcast::(cx) { Ok(v) => v, Err(_) => return Ok(Err(Self::Error::new())), }; Ok(Ok(Buffer(B::from(v.as_slice(cx))))) } } impl<'cx, B> TryIntoJs<'cx> for Buffer where B: AsRef<[u8]>, { type Value = JsBuffer; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsBuffer::from_slice(cx, self.0.as_ref()) } } impl private::Sealed for Buffer {} impl<'cx, T> TryIntoJs<'cx> for Vec where JsTypedArray: Value, T: Binary, { type Value = JsTypedArray; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsTypedArray::from_slice(cx, self.as_slice()) } } impl<'cx, T> TryFromJs<'cx> for Vec where JsTypedArray: Value, T: Binary, { type Error = TypeExpected>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let v = match v.downcast::, _>(cx) { Ok(v) => v, Err(_) => return Ok(Err(Self::Error::new())), }; Ok(Ok(v.as_slice(cx).to_vec())) } } impl private::Sealed for Vec where JsTypedArray: Value, T: Binary, { } impl<'cx, T, const N: usize> TryIntoJs<'cx> for [T; N] where JsTypedArray: Value, T: Binary, { type Value = JsTypedArray; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsTypedArray::from_slice(cx, self.as_slice()) } } impl private::Sealed for [T; N] where JsTypedArray: Value, T: Binary, { } impl<'cx, T> TryIntoJs<'cx> for &[T] where JsTypedArray: Value, T: Binary, { type Value = JsTypedArray; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsTypedArray::from_slice(cx, self) } } impl private::Sealed for &[T] where JsTypedArray: Value, T: Binary, { } macro_rules! typed_array { ($js:ident, $name:ident, $type:ty) => { #[doc = concat!( "Wrapper for converting between a Rust `[", stringify!($type), "]` array type and a [`", stringify!($js), "`]", )] pub struct $name(pub T); impl<'cx, T> TryIntoJs<'cx> for $name where T: AsRef<[$type]>, { type Value = $js; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { $js::from_slice(cx, self.0.as_ref()) } } impl<'cx, T> TryFromJs<'cx> for $name where for<'a> T: From<&'a [$type]>, { type Error = TypeExpected<$js>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let v = match v.downcast::<$js, _>(cx) { Ok(v) => v, Err(_) => return Ok(Err(TypeExpected::new())), }; Ok(Ok(Self(T::from(v.as_slice(cx))))) } } impl private::Sealed for $name {} }; ($(($js:ident, $name:ident, $type:ty),)*) => { $(typed_array!($js, $name, $type);)* }; } typed_array![ (JsInt8Array, Int8Array, i8), (JsUint8Array, Uint8Array, u8), (JsInt16Array, Int16Array, i16), (JsUint16Array, Uint16Array, u16), (JsInt32Array, Int32Array, i32), (JsUint32Array, Uint32Array, u32), (JsFloat32Array, Float32Array, f32), (JsFloat64Array, Float64Array, f64), (JsBigInt64Array, BigInt64Array, i64), (JsBigUint64Array, BigUint64Array, u64), ]; ================================================ FILE: crates/neon/src/types_impl/extract/container.rs ================================================ use std::{ cell::{Ref, RefCell, RefMut}, rc::Rc, sync::{Arc, LazyLock}, }; use crate::{ context::{Context, Cx}, handle::Handle, result::{JsResult, NeonResult}, types::{ extract::{TryFromJs, TryIntoJs}, JsBox, JsValue, Value, }, }; use super::error::TypeExpected; impl<'cx, T: 'static> TryFromJs<'cx> for &'cx RefCell { type Error = TypeExpected>>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::>, _>(cx) { Ok(v) => Ok(Ok(JsBox::deref(&v))), Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T: 'static> TryFromJs<'cx> for Ref<'cx, T> { type Error = TypeExpected>>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::>, _>(cx) { Ok(v) => match JsBox::deref(&v).try_borrow() { Ok(r) => Ok(Ok(r)), Err(_) => cx.throw_error("RefCell is already mutably borrowed"), }, Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T: 'static> TryFromJs<'cx> for RefMut<'cx, T> { type Error = TypeExpected>>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::>, _>(cx) { Ok(v) => match JsBox::deref(&v).try_borrow_mut() { Ok(r) => Ok(Ok(r)), Err(_) => cx.throw_error("RefCell is already borrowed"), }, Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T> TryIntoJs<'cx> for RefCell where T: 'static, { type Value = JsBox>; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(JsBox::manually_finalize(cx, self)) } } impl<'cx, T: 'static> TryFromJs<'cx> for Rc { type Error = TypeExpected>>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::>, _>(cx) { Ok(v) => Ok(Ok(Rc::clone(&v))), Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T> TryIntoJs<'cx> for Rc where T: 'static, { type Value = JsBox>; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(JsBox::manually_finalize(cx, self)) } } impl<'cx, T: 'static> TryFromJs<'cx> for Arc { type Error = TypeExpected>>; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { match v.downcast::>, _>(cx) { Ok(v) => Ok(Ok(Arc::clone(&v))), Err(_) => Ok(Err(TypeExpected::new())), } } } impl<'cx, T> TryIntoJs<'cx> for Arc where T: 'static, { type Value = JsBox>; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(JsBox::manually_finalize(cx, self)) } } impl<'cx, T, V> TryIntoJs<'cx> for &LazyLock where T: 'static, V: Value, for<'a> &'a T: TryIntoJs<'cx, Value = V>, { type Value = V; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { LazyLock::force(self).try_into_js(cx) } } impl super::private::Sealed for &LazyLock {} ================================================ FILE: crates/neon/src/types_impl/extract/either.rs ================================================ use std::{any, error, fmt}; use either::Either; use crate::{ context::Cx, handle::Handle, object::Object, result::{JsResult, NeonResult}, types::{ extract::{private, TryFromJs, TryIntoJs}, JsError, JsValue, }, }; impl<'cx, L, R> TryFromJs<'cx> for Either where L: TryFromJs<'cx>, R: TryFromJs<'cx>, { type Error = Error; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let left = match L::try_from_js(cx, v)? { Ok(l) => return Ok(Ok(Either::Left(l))), Err(l) => l, }; let right = match R::try_from_js(cx, v)? { Ok(r) => return Ok(Ok(Either::Right(r))), Err(r) => r, }; Ok(Err(Error::new::(left, right))) } } impl<'cx, L, R> TryIntoJs<'cx> for Either where L: TryIntoJs<'cx>, R: TryIntoJs<'cx>, { type Value = JsValue; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { match self { Either::Left(v) => v.try_into_js(cx).map(|v| v.upcast()), Either::Right(v) => v.try_into_js(cx).map(|v| v.upcast()), } } } impl private::Sealed for Either {} #[derive(Debug)] pub struct Error { left: (&'static str, L), right: (&'static str, R), } impl<'cx, L, R> Error { fn new(left: L, right: R) -> Self where LT: TryFromJs<'cx, Error = L>, RT: TryFromJs<'cx, Error = R>, { Self { left: (any::type_name::(), left), right: (any::type_name::(), right), } } } impl fmt::Display for Error where L: fmt::Display, R: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "Either::Left: {}", self.left.1)?; write!(f, "Either::Right: {}", self.right.1) } } impl error::Error for Error where L: error::Error, R: error::Error, { } impl<'cx, L, R> TryIntoJs<'cx> for Error where L: TryIntoJs<'cx>, R: TryIntoJs<'cx>, { type Value = JsError; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { let err = JsError::type_error( cx, format!("expected either {} or {}", self.left.0, self.right.0,), )?; err.prop(cx, "left").set(self.left.1)?; err.prop(cx, "right").set(self.right.1)?; Ok(err) } } impl private::Sealed for Error {} ================================================ FILE: crates/neon/src/types_impl/extract/error.rs ================================================ use std::{convert::Infallible, error, fmt, marker::PhantomData}; use crate::{ context::{Context, Cx}, result::JsResult, types::{ extract::{private, TryIntoJs}, JsError, JsValue, Value, }, }; type BoxError = Box; /// Error returned when a JavaScript value is not the type expected. pub struct TypeExpected(PhantomData); impl TypeExpected { pub(super) fn new() -> Self { Self(PhantomData) } } impl fmt::Display for TypeExpected { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "expected {}", T::name()) } } impl fmt::Debug for TypeExpected { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("TypeExpected").field(&T::name()).finish() } } impl error::Error for TypeExpected {} impl<'cx, T: Value> TryIntoJs<'cx> for TypeExpected { type Value = JsError; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsError::type_error(cx, self.to_string()) } } impl private::Sealed for TypeExpected {} impl<'cx> TryIntoJs<'cx> for Infallible { type Value = JsValue; fn try_into_js(self, _: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { unreachable!() } } impl private::Sealed for Infallible {} #[derive(Debug)] /// Error that implements [`TryIntoJs`] and can produce specific error types. /// /// [`Error`] implements [`From`] for most error types, allowing ergonomic error handling in /// exported functions with the `?` operator. /// /// ### Example /// /// ``` /// use neon::types::extract::Error; /// /// #[neon::export] /// fn read_file(path: String) -> Result { /// let contents = std::fs::read_to_string(path)?; /// Ok(contents) /// } /// ``` pub struct Error { cause: BoxError, kind: Option, } #[derive(Debug)] enum ErrorKind { Error, RangeError, TypeError, } impl Error { /// Create a new [`Error`] from a `cause` pub fn new(cause: E) -> Self where E: Into, { Self::create(ErrorKind::Error, cause) } /// Create a `RangeError` pub fn range_error(cause: E) -> Self where E: Into, { Self::create(ErrorKind::RangeError, cause) } /// Create a `TypeError` pub fn type_error(cause: E) -> Self where E: Into, { Self::create(ErrorKind::TypeError, cause) } /// Check if error is a `RangeError` pub fn is_range_error(&self) -> bool { matches!(self.kind, Some(ErrorKind::RangeError)) } /// Check if error is a `TypeError` pub fn is_type_error(&self) -> bool { matches!(self.kind, Some(ErrorKind::TypeError)) } /// Get a reference to the underlying `cause` pub fn cause(&self) -> &BoxError { &self.cause } /// Extract the `std::error::Error` cause pub fn into_cause(self) -> BoxError { self.cause } fn create(kind: ErrorKind, cause: E) -> Self where E: Into, { Self { cause: cause.into(), kind: Some(kind), } } } // Blanket impl allow for ergonomic `?` error handling from typical error types (including `anyhow`) impl From for Error where E: Into, { fn from(cause: E) -> Self { Self::new(cause) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}: {}", self.kind, self.cause) } } // N.B.: `TryFromJs` is not included. If Neon were to add support for additional error types, // this would be a *breaking* change. We will wait for user demand before providing this feature. impl<'cx> TryIntoJs<'cx> for Error { type Value = JsError; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { let message = self.cause.to_string(); match self.kind { Some(ErrorKind::RangeError) => cx.range_error(message), Some(ErrorKind::TypeError) => cx.type_error(message), _ => cx.error(message), } } } ================================================ FILE: crates/neon/src/types_impl/extract/json.rs ================================================ //! Extract JavaScript values with JSON serialization //! //! For complex objects that implement [`serde::Serialize`] and [`serde::Deserialize`], //! it is more ergonomic--and often faster--to extract with JSON serialization. The [`Json`] //! extractor automatically calls `JSON.stringify` and `JSON.parse` as necessary. //! //! ``` //! use neon::types::extract::Json; //! //! #[neon::export] //! fn sort(Json(mut strings): Json>) -> Json> { //! strings.sort(); //! Json(strings) //! } //! ``` use std::{error, fmt}; use crate::{ context::{Context, Cx}, handle::Handle, object::Object, result::{JsResult, NeonResult}, types::{ extract::{private, TryFromJs, TryIntoJs}, JsError, JsFunction, JsObject, JsValue, }, }; #[cfg(feature = "napi-6")] use crate::{handle::Root, thread::LocalKey}; fn global_json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { cx.global::("JSON")?.get(cx, "stringify") } #[cfg(not(feature = "napi-6"))] // N.B.: This is not semantically identical to Node-API >= 6. Patching the global // method could cause differences between calls. However, threading a `Root` through // would require a significant refactor and "don't do this or things will break" is // fairly common in JS. fn json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { global_json_stringify(cx) } #[cfg(feature = "napi-6")] fn json_stringify<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { static STRINGIFY: LocalKey> = LocalKey::new(); STRINGIFY .get_or_try_init(cx, |cx| global_json_stringify(cx).map(|f| f.root(cx))) .map(|f| f.to_inner(cx)) } fn global_json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { cx.global::("JSON")?.get(cx, "parse") } #[cfg(not(feature = "napi-6"))] fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { global_json_parse(cx) } #[cfg(feature = "napi-6")] fn json_parse<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsFunction> { static PARSE: LocalKey> = LocalKey::new(); PARSE .get_or_try_init(cx, |cx| global_json_parse(cx).map(|f| f.root(cx))) .map(|f| f.to_inner(cx)) } fn parse<'cx>(cx: &mut Cx<'cx>, s: &str) -> JsResult<'cx, JsValue> { let s = cx.string(s).upcast(); json_parse(cx)?.call(cx, s, [s]) } /// Wrapper for converting between `T` and [`JsValue`](crate::types::JsValue) by /// serializing with JSON. pub struct Json(pub T); impl<'cx, T> TryFromJs<'cx> for Json where for<'de> T: serde::de::Deserialize<'de>, { type Error = Error; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let s = json_stringify(cx)?.call(cx, v, [v])?; let res = match String::try_from_js(cx, s)? { Ok(s) => serde_json::from_str(&s), // If the type was not a `string`, it must be `undefined` Err(_) => T::deserialize(serde::de::value::UnitDeserializer::new()), }; Ok(res.map(Json).map_err(Error)) } } impl<'cx, T> TryIntoJs<'cx> for Json where T: serde::Serialize, { type Value = JsValue; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { TryIntoJs::try_into_js(&self, cx) } } impl<'cx, T> TryIntoJs<'cx> for &Json where T: serde::Serialize, { type Value = JsValue; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { let s = serde_json::to_string(&self.0).or_else(|err| cx.throw_error(err.to_string()))?; parse(cx, &s) } } impl private::Sealed for Json {} impl private::Sealed for &Json {} /// Error returned when a value is invalid JSON pub struct Error(serde_json::Error); impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } impl error::Error for Error {} impl<'cx> TryIntoJs<'cx> for Error { type Value = JsError; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { JsError::error(cx, self.to_string()) } } impl private::Sealed for Error {} ================================================ FILE: crates/neon/src/types_impl/extract/mod.rs ================================================ //! Traits and utilities for extract Rust data from JavaScript values. //! //! The full list of included extractors can be found on [`TryFromJs`]. //! //! ## Extracting Handles //! //! JavaScript arguments may be extracted into a Rust tuple. //! //! ``` //! # use neon::{prelude::*, types::extract::*}; //! fn greet(mut cx: FunctionContext) -> JsResult { //! let (greeting, name): (Handle, Handle) = cx.args()?; //! let message = format!("{}, {}!", greeting.value(&mut cx), name.value(&mut cx)); //! //! Ok(cx.string(message)) //! } //! ``` //! //! ## Extracting Native Types //! //! It's also possible to extract directly into native Rust types instead of a [`Handle`]. //! //! ``` //! # use neon::{prelude::*, types::extract::*}; //! fn add(mut cx: FunctionContext) -> JsResult { //! let (a, b): (f64, f64) = cx.args()?; //! //! Ok(cx.number(a + b)) //! } //! ``` //! //! ## Extracting [`Option`] //! //! It's also possible to mix [`Handle`], Rust types, and even [`Option`] for //! handling `null` and `undefined`. //! //! ``` //! # use neon::{prelude::*, types::extract::*}; //! fn get_or_default(mut cx: FunctionContext) -> JsResult { //! let (n, default_value): (Option, Handle) = cx.args()?; //! //! if let Some(n) = n { //! return Ok(cx.number(n).upcast()); //! } //! //! Ok(default_value) //! } //! ``` //! //! ## Additional Extractors //! //! In some cases, the expected JavaScript type is ambiguous. For example, when //! trying to extract an [`f64`], the argument may be a `Date` instead of a `number`. //! Newtype extractors are provided to help. //! //! ``` //! # use neon::{prelude::*, types::extract::*}; //! # #[cfg(feature = "napi-5")] //! # use neon::types::JsDate; //! //! # #[cfg(feature = "napi-5")] //! fn add_hours(mut cx: FunctionContext) -> JsResult { //! const MS_PER_HOUR: f64 = 60.0 * 60.0 * 1000.0; //! //! let (Date(date), hours): (Date, f64) = cx.args()?; //! let date = date + hours * MS_PER_HOUR; //! //! cx.date(date).or_throw(&mut cx) //! } //! ``` //! //! ## Overloaded Functions //! //! It's common in JavaScript to overload function signatures. This can be implemented with //! [`FunctionContext::args_opt`] or [`Context::try_catch`]. //! //! ``` //! # use neon::{prelude::*, types::extract::*}; //! //! fn add(mut cx: FunctionContext, a: f64, b: f64) -> Handle { //! cx.number(a + b) //! } //! //! fn concat(mut cx: FunctionContext, a: String, b: String) -> Handle { //! cx.string(a + &b) //! } //! //! fn combine(mut cx: FunctionContext) -> JsResult { //! if let Some((a, b)) = cx.args_opt()? { //! return Ok(add(cx, a, b).upcast()); //! } //! //! let (a, b) = cx.args()?; //! //! Ok(concat(cx, a, b).upcast()) //! } //! ``` //! //! Note well, in this example, type annotations are not required on the tuple because //! Rust is able to infer it from the type arguments on `add` and `concat`. use crate::{ context::{Context, Cx, FunctionContext}, handle::Handle, result::{JsResult, NeonResult}, types::{JsValue, Value}, }; pub use self::{ array::{Array, ArrayError}, boxed::Boxed, buffer::{ ArrayBuffer, BigInt64Array, BigUint64Array, Buffer, Float32Array, Float64Array, Int16Array, Int32Array, Int8Array, Uint16Array, Uint32Array, Uint8Array, }, error::{Error, TypeExpected}, with::with, }; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub use self::json::Json; #[cfg(feature = "serde")] #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod json; mod array; mod boxed; mod buffer; mod container; mod either; mod error; pub(crate) mod private; mod try_from_js; mod try_into_js; mod with; /// Extract Rust data from a JavaScript value pub trait TryFromJs<'cx> where Self: private::Sealed + Sized, { type Error: TryIntoJs<'cx>; /// Extract this Rust type from a JavaScript value fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult>; /// Same as [`TryFromJs`], but all errors are converted to JavaScript exceptions fn from_js(cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>) -> NeonResult { match Self::try_from_js(cx, v)? { Ok(v) => Ok(v), Err(err) => { let err = err.try_into_js(cx)?; cx.throw(err) } } } } /// Convert Rust data into a JavaScript value pub trait TryIntoJs<'cx> where Self: private::Sealed, { /// The type of JavaScript value that will be created type Value: Value; /// Convert `self` into a JavaScript value fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value>; } /// Extract a borrowed reference to Rust data from a JavaScript value /// /// This trait is similar to [`TryFromJs`], but instead of extracting an owned value, /// it returns a guard that dereferences to a borrowed reference. This is useful for /// efficiently passing class instances by reference in method calls. /// /// # Example /// /// ```ignore /// // In a class method, accept another instance by reference /// pub fn distance(&self, other: &Point) -> f64 { /// // other is borrowed, not cloned /// } /// ``` /// /// The macro will automatically use `TryFromJsRef` when it sees `&T` parameters. pub trait TryFromJsRef<'cx> where Self: private::Sealed + Sized, { /// A guard type that dereferences to `&Self` and keeps the borrow alive type Guard: std::ops::Deref; /// The error type returned when extraction fails type Error: TryIntoJs<'cx>; /// Extract a borrowed reference from a JavaScript value fn try_from_js_ref( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult>; /// Same as [`TryFromJsRef::try_from_js_ref`], but all errors are converted to JavaScript exceptions fn from_js_ref(cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>) -> NeonResult { match Self::try_from_js_ref(cx, v)? { Ok(guard) => Ok(guard), Err(err) => { let err = err.try_into_js(cx)?; cx.throw(err) } } } } /// Extract a mutable borrowed reference to Rust data from a JavaScript value /// /// This trait is similar to [`TryFromJsRef`], but returns a guard that allows /// mutable access. This is useful for passing class instances by mutable reference /// in method calls. /// /// # Example /// /// ```ignore /// // In a class method, accept another instance by mutable reference /// pub fn swap_coordinates(&mut self, other: &mut Point) { /// std::mem::swap(&mut self.x, &mut other.x); /// std::mem::swap(&mut self.y, &mut other.y); /// } /// ``` /// /// The macro will automatically use `TryFromJsRefMut` when it sees `&mut T` parameters. pub trait TryFromJsRefMut<'cx> where Self: private::Sealed + Sized, { /// A guard type that dereferences to `&mut Self` and keeps the mutable borrow alive type Guard: std::ops::DerefMut; /// The error type returned when extraction fails type Error: TryIntoJs<'cx>; /// Extract a mutable borrowed reference from a JavaScript value fn try_from_js_ref_mut( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult>; /// Same as [`TryFromJsRefMut::try_from_js_ref_mut`], but all errors are converted to JavaScript exceptions fn from_js_ref_mut(cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>) -> NeonResult { match Self::try_from_js_ref_mut(cx, v)? { Ok(guard) => Ok(guard), Err(err) => { let err = err.try_into_js(cx)?; cx.throw(err) } } } } #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] #[cfg(feature = "napi-5")] /// Wrapper for converting between [`f64`] and [`JsDate`](super::JsDate) pub struct Date(pub f64); /// Trait specifying values that may be extracted from function arguments. /// /// **Note:** This trait is implemented for tuples of up to 32 values, but for /// the sake of brevity, only tuples up to size 8 are shown in this documentation. pub trait FromArgs<'cx>: private::FromArgsInternal<'cx> {} // Functions as a noop zero-argument base case of [`from_args_impl`] impl<'cx> private::FromArgsInternal<'cx> for () { fn from_args(_cx: &mut FunctionContext<'cx>) -> NeonResult { Ok(()) } fn from_args_opt(_cx: &mut FunctionContext<'cx>) -> NeonResult> { Ok(Some(())) } } // Functions as a zero-argument base case of [`from_args_impl`] impl<'cx> FromArgs<'cx> for () {} // N.B.: `FromArgs` _could_ have a blanket impl for `T` where `T: FromArgsInternal`. // However, it is explicitly implemented in the macro in order for it to be included in docs. macro_rules! from_args_impl { ($(#[$attrs:meta])? [$($ty:ident),*]) => { $(#[$attrs])? impl<'cx, $($ty,)*> FromArgs<'cx> for ($($ty,)*) where $($ty: TryFromJs<'cx>,)* {} #[allow(non_snake_case)] impl<'cx, $($ty,)*> private::FromArgsInternal<'cx> for ($($ty,)*) where $($ty: TryFromJs<'cx>,)* { fn from_args(cx: &mut FunctionContext<'cx>) -> NeonResult { let [$($ty,)*] = cx.argv(); Ok(($($ty::from_js(cx, $ty)?,)*)) } fn from_args_opt(cx: &mut FunctionContext<'cx>) -> NeonResult> { let [$($ty,)*] = cx.argv(); Ok(Some(( $(match $ty::try_from_js(cx, $ty)? { Ok(v) => v, Err(_) => return Ok(None), },)* ))) } } } } macro_rules! from_args_expand { ($(#[$attrs:meta])? [$($head:ident),*], []) => {}; ($(#[$attrs:meta])? [$($head:ident),*], [$cur:ident $(, $tail:ident)*]) => { from_args_impl!($(#[$attrs])? [$($head,)* $cur]); from_args_expand!($(#[$attrs])? [$($head,)* $cur], [$($tail),*]); }; } macro_rules! from_args { ([$($show:ident),*], [$($hide:ident),*]) => { from_args_expand!([], [$($show),*]); from_args_expand!(#[doc(hidden)] [$($show),*], [$($hide),*]); }; } // Implement `FromArgs` for tuples up to length `32`. The first list is included // in docs and the second list is `#[doc(hidden)]`. from_args!( [T1, T2, T3, T4, T5, T6, T7, T8], [ T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, T21, T22, T23, T24, T25, T26, T27, T28, T29, T30, T31, T32 ] ); ================================================ FILE: crates/neon/src/types_impl/extract/private.rs ================================================ use std::{ cell::{Ref, RefCell, RefMut}, rc::Rc, sync::Arc, }; use crate::{ context::FunctionContext, handle::{Handle, Root}, object::Object, result::{NeonResult, Throw}, types::{ extract::{Error, TryIntoJs}, Value, }, }; pub trait Sealed {} pub trait FromArgsInternal<'cx>: Sized { fn from_args(cx: &mut FunctionContext<'cx>) -> NeonResult; fn from_args_opt(cx: &mut FunctionContext<'cx>) -> NeonResult>; } macro_rules! impl_sealed { ($ty:ident) => { impl Sealed for $ty {} }; ($($ty:ident),* $(,)*) => { $( impl_sealed!($ty); )* } } impl Sealed for () {} impl Sealed for &str {} impl Sealed for &String {} impl<'cx, V: Value> Sealed for Handle<'cx, V> {} impl Sealed for Root {} impl Sealed for Option {} impl Sealed for Result {} impl<'cx, T> Sealed for Box where T: TryIntoJs<'cx> {} impl Sealed for RefCell {} impl Sealed for &RefCell {} impl Sealed for Arc {} impl Sealed for Rc {} impl Sealed for Ref<'_, T> {} impl Sealed for RefMut<'_, T> {} #[cfg(feature = "napi-5")] impl Sealed for super::Date {} impl_sealed!(u8, u16, u32, i8, i16, i32, f32, f64, bool, String, Throw, Error,); ================================================ FILE: crates/neon/src/types_impl/extract/try_from_js.rs ================================================ // Implementations in this file are equivalent to a call to `.downcast()` and // `.value(&mut cx)`. These specialized versions provide a performance benefit // because they can combine two Node-API calls into a single call that both // gets the value and checks the type at the same time. use std::{convert::Infallible, ptr}; use crate::{ context::{internal::ContextInternal, Cx}, handle::{Handle, Root}, object::Object, result::{NeonResult, Throw}, sys, types::{ extract::{TryFromJs, TypeExpected}, private::ValueInternal, JsBoolean, JsNumber, JsString, JsValue, Value, }, }; #[cfg(feature = "napi-5")] use crate::types::{extract::Date, JsDate}; impl<'cx, V> TryFromJs<'cx> for Handle<'cx, V> where V: Value, { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { Ok(v.downcast(cx).map_err(|_| TypeExpected::new())) } } impl<'cx, O> TryFromJs<'cx> for Root where O: Object, { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { Ok(match v.downcast::(cx) { Ok(v) => Ok(v.root(cx)), Err(_) => Err(TypeExpected::new()), }) } } impl<'cx, T> TryFromJs<'cx> for Option where T: TryFromJs<'cx>, { type Error = T::Error; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { if is_null_or_undefined(cx, v)? { return Ok(Ok(None)); } T::try_from_js(cx, v).map(|v| v.map(Some)) } } impl<'cx> TryFromJs<'cx> for f64 { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let mut n = 0f64; unsafe { match sys::get_value_double(cx.env().to_raw(), v.to_local(), &mut n) { Err(sys::Status::NumberExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(Ok(n)) } } impl<'cx> TryFromJs<'cx> for u32 { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let mut n = 0u32; unsafe { match sys::get_value_uint32(cx.env().to_raw(), v.to_local(), &mut n) { Err(sys::Status::NumberExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(Ok(n)) } } impl<'cx> TryFromJs<'cx> for i32 { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let mut n = 0i32; unsafe { match sys::get_value_int32(cx.env().to_raw(), v.to_local(), &mut n) { Err(sys::Status::NumberExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(Ok(n)) } } impl<'cx> TryFromJs<'cx> for bool { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let mut b = false; unsafe { match sys::get_value_bool(cx.env().to_raw(), v.to_local(), &mut b) { Err(sys::Status::BooleanExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(Ok(b)) } } impl<'cx> TryFromJs<'cx> for String { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let env = cx.env().to_raw(); let v = v.to_local(); let mut len = 0usize; unsafe { match sys::get_value_string_utf8(env, v, ptr::null_mut(), 0, &mut len) { Err(sys::Status::StringExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } // Make room for null terminator to avoid losing a character let mut buf = Vec::::with_capacity(len + 1); let mut written = 0usize; unsafe { assert_eq!( sys::get_value_string_utf8( env, v, buf.as_mut_ptr().cast(), buf.capacity(), &mut written, ), Ok(()) ); debug_assert_eq!(len, written); buf.set_len(len); Ok(Ok(String::from_utf8_unchecked(buf))) } } } #[cfg_attr(docsrs, doc(cfg(feature = "napi-5")))] #[cfg(feature = "napi-5")] impl<'cx> TryFromJs<'cx> for Date { type Error = TypeExpected; fn try_from_js( cx: &mut Cx<'cx>, v: Handle<'cx, JsValue>, ) -> NeonResult> { let mut d = 0f64; unsafe { match sys::get_date_value(cx.env().to_raw(), v.to_local(), &mut d) { Err(sys::Status::DateExpected) => return Ok(Err(TypeExpected::new())), Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(Ok(Date(d))) } } // This implementation primarily exists for macro authors. It is infallible, rather // than checking a type, to match the JavaScript conventions of ignoring additional // arguments. // // N.B.: There is a blanket impl of `FromArgs` for `T` where `T: TryFromJs` to make // the common case of `arity == 1` more ergonomic and avoid `(T)` is *not* a tuple // foot-gun (but, `(T,)` is). This creates ambiguity for `()`. Are we extracting // unit from the first argument of a function with `arity == 1` or is this a function // with `arity == 0`? By making extraction of unit infallible, we eliminate any // impact from the ambiguity. impl<'cx> TryFromJs<'cx> for () { type Error = Infallible; fn try_from_js( _cx: &mut Cx<'cx>, _v: Handle<'cx, JsValue>, ) -> NeonResult> { Ok(Ok(())) } } fn is_null_or_undefined(cx: &mut Cx, v: Handle) -> NeonResult where V: Value, { let mut ty = sys::ValueType::Object; unsafe { match sys::typeof_value(cx.env().to_raw(), v.to_local(), &mut ty) { Err(sys::Status::PendingException) => return Err(Throw::new()), status => status.unwrap(), }; } Ok(matches!( ty, sys::ValueType::Undefined | sys::ValueType::Null, )) } ================================================ FILE: crates/neon/src/types_impl/extract/try_into_js.rs ================================================ use crate::{ context::{Context, Cx}, handle::{Handle, Root}, object::Object, result::{JsResult, Throw}, types::{extract::TryIntoJs, JsBoolean, JsNumber, JsString, JsUndefined, JsValue, Value}, }; #[cfg(feature = "napi-5")] use crate::{ result::ResultExt, types::{extract::Date, JsDate}, }; impl<'cx, T> TryIntoJs<'cx> for Handle<'cx, T> where T: Value, { type Value = T; fn try_into_js(self, _cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(self) } } impl<'cx, O> TryIntoJs<'cx> for Root where O: Object, { type Value = O; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(self.into_inner(cx)) } } impl<'cx, T, E> TryIntoJs<'cx> for Result where T: TryIntoJs<'cx>, E: TryIntoJs<'cx>, { type Value = T::Value; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { match self { Ok(v) => v.try_into_js(cx), Err(err) => { let err = err.try_into_js(cx)?; cx.throw(err) } } } } impl<'cx> TryIntoJs<'cx> for Throw { type Value = JsValue; fn try_into_js(self, _cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Err(self) } } impl<'cx, T> TryIntoJs<'cx> for Option where T: TryIntoJs<'cx>, { type Value = JsValue; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { if let Some(val) = self { val.try_into_js(cx).map(|v| v.upcast()) } else { Ok(cx.undefined().upcast()) } } } impl<'cx, T> TryIntoJs<'cx> for Box where T: TryIntoJs<'cx>, { type Value = T::Value; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { (*self).try_into_js(cx) } } macro_rules! impl_number { ($ty:ident) => { impl<'cx> TryIntoJs<'cx> for $ty { type Value = JsNumber; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.number(self)) } } }; ($($ty:ident),* $(,)?) => { $( impl_number!($ty); )* } } impl_number!(u8, u16, u32, i8, i16, i32, f32, f64); impl<'cx> TryIntoJs<'cx> for String { type Value = JsString; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.string(self)) } } impl<'cx> TryIntoJs<'cx> for &str { type Value = JsString; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.string(self)) } } impl<'cx> TryIntoJs<'cx> for &String { type Value = JsString; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.string(self)) } } impl<'cx> TryIntoJs<'cx> for bool { type Value = JsBoolean; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.boolean(self)) } } impl<'cx> TryIntoJs<'cx> for () { type Value = JsUndefined; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { Ok(cx.undefined()) } } #[cfg(feature = "napi-5")] impl<'cx> TryIntoJs<'cx> for Date { type Value = JsDate; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { cx.date(self.0).or_throw(cx) } } ================================================ FILE: crates/neon/src/types_impl/extract/with.rs ================================================ use crate::{ context::Cx, result::JsResult, types::{extract::TryIntoJs, Value}, }; struct With(pub F); /// Wraps a closure that will be lazily evaluated when [`TryIntoJs::try_into_js`] is /// called. /// /// Useful for executing arbitrary code on the main thread before returning from a /// function exported with [`neon::export`](crate::export). /// /// **Note:** The return type is [`JsResult`]. If you need to return a non-JavaScript type, /// call [`TryIntoJs::try_into_js`]. /// /// _See [`With`](With#Example) for example usage._ /// /// ## Example /// /// ``` /// # use neon::{prelude::*, types::extract::{self, TryIntoJs}}; /// use std::time::Instant; /// /// #[neon::export(task)] /// fn sum(nums: Vec) -> impl for<'cx> TryIntoJs<'cx> { /// let start = Instant::now(); /// let sum = nums.into_iter().sum::(); /// let log = format!("sum took {} ms", start.elapsed().as_millis()); /// /// extract::with(move |cx| -> NeonResult<_> { /// cx.global::("console")? /// .method(cx, "log")? /// .arg(&log)? /// .exec()?; /// /// sum.try_into_js(cx) /// }) /// } /// ``` pub fn with(f: F) -> impl for<'cx> TryIntoJs<'cx, Value = V> where V: Value, for<'cx> F: FnOnce(&mut Cx<'cx>) -> JsResult<'cx, V>, { With(f) } impl<'cx, O, F> TryIntoJs<'cx> for With where O: TryIntoJs<'cx>, F: FnOnce(&mut Cx<'cx>) -> O, { type Value = O::Value; fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { (self.0)(cx).try_into_js(cx) } } impl super::private::Sealed for With {} ================================================ FILE: crates/neon/src/types_impl/function/mod.rs ================================================ //! Types and traits for working with JavaScript functions. use smallvec::smallvec; use crate::{ context::{Context, Cx}, handle::Handle, object::Object, result::{JsResult, NeonResult}, types::{ extract::{TryFromJs, TryIntoJs}, private::ValueInternal, JsFunction, JsObject, JsValue, Value, }, }; pub(crate) mod private; /// A builder for making a JavaScript function call like `parseInt("42")`. /// /// The builder methods make it convenient to assemble the call from parts: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// # let parse_int: Handle = cx.global("parseInt")?; /// let x: f64 = parse_int /// .bind(&mut cx) /// .arg("42")? /// .call()?; /// # Ok(cx.number(x)) /// # } /// ``` pub struct BindOptions<'a, 'cx: 'a> { pub(crate) cx: &'a mut Cx<'cx>, pub(crate) callee: Handle<'cx, JsValue>, pub(crate) this: Option>, pub(crate) args: private::ArgsVec<'cx>, } impl<'a, 'cx: 'a> BindOptions<'a, 'cx> { /// Set the value of `this` for the function call. pub fn this>(&mut self, this: T) -> NeonResult<&mut Self> { let v = this.try_into_js(self.cx)?; self.this = Some(v.upcast()); Ok(self) } /// Replaces the arguments list with the given arguments. pub fn args>(&mut self, a: A) -> NeonResult<&mut Self> { self.args = a.try_into_args_vec(self.cx)?; Ok(self) } /// Replaces the arguments list with a list computed from a closure. pub fn args_with(&mut self, f: F) -> NeonResult<&mut Self> where R: TryIntoArguments<'cx>, F: FnOnce(&mut Cx<'cx>) -> R, { self.args = f(self.cx).try_into_args_vec(self.cx)?; Ok(self) } /// Add an argument to the arguments list. pub fn arg>(&mut self, a: A) -> NeonResult<&mut Self> { let v = a.try_into_js(self.cx)?; self.args.push(v.upcast()); Ok(self) } /// Add an argument to the arguments list, computed from a closure. pub fn arg_with(&mut self, f: F) -> NeonResult<&mut Self> where R: TryIntoJs<'cx>, F: FnOnce(&mut Cx<'cx>) -> R, { let v = f(self.cx).try_into_js(self.cx)?; self.args.push(v.upcast()); Ok(self) } /// Make the function call. If the function returns without throwing, the result value /// is converted to a Rust value with `TryFromJs::from_js`. pub fn call>(&mut self) -> NeonResult { let this = self.this.unwrap_or_else(|| self.cx.undefined().upcast()); let v: Handle = unsafe { self.callee.try_call(self.cx, this, &self.args)? }; R::from_js(self.cx, v) } /// Make the function call as a constructor. If the function returns without throwing, the /// result value is converted to a Rust value with `TryFromJs::from_js`. pub fn construct>(&mut self) -> NeonResult { let v: Handle = unsafe { self.callee.try_construct(self.cx, &self.args)? }; R::from_js(self.cx, v) } /// Make the function call for side effect, discarding the result value. This method is /// preferable to [`call()`](BindOptions::call) when the result value isn't needed, /// since it doesn't require specifying a result type. pub fn exec(&mut self) -> NeonResult<()> { let _ignore: Handle = self.call()?; Ok(()) } } /// A builder for making a JavaScript function call like `parseInt("42")`. /// /// The builder methods make it convenient to assemble the call from parts: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// # let parse_int: Handle = cx.global("parseInt")?; /// let x: Handle = parse_int /// .call_with(&cx) /// .arg(cx.string("42")) /// .apply(&mut cx)?; /// # Ok(x) /// # } /// ``` #[deprecated(since = "TBD", note = "use `JsFunction::bind()` instead")] #[derive(Clone)] pub struct CallOptions<'a> { pub(crate) callee: Handle<'a, JsFunction>, pub(crate) this: Option>, pub(crate) args: private::ArgsVec<'a>, } impl<'a> CallOptions<'a> { /// Set the value of `this` for the function call. pub fn this(&mut self, this: Handle<'a, V>) -> &mut Self { self.this = Some(this.upcast()); self } /// Add an argument to the arguments list. pub fn arg(&mut self, arg: Handle<'a, V>) -> &mut Self { self.args.push(arg.upcast()); self } /// Replaces the arguments list with the given arguments. pub fn args>(&mut self, args: A) -> &mut Self { self.args = args.into_args_vec(); self } /// Make the function call. If the function returns without throwing, the result value /// is downcast to the type `V`, throwing a `TypeError` if the downcast fails. pub fn apply<'b: 'a, V: Value, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, V> { let this = self.this.unwrap_or_else(|| cx.undefined().upcast()); let v: Handle = self.callee.call(cx, this, &self.args)?; v.downcast_or_throw(cx) } /// Make the function call for side effect, discarding the result value. This method is /// preferable to [`apply()`](CallOptions::apply) when the result value isn't needed, /// since it doesn't require specifying a result type. pub fn exec<'b: 'a, C: Context<'b>>(&self, cx: &mut C) -> NeonResult<()> { let this = self.this.unwrap_or_else(|| cx.undefined().upcast()); self.callee.call(cx, this, &self.args)?; Ok(()) } } /// A builder for making a JavaScript constructor call like `new Array(16)`. /// /// The builder methods make it convenient to assemble the call from parts: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// # let url: Handle = cx.global("URL")?; /// let obj = url /// .construct_with(&cx) /// .arg(cx.string("https://neon-bindings.com")) /// .apply(&mut cx)?; /// # Ok(obj) /// # } /// ``` #[deprecated(since = "TBD", note = "use `JsFunction::bind()` instead")] #[derive(Clone)] pub struct ConstructOptions<'a> { pub(crate) callee: Handle<'a, JsFunction>, pub(crate) args: private::ArgsVec<'a>, } impl<'a> ConstructOptions<'a> { /// Add an argument to the arguments list. pub fn arg(&mut self, arg: Handle<'a, V>) -> &mut Self { self.args.push(arg.upcast()); self } /// Replaces the arguments list with the given arguments. pub fn args>(&mut self, args: A) -> &mut Self { self.args = args.into_args_vec(); self } /// Make the constructor call. If the function returns without throwing, returns /// the resulting object. pub fn apply<'b: 'a, O: Object, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, O> { let v: Handle = self.callee.construct(cx, &self.args)?; v.downcast_or_throw(cx) } } /// The trait for specifying values to be converted into arguments for a function call. /// This trait is sealed and cannot be implemented by types outside of the Neon crate. /// /// **Note:** This trait is implemented for tuples of up to 32 JavaScript values, /// but for the sake of brevity, only tuples up to size 8 are shown in this documentation. pub trait TryIntoArguments<'cx>: private::TryIntoArgumentsInternal<'cx> {} impl<'cx> private::TryIntoArgumentsInternal<'cx> for () { fn try_into_args_vec(self, _cx: &mut Cx<'cx>) -> NeonResult> { Ok(smallvec![]) } } impl<'cx, T, E> private::TryIntoArgumentsInternal<'cx> for Result where T: private::TryIntoArgumentsInternal<'cx>, E: TryIntoJs<'cx>, { fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult> { match self { Ok(v) => v.try_into_args_vec(cx), Err(err) => err.try_into_js(cx).and_then(|err| cx.throw(err)), } } } impl<'cx, T, E> TryIntoArguments<'cx> for Result where T: TryIntoArguments<'cx>, E: TryIntoJs<'cx>, { } macro_rules! impl_into_arguments_expand { { $(#[$attrs:meta])? [ $($prefix:ident ),* ]; []; } => {}; { $(#[$attrs:meta])? [ $($prefix:ident),* ]; [ $head:ident $(, $tail:ident)* ]; } => { $(#[$attrs])? impl<'cx, $($prefix: TryIntoJs<'cx> + 'cx, )* $head: TryIntoJs<'cx> + 'cx> private::TryIntoArgumentsInternal<'cx> for ($($prefix, )* $head, ) { #[allow(non_snake_case)] fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult> { let ($($prefix, )* $head, ) = self; Ok(smallvec![ $($prefix.try_into_js(cx)?.upcast(),)* $head.try_into_js(cx)?.upcast() ]) } } $(#[$attrs])? impl<'cx, $($prefix: TryIntoJs<'cx> + 'cx, )* $head: TryIntoJs<'cx> + 'cx> TryIntoArguments<'cx> for ($($prefix, )* $head, ) {} impl_into_arguments_expand! { $(#[$attrs])? [ $($prefix, )* $head ]; [ $($tail),* ]; } } } macro_rules! impl_into_arguments { { [ $($show:ident),* ]; [ $($hide:ident),* ]; } => { impl_into_arguments_expand! { []; [ $($show),* ]; } impl_into_arguments_expand! { #[doc(hidden)] [ $($show),* ]; [ $($hide),* ]; } } } impl_into_arguments! { // Tuples up to length 8 are included in the docs. [V1, V2, V3, V4, V5, V6, V7, V8]; // Tuples up to length 32 are not included in the docs. [ V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30, V31, V32 ]; } /// The trait for specifying arguments for a function call. This trait is sealed and cannot /// be implemented by types outside of the Neon crate. /// /// **Note:** This trait is implemented for tuples of up to 32 JavaScript values, /// but for the sake of brevity, only tuples up to size 8 are shown in this documentation. pub trait Arguments<'a>: private::ArgumentsInternal<'a> {} impl<'a> private::ArgumentsInternal<'a> for () { fn into_args_vec(self) -> private::ArgsVec<'a> { smallvec![] } } impl<'a> Arguments<'a> for () {} macro_rules! impl_arguments_expand { { $(#[$attrs:meta])? [ $($prefix:ident),* ]; []; } => {}; { $(#[$attrs:meta])? [ $($prefix:ident),* ]; [ $head:ident $(, $tail:ident)* ]; } => { $(#[$attrs])? impl<'a, $($prefix: Value, )* $head: Value> private::ArgumentsInternal<'a> for ($(Handle<'a, $prefix>, )* Handle<'a, $head>, ) { #[allow(non_snake_case)] fn into_args_vec(self) -> private::ArgsVec<'a> { let ($($prefix, )* $head, ) = self; smallvec![$($prefix.upcast(),)* $head.upcast()] } } $(#[$attrs])? impl<'a, $($prefix: Value, )* $head: Value> Arguments<'a> for ($(Handle<'a, $prefix>, )* Handle<'a, $head>, ) {} impl_arguments_expand! { $(#[$attrs])? [ $($prefix, )* $head ]; [ $($tail),* ]; } }; } macro_rules! impl_arguments { { [ $($show:ident),* ]; [ $($hide:ident),* ]; } => { impl_arguments_expand! { []; [ $($show),* ]; } impl_arguments_expand! { #[doc(hidden)] [ $($show),* ]; [ $($hide),* ]; } } } impl_arguments! { // Tuples up to length 8 are included in the docs. [V1, V2, V3, V4, V5, V6, V7, V8]; // Tuples up to length 32 are not included in the docs. [ V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30, V31, V32 ]; } ================================================ FILE: crates/neon/src/types_impl/function/private.rs ================================================ use smallvec::SmallVec; use crate::{context::Cx, handle::Handle, result::NeonResult, types::JsValue}; pub type ArgsVec<'a> = SmallVec<[Handle<'a, JsValue>; 8]>; /// This type marks the `TryIntoArguments` trait as sealed. pub trait TryIntoArgumentsInternal<'cx> { fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult>; } /// This type marks the `Arguments` trait as sealed. pub trait ArgumentsInternal<'a> { fn into_args_vec(self) -> ArgsVec<'a>; } ================================================ FILE: crates/neon/src/types_impl/mod.rs ================================================ // See types_docs.rs for top-level module API docs. #[cfg(feature = "napi-6")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] pub mod bigint; pub(crate) mod boxed; pub mod buffer; #[cfg(feature = "napi-5")] pub(crate) mod date; pub(crate) mod error; pub mod extract; pub mod function; pub(crate) mod promise; pub(crate) mod private; pub(crate) mod utf8; use std::{ any, fmt::{self, Debug}, }; use private::prepare_call; use smallvec::smallvec; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, FunctionContext, }, handle::{ internal::{SuperType, TransparentNoCopyWrapper}, Handle, }, object::Object, result::{JsResult, NeonResult, ResultExt, Throw}, sys::{self, raw}, types::{ function::{BindOptions, CallOptions, ConstructOptions}, private::ValueInternal, utf8::Utf8, }, }; pub use self::{ boxed::{Finalize, JsBox}, buffer::types::{ JsArrayBuffer, JsBigInt64Array, JsBigUint64Array, JsBuffer, JsFloat32Array, JsFloat64Array, JsInt16Array, JsInt32Array, JsInt8Array, JsTypedArray, JsUint16Array, JsUint32Array, JsUint8Array, }, error::JsError, promise::{Deferred, JsPromise}, }; #[cfg(feature = "napi-5")] pub use self::date::{DateError, DateErrorKind, JsDate}; #[cfg(all(feature = "napi-5", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))] pub use self::promise::JsFuture; // This should be considered deprecated and will be removed: // https://github.com/neon-bindings/neon/issues/983 pub(crate) fn build<'a, T: Value, F: FnOnce(&mut raw::Local) -> bool>( env: Env, init: F, ) -> JsResult<'a, T> { unsafe { let mut local: raw::Local = std::mem::zeroed(); if init(&mut local) { Ok(Handle::new_internal(T::from_local(env, local))) } else { Err(Throw::new()) } } } impl SuperType for JsValue { fn upcast_internal(v: &T) -> JsValue { JsValue(v.to_local()) } } impl SuperType for JsObject { fn upcast_internal(v: &T) -> JsObject { JsObject(v.to_local()) } } /// The trait shared by all JavaScript values. pub trait Value: ValueInternal { fn to_string<'cx, C: Context<'cx>>(&self, cx: &mut C) -> JsResult<'cx, JsString> { let env = cx.env(); build(env, |out| unsafe { sys::convert::to_string(out, env.to_raw(), self.to_local()) }) } fn as_value<'cx, C: Context<'cx>>(&self, _: &mut C) -> Handle<'cx, JsValue> { JsValue::new_internal(self.to_local()) } #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] /// Get a raw reference to the wrapped Node-API value. fn to_raw(&self) -> sys::Value { self.to_local() } #[cfg(feature = "sys")] #[cfg_attr(docsrs, doc(cfg(feature = "sys")))] /// Creates a value from a raw Node-API value. /// /// # Safety /// /// * `value` must be of type `Self` /// * `value` must be valid for `'cx` unsafe fn from_raw<'cx, C: Context<'cx>>(cx: &C, value: sys::Value) -> Handle<'cx, Self> { Handle::new_internal(Self::from_local(cx.env(), value)) } } /// The type of any JavaScript value, i.e., the root of all types. /// /// The `JsValue` type is a catch-all type that sits at the top of the /// [JavaScript type hierarchy](./index.html#the-javascript-type-hierarchy). /// All JavaScript values can be safely and statically /// [upcast](crate::handle::Handle::upcast) to `JsValue`; by contrast, a /// [downcast](crate::handle::Handle::downcast) of a `JsValue` to another type /// requires a runtime check. /// (For TypeScript programmers, this can be thought of as similar to TypeScript's /// [`unknown`](https://www.typescriptlang.org/docs/handbook/2/functions.html#unknown) /// type.) /// /// The `JsValue` type can be useful for generic, dynamic, or otherwise /// hard-to-express API signatures, such as overloaded types: /// /// ``` /// # use neon::prelude::*; /// // Takes a string and adds the specified padding to the left. /// // If the padding is a string, it's added as-is. /// // If the padding is a number, then that number of spaces is added. /// fn pad_left(mut cx: FunctionContext) -> JsResult { /// let string: Handle = cx.argument(0)?; /// let padding: Handle = cx.argument(1)?; /// /// let padding: String = if let Ok(str) = padding.downcast::(&mut cx) { /// str.value(&mut cx) /// } else if let Ok(num) = padding.downcast::(&mut cx) { /// " ".repeat(num.value(&mut cx) as usize) /// } else { /// return cx.throw_type_error("expected string or number"); /// }; /// /// let new_value = padding + &string.value(&mut cx); /// Ok(cx.string(&new_value)) /// } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsValue(raw::Local); impl Value for JsValue {} unsafe impl TransparentNoCopyWrapper for JsValue { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsValue { fn name() -> &'static str { "any" } fn is_typeof(_cx: &mut Cx, _other: &Other) -> bool { true } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsValue(h) } } impl JsValue { pub(crate) fn new_internal<'a>(value: raw::Local) -> Handle<'a, JsValue> { Handle::new_internal(JsValue(value)) } } /// The type of JavaScript /// [`undefined`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) /// primitives. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // Extract the console object: /// let console: Handle = cx.global("console")?; /// /// // The undefined value: /// let undefined = cx.undefined(); /// /// // Call console.log(undefined): /// console.method(&mut cx, "log")?.arg(undefined)?.exec()?; /// # Ok(undefined) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsUndefined(raw::Local); impl JsUndefined { /// Creates an `undefined` value. /// /// Although this method can be called many times, all `undefined` /// values are indistinguishable. /// /// **See also:** [`Context::undefined`] pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Handle<'a, JsUndefined> { JsUndefined::new_internal(cx.env()) } pub(crate) fn new_internal<'a>(env: Env) -> Handle<'a, JsUndefined> { unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::primitive::undefined(&mut local, env.to_raw()); Handle::new_internal(JsUndefined(local)) } } } impl Value for JsUndefined {} unsafe impl TransparentNoCopyWrapper for JsUndefined { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsUndefined { fn name() -> &'static str { "undefined" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_undefined(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsUndefined(h) } } /// The type of JavaScript /// [`null`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) /// primitives. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// let null = cx.null(); /// cx.global::("console")? /// .method(&mut cx, "log")? /// .arg(null)? /// .exec()?; /// # Ok(null) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsNull(raw::Local); impl JsNull { /// Creates a `null` value. /// /// Although this method can be called many times, all `null` /// values are indistinguishable. /// /// **See also:** [`Context::null`] pub fn new<'a, C: Context<'a>>(cx: &mut C) -> Handle<'a, JsNull> { JsNull::new_internal(cx.env()) } pub(crate) fn new_internal<'a>(env: Env) -> Handle<'a, JsNull> { unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::primitive::null(&mut local, env.to_raw()); Handle::new_internal(JsNull(local)) } } } impl Value for JsNull {} unsafe impl TransparentNoCopyWrapper for JsNull { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsNull { fn name() -> &'static str { "null" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_null(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsNull(h) } } /// The type of JavaScript /// [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) /// primitives. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // The two Boolean values: /// let t = cx.boolean(true); /// let f = cx.boolean(false); /// /// // Call console.log(true, false): /// cx.global::("console")?.method(&mut cx, "log")?.args((t, f))?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsBoolean(raw::Local); impl JsBoolean { /// Creates a Boolean value with value `b`. /// /// **See also:** [`Context::boolean`] pub fn new<'a, C: Context<'a>>(cx: &mut C, b: bool) -> Handle<'a, JsBoolean> { JsBoolean::new_internal(cx.env(), b) } pub(crate) fn new_internal<'a>(env: Env, b: bool) -> Handle<'a, JsBoolean> { unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::primitive::boolean(&mut local, env.to_raw(), b); Handle::new_internal(JsBoolean(local)) } } /// Returns the value of this Boolean as a Rust `bool`. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { let env = cx.env().to_raw(); unsafe { sys::primitive::boolean_value(env, self.to_local()) } } } impl Value for JsBoolean {} unsafe impl TransparentNoCopyWrapper for JsBoolean { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsBoolean { fn name() -> &'static str { "boolean" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_boolean(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsBoolean(h) } } /// The type of JavaScript /// [string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) /// primitives. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // Create a string: /// let s = cx.string("hello 🥹"); /// /// // Call console.log(s): /// cx.global::("console")?.method(&mut cx, "log")?.arg(s)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsString(raw::Local); /// An error produced when constructing a string that exceeds the limits of the runtime. #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] pub struct StringOverflow(usize); impl fmt::Display for StringOverflow { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "string size out of range: {}", self.0) } } /// The result of constructing a new `JsString`. pub type StringResult<'a> = Result, StringOverflow>; impl<'a> ResultExt> for StringResult<'a> { fn or_throw<'b, C: Context<'b>>(self, cx: &mut C) -> JsResult<'a, JsString> { match self { Ok(v) => Ok(v), Err(e) => cx.throw_range_error(e.to_string()), } } } impl Value for JsString {} unsafe impl TransparentNoCopyWrapper for JsString { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsString { fn name() -> &'static str { "string" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_string(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsString(h) } } impl JsString { /// Returns the size of the UTF-8 representation of this string, /// measured in 8-bit code units. /// /// Equivalent to `self.value(cx).len()` (but more efficient). /// /// # Example /// /// The string `"hello 🥹"` encodes as 10 bytes in UTF-8: /// /// - 6 bytes for `"hello "` (including the space). /// - 4 bytes for the emoji `"🥹"`. /// /// ```rust /// # use neon::prelude::*; /// # fn string_len(mut cx: FunctionContext) -> JsResult { /// let str = cx.string("hello 🥹"); /// assert_eq!(10, str.size(&mut cx)); /// # Ok(cx.undefined()) /// # } /// ``` pub fn size<'a, C: Context<'a>>(&self, cx: &mut C) -> usize { let env = cx.env().to_raw(); unsafe { sys::string::utf8_len(env, self.to_local()) } } /// Returns the size of the UTF-16 representation of this string, /// measured in 16-bit code units. /// /// Equivalent to `self.to_utf16(cx).len()` (but more efficient). /// /// # Example /// /// The string `"hello 🥹"` encodes as 8 code units in UTF-16: /// /// - 6 `u16`s for `"hello "` (including the space). /// - 2 `u16`s for the emoji `"🥹"`. /// /// ```rust /// # use neon::prelude::*; /// # fn string_len_utf16(mut cx: FunctionContext) -> JsResult { /// let str = cx.string("hello 🥹"); /// assert_eq!(8, str.size_utf16(&mut cx)); /// # Ok(cx.undefined()) /// # } /// ``` pub fn size_utf16<'a, C: Context<'a>>(&self, cx: &mut C) -> usize { let env = cx.env().to_raw(); unsafe { sys::string::utf16_len(env, self.to_local()) } } /// Convert this JavaScript string into a Rust [`String`]. /// /// # Example /// /// This example function expects a single JavaScript string as argument /// and prints it out. /// /// ```rust /// # use neon::prelude::*; /// fn print_string(mut cx: FunctionContext) -> JsResult { /// let s = cx.argument::(0)?.value(&mut cx); /// println!("JavaScript string contents: {}", s); /// /// Ok(cx.undefined()) /// } /// ``` pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> String { let env = cx.env().to_raw(); unsafe { let capacity = sys::string::utf8_len(env, self.to_local()) + 1; let mut buffer: Vec = Vec::with_capacity(capacity); let len = sys::string::data(env, buffer.as_mut_ptr(), capacity, self.to_local()); buffer.set_len(len); String::from_utf8_unchecked(buffer) } } /// Convert this JavaScript string into a [`Vec`] encoded as UTF-16. /// /// The returned vector is guaranteed to be valid UTF-16, so libraries that handle /// UTF-16-encoded strings can assume the content to be valid. /// /// # Example /// /// This example function expects a single JavaScript string as argument and prints it out /// as a raw vector of `u16`s. /// /// ```rust /// # use neon::prelude::*; /// fn print_string_as_utf16(mut cx: FunctionContext) -> JsResult { /// let s = cx.argument::(0)?.to_utf16(&mut cx); /// println!("JavaScript string as raw UTF-16: {:?}", s); /// /// Ok(cx.undefined()) /// } /// ``` /// /// This next example function also expects a single JavaScript string as argument and converts /// to a [`Vec`], but utilizes the [`widestring`](https://crates.io/crates/widestring) /// crate to handle the vector as a typical string. /// /// ```rust /// # use neon::prelude::*; /// use widestring::Utf16String; /// /// fn print_with_widestring(mut cx: FunctionContext) -> JsResult { /// let s = cx.argument::(0)?.to_utf16(&mut cx); /// /// // The returned vector is guaranteed to be valid UTF-16, so we can /// // safely skip the validation step. /// let s = unsafe { Utf16String::from_vec_unchecked(s) }; /// /// println!("JavaScript string as UTF-16: {}", s); /// /// Ok(cx.undefined()) /// } /// ``` pub fn to_utf16<'a, C: Context<'a>>(&self, cx: &mut C) -> Vec { let env = cx.env().to_raw(); unsafe { let capacity = sys::string::utf16_len(env, self.to_local()) + 1; let mut buffer: Vec = Vec::with_capacity(capacity); let len = sys::string::data_utf16(env, buffer.as_mut_ptr(), capacity, self.to_local()); buffer.set_len(len); buffer } } /// Creates a new `JsString` value from a Rust string by copying its contents. /// /// This method panics if the string is longer than the maximum string size allowed /// by the JavaScript engine. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn string_new(mut cx: FunctionContext) -> JsResult { /// let str = JsString::new(&mut cx, "hello 🥹"); /// assert_eq!(10, str.size(&mut cx)); /// # Ok(cx.undefined()) /// # } /// ``` /// /// **See also:** [`Context::string`] pub fn new<'a, C: Context<'a>, S: AsRef>(cx: &mut C, val: S) -> Handle<'a, JsString> { JsString::try_new(cx, val).unwrap() } /// Tries to create a new `JsString` value from a Rust string by copying its contents. /// /// Returns `Err(StringOverflow)` if the string is longer than the maximum string size /// allowed by the JavaScript engine. /// /// # Example /// /// This example tries to construct a JavaScript string from a Rust string of /// unknown length, and on overflow generates an alternate truncated string with /// a suffix (`"[…]"`) to indicate the truncation. /// /// ``` /// # use neon::prelude::*; /// # fn string_try_new(mut cx: FunctionContext) -> JsResult { /// # static str: &'static str = "hello 🥹"; /// let s = match JsString::try_new(&mut cx, str) { /// Ok(s) => s, /// Err(_) => cx.string(format!("{}[…]", &str[0..32])), /// }; /// # Ok(s) /// # } /// ``` pub fn try_new<'a, C: Context<'a>, S: AsRef>(cx: &mut C, val: S) -> StringResult<'a> { let val = val.as_ref(); match JsString::new_internal(cx.env(), val) { Some(s) => Ok(s), None => Err(StringOverflow(val.len())), } } pub(crate) fn new_internal<'a>(env: Env, val: &str) -> Option> { let (ptr, len) = if let Some(small) = Utf8::from(val).into_small() { small.lower() } else { return None; }; unsafe { let mut local: raw::Local = std::mem::zeroed(); if sys::string::new(&mut local, env.to_raw(), ptr, len) { Some(Handle::new_internal(JsString(local))) } else { None } } } } /// The type of JavaScript /// [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#primitive_values) /// primitives. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // Create a number: /// let n = cx.number(17.0); /// /// // Call console.log(n): /// cx.global::("console")?.method(&mut cx, "log")?.arg(n)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsNumber(raw::Local); impl JsNumber { /// Creates a new number with value `x`. /// /// **See also:** [`Context::number`] pub fn new<'a, C: Context<'a>, T: Into>(cx: &mut C, x: T) -> Handle<'a, JsNumber> { JsNumber::new_internal(cx.env(), x.into()) } pub(crate) fn new_internal<'a>(env: Env, v: f64) -> Handle<'a, JsNumber> { unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::primitive::number(&mut local, env.to_raw(), v); Handle::new_internal(JsNumber(local)) } } /// Returns the value of this number as a Rust `f64`. pub fn value<'a, C: Context<'a>>(&self, cx: &mut C) -> f64 { let env = cx.env().to_raw(); unsafe { sys::primitive::number_value(env, self.to_local()) } } } impl Value for JsNumber {} unsafe impl TransparentNoCopyWrapper for JsNumber { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsNumber { fn name() -> &'static str { "number" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_number(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsNumber(h) } } /// The type of JavaScript /// [objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#objects), /// i.e., the root of all object types. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { /// // Create an object: /// let obj = cx.empty_object() /// .prop(&mut cx, "name") /// .set("Neon")? /// .prop("url") /// .set("https://neon-bindings.com")? /// .this(); /// /// // Call console.log(obj): /// cx.global::("console")?.method(&mut cx, "log")?.arg(obj)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsObject(raw::Local); impl Value for JsObject {} unsafe impl TransparentNoCopyWrapper for JsObject { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsObject { fn name() -> &'static str { "object" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_object(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsObject(h) } } impl Object for JsObject {} impl JsObject { /// Creates a new empty object. /// /// **See also:** [`Context::empty_object`] pub fn new<'a, C: Context<'a>>(c: &mut C) -> Handle<'a, JsObject> { JsObject::new_internal(c.env()) } pub(crate) fn new_internal<'a>(env: Env) -> Handle<'a, JsObject> { JsObject::build(|out| unsafe { sys::object::new(out, env.to_raw()) }) } pub(crate) fn build<'a, F: FnOnce(&mut raw::Local)>(init: F) -> Handle<'a, JsObject> { unsafe { let mut local: raw::Local = std::mem::zeroed(); init(&mut local); Handle::new_internal(JsObject(local)) } } } /// The type of JavaScript /// [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) /// objects. /// /// An array is any JavaScript value for which /// [`Array.isArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) /// would return `true`. /// /// # Example /// /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// // Create a new empty array: /// let a: Handle = cx.empty_array(); /// /// // Push some values onto the array: /// a.prop(&mut cx, 0).set(17)?; /// a.prop(&mut cx, 1).set("hello")?; /// # Ok(a) /// # } /// ``` #[derive(Debug)] #[repr(transparent)] pub struct JsArray(raw::Local); impl JsArray { /// Constructs a new empty array of length `len`, equivalent to the JavaScript /// expression `new Array(len)`. /// /// Note that for non-zero `len`, this creates a /// [sparse array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#sparse_arrays), /// which can sometimes have surprising behavior. To ensure that a new array /// is and remains dense (i.e., not sparse), consider creating an empty array /// with `JsArray::new(cx, 0)` or `cx.empty_array()` and only appending /// elements to the end of the array. /// /// **See also:** [`Context::empty_array`] pub fn new<'a, C: Context<'a>>(cx: &mut C, len: usize) -> Handle<'a, JsArray> { JsArray::new_internal(cx.env(), len) } pub(crate) fn new_internal<'a>(env: Env, len: usize) -> Handle<'a, JsArray> { unsafe { let mut local: raw::Local = std::mem::zeroed(); sys::array::new(&mut local, env.to_raw(), len); Handle::new_internal(JsArray(local)) } } /// Copies the array contents into a new [`Vec`] by iterating through all indices /// from 0 to `self.len()`. /// /// The length is dynamically checked on each iteration in case the array is modified /// during the computation. pub fn to_vec<'a, C: Context<'a>>(&self, cx: &mut C) -> NeonResult>> { let mut result = Vec::with_capacity(self.len_inner(cx.env()) as usize); let mut i = 0; loop { // Since getting a property can trigger arbitrary code, // we have to re-check the length on every iteration. if i >= self.len_inner(cx.env()) { return Ok(result); } result.push(self.get(cx, i)?); i += 1; } } fn len_inner(&self, env: Env) -> u32 { unsafe { sys::array::len(env.to_raw(), self.to_local()) } } #[allow(clippy::len_without_is_empty)] /// Returns the length of the array, equivalent to the JavaScript expression /// [`this.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/length). pub fn len<'a, C: Context<'a>>(&self, cx: &mut C) -> u32 { self.len_inner(cx.env()) } /// Indicates whether the array is empty, equivalent to /// `self.len() == 0`. pub fn is_empty<'a, C: Context<'a>>(&self, cx: &mut C) -> bool { self.len(cx) == 0 } } impl Value for JsArray {} unsafe impl TransparentNoCopyWrapper for JsArray { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsArray { fn name() -> &'static str { "Array" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_array(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsArray(h) } } impl Object for JsArray {} /// The type of JavaScript /// [`Function`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) /// objects. #[derive(Debug)] #[repr(transparent)] /// /// A `JsFunction` may come from an existing JavaScript function, for example /// by extracting it from the property of another object such as the /// [global object](crate::context::Context::global), or it may be defined in Rust /// with [`JsFunction::new()`](JsFunction::new). /// /// ## Calling functions /// /// Neon provides a convenient syntax for calling JavaScript functions with the /// [`bind()`](JsFunction::bind) method, which produces a [`BindOptions`](BindOptions) /// struct that can be used to provide the function arguments (and optionally, the binding for /// `this`) before calling the function: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// // Extract the parseInt function from the global object /// let parse_int: Handle = cx.global("parseInt")?; /// /// // Call parseInt("42") /// let x: Handle = parse_int /// .bind(&mut cx) /// .arg("42")? /// .call()?; /// # Ok(x) /// # } /// ``` /// /// ## Calling functions as constructors /// /// A `JsFunction` can be called as a constructor (like `new Array(16)` or /// `new URL("https://neon-bindings.com")`) with the /// [`construct()`](BindOptions::construct) method: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// // Extract the URL constructor from the global object /// let url: Handle = cx.global("URL")?; /// /// // Call new URL("https://neon-bindings.com") /// let obj = url /// .bind(&mut cx) /// .arg("https://neon-bindings.com")? /// .construct()?; /// # Ok(obj) /// # } /// ``` /// /// ## Defining functions /// /// JavaScript functions can be defined in Rust with the /// [`JsFunction::new()`](JsFunction::new) constructor, which takes /// a Rust implementation function and produces a JavaScript function. /// /// ``` /// # use neon::prelude::*; /// // A function implementation that adds 1 to its first argument /// fn add1(mut cx: FunctionContext) -> JsResult { /// let x: Handle = cx.argument(0)?; /// let v = x.value(&mut cx); /// Ok(cx.number(v + 1.0)) /// } /// /// # fn foo(mut cx: FunctionContext) -> JsResult { /// // Define a new JsFunction implemented with the add1 function /// let f = JsFunction::new(&mut cx, add1)?; /// # Ok(f) /// # } /// ``` pub struct JsFunction { raw: raw::Local, } impl Object for JsFunction {} impl JsFunction { #[cfg(not(feature = "napi-5"))] /// Returns a new `JsFunction` implemented by `f`. pub fn new<'a, C, U>( cx: &mut C, f: fn(FunctionContext) -> JsResult, ) -> JsResult<'a, JsFunction> where C: Context<'a>, U: Value, { let name = any::type_name_of_val(&f); Self::new_internal(cx, f, name) } #[cfg(feature = "napi-5")] /// Returns a new `JsFunction` implemented by `f`. pub fn new<'a, C, F, V>(cx: &mut C, f: F) -> JsResult<'a, JsFunction> where C: Context<'a>, F: Fn(FunctionContext) -> JsResult + 'static, V: Value, { let name = any::type_name::(); Self::new_internal(cx, f, name) } #[cfg(not(feature = "napi-5"))] /// Returns a new `JsFunction` implemented by `f` with specified name pub fn with_name<'a, C, U>( cx: &mut C, name: &str, f: fn(FunctionContext) -> JsResult, ) -> JsResult<'a, JsFunction> where C: Context<'a>, U: Value, { Self::new_internal(cx, f, name) } #[cfg(feature = "napi-5")] /// Returns a new `JsFunction` implemented by `f` with specified name pub fn with_name<'a, C, F, V>(cx: &mut C, name: &str, f: F) -> JsResult<'a, JsFunction> where C: Context<'a>, F: Fn(FunctionContext) -> JsResult + 'static, V: Value, { Self::new_internal(cx, f, name) } fn new_internal<'a, C, F, V>(cx: &mut C, f: F, name: &str) -> JsResult<'a, JsFunction> where C: Context<'a>, F: Fn(FunctionContext) -> JsResult + 'static, V: Value, { use std::panic::AssertUnwindSafe; use std::ptr; use crate::context::CallbackInfo; use crate::types::error::convert_panics; let f = move |env: raw::Env, info| { let env = env.into(); let info = unsafe { CallbackInfo::new(info) }; FunctionContext::with(env, &info, |cx| { convert_panics(env, AssertUnwindSafe(|| f(cx))) .map(|v| v.to_local()) // We do not have a Js Value to return, most likely due to an exception. // If we are in a throwing state, constructing a Js Value would be invalid. // While not explicitly written, the Node-API documentation includes many examples // of returning `NULL` when a native function does not return a value. // https://nodejs.org/api/n-api.html#n_api_napi_create_function .unwrap_or_else(|_: Throw| ptr::null_mut()) }) }; unsafe { if let Ok(raw) = sys::fun::new(cx.env().to_raw(), name, f) { Ok(Handle::new_internal(JsFunction { raw })) } else { Err(Throw::new()) } } } } impl JsFunction { /// Calls this function. /// /// **See also:** [`JsFunction::bind`]. pub fn call<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, this: Handle<'b, T>, args: AS, ) -> JsResult<'a, JsValue> where T: Value, AS: AsRef<[Handle<'b, JsValue>]>, { unsafe { self.try_call(cx, this, args) } } /// Calls this function for side effect, discarding its result. /// /// **See also:** [`JsFunction::bind`]. pub fn exec<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, this: Handle<'b, T>, args: AS, ) -> NeonResult<()> where T: Value, AS: AsRef<[Handle<'b, JsValue>]>, { self.call(cx, this, args)?; Ok(()) } /// Calls this function as a constructor. /// /// **See also:** [`JsFunction::bind`]. pub fn construct<'a, 'b, C: Context<'a>, AS>( &self, cx: &mut C, args: AS, ) -> JsResult<'a, JsObject> where AS: AsRef<[Handle<'b, JsValue>]>, { let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; let env = cx.env().to_raw(); build(cx.env(), |out| unsafe { sys::fun::construct(out, env, self.to_local(), argc, argv) }) } } impl JsFunction { /// Create a [`BindOptions`] builder for calling this function. /// /// The builder methods make it convenient to assemble the call from parts: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { /// # let parse_int: Handle = cx.global("parseInt")?; /// let x: f64 = parse_int /// .bind(&mut cx) /// .arg("42")? /// .call()?; /// # Ok(cx.number(x)) /// # } /// ``` pub fn bind<'a, 'cx: 'a>(&self, cx: &'a mut Cx<'cx>) -> BindOptions<'a, 'cx> { let callee = self.as_value(cx); BindOptions { cx, callee, this: None, args: smallvec![], } } } impl JsFunction { /// Create a [`CallOptions`](function::CallOptions) for calling this function. #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn call_with<'a, C: Context<'a>>(&self, _cx: &C) -> CallOptions<'a> { CallOptions { this: None, // # Safety // Only a single context may be used at a time because parent scopes // are locked with `&mut self`. Therefore, the lifetime of `CallOptions` // will always be the most narrow scope possible. callee: Handle::new_internal(unsafe { self.clone() }), args: smallvec![], } } /// Create a [`ConstructOptions`](function::ConstructOptions) for calling this function /// as a constructor. #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn construct_with<'a, C: Context<'a>>(&self, _cx: &C) -> ConstructOptions<'a> { ConstructOptions { // # Safety // Only a single context may be used at a time because parent scopes // are locked with `&mut self`. Therefore, the lifetime of `ConstructOptions` // will always be the most narrow scope possible. callee: Handle::new_internal(unsafe { self.clone() }), args: smallvec![], } } /// # Safety /// The caller must wrap in a `Handle` with an appropriate lifetime. unsafe fn clone(&self) -> Self { Self { raw: self.raw } } } impl Value for JsFunction {} unsafe impl TransparentNoCopyWrapper for JsFunction { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.raw } } impl ValueInternal for JsFunction { fn name() -> &'static str { "function" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_function(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.raw } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { JsFunction { raw: h } } } #[cfg(feature = "napi-6")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-6")))] #[derive(Debug)] #[repr(transparent)] /// The type of JavaScript /// [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) /// values. /// /// # Example /// /// The following shows an example of adding two numbers that exceed /// [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER). /// /// ``` /// # use neon::{prelude::*, types::JsBigInt}; /// /// fn add_bigint(mut cx: FunctionContext) -> JsResult { /// // Get references to the `BigInt` arguments /// let a = cx.argument::(0)?; /// let b = cx.argument::(1)?; /// /// // Convert the `BigInt` to `i64` /// let a = a.to_i64(&mut cx) /// // On failure, convert err to a `RangeError` exception /// .or_throw(&mut cx)?; /// /// let b = b.to_i64(&mut cx).or_throw(&mut cx)?; /// let sum = a + b; /// /// // Create a `BigInt` from the `i64` sum /// Ok(JsBigInt::from_i64(&mut cx, sum)) /// } /// ``` pub struct JsBigInt(raw::Local); ================================================ FILE: crates/neon/src/types_impl/private.rs ================================================ use std::{ffi::c_void, mem::MaybeUninit}; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, result::{JsResult, NeonResult, Throw}, sys::{self, bindings as napi, raw}, types::Value, }; use super::JsValue; // Maximum number of function arguments in V8. const V8_ARGC_LIMIT: usize = 65535; pub(crate) unsafe fn prepare_call<'a, 'b, C: Context<'a>>( cx: &mut C, args: &[Handle<'b, JsValue>], ) -> NeonResult<(usize, *const c_void)> { // Note: This cast is only save because `Handle<'_, JsValue>` is // guaranteed to have the same layout as a pointer because `Handle` // and `JsValue` are both `repr(C)` newtypes. let argv = args.as_ptr().cast(); let argc = args.len(); if argc > V8_ARGC_LIMIT { return cx.throw_range_error("too many arguments"); } Ok((argc, argv)) } pub trait ValueInternal: TransparentNoCopyWrapper + 'static { fn name() -> &'static str; fn is_typeof(cx: &mut Cx, other: &Other) -> bool; fn downcast(cx: &mut Cx, other: &Other) -> Option { if Self::is_typeof(cx, other) { // # Safety // `is_typeof` check ensures this is the correct JavaScript type Some(unsafe { Self::from_local(cx.env(), other.to_local()) }) } else { None } } fn cast<'a, T: Value, F: FnOnce(raw::Local) -> T>(self, f: F) -> Handle<'a, T> { Handle::new_internal(f(self.to_local())) } fn to_local(&self) -> raw::Local; // # Safety // JavaScript value must be of type `Self` unsafe fn from_local(env: Env, h: raw::Local) -> Self; unsafe fn try_call<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, this: Handle<'b, T>, args: AS, ) -> JsResult<'a, JsValue> where T: Value, AS: AsRef<[Handle<'b, JsValue>]>, { let callee = self.to_local(); let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; let env = cx.env(); let mut result: MaybeUninit = MaybeUninit::zeroed(); let status = napi::call_function( env.to_raw(), this.to_local(), callee, argc, argv.cast(), result.as_mut_ptr(), ); check_call_status(cx, callee, status)?; Ok(Handle::new_internal(JsValue::from_local( env, result.assume_init(), ))) } unsafe fn try_construct<'a, 'b, C: Context<'a>, AS>( &self, cx: &mut C, args: AS, ) -> JsResult<'a, JsValue> where AS: AsRef<[Handle<'b, JsValue>]>, { let callee = self.to_local(); let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; let env = cx.env(); let mut result: MaybeUninit = MaybeUninit::zeroed(); let status = napi::new_instance(env.to_raw(), callee, argc, argv.cast(), result.as_mut_ptr()); check_call_status(cx, callee, status)?; Ok(Handle::new_internal(JsValue::from_local( env, result.assume_init(), ))) } } unsafe fn check_call_status<'a, C: Context<'a>>( cx: &mut C, callee: raw::Local, status: Result<(), sys::Status>, ) -> NeonResult<()> { match status { Err(sys::Status::InvalidArg) if !sys::tag::is_function(cx.env().to_raw(), callee) => { return cx.throw_error("not a function"); } Err(sys::Status::PendingException) => { return Err(Throw::new()); } status => status.unwrap(), } Ok(()) } ================================================ FILE: crates/neon/src/types_impl/promise.rs ================================================ use std::ptr; use crate::{ context::{ internal::{ContextInternal, Env}, Context, Cx, }, handle::{internal::TransparentNoCopyWrapper, Handle}, object::Object, result::JsResult, sys::{self, no_panic::FailureBoundary, raw}, types::{private::ValueInternal, Value}, }; #[cfg(feature = "napi-4")] use crate::{ event::{Channel, JoinHandle, SendError}, types::extract::TryIntoJs, }; #[cfg(feature = "napi-6")] use crate::{ lifecycle::{DropData, InstanceData}, sys::tsfn::ThreadsafeFunction, }; #[cfg(all(feature = "napi-5", feature = "futures"))] use { crate::event::{JoinError, SendThrow}, crate::result::NeonResult, crate::types::{JsFunction, JsValue}, std::future::Future, std::pin::Pin, std::sync::Mutex, std::task::{self, Poll}, tokio::sync::oneshot, }; #[cfg(any(feature = "napi-6", all(feature = "napi-5", feature = "futures")))] use std::sync::Arc; const BOUNDARY: FailureBoundary = FailureBoundary { both: "A panic and exception occurred while resolving a `neon::types::Deferred`", exception: "An exception occurred while resolving a `neon::types::Deferred`", panic: "A panic occurred while resolving a `neon::types::Deferred`", }; #[derive(Debug)] #[repr(transparent)] /// The type of JavaScript /// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) /// objects. /// /// [`JsPromise`] instances may be constructed with [`Context::promise`], which /// produces both a promise and a [`Deferred`], which can be used to control /// the behavior of the promise. A `Deferred` struct is similar to the `resolve` /// and `reject` functions produced by JavaScript's standard /// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) /// constructor: /// /// ```javascript /// let deferred; /// let promise = new Promise((resolve, reject) => { /// deferred = { resolve, reject }; /// }); /// ``` /// /// # Example /// /// ``` /// # use neon::prelude::*; /// fn resolve_promise(mut cx: FunctionContext) -> JsResult { /// let (deferred, promise) = cx.promise(); /// let msg = cx.string("Hello, World!"); /// /// deferred.resolve(&mut cx, msg); /// /// Ok(promise) /// } /// ``` /// /// # Example: Asynchronous task /// /// This example uses the [linkify](https://crates.io/crates/linkify) crate in an /// asynchronous task, i.e. a /// [Node worker pool](https://nodejs.org/en/docs/guides/dont-block-the-event-loop/) /// thread, to find all the links in a text string. /// /// Alternate implementations might use a custom Rust thread or thread pool to avoid /// blocking the worker pool; for more information, see the [`JsFuture`] example. /// /// ``` /// # use neon::prelude::*; /// use linkify::{LinkFinder, LinkKind}; /// # #[cfg(feature = "doc-dependencies")] /// use easy_cast::Cast; // for safe numerical conversions /// /// # #[cfg(feature = "doc-dependencies")] /// fn linkify(mut cx: FunctionContext) -> JsResult { /// let text = cx.argument::(0)?.value(&mut cx); /// /// let promise = cx /// .task(move || { /// let (indices, kinds): (Vec<_>, Vec<_>) = LinkFinder::new() /// // The spans() method fully partitions the text /// // into a sequence of contiguous spans, some of which /// // are plain text and some of which are links. /// .spans(&text) /// .map(|span| { /// // The first span starts at 0 and the rest start /// // at their preceding span's end index. /// let end: u32 = span.end().cast(); /// /// let kind: u8 = match span.kind() { /// Some(LinkKind::Url) => 1, /// Some(LinkKind::Email) => 2, /// _ => 0, /// }; /// /// (end, kind) /// }) /// .unzip(); /// (indices, kinds) /// }) /// .promise(|mut cx, (indices, kinds)| { /// let indices = JsUint32Array::from_slice(&mut cx, &indices)?; /// let kinds = JsUint8Array::from_slice(&mut cx, &kinds)?; /// Ok(cx.empty_object() /// .prop(&mut cx, "indices") /// .set(indices)? /// .prop("kinds") /// .set(kinds)? /// .this()) /// }); /// /// Ok(promise) /// } /// ``` pub struct JsPromise(raw::Local); impl JsPromise { pub(crate) fn new<'a, C: Context<'a>>(cx: &mut C) -> (Deferred, Handle<'a, Self>) { let (deferred, promise) = unsafe { sys::promise::create(cx.env().to_raw()) }; let deferred = Deferred { internal: Some(NodeApiDeferred(deferred)), #[cfg(feature = "napi-6")] drop_queue: InstanceData::drop_queue(cx), }; (deferred, Handle::new_internal(JsPromise(promise))) } /// Creates a new `Promise` immediately resolved with the given value. If the value is a /// `Promise` or a then-able, it will be flattened. /// /// `JsPromise::resolve` is useful to ensure a value that might not be a `Promise` or /// might not be a native promise is converted to a `Promise` before use. pub fn resolve<'a, C: Context<'a>, T: Value>(cx: &mut C, value: Handle) -> Handle<'a, Self> { let (deferred, promise) = cx.promise(); deferred.resolve(cx, value); promise } /// Creates a nwe `Promise` immediately rejected with the given error. pub fn reject<'a, C: Context<'a>, E: Value>(cx: &mut C, err: Handle) -> Handle<'a, Self> { let (deferred, promise) = cx.promise(); deferred.reject(cx, err); promise } #[cfg(all(feature = "napi-5", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))] /// Creates a [`Future`](std::future::Future) that can be awaited to receive the result of a /// JavaScript `Promise`. /// /// A callback must be provided that maps a `Result` representing the resolution or rejection of /// the `Promise` and returns a value as the `Future` output. /// /// _Note_: Unlike `Future`, `Promise` are eagerly evaluated and so are `JsFuture`. pub fn to_future<'a, O, C, F>(&self, cx: &mut C, f: F) -> NeonResult> where O: Send + 'static, C: Context<'a>, F: FnOnce(Cx, Result, Handle>) -> NeonResult + Send + 'static, { let then = self.get::(cx, "then")?; let (tx, rx) = oneshot::channel(); let take_state = { // Note: If this becomes a bottleneck, `unsafe` could be used to avoid it. // The promise spec guarantees that it will only be used once. let state = Arc::new(Mutex::new(Some((f, tx)))); move || { state .lock() .ok() .and_then(|mut lock| lock.take()) // This should never happen because `self` is a native `Promise` // and settling multiple times is a violation of the spec. .expect("Attempted to settle JsFuture multiple times") } }; let resolve = JsFunction::new(cx, { let take_state = take_state.clone(); move |mut cx| { let (f, tx) = take_state(); let v = cx.argument::(0)?; Cx::with_context(cx.env(), move |cx| { // Error indicates that the `Future` has already dropped; ignore let _ = tx.send(f(cx, Ok(v)).map_err(Into::into)); }); Ok(cx.undefined()) } })?; let reject = JsFunction::new(cx, { move |mut cx| { let (f, tx) = take_state(); let v = cx.argument::(0)?; Cx::with_context(cx.env(), move |cx| { // Error indicates that the `Future` has already dropped; ignore let _ = tx.send(f(cx, Err(v)).map_err(Into::into)); }); Ok(cx.undefined()) } })?; then.exec( cx, Handle::new_internal(Self(self.0)), [resolve.upcast(), reject.upcast()], )?; Ok(JsFuture { rx }) } } unsafe impl TransparentNoCopyWrapper for JsPromise { type Inner = raw::Local; fn into_inner(self) -> Self::Inner { self.0 } } impl ValueInternal for JsPromise { fn name() -> &'static str { "Promise" } fn is_typeof(cx: &mut Cx, other: &Other) -> bool { unsafe { sys::tag::is_promise(cx.env().to_raw(), other.to_local()) } } fn to_local(&self) -> raw::Local { self.0 } unsafe fn from_local(_env: Env, h: raw::Local) -> Self { Self(h) } } impl Value for JsPromise {} impl Object for JsPromise {} /// A controller struct that can be used to resolve or reject a [`JsPromise`]. /// /// It is recommended to settle a [`Deferred`] with [`Deferred::settle_with`] to ensure /// exceptions are caught. /// /// On Node-API versions less than 6, dropping a [`Deferred`] without settling will /// cause a panic. On Node-API 6+, the associated [`JsPromise`] will be automatically /// rejected. /// /// # Examples /// /// See [`JsPromise`], [`JsFuture`]. pub struct Deferred { internal: Option, #[cfg(feature = "napi-6")] drop_queue: Arc>, } impl Deferred { /// Resolve a [`JsPromise`] with a JavaScript value pub fn resolve<'a, V, C>(self, cx: &mut C, value: Handle) where V: Value, C: Context<'a>, { unsafe { sys::promise::resolve(cx.env().to_raw(), self.into_inner(), value.to_local()); } } /// Reject a [`JsPromise`] with a JavaScript value pub fn reject<'a, V, C>(self, cx: &mut C, value: Handle) where V: Value, C: Context<'a>, { unsafe { sys::promise::reject(cx.env().to_raw(), self.into_inner(), value.to_local()); } } #[cfg(feature = "napi-4")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] /// Settle the [`JsPromise`] by sending a closure across a [`Channel`][`crate::event::Channel`] /// to be executed on the main JavaScript thread. /// /// Usage is identical to [`Deferred::settle_with`]. /// /// Returns a [`SendError`][crate::event::SendError] if sending the closure to the main JavaScript thread fails. /// See [`Channel::try_send`][crate::event::Channel::try_send] for more details. pub fn try_settle_with( self, channel: &Channel, complete: F, ) -> Result, SendError> where V: Value, F: FnOnce(Cx) -> JsResult + Send + 'static, { channel.try_send(move |cx| { self.try_catch_settle(cx, complete); Ok(()) }) } #[cfg(feature = "napi-4")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] /// Settle the [`JsPromise`] by sending a closure across a [`Channel`][crate::event::Channel] /// to be executed on the main JavaScript thread. /// /// Panics if there is a libuv error. /// /// ``` /// # use neon::prelude::*; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let channel = cx.channel(); /// let (deferred, promise) = cx.promise(); /// /// deferred.settle_with(&channel, move |mut cx| Ok(cx.number(42))); /// /// # Ok(promise) /// # } /// ``` pub fn settle_with(self, channel: &Channel, complete: F) -> JoinHandle<()> where V: Value, F: FnOnce(Cx) -> JsResult + Send + 'static, { self.try_settle_with(channel, complete).unwrap() } #[cfg(feature = "napi-4")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] /// Settle the [`JsPromise`] by sending a type that implements [`TryIntoJs`] to the main JavaScript thread. /// /// Usage is identical to [`Deferred::settle`]. /// /// Returns a [`SendError`][crate::event::SendError] if sending the closure to the main JavaScript thread fails. /// See [`Channel::try_send`][crate::event::Channel::try_send] for more details. pub fn try_settle(self, channel: &Channel, v: T) -> Result, SendError> where for<'cx> T: TryIntoJs<'cx, Value = V> + Send + 'static, V: Value, { self.try_settle_with::(channel, move |mut cx| v.try_into_js(&mut cx)) } #[cfg(feature = "napi-4")] #[cfg_attr(docsrs, doc(cfg(feature = "napi-4")))] /// Settle the [`JsPromise`] by sending a type that implements [`TryIntoJs`] to the main JavaScript thread. /// /// Panics if there is a libuv error. /// /// ``` /// # use neon::prelude::*; /// # fn example(mut cx: FunctionContext) -> JsResult { /// let channel = cx.channel(); /// let (deferred, promise) = cx.promise(); /// /// deferred.settle(&channel, 42f64); /// /// # Ok(promise) /// # } /// ``` pub fn settle(self, channel: &Channel, v: T) -> JoinHandle<()> where for<'cx> T: TryIntoJs<'cx, Value = V> + Send + 'static, V: Value, { self.try_settle(channel, v).unwrap() } pub(crate) fn try_catch_settle<'a, C, V, F>(self, cx: C, f: F) where C: Context<'a>, V: Value, F: FnOnce(C) -> JsResult<'a, V>, { unsafe { BOUNDARY.catch_failure( cx.env().to_raw(), Some(self.into_inner()), move |_| match f(cx) { Ok(value) => value.to_local(), Err(_) => ptr::null_mut(), }, ); } } pub(crate) fn into_inner(mut self) -> sys::Deferred { self.internal.take().unwrap().0 } } #[repr(transparent)] pub(crate) struct NodeApiDeferred(sys::Deferred); unsafe impl Send for NodeApiDeferred {} #[cfg(feature = "napi-6")] impl NodeApiDeferred { pub(crate) unsafe fn leaked(self, env: raw::Env) { sys::promise::reject_err_message( env, self.0, "`neon::types::Deferred` was dropped without being settled", ); } } impl Drop for Deferred { #[cfg(not(feature = "napi-6"))] fn drop(&mut self) { // If `None`, the `Deferred` has already been settled if self.internal.is_none() { return; } // Destructors are called during stack unwinding, prevent a double // panic and instead prefer to leak. if std::thread::panicking() { eprintln!("Warning: neon::types::JsPromise leaked during a panic"); return; } // Only panic if the event loop is still running if let Ok(true) = crate::context::internal::IS_RUNNING.try_with(|v| *v.borrow()) { panic!("Must settle a `neon::types::JsPromise` with `neon::types::Deferred`"); } } #[cfg(feature = "napi-6")] fn drop(&mut self) { // If `None`, the `Deferred` has already been settled if let Some(internal) = self.internal.take() { let _ = self.drop_queue.call(DropData::Deferred(internal), None); } } } #[cfg(all(feature = "napi-5", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))] /// A type of JavaScript /// [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) /// object that acts as a [`Future`](std::future::Future). /// /// Unlike typical `Future` implementations, `JsFuture`s are eagerly executed /// because they are backed by a `Promise`. /// /// # Example /// /// This example uses a `JsFuture` to take asynchronous binary data and perform /// potentially expensive computations on that data in a Rust thread. /// /// The example uses a [Tokio](https://tokio.rs) thread pool (allocated and /// stored on demand with a [`OnceCell`](https://crates.io/crates/once_cell)) /// to run the computations. /// /// ``` /// # use neon::prelude::*; /// use neon::types::buffer::TypedArray; /// use once_cell::sync::OnceCell; /// use tokio::runtime::Runtime; /// /// // Lazily allocate a Tokio runtime to use as the thread pool. /// fn runtime(cx: &mut Cx) -> NeonResult<&'static Runtime> { /// static RUNTIME: OnceCell = OnceCell::new(); /// /// RUNTIME /// .get_or_try_init(Runtime::new) /// .or_else(|err| cx.throw_error(&err.to_string())) /// } /// /// // async_compute: Promise -> Promise /// // /// // Takes a promise that produces a typed array and returns a promise that: /// // - awaits the typed array from the original promise; /// // - computes a value from the contents of the array in a background thread; and /// // - resolves once the computation is completed /// pub fn async_compute(mut cx: FunctionContext) -> JsResult { /// let nums: Handle = cx.argument(0)?; /// /// // Convert the JS Promise to a Rust Future for use in a compute thread. /// let nums = nums.to_future(&mut cx, |mut cx, result| { /// // Get the promise's result value (or throw if it was rejected). /// let value = result.or_throw(&mut cx)?; /// /// // Downcast the result value to a Float64Array. /// let array: Handle = value.downcast_or_throw(&mut cx)?; /// /// // Convert the typed array to a Rust vector. /// let vec = array.as_slice(&cx).to_vec(); /// Ok(vec) /// })?; /// /// // Construct a result promise which will be fulfilled when the computation completes. /// let (deferred, promise) = cx.promise(); /// let channel = cx.channel(); /// let runtime = runtime(&mut cx)?; /// /// // Perform the computation in a background thread using the Tokio thread pool. /// runtime.spawn(async move { /// // Await the JsFuture, which yields Result, JoinError>. /// let result = match nums.await { /// // Perform the computation. In this example, we just calculate the sum /// // of all values in the array; more involved examples might be running /// // compression or decompression algorithms, encoding or decoding media /// // codecs, image filters or other media transformations, etc. /// Ok(nums) => Ok(nums.into_iter().sum::()), /// Err(err) => Err(err) /// }; /// /// // Resolve the result promise with the result of the computation. /// deferred.settle_with(&channel, |mut cx| { /// let result = result.or_throw(&mut cx)?; /// Ok(cx.number(result)) /// }); /// }); /// /// Ok(promise) /// } /// ``` pub struct JsFuture { // `Err` is always `Throw`, but `Throw` cannot be sent across threads rx: oneshot::Receiver>, } #[cfg(all(feature = "napi-5", feature = "futures"))] #[cfg_attr(docsrs, doc(cfg(all(feature = "napi-5", feature = "futures"))))] impl Future for JsFuture { type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { match Pin::new(&mut self.rx).poll(cx) { Poll::Ready(result) => { // Flatten `Result, RecvError>` by mapping to // `Result`. This can be simplified by replacing the // closure with a try-block after stabilization. // https://doc.rust-lang.org/beta/unstable-book/language-features/try-blocks.html let get_result = move || Ok(result??); Poll::Ready(get_result()) } Poll::Pending => Poll::Pending, } } } ================================================ FILE: crates/neon/src/types_impl/utf8.rs ================================================ use std::borrow::Cow; const SMALL_MAX: usize = i32::MAX as usize; /// V8 APIs that take UTF-8 strings take their length in the form of 32-bit /// signed integers. This type represents a UTF-8 string that contains no /// more than `i32::MAX` bytes of code units. pub struct SmallUtf8<'a> { contents: Cow<'a, str>, } impl<'a> SmallUtf8<'a> { pub fn lower(&self) -> (*const u8, i32) { (self.contents.as_ptr(), self.contents.len() as i32) } } /// A UTF-8 string that can be lowered to a representation usable for V8 /// APIs. pub struct Utf8<'a> { contents: Cow<'a, str>, } impl<'a> From<&'a str> for Utf8<'a> { fn from(s: &'a str) -> Self { Utf8 { contents: Cow::from(s), } } } impl<'a> Utf8<'a> { pub fn size(&self) -> usize { self.contents.len() } pub fn into_small(self) -> Option> { if self.size() < SMALL_MAX { Some(SmallUtf8 { contents: self.contents, }) } else { None } } pub fn into_small_unwrap(self) -> SmallUtf8<'a> { let size = self.size(); self.into_small().unwrap_or_else(|| { panic!("{size} >= i32::MAX"); }) } pub fn truncate(self) -> SmallUtf8<'a> { let size = self.size(); let mut contents = self.contents; if size >= SMALL_MAX { let s: &mut String = contents.to_mut(); s.truncate(SMALL_MAX - 3); s.push_str("..."); } SmallUtf8 { contents } } } ================================================ FILE: crates/neon-macros/Cargo.toml ================================================ [package] name = "neon-macros" version = "1.1.1" authors = ["Dave Herman "] description = "Procedural macros supporting Neon" repository = "https://github.com/neon-bindings/neon" license = "MIT/Apache-2.0" edition = "2021" [lib] proc-macro = true [dependencies] proc-macro2 = "1.0.79" quote = "1.0.33" syn = { version = "2.0.57", features = ["full"] } ================================================ FILE: crates/neon-macros/LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: crates/neon-macros/LICENSE-MIT ================================================ Copyright (c) 2015 David Herman 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. ================================================ FILE: crates/neon-macros/src/class/meta.rs ================================================ #[derive(Default)] pub(crate) struct Meta { pub(super) kind: Kind, pub(super) name: Option, pub(super) json: bool, pub(super) context: bool, pub(super) this: bool, } #[derive(Default)] pub(super) enum Kind { Async, AsyncFn, #[default] Normal, Task, } // Property metadata structure (simpler than method metadata) #[derive(Default)] pub(crate) struct PropertyMeta { pub(super) name: Option, pub(super) json: bool, } pub(crate) struct PropertyParser; impl syn::parse::Parser for PropertyParser { type Output = PropertyMeta; fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { let mut attr = PropertyMeta::default(); let parser = syn::meta::parser(|meta: syn::meta::ParseNestedMeta<'_>| { if meta.path.is_ident("name") { attr.name = Some(meta.value()?.parse::()?); return Ok(()); } if meta.path.is_ident("json") { attr.json = true; return Ok(()); } // Properties don't support method-specific attributes if meta.path.is_ident("async") || meta.path.is_ident("task") || meta.path.is_ident("context") || meta.path.is_ident("this") { return Err(meta.error("attribute not supported on const properties")); } Err(meta.error("unsupported property attribute")) }); parser.parse2(tokens)?; Ok(attr) } } // Parser that preserves existing metadata (like async detection) pub(crate) struct Parser(pub(crate) Meta); impl syn::parse::Parser for Parser { type Output = Meta; fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { let Parser(mut attr) = self; let parser = syn::meta::parser(|meta: syn::meta::ParseNestedMeta<'_>| { if meta.path.is_ident("name") { attr.name = Some(meta.value()?.parse::()?); return Ok(()); } if meta.path.is_ident("json") { attr.json = true; return Ok(()); } if meta.path.is_ident("context") { attr.context = true; return Ok(()); } if meta.path.is_ident("this") { attr.this = true; return Ok(()); } if meta.path.is_ident("async") { if matches!(attr.kind, Kind::AsyncFn) { return Err( meta.error("`async` attribute should not be used with an `async fn`") ); } attr.kind = Kind::Async; return Ok(()); } if meta.path.is_ident("task") { attr.kind = Kind::Task; return Ok(()); } Err(meta.error("unsupported property")) }); parser.parse2(tokens)?; Ok(attr) } } ================================================ FILE: crates/neon-macros/src/class/mod.rs ================================================ mod meta; use super::name::is_valid_js_identifier; use proc_macro2::TokenStream; use syn::{spanned::Spanned, Ident, ImplItemFn, Type}; struct ClassItems { consts: Vec, fns: Vec, constructor: Option, has_finalizer: bool, } // Check if a method receiver is mutable (&mut self) fn is_receiver_mutable(sig: &syn::Signature) -> bool { if let Some(syn::FnArg::Receiver(receiver)) = sig.inputs.first() { receiver.mutability.is_some() } else { false } } // Enum to track what kind of parameter we're dealing with enum ParamKind { Value, Ref(Box), // The inner type of the reference (e.g., Point for &Point) RefMut(Box), // The inner type of the mutable reference (e.g., Point for &mut Point) } impl ParamKind { fn from_type(ty: &syn::Type) -> Self { match ty { syn::Type::Reference(type_ref) => { // Any reference to a path type (e.g., &Point, &mut Point, &Message) if let syn::Type::Path(_) = &*type_ref.elem { if type_ref.mutability.is_some() { ParamKind::RefMut(type_ref.elem.clone()) } else { ParamKind::Ref(type_ref.elem.clone()) } } else { ParamKind::Value } } _ => ParamKind::Value, } } } // Extract parameter types from signature, skipping self, context, and this fn extract_param_types(sig: &syn::Signature, has_context: bool, has_this: bool) -> Vec { let skip_count = 1 + (has_context as usize) + (has_this as usize); sig.inputs .iter() .skip(skip_count) .filter_map(|arg| match arg { syn::FnArg::Typed(pat_type) => Some(ParamKind::from_type(&pat_type.ty)), syn::FnArg::Receiver(_) => None, }) .collect() } fn generate_method_wrapper( meta: &meta::Meta, class_id: &syn::Ident, sig: &syn::Signature, ) -> TokenStream { // Validate method attributes if let Err(err) = validate_method_attributes(meta, sig) { return err.into_compile_error(); } let internal_name = &sig.ident; let external_name_string = match &meta.name { Some(name) => name.value(), None => crate::name::to_camel_case(&internal_name.to_string()), }; let external_name_str = &external_name_string[..]; let is_mut = is_receiver_mutable(sig); // Check for context parameter and generate context extraction/argument let (context_extract, context_arg) = match context_parse(meta, sig) { Ok((extract, arg)) => (extract, arg), Err(err) => return err.into_compile_error(), }; // Determine if method has context parameter let has_context = context_arg.is_some(); // Check for this parameter let has_this = check_this(meta, sig, has_context); // Generate this extraction if needed let this_extract = if has_this { quote::quote!( let js_this: neon::handle::Handle = cx.this()?; let this = neon::types::extract::TryFromJs::from_js(&mut cx, neon::handle::Handle::upcast(&js_this))?; ) } else { quote::quote!() }; // Generate this argument for method call let this_arg = if has_this { quote::quote!(this,) } else { quote::quote!() }; // Analyze parameter types to determine which are references let param_types = extract_param_types(sig, has_context, has_this); let num_args = param_types.len(); // Generate tuple fields for cx.args() extraction and type annotations let (tuple_fields, ref_type_annotations): (Vec<_>, Vec<_>) = param_types .iter() .enumerate() .map(|(i, param_kind)| { let name = quote::format_ident!("a{i}"); match param_kind { ParamKind::Ref(_) | ParamKind::RefMut(_) => { // For reference parameters, extract as a generic arg then type annotate let obj_name = quote::format_ident!("a{i}_obj"); let annotation = quote::quote! { let #obj_name: neon::handle::Handle = #obj_name; }; (quote::quote!(#obj_name), Some(annotation)) } ParamKind::Value => { // For value parameters, use existing logic with JSON wrapping if needed let field = if meta.json { quote::quote!(neon::types::extract::Json(#name)) } else { quote::quote!(#name) }; (field, None) } } }) .unzip(); // Generate guard extraction code for reference parameters let ref_guards = param_types.iter().enumerate().filter_map(|(i, param_kind)| { match param_kind { ParamKind::Ref(inner_type) => { let obj_name = quote::format_ident!("a{i}_obj"); let guard_name = quote::format_ident!("_guard_a{i}"); let arg_name = quote::format_ident!("a{i}"); Some(quote::quote! { let #guard_name = <#inner_type as neon::types::extract::TryFromJsRef>::from_js_ref( &mut cx, neon::handle::Handle::upcast(&#obj_name) )?; let #arg_name: &#inner_type = &*#guard_name; }) } ParamKind::RefMut(inner_type) => { let obj_name = quote::format_ident!("a{i}_obj"); let guard_name = quote::format_ident!("_guard_a{i}"); let arg_name = quote::format_ident!("a{i}"); Some(quote::quote! { let mut #guard_name = <#inner_type as neon::types::extract::TryFromJsRefMut>::from_js_ref_mut( &mut cx, neon::handle::Handle::upcast(&#obj_name) )?; let #arg_name: &mut #inner_type = &mut *#guard_name; }) } ParamKind::Value => None, } }); // Generate argument list for method call let args = (0..num_args).map(|i| quote::format_ident!("a{i}")); // Tag whether we should JSON wrap results let return_tag = if meta.json { quote::format_ident!("NeonJsonTag") } else { quote::format_ident!("NeonValueTag") }; // Generate result conversion based on JSON setting let result_extract = quote::quote!({ use neon::macro_internal::{ToNeonMarker, #return_tag as NeonReturnTag}; (&res).to_neon_marker::().neon_into_js(&mut cx, res) }); match meta.kind { meta::Kind::Async => { let borrow_call = if is_mut { quote::quote! { let mut instance = instance_cell.borrow_mut(); let instance = &mut *instance; } } else { quote::quote! { let instance = instance_cell.borrow(); let instance = &*instance; } }; quote::quote! { JsFunction::with_name(cx, #external_name_str, |mut cx| { use neon::result::ResultExt; let js_this: neon::handle::Handle = cx.this()?; let instance_cell: &std::cell::RefCell<#class_id> = neon::object::unwrap(&mut cx, js_this)?.or_throw(&mut cx)?; // Context extraction if needed #context_extract // This extraction if needed #this_extract // Extract non-context/this arguments with JSON wrapping if needed let (#(#tuple_fields,)*) = cx.args()?; // Type annotations for reference parameters #(#ref_type_annotations)* // Unwrap reference parameters and create guards #(#ref_guards)* // Borrow from RefCell #borrow_call // Call the method with &self or &mut self - developer controls cloning in their impl let fut = instance.#internal_name(#context_arg #this_arg #(#args),*); // Always use NeonValueTag for Future conversion, JSON only applies to final result let fut = { use neon::macro_internal::{ToNeonMarker, NeonValueTag}; (&fut).to_neon_marker::().into_neon_result(&mut cx, fut)? }; neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract) }) } } meta::Kind::AsyncFn => { quote::quote! { JsFunction::with_name(cx, #external_name_str, |mut cx| { use neon::result::ResultExt; let js_this: neon::handle::Handle = cx.this()?; let instance_cell: &std::cell::RefCell<#class_id> = neon::object::unwrap(&mut cx, js_this)?.or_throw(&mut cx)?; // Context extraction if needed #context_extract // This extraction if needed #this_extract // Extract non-context/this arguments with JSON wrapping if needed let (#(#tuple_fields,)*) = cx.args()?; // Type annotations for reference parameters #(#ref_type_annotations)* // Unwrap reference parameters and create guards #(#ref_guards)* // Clone the instance to move into async fn (takes self by value) let instance_clone = instance_cell.borrow().clone(); // Call the async fn method - it takes self by value to produce 'static Future let fut = instance_clone.#internal_name(#context_arg #this_arg #(#args),*); neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract) }) } } meta::Kind::Task => { quote::quote! { JsFunction::with_name(cx, #external_name_str, |mut cx| { use neon::result::ResultExt; let js_this: neon::handle::Handle = cx.this()?; let instance_cell: &std::cell::RefCell<#class_id> = neon::object::unwrap(&mut cx, js_this)?.or_throw(&mut cx)?; // Context extraction if needed #context_extract // This extraction if needed #this_extract // Clone the instance to move into the task (takes self by value) let instance_clone = instance_cell.borrow().clone(); // Extract non-context/this arguments with JSON wrapping if needed let (#(#tuple_fields,)*) = cx.args()?; // Type annotations for reference parameters #(#ref_type_annotations)* // Unwrap reference parameters and create guards #(#ref_guards)* let promise = neon::context::Context::task(&mut cx, move || { instance_clone.#internal_name(#context_arg #this_arg #(#args),*) }) .promise(|mut cx, res| #result_extract); Ok(promise.upcast::()) }) } } meta::Kind::Normal => { let borrow_call = if is_mut { quote::quote! { let mut instance = instance_cell.borrow_mut(); let instance = &mut *instance; } } else { quote::quote! { let instance = instance_cell.borrow(); let instance = &*instance; } }; quote::quote! { JsFunction::with_name(cx, #external_name_str, |mut cx| { use neon::result::ResultExt; let js_this: neon::handle::Handle = cx.this()?; let instance_cell: &std::cell::RefCell<#class_id> = neon::object::unwrap(&mut cx, js_this)?.or_throw(&mut cx)?; // Context extraction if needed #context_extract // This extraction if needed #this_extract // Extract non-context/this arguments with JSON wrapping if needed let (#(#tuple_fields,)*) = cx.args()?; // Type annotations for reference parameters #(#ref_type_annotations)* // Unwrap reference parameters and create guards #(#ref_guards)* // Borrow from RefCell #borrow_call let res = instance.#internal_name(#context_arg #this_arg #(#args),*); #result_extract }) } } } } // Generate context extraction and argument for class methods fn context_parse( opts: &meta::Meta, sig: &syn::Signature, ) -> syn::Result<( Option, Option, )> { match opts.kind { // Allow borrowing from context meta::Kind::Async | meta::Kind::Normal if check_context(opts, sig)? => { Ok((None, Some(quote::quote!(&mut cx,)))) } // Require `'static` arguments meta::Kind::AsyncFn | meta::Kind::Task if check_channel(opts, sig)? => Ok(( Some(quote::quote!(let ch = neon::context::Context::channel(&mut cx);)), Some(quote::quote!(ch,)), )), _ => Ok((None, None)), } } // Check if a sync method has a context argument (adapted from export function) // Key difference from #[export]: methods have &self as first param, so context is second param fn check_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument (after &self) let ty = match first_arg(opts, sig)? { Some(arg) => arg, None => return Ok(false), }; // Extract the reference type let ty = match &*ty.ty { // Tried to use a borrowed Channel syn::Type::Reference(ty) if !opts.context && is_channel_type(&ty.elem) => { return Err(syn::Error::new( ty.elem.span(), "Expected `&mut Cx` instead of a `Channel` reference.", )) } syn::Type::Reference(ty) => ty, // Context needs to be a reference _ if opts.context || is_context_type(&ty.ty) => { return Err(syn::Error::new( ty.ty.span(), "Context parameters must be a `&mut` reference. Try `&mut FunctionContext` or `&mut Cx`.", )) } // Hint that `Channel` should be swapped for `&mut Cx` _ if is_channel_type(&ty.ty) => { return Err(syn::Error::new( ty.ty.span(), "Unexpected `Channel` in sync method. Use `&mut FunctionContext` for sync methods, or `Channel` in async/task methods.", )) } _ => return Ok(false), }; // Not a forced or inferred context if !opts.context && !is_context_type(&ty.elem) { return Ok(false); } // Context argument must be mutable if ty.mutability.is_none() { return Err(syn::Error::new(ty.span(), "Must be a `&mut` reference.")); } // All tests passed! Ok(true) } // Check if an async method has a Channel argument (adapted from export function) fn check_channel(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument (after &self) let ty = match first_arg(opts, sig)? { Some(arg) => arg, None => return Ok(false), }; // Check the type match &*ty.ty { // Provided `&mut Channel` instead of `Channel` syn::Type::Reference(ty) if opts.context || is_channel_type(&ty.elem) => { Err(syn::Error::new( ty.span(), "Expected an owned `Channel` instead of a reference.", )) } // Provided a `&mut Cx` instead of a `Channel` syn::Type::Reference(ty) if is_context_type(&ty.elem) => Err(syn::Error::new( ty.elem.span(), "Expected an owned `Channel` instead of a context reference.", )), // Found a `Channel` _ if opts.context || is_channel_type(&ty.ty) => Ok(true), // Tried to use an owned `Cx` _ if is_context_type(&ty.ty) => Err(syn::Error::new( ty.ty.span(), "Context is not available in async functions. Try a `Channel` instead.", )), _ => Ok(false), } } // Extract the first argument (after &self) from a method signature fn first_arg<'a>( opts: &meta::Meta, sig: &'a syn::Signature, ) -> syn::Result> { // Extract the second argument (skip &self) let arg = match sig.inputs.iter().nth(1) { Some(arg) => arg, // If context was forced, error to let the user know the mistake None if opts.context => { return Err(syn::Error::new( sig.inputs.span(), "Expected a context argument after `&self` when using `#[neon(context)]`. Add a parameter like `cx: &mut FunctionContext` or remove the `context` attribute.", )) } None => return Ok(None), }; // Expect a typed pattern; self receivers are not supported (but shouldn't appear here) match arg { syn::FnArg::Typed(ty) => Ok(Some(ty)), syn::FnArg::Receiver(arg) => Err(syn::Error::new( arg.span(), "Unexpected second receiver argument.", )), } } fn is_context_type(ty: &syn::Type) -> bool { let ident = match type_path_ident(ty) { Some(ident) => ident, None => return false, }; ident == "FunctionContext" || ident == "Cx" } fn is_channel_type(ty: &syn::Type) -> bool { let ident = match type_path_ident(ty) { Some(ident) => ident, None => return false, }; ident == "Channel" } // Extract the identifier from the last segment of a type's path fn type_path_ident(ty: &syn::Type) -> Option<&syn::Ident> { let segment = match ty { syn::Type::Path(ty) => ty.path.segments.last()?, _ => return None, }; Some(&segment.ident) } // Validate method attributes for common errors and conflicts fn validate_method_attributes(meta: &meta::Meta, sig: &syn::Signature) -> syn::Result<()> { // Check for conflicting async attributes if matches!(meta.kind, meta::Kind::AsyncFn) && matches!(meta.kind, meta::Kind::Async) { return Err(syn::Error::new( sig.span(), "Cannot combine `async fn` with `#[neon(async)]` attribute", )); } // Check for async + task conflict if matches!(meta.kind, meta::Kind::AsyncFn | meta::Kind::Async) && matches!(meta.kind, meta::Kind::Task) { return Err(syn::Error::new( sig.span(), "Cannot combine async method with `#[neon(task)]` attribute", )); } // Validate that async fn and task methods take self by value if matches!(meta.kind, meta::Kind::AsyncFn | meta::Kind::Task) { if let Some(syn::FnArg::Receiver(receiver)) = sig.inputs.first() { if receiver.reference.is_some() { // This is &self or &mut self, but we need self by value let method_type = if matches!(meta.kind, meta::Kind::AsyncFn) { "Async functions" } else { "Task methods" }; let reason = if matches!(meta.kind, meta::Kind::AsyncFn) { "This is required because async functions capture `self` in the Future, which must be `'static` for spawning." } else { "Since the instance is cloned before moving to the worker thread, taking `&self` would operate on a temporary reference to the clone, which is misleading." }; return Err(syn::Error::new( receiver.span(), format!( "{} in classes must take `self` by value, not `&self` or `&mut self`. {}", method_type, reason ), )); } } else { let method_type = if matches!(meta.kind, meta::Kind::AsyncFn) { "Async functions" } else { "Task methods" }; return Err(syn::Error::new( sig.span(), format!( "{} in classes must take `self` as their first parameter.", method_type ), )); } } // Check for self parameter in constructor if sig.ident == "new" { if let Some(syn::FnArg::Receiver(_)) = sig.inputs.first() { return Err(syn::Error::new( sig.ident.span(), "Constructor methods cannot have a `self` receiver", )); } } else { fn starts_with_self_arg(sig: &syn::Signature) -> bool { if let Some(first_arg) = sig.inputs.first() { matches!(first_arg, syn::FnArg::Receiver(_)) } else { false } } // Check for self parameter in non-constructor methods if !starts_with_self_arg(sig) { return Err(syn::Error::new( sig.ident.span(), "Class methods must have a `self` receiver (`&self` or `&mut self`) as their first parameter", )); } } Ok(()) } // Check if a method has a `this` parameter (adapted from export function) // For methods: &self is 1st, context is 2nd (optional), this is 3rd (or 2nd if no context) fn check_this(opts: &meta::Meta, sig: &syn::Signature, has_context: bool) -> bool { static THIS: &str = "this"; // Forced `this` argument if opts.this { return true; } // Get the parameter after &self and optional context let param_index = if has_context { 2 } else { 1 }; // Skip &self and optional context let param = match sig.inputs.iter().nth(param_index) { Some(param) => param, None => return false, }; // Ignore `self` type receivers (shouldn't happen at this index, but be safe) let ty = match param { syn::FnArg::Receiver(_) => return false, syn::FnArg::Typed(ty) => ty, }; // Check for `this` ident or a tuple struct let pat = match &*ty.pat { syn::Pat::Ident(ident) if ident.ident == THIS => return true, syn::Pat::TupleStruct(pat) => pat, _ => return false, }; // Expect exactly one element in the tuple struct let elem = match pat.elems.first() { Some(elem) if pat.elems.len() == 1 => elem, _ => return false, }; // Must be an identifier named `this` match elem { syn::Pat::Ident(ident) => ident.ident == THIS, _ => false, } } // Check if constructor has a context parameter using same heuristic as export functions // * If the `context` attribute is included, must have at least one argument // * Inferred to be context if first arg is `&mut FunctionContext` or `&mut Cx` // * Context argument must be a `&mut` reference fn check_constructor_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument let ty = match sig.inputs.first() { Some(syn::FnArg::Typed(ty)) => ty, Some(syn::FnArg::Receiver(_)) => { return Err(syn::Error::new( sig.inputs.span(), "Constructor cannot have a `self` receiver", )) } None if opts.context => { return Err(syn::Error::new( sig.inputs.span(), "Expected a context argument. Try removing the `context` attribute.", )) } None => return Ok(false), }; // Extract the reference type let ty = match &*ty.ty { syn::Type::Reference(ty) => ty, // Context needs to be a reference _ if opts.context || is_context_type(&ty.ty) => { return Err(syn::Error::new( ty.ty.span(), "Context must be a `&mut` reference.", )) } _ => return Ok(false), }; // Not a forced or inferred context if !opts.context && !is_context_type(&ty.elem) { return Ok(false); } // Context argument must be mutable if ty.mutability.is_none() { return Err(syn::Error::new(ty.span(), "Must be a `&mut` reference.")); } // All tests passed! Ok(true) } // Generate constructor wrapper with support for context, JSON, and Result types fn generate_constructor_wrapper( meta: &meta::Meta, class_id: &syn::Ident, sig: &syn::Signature, ctor_locals: &[Ident], ) -> syn::Result { // Check for context parameter in constructor using same heuristic as export functions let has_context = check_constructor_context(meta, sig)?; // Determine which parameters are actual constructor arguments (skip context if present) let param_offset = if has_context { 1 } else { 0 }; let arg_locals = &ctor_locals[param_offset..]; // Generate the arguments tuple extraction let args_extract = { // Wrap arguments in Json() if needed for deserialization let tuple_fields = arg_locals.iter().map(|name| { if meta.json { quote::quote!(neon::types::extract::Json(#name)) } else { quote::quote!(#name) } }); // Generate type inference tokens for each argument let type_infers = arg_locals.iter().map(|_| { quote::quote! { _ } }); quote::quote! { let (this, #(#tuple_fields,)*): (neon::handle::Handle, #(#type_infers),*) = cx.args()?; } }; // Build the argument list for calling the constructor let ctor_args = if has_context { if arg_locals.is_empty() { quote::quote! { &mut cx } } else { quote::quote! { &mut cx, #(#arg_locals),* } } } else { quote::quote! { #(#arg_locals),* } }; // Generate the constructor call with Result handling let ctor_call = quote::quote! { let result = #class_id::new(#ctor_args); // Use ToNeonMarker to detect if result is Result or just T use neon::macro_internal::{ToNeonMarker, NeonValueTag}; let instance = (&result).to_neon_marker::().into_neon_result(&mut cx, result)?; }; // Generate the full wrapper Ok(quote::quote! { #args_extract #ctor_call neon::object::wrap(&mut cx, this, std::cell::RefCell::new(instance))?.or_throw(&mut cx)?; Ok(cx.undefined()) }) } fn group_class_items(items: Vec) -> Result { let mut consts = Vec::new(); let mut fns = Vec::new(); let mut constructor = None; let mut has_finalizer = false; for item in items { match item { syn::ImplItem::Const(c) => consts.push(c), syn::ImplItem::Fn(f) => { // Check if the function is a constructor if f.sig.ident == "new" { if constructor.is_some() { let span = syn::spanned::Spanned::span(&f); let msg = "Only one `new` constructor is allowed in a class."; return Err(syn::Error::new(span, msg)); } constructor = Some(f); continue; // Skip adding to fns } else if f.sig.ident == "finalize" { if has_finalizer { let span = syn::spanned::Spanned::span(&f); let msg = "Only one `finalize` method is allowed in a class."; return Err(syn::Error::new(span, msg)); } has_finalizer = true; continue; // Skip adding to fns } fns.push(f) } _ => { let span = syn::spanned::Spanned::span(&item); let msg = "`neon::class` can only contain `const` and `fn` items."; return Err(syn::Error::new(span, msg)); } } } Ok(ClassItems { consts, fns, constructor, has_finalizer, }) } pub(crate) fn class( _attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { class_with_name(_attr, item, None) } pub(crate) fn class_with_name( _attr: proc_macro::TokenStream, item: proc_macro::TokenStream, custom_class_name: Option, ) -> proc_macro::TokenStream { let mut impl_block = syn::parse_macro_input!(item as syn::ItemImpl); // Parse the item as an implementation block let syn::ItemImpl { self_ty, items, .. } = impl_block.clone(); let class_ident = match *self_ty { syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) => { let syn::PathSegment { ident, .. } = segments.last().unwrap(); ident.clone() } _ => { panic!("class must be implemented for a type name"); } }; let class_name = custom_class_name.unwrap_or_else(|| class_ident.to_string()); // Group the items into `const` and `fn` categories let ClassItems { consts, fns, constructor, has_finalizer, } = match group_class_items(items.clone()) { Ok(items) => items, Err(err) => { // If sorting fails, return the error as a compile error return err.to_compile_error().into(); } }; // Parse constructor metadata let ctor_meta = match &constructor { Some(ctor) => { let mut meta = meta::Meta::default(); let mut found_neon_attr = false; for attr in &ctor.attrs { if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta { if path.is_ident("neon") { if found_neon_attr { return syn::Error::new_spanned( attr, "multiple #[neon(...)] attributes on constructor are not allowed", ) .to_compile_error() .into(); } found_neon_attr = true; let parser = meta::Parser(meta); let tokens = tokens.clone(); match syn::parse::Parser::parse2(parser, tokens) { Ok(parsed_meta) => meta = parsed_meta, Err(err) => return err.to_compile_error().into(), } } } } // Validate that constructors don't use async/task attributes if !matches!(meta.kind, meta::Kind::Normal) { return syn::Error::new_spanned( &ctor.sig, "Constructors cannot use #[neon(async)] or #[neon(task)] attributes", ) .to_compile_error() .into(); } // Validate that constructors don't use 'this' attribute if meta.this { return syn::Error::new_spanned( &ctor.sig, "Constructors cannot use #[neon(this)] attribute", ) .to_compile_error() .into(); } Some(meta) } None => None, }; let ctor_params = match &constructor { Some(ImplItemFn { sig, .. }) => { let mut params = Vec::new(); for (i, arg) in sig.inputs.iter().enumerate() { match arg { syn::FnArg::Typed(pat_type) => { params.push(match pat_type.pat.as_ref() { syn::Pat::Ident(ident) => ident.ident.to_string(), // Rust identifiers can't begin with '$' so this can't conflict // with any user-provided identifiers. _ => format!("$arg{}", i), }); } syn::FnArg::Receiver(_) => { return syn::Error::new_spanned( arg, "Constructor methods cannot have a `self` receiver", ) .to_compile_error() .into(); } } } params } None => { vec![] } }; let ctor_locals: Vec = match &constructor { Some(ImplItemFn { sig, .. }) => sig .inputs .iter() .enumerate() .map(|(i, arg)| Ident::new(&format!("a{i}"), arg.span())) .collect::>(), None => { vec![] } }; let ctor_infers: Vec = match &constructor { Some(ImplItemFn { sig, .. }) => sig .inputs .iter() .map(|_| { Type::Infer(syn::TypeInfer { underscore_token: Default::default(), }) }) .collect::>(), None => { vec![] } }; let ctor_param_list = ctor_params.join(", "); let ctor_arg_list = format!( "{}{}", if ctor_params.is_empty() { "" } else { ", " }, ctor_param_list ); let mut method_names = String::new(); let mut prototype_patches = String::new(); let method_ids = fns.iter().map(|f| f.sig.ident.clone()).collect::>(); let mut method_metas = Vec::new(); for f in &fns { let fn_name = f.sig.ident.to_string(); method_names.push_str(&format!(", {fn_name}Method")); // syn::parse_macro_input!(attr with parser); let mut parsed = meta::Meta::default(); // Check if the function itself is async if f.sig.asyncness.is_some() { parsed.kind = meta::Kind::AsyncFn; } let mut found_neon_attr = false; for syn::Attribute { meta, .. } in &f.attrs { match meta { syn::Meta::List(syn::MetaList { path, tokens, .. }) if path.is_ident("neon") => { if found_neon_attr { return syn::Error::new_spanned( meta, "multiple #[neon(...)] attributes on class method are not allowed", ) .to_compile_error() .into(); } found_neon_attr = true; let parser = meta::Parser(parsed); let tokens = tokens.clone().into(); parsed = syn::parse_macro_input!(tokens with parser); } _ => {} } } let js_name = match parsed.name.clone() { Some(name) => name.value(), None => crate::name::to_camel_case(&fn_name), }; prototype_patches.push_str(&format!("\n prototype.{js_name} = {fn_name}Method;")); method_metas.push(parsed); } // Process const items into static class properties let mut property_names = Vec::new(); let mut property_assignments = Vec::new(); let mut property_ids = Vec::new(); let mut property_wrappers = Vec::new(); let mut used_js_names = std::collections::HashSet::with_capacity(consts.len()); // Pre-allocate for const_item in &consts { let const_name = &const_item.ident; let property_id = quote::format_ident!("__{const_name}Property"); // Parse property attributes let mut property_meta = meta::PropertyMeta::default(); let mut found_neon_attr = false; for attr in &const_item.attrs { if let syn::Meta::List(syn::MetaList { path, tokens, .. }) = &attr.meta { if path.is_ident("neon") { if found_neon_attr { return syn::Error::new_spanned( attr, "multiple #[neon(...)] attributes on const property are not allowed", ) .to_compile_error() .into(); } found_neon_attr = true; let parser = meta::PropertyParser; let tokens = tokens.clone().into(); property_meta = syn::parse_macro_input!(tokens with parser); } } } // Determine JavaScript property name (use custom name or default) let js_property_name = match &property_meta.name { Some(name) => { let name_value = name.value(); // Validate JavaScript identifier if !is_valid_js_identifier(&name_value) { return syn::Error::new_spanned( name, format!("'{}' is not a valid JavaScript identifier", name_value), ) .to_compile_error() .into(); } name_value } None => const_name.to_string(), }; // Check for name collisions if !used_js_names.insert(js_property_name.clone()) { return syn::Error::new_spanned( const_item, format!("duplicate property name '{}' - const property names must be unique in JavaScript", js_property_name) ).to_compile_error().into(); } // Add to parameter list for JavaScript function property_names.push(property_id.to_string()); // Add property assignment in JavaScript (immutable const properties) property_assignments.push(format!( "\n Object.defineProperty({class_name}, '{js_property_name}', {{ value: {property_id}(), enumerable: true }});" )); // Create property getter function with JSON support let value_expr = if property_meta.json { quote::quote! { use neon::types::extract::{TryIntoJs, Json}; let value = Json(#class_ident::#const_name); value.try_into_js(&mut cx).map(|v| v.upcast()) } } else { quote::quote! { use neon::types::extract::TryIntoJs; let value = #class_ident::#const_name; value.try_into_js(&mut cx).map(|v| v.upcast()) } }; let property_wrapper = quote::quote! { neon::types::JsFunction::new(cx, |mut cx| -> neon::result::JsResult { #value_expr }) }; property_ids.push(property_id); property_wrappers.push(property_wrapper); } let property_params = if property_names.is_empty() { String::new() } else { format!(", {}", property_names.join(", ")) }; let property_assignments = property_assignments.join(""); let script = format!( r#" (function makeClass(wrap{method_names}{property_params}) {{ // Create the class exposed directly to JavaScript. // // The variables listed in method_names all come from // Rust identifiers, so they cannot start with '$'. function $makeExternal() {{ class {class_name} {{ constructor({ctor_param_list}) {{ wrap.call(this, this{ctor_arg_list}); }} }} const prototype = {class_name}.prototype;{prototype_patches} // Add static class properties{property_assignments} return {class_name}; }} // Create the constructor used by Neon internally to construct // instances from a pre-existing Rust struct. Calling this // constructor directly will not create a valid instance. // Neon must wrap the resulting object with the Rust struct // before it can be handed back to JavaScript to be used. // // The variables listed in method_names all come from // Rust identifiers, so they cannot start with '$'. function $makeInternal(prototype) {{ function {class_name}() {{ }} {class_name}.prototype = prototype; return {class_name}; }} // The variables listed in method_names all come from // Rust identifiers, so they cannot start with '$'. const $external = $makeExternal(); const $internal = $makeInternal($external.prototype); return {{ external: $external, internal: $internal }}; }}) "# ); let make_new: TokenStream = if constructor.is_some() { quote::quote! { #class_ident::new } } else { quote::quote! { ::default } }; // Generate method wrappers based on their metadata let method_wrappers: Vec = fns .iter() .zip(&method_metas) .map(|(f, meta)| generate_method_wrapper(meta, &class_ident, &f.sig)) .collect(); // Generate constructor wrapper implementation let ctor_wrapper_impl = match (&constructor, &ctor_meta) { (Some(ctor), Some(meta)) => { match generate_constructor_wrapper(meta, &class_ident, &ctor.sig, &ctor_locals) { Ok(wrapper) => wrapper, Err(err) => return err.into_compile_error().into(), } } _ => { // Default constructor without metadata quote::quote! { let (this, #(#ctor_locals,)*): (Handle, #(#ctor_infers),*) = cx.args()?; let instance = #make_new(#(#ctor_locals),*); neon::object::wrap(&mut cx, this, std::cell::RefCell::new(instance))?.or_throw(&mut cx)?; Ok(cx.undefined()) } } }; let impl_class_internal: TokenStream = quote::quote! { impl neon::macro_internal::ClassInternal for #class_ident { fn local<'cx>(cx: &mut neon::context::Cx<'cx>) -> neon::result::NeonResult> { use neon::handle::{Handle, Root}; use neon::macro_internal::{ClassMetadata, RootClassMetadata}; use neon::thread::LocalKey; use neon::types::{JsFunction, JsObject}; static CLASS_METADATA: LocalKey = LocalKey::new(); CLASS_METADATA .get_or_try_init(cx, |cx| Self::create(cx).map(|v| v.root(cx))) .map(|v| v.to_inner(cx)) } fn create<'cx>(cx: &mut neon::context::Cx<'cx>) -> neon::result::NeonResult> { use neon::handle::Handle; use neon::types::{JsFunction, JsObject}; use neon::result::ResultExt; use neon::context::Context; use neon::object::Object; let wrap = JsFunction::new(cx, |mut cx| { use neon::types::extract::TryFromJs; #ctor_wrapper_impl }); // Create method functions using the appropriate wrapper based on metadata #(let #method_ids = #method_wrappers;)* // Create property getter functions #(let #property_ids = #property_wrappers;)* const CLASS_MAKER_SCRIPT: &str = #script; let src = cx.string(CLASS_MAKER_SCRIPT); let factory: Handle = neon::reflect::eval(cx, src)? .downcast(cx) .or_throw(cx)?; let pair: Handle = factory .bind(cx) .arg(wrap)? #( .arg(#method_ids)? )* #( .arg(#property_ids)? )* .call()?; let external: Handle = pair.prop(cx, "external").get()?; let internal: Handle = pair.prop(cx, "internal").get()?; Ok(neon::macro_internal::new_class_metadata(external, internal)) } } }; // Generate the impl of `neon::object::Class` for the struct let impl_class: TokenStream = quote::quote! { impl neon::object::Class for #class_ident { fn name() -> String { stringify!(#class_ident).into() } fn constructor<'cx>(cx: &mut neon::context::Cx<'cx>) -> neon::result::JsResult<'cx, neon::types::JsFunction> { Ok(::local(cx)?.constructor()) } } }; let impl_finalize: TokenStream = if has_finalizer { quote::quote! { impl neon::types::Finalize for #class_ident { fn finalize<'a, C: neon::context::Context<'a>>(self, cx: &mut C) { Self::finalize(self, cx) } } } } else { quote::quote! { impl neon::types::Finalize for #class_ident { fn finalize<'a, C: neon::context::Context<'a>>(self, _cx: &mut C) {} } } }; let impl_sealed: TokenStream = quote::quote! { impl neon::macro_internal::Sealed for #class_ident {} }; // This leverages the following hack: https://github.com/rust-lang/rust/issues/48214#issuecomment-2557829956 // Adding an artificial `for<'a>` generalization circumvents the need for the unstable `trivial_bounds` feature. // This allows us to conditionally require `Clone` only for types that actually make use of `TryFromJs`. let impl_try_from_js: TokenStream = quote::quote! { impl<'cx> neon::types::extract::TryFromJs<'cx> for #class_ident where for<'a> Self: Clone { type Error = neon::macro_internal::WrapError; fn try_from_js(cx: &mut neon::context::Cx<'cx>, value: neon::handle::Handle<'cx, neon::types::JsValue>) -> neon::result::NeonResult> { use neon::result::ResultExt; match neon::object::unwrap::, _>(cx, value) { Ok(Ok(instance_cell)) => Ok(Ok(Self::clone(&*instance_cell.borrow()))), Ok(Err(err)) => Ok(Err(err)), Err(err) => Err(err), } } } }; let impl_try_into_js: TokenStream = quote::quote! { impl<'cx> neon::types::extract::TryIntoJs<'cx> for #class_ident { type Value = neon::types::JsObject; fn try_into_js(self, cx: &mut neon::context::Cx<'cx>) -> neon::result::JsResult<'cx, Self::Value> { use neon::result::ResultExt; let object: neon::handle::Handle = neon::macro_internal::internal_constructor::(cx)?.bind(cx).construct()?; neon::macro_internal::object::wrap(cx, object, std::cell::RefCell::new(self))?.or_throw(cx)?; Ok(object) } } }; let impl_try_from_js_ref: TokenStream = quote::quote! { impl<'cx> neon::types::extract::TryFromJsRef<'cx> for #class_ident { type Guard = std::cell::Ref<'cx, Self>; type Error = neon::macro_internal::WrapError; fn try_from_js_ref( cx: &mut neon::context::Cx<'cx>, value: neon::handle::Handle<'cx, neon::types::JsValue> ) -> neon::result::NeonResult> { use neon::result::ResultExt; match neon::object::unwrap::, _>(cx, value) { Ok(Ok(instance_cell)) => Ok(Ok(instance_cell.borrow())), Ok(Err(err)) => Ok(Err(err)), Err(err) => Err(err), } } } }; let impl_try_from_js_ref_mut: TokenStream = quote::quote! { impl<'cx> neon::types::extract::TryFromJsRefMut<'cx> for #class_ident { type Guard = std::cell::RefMut<'cx, Self>; type Error = neon::macro_internal::WrapError; fn try_from_js_ref_mut( cx: &mut neon::context::Cx<'cx>, value: neon::handle::Handle<'cx, neon::types::JsValue> ) -> neon::result::NeonResult> { use neon::result::ResultExt; match neon::object::unwrap::, _>(cx, value) { Ok(Ok(instance_cell)) => Ok(Ok(instance_cell.borrow_mut())), Ok(Err(err)) => Ok(Err(err)), Err(err) => Err(err), } } } }; // Remove #[neon(...)] attributes from methods and const items in the impl block for item in &mut impl_block.items { match item { syn::ImplItem::Fn(f) => { f.attrs.retain( |attr| !matches!(&attr.meta, syn::Meta::List(list) if list.path.is_ident("neon")), ); } syn::ImplItem::Const(c) => { c.attrs.retain( |attr| !matches!(&attr.meta, syn::Meta::List(list) if list.path.is_ident("neon")), ); } _ => {} } } quote::quote! { #impl_block #impl_class_internal #impl_class #impl_finalize #impl_sealed #impl_try_from_js #impl_try_into_js #impl_try_from_js_ref #impl_try_from_js_ref_mut } .into() } ================================================ FILE: crates/neon-macros/src/export/class/meta.rs ================================================ use syn::parse::{Parse, ParseStream}; /// Metadata for class exports #[derive(Default)] pub(crate) struct Meta { /// Name for the JavaScript class itself (used in class definition) pub class_name: Option, /// Name for the module export binding pub export_name: Option, } impl Parse for Meta { fn parse(input: ParseStream) -> syn::Result { // Parse "class" token let class_token: syn::Ident = input.parse()?; if class_token != "class" { return Err(syn::Error::new( class_token.span(), "Expected 'class' in export attribute", )); } let mut meta = Meta::default(); // Check for parenthesized attributes: class(name = "...") if input.peek(syn::token::Paren) { let content; syn::parenthesized!(content in input); // Parse attributes inside parentheses while !content.is_empty() { let name_token: syn::Ident = content.parse()?; match name_token.to_string().as_str() { "name" => { content.parse::()?; let name_value: syn::LitStr = content.parse()?; meta.class_name = Some(name_value.value()); } _ => { return Err(syn::Error::new( name_token.span(), format!("Unknown class attribute '{}'", name_token), )); } } // Parse optional comma if content.parse::().is_err() { break; } } } // Check if there are additional attributes after "class" or "class(...)" if input.parse::().is_ok() { // Parse additional attributes like name = "..." while !input.is_empty() { let name_token: syn::Ident = input.parse()?; match name_token.to_string().as_str() { "name" => { input.parse::()?; let name_value: syn::LitStr = input.parse()?; meta.export_name = Some(name_value.value()); } _ => { return Err(syn::Error::new( name_token.span(), format!("Unknown attribute '{}'", name_token), )); } } // Parse optional comma if input.parse::().is_err() { break; } } } Ok(meta) } } /// Parser for class export metadata pub(crate) struct Parser; impl syn::parse::Parser for Parser { type Output = Meta; fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { syn::parse2(tokens) } } ================================================ FILE: crates/neon-macros/src/export/class.rs ================================================ use proc_macro2::TokenStream; use quote::quote; use syn::spanned::Spanned; pub(crate) mod meta; pub(super) fn export(meta: meta::Meta, input: syn::ItemImpl) -> proc_macro::TokenStream { // Extract the class name from the impl block let class_ident = match extract_class_ident(&input) { Ok(ident) => ident, Err(err) => return err.into_compile_error().into(), }; let rust_class_name = class_ident.to_string(); // Determine the JavaScript class name and export name based on the parsed metadata // CASE 1: #[export(class, name = "X")] -> both class and export use "X" // CASE 2: #[export(class(name = "X"))] -> both class and export use "X" // CASE 3: #[export(class(name = "X"), name = "Y")] -> class uses "X", export uses "Y" let (js_class_name, export_name) = match (meta.class_name, meta.export_name) { // CASE 3: Both specified - use each for its purpose (Some(class_name), Some(export_name)) => (Some(class_name), export_name), // CASE 2: Only class name specified - use for both (Some(class_name), None) => { let export_name = class_name.clone(); (Some(class_name), export_name) } // CASE 1: Only export name specified - use for both (None, Some(export_name)) => { let class_name = export_name.clone(); (Some(class_name), export_name) } // Default: No names specified - use Rust type name (None, None) => (None, rust_class_name.clone()), }; // Generate the class using the existing class implementation let class_input: proc_macro::TokenStream = quote!(#input).into(); let class_output = crate::class::class_with_name(proc_macro::TokenStream::new(), class_input, js_class_name); let class_tokens: TokenStream = class_output.into(); // Use the determined export name let export_name = quote!(#export_name); // Create the export registration function let create_name = quote::format_ident!("__NEON_EXPORT_CREATE__{}", class_ident); let create_fn = quote!( #[doc(hidden)] #[allow(non_snake_case)] #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::EXPORTS)] #[linkme(crate = neon::macro_internal::linkme)] fn #create_name<'cx>( cx: &mut neon::context::ModuleContext<'cx>, ) -> neon::result::NeonResult<(&'static str, neon::handle::Handle<'cx, neon::types::JsValue>)> { use neon::object::Class; static NAME: &str = #export_name; #class_ident::constructor(cx).map(|v| ( NAME, neon::handle::Handle::upcast(&v), )) } ); // Combine the class implementation with the export registration quote!( #class_tokens #create_fn ) .into() } // Extract the class identifier from an impl block fn extract_class_ident(input: &syn::ItemImpl) -> syn::Result { match &*input.self_ty { syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) => { let syn::PathSegment { ident, .. } = segments .last() .ok_or_else(|| syn::Error::new(input.self_ty.span(), "Expected type name"))?; Ok(ident.clone()) } _ => Err(syn::Error::new( input.self_ty.span(), "Class export can only be applied to named types", )), } } ================================================ FILE: crates/neon-macros/src/export/function/meta.rs ================================================ #[derive(Default)] pub(crate) struct Meta { pub(super) kind: Kind, pub(super) name: Option, pub(super) json: bool, pub(super) context: bool, pub(super) this: bool, } #[derive(Default)] pub(super) enum Kind { Async, AsyncFn, #[default] Normal, Task, } impl Meta { fn set_name(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.name = Some(meta.value()?.parse::()?); Ok(()) } fn force_json(&mut self, _meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.json = true; Ok(()) } fn force_context(&mut self, _meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.context = true; Ok(()) } fn force_this(&mut self, _meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.this = true; Ok(()) } fn make_async(&mut self, meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { if matches!(self.kind, Kind::AsyncFn) { return Err(meta.error("`async` attribute should not be used with an `async fn`")); } self.kind = Kind::Async; Ok(()) } fn make_task(&mut self, _meta: syn::meta::ParseNestedMeta) -> syn::Result<()> { self.kind = Kind::Task; Ok(()) } } pub(crate) struct Parser(syn::ItemFn); impl Parser { pub(crate) fn new(item: syn::ItemFn) -> Self { Self(item) } } impl syn::parse::Parser for Parser { type Output = (syn::ItemFn, Meta); fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { let Self(item) = self; let mut attr = Meta::default(); if item.sig.asyncness.is_some() { attr.kind = Kind::AsyncFn; } let parser = syn::meta::parser(|meta| { if meta.path.is_ident("name") { return attr.set_name(meta); } if meta.path.is_ident("json") { return attr.force_json(meta); } if meta.path.is_ident("context") { return attr.force_context(meta); } if meta.path.is_ident("this") { return attr.force_this(meta); } if meta.path.is_ident("async") { return attr.make_async(meta); } if meta.path.is_ident("task") { return attr.make_task(meta); } Err(meta.error("unsupported property")) }); parser.parse2(tokens)?; Ok((item, attr)) } } ================================================ FILE: crates/neon-macros/src/export/function/mod.rs ================================================ use syn::spanned::Spanned; use crate::export::function::meta::Kind; pub(crate) mod meta; pub(super) fn export(meta: meta::Meta, input: syn::ItemFn) -> proc_macro::TokenStream { let syn::ItemFn { attrs, vis, sig, block, } = input; let name = &sig.ident; // Generate the context or channel argument for the function let (context_extract, context_arg) = match context_parse(&meta, &sig) { Ok(arg) => arg, Err(err) => return err.into_compile_error().into(), }; // Extract `this` if necessary let has_this = check_this(&meta, &sig, context_arg.is_some()); let this_arg = has_this.then(|| quote::quote!(this,)); let this_extract = has_this.then(|| { quote::quote!( let this = cx.this()?; let this = neon::types::extract::TryFromJs::from_js(&mut cx, this)?; ) }); // Generate an argument list used when calling the original function let num_args = count_args(&sig, context_arg.is_some(), has_this); let args = (0..num_args).map(|i| quote::format_ident!("a{i}")); // Generate the tuple fields used to destructure `cx.args()`. Wrap in `Json` if necessary. let tuple_fields = args.clone().map(|name| { if meta.json { quote::quote!(neon::types::extract::Json(#name)) } else { quote::quote!(#name) } }); // Tag whether we should JSON wrap results let return_tag = if meta.json { quote::format_ident!("NeonJsonTag") } else { quote::format_ident!("NeonValueTag") }; // Convert the result // N.B.: Braces are intentionally included to avoid leaking trait to function body let result_extract = quote::quote!({ use neon::macro_internal::{ToNeonMarker, #return_tag as NeonReturnTag}; (&res).to_neon_marker::().neon_into_js(&mut cx, res) }); // Generate the call to the original function let call_body = match meta.kind { Kind::Async => quote::quote!( #context_extract #this_extract let (#(#tuple_fields,)*) = cx.args()?; let fut = #name(#context_arg #this_arg #(#args),*); let fut = { use neon::macro_internal::{ToNeonMarker, NeonValueTag}; (&fut).to_neon_marker::().into_neon_result(&mut cx, fut)? }; neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract) ), Kind::AsyncFn => quote::quote!( #context_extract #this_extract let (#(#tuple_fields,)*) = cx.args()?; let fut = #name(#context_arg #this_arg #(#args),*); neon::macro_internal::spawn(&mut cx, fut, |mut cx, res| #result_extract) ), Kind::Normal => quote::quote!( #context_extract #this_extract let (#(#tuple_fields,)*) = cx.args()?; let res = #name(#context_arg #this_arg #(#args),*); #result_extract ), Kind::Task => quote::quote!( #context_extract #this_extract let (#(#tuple_fields,)*) = cx.args()?; let promise = neon::context::Context::task(&mut cx, move || #name(#context_arg #this_arg #(#args),*)) .promise(|mut cx, res| #result_extract); Ok(neon::handle::Handle::upcast(&promise)) ), }; // Generate the wrapper function let wrapper_name = quote::format_ident!("__NEON_EXPORT_WRAPPER__{name}"); let wrapper_fn = quote::quote!( #[doc(hidden)] fn #wrapper_name(mut cx: neon::context::FunctionContext) -> neon::result::JsResult { #call_body } ); // Default export name as identity unless a name is provided let export_name = meta .name .map(|name| quote::quote!(#name)) .unwrap_or_else(|| { let name = crate::name::to_camel_case(&name.to_string()); quote::quote!(#name) }); // Generate the function that is registered to create the function on addon initialization. // Braces are included to prevent names from polluting user code. let create_name = quote::format_ident!("__NEON_EXPORT_CREATE__{name}"); let create_fn = quote::quote!({ #[doc(hidden)] #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::EXPORTS)] #[linkme(crate = neon::macro_internal::linkme)] fn #create_name<'cx>( cx: &mut neon::context::ModuleContext<'cx>, ) -> neon::result::NeonResult<(&'static str, neon::handle::Handle<'cx, neon::types::JsValue>)> { static NAME: &str = #export_name; #wrapper_fn neon::types::JsFunction::with_name(cx, NAME, #wrapper_name).map(|v| ( NAME, neon::handle::Handle::upcast(&v), )) } }); // Output the original function with the generated `create_fn` inside of it quote::quote!( #(#attrs) * #vis #sig { #create_fn #block } ) .into() } // Determine the number of arguments to the function fn count_args(sig: &syn::Signature, has_context: bool, has_this: bool) -> usize { let n = sig.inputs.len(); match (has_context, has_this) { (true, true) => n - 2, (false, false) => n, _ => n - 1, } } // Generate the context extraction and argument for the function fn context_parse( opts: &meta::Meta, sig: &syn::Signature, ) -> syn::Result<( Option, Option, )> { match opts.kind { // Allow borrowing from context Kind::Async | Kind::Normal if check_context(opts, sig)? => { Ok((None, Some(quote::quote!(&mut cx,)))) } // Require `'static` arguments Kind::AsyncFn | Kind::Task if check_channel(opts, sig)? => Ok(( Some(quote::quote!(let ch = neon::context::Context::channel(&mut cx);)), Some(quote::quote!(ch,)), )), _ => Ok((None, None)), } } // Checks if a _sync_ function has a context argument and if it is valid // * If the `context` attribute is included, must be at least one argument // * Inferred to be context if named `FunctionContext` or `Cx` // * Context argument must be a `&mut` reference // * First argument must not be `Channel` // * Must not be a `self` receiver fn check_context(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument let ty = match first_arg(opts, sig)? { Some(arg) => arg, None => return Ok(false), }; // Extract the reference type let ty = match &*ty.ty { // Tried to use a borrowed Channel syn::Type::Reference(ty) if !opts.context && is_channel_type(&ty.elem) => { return Err(syn::Error::new( ty.elem.span(), "Expected `&mut Cx` instead of a `Channel` reference.", )) } syn::Type::Reference(ty) => ty, // Context needs to be a reference _ if opts.context || is_context_type(&ty.ty) => { return Err(syn::Error::new( ty.ty.span(), "Context must be a `&mut` reference.", )) } // Hint that `Channel` should be swapped for `&mut Cx` _ if is_channel_type(&ty.ty) => { return Err(syn::Error::new( ty.ty.span(), "Expected `&mut Cx` instead of `Channel`.", )) } _ => return Ok(false), }; // Not a forced or inferred context if !opts.context && !is_context_type(&ty.elem) { return Ok(false); } // Context argument must be mutable if ty.mutability.is_none() { return Err(syn::Error::new(ty.span(), "Must be a `&mut` reference.")); } // All tests passed! Ok(true) } // Checks if a _async_ function has a Channel argument and if it is valid // * If the `context` attribute is included, must be at least one argument // * Inferred to be channel if named `Channel` // * Channel argument must not be a reference // * First argument must not be `FunctionContext` or `Cx` // * Must not be a `self` receiver fn check_channel(opts: &meta::Meta, sig: &syn::Signature) -> syn::Result { // Extract the first argument let ty = match first_arg(opts, sig)? { Some(arg) => arg, None => return Ok(false), }; // Check the type match &*ty.ty { // Provided `&mut Channel` instead of `Channel` syn::Type::Reference(ty) if opts.context || is_channel_type(&ty.elem) => { Err(syn::Error::new( ty.span(), "Expected an owned `Channel` instead of a reference.", )) } // Provided a `&mut Cx` instead of a `Channel` syn::Type::Reference(ty) if is_context_type(&ty.elem) => Err(syn::Error::new( ty.elem.span(), "Expected an owned `Channel` instead of a context reference.", )), // Found a `Channel` _ if opts.context || is_channel_type(&ty.ty) => Ok(true), // Tried to use an owned `Cx` _ if is_context_type(&ty.ty) => Err(syn::Error::new( ty.ty.span(), "Context is not available in async functions. Try a `Channel` instead.", )), _ => Ok(false), } } // Extract the first argument, that may be a context, of a function fn first_arg<'a>( opts: &meta::Meta, sig: &'a syn::Signature, ) -> syn::Result> { // Extract the first argument let arg = match sig.inputs.first() { Some(arg) => arg, // If context was forced, error to let the user know the mistake None if opts.context => { return Err(syn::Error::new( sig.inputs.span(), "Expected a context argument. Try removing the `context` attribute.", )) } None => return Ok(None), }; // Expect a typed pattern; self receivers are not supported match arg { syn::FnArg::Typed(ty) => Ok(Some(ty)), syn::FnArg::Receiver(arg) => Err(syn::Error::new( arg.span(), "Exported functions cannot receive `self`.", )), } } fn is_context_type(ty: &syn::Type) -> bool { let ident = match type_path_ident(ty) { Some(ident) => ident, None => return false, }; ident == "FunctionContext" || ident == "Cx" } fn is_channel_type(ty: &syn::Type) -> bool { let ident = match type_path_ident(ty) { Some(ident) => ident, None => return false, }; ident == "Channel" } // Extract the identifier from the last segment of a type's path fn type_path_ident(ty: &syn::Type) -> Option<&syn::Ident> { let segment = match ty { syn::Type::Path(ty) => ty.path.segments.last()?, _ => return None, }; Some(&segment.ident) } // Determine if the function has a `this` argument. It will be either the `0th` element // or, if a context argument is included, the `1st`. fn check_this(opts: &meta::Meta, sig: &syn::Signature, has_context: bool) -> bool { static THIS: &str = "this"; // Forced `this` argument if opts.this { return true; } // Get the first argument, skipping context let first = if has_context { sig.inputs.iter().nth(1) } else { sig.inputs.first() }; // No other arguments; return early let first = match first { Some(first) => first, None => return false, }; // Ignore `self` type receivers; those aren't used for `this` let ty = match first { syn::FnArg::Receiver(_) => return false, syn::FnArg::Typed(ty) => ty, }; // Check for `this` ident or a tuple struct let pat = match &*ty.pat { syn::Pat::Ident(ident) if ident.ident == THIS => return true, syn::Pat::TupleStruct(pat) => pat, _ => return false, }; // Expect exactly one element in the tuple struct let elem = match pat.elems.first() { Some(elem) if pat.elems.len() == 1 => elem, _ => return false, }; // Must be an identifier named `this` match elem { syn::Pat::Ident(ident) => ident.ident == THIS, _ => false, } } ================================================ FILE: crates/neon-macros/src/export/global/meta.rs ================================================ #[derive(Default)] pub(crate) struct Meta { pub(super) name: Option, pub(super) json: bool, } pub(crate) struct Parser; impl syn::parse::Parser for Parser { type Output = Meta; fn parse2(self, tokens: proc_macro2::TokenStream) -> syn::Result { let mut attr = Meta::default(); let parser = syn::meta::parser(|meta| { if meta.path.is_ident("name") { attr.name = Some(meta.value()?.parse::()?); return Ok(()); } if meta.path.is_ident("json") { attr.json = true; return Ok(()); } Err(meta.error("unsupported property")) }); parser.parse2(tokens)?; Ok(attr) } } ================================================ FILE: crates/neon-macros/src/export/global/mod.rs ================================================ pub(crate) mod meta; // Create a new block expression for the RHS of an assignment pub(super) fn export(meta: meta::Meta, name: &syn::Ident, expr: Box) -> Box { // Name for the registered create function let create_name = quote::format_ident!("__NEON_EXPORT_CREATE__{name}"); // Default export name as identity unless a name is provided let export_name = meta .name .map(|name| quote::quote!(#name)) .unwrap_or_else(|| quote::quote!(stringify!(#name))); // If `json` is enabled, wrap the value in `Json` before `TryIntoJs` is called let value = if meta.json { quote::quote!(neon::types::extract::Json(&#name)) } else { quote::quote!(#name) }; // Generate the function that is registered to create the global on addon initialization. // Braces are included to prevent names from polluting user code. // // N.B.: The `linkme(..)` attribute informs the `distributed_slice(..)` macro where // to find the `linkme` crate. It is re-exported from neon to avoid dependents from // needing to adding a direct dependency on `linkme`. It is an undocumented feature. // https://github.com/dtolnay/linkme/issues/54 let create_fn = quote::quote!({ #[doc(hidden)] #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::EXPORTS)] #[linkme(crate = neon::macro_internal::linkme)] fn #create_name<'cx>( cx: &mut neon::context::ModuleContext<'cx>, ) -> neon::result::NeonResult<(&'static str, neon::handle::Handle<'cx, neon::types::JsValue>)> { use neon::types::extract::TryIntoJs; // This leverages autoderef to support several different types: // * If value is `&T` and `&T: TryIntoJs`, it will deref `&&T` to `&T` // * If value is `T` and `&T: TryIntoJs`, it will call as is // * If value is `&T` and `T: TryIntoJs` and `Copy`, it will copy and call (&#value).try_into_js(cx).map(|v| (#export_name, neon::handle::Handle::upcast(&v))) } }); // Create a block to hold the original expression and the registered crate function let expr = quote::quote!({ #create_fn #expr }); // Create an expression from the token stream Box::new(syn::Expr::Verbatim(expr)) } ================================================ FILE: crates/neon-macros/src/export/mod.rs ================================================ mod class; mod function; mod global; // N.B.: Meta attribute parsing happens in this function because `syn::parse_macro_input!` // must be called from a function that returns `proc_macro::TokenStream`. pub(crate) fn export( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { // Parse item to determine the type of export let item = syn::parse_macro_input!(item as syn::Item); match item { // Export a function syn::Item::Fn(item) => { let parser = function::meta::Parser::new(item); let (item, meta) = syn::parse_macro_input!(attr with parser); function::export(meta, item) } // Export a `const` syn::Item::Const(mut item) => { let meta = syn::parse_macro_input!(attr with global::meta::Parser); item.expr = global::export(meta, &item.ident, item.expr); quote::quote!(#item).into() } // Export a `static` syn::Item::Static(mut item) => { let meta = syn::parse_macro_input!(attr with global::meta::Parser); item.expr = global::export(meta, &item.ident, item.expr); quote::quote!(#item).into() } // Export a class (impl block) syn::Item::Impl(item) => { let meta = syn::parse_macro_input!(attr with class::meta::Parser); class::export(meta, item) } // Return an error span for all other types _ => unsupported(item), } } // Generate an error for unsupported item types fn unsupported(item: syn::Item) -> proc_macro::TokenStream { let span = syn::spanned::Spanned::span(&item); let msg = "`neon::export` can only be applied to functions, consts, statics, and classes (impl blocks)."; let err = syn::Error::new(span, msg); err.into_compile_error().into() } ================================================ FILE: crates/neon-macros/src/lib.rs ================================================ //! Procedural macros supporting [Neon](https://docs.rs/neon/latest/neon/) mod class; mod export; pub(crate) mod name; #[proc_macro_attribute] pub fn main( _attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let syn::ItemFn { attrs, vis, sig, block, } = syn::parse_macro_input!(item as syn::ItemFn); let name = &sig.ident; let export_name = quote::format_ident!("__NEON_MAIN__{name}"); let export_fn = quote::quote!({ #[neon::macro_internal::linkme::distributed_slice(neon::macro_internal::MAIN)] #[linkme(crate = neon::macro_internal::linkme)] fn #export_name(cx: neon::context::ModuleContext) -> neon::result::NeonResult<()> { #name(cx) } }); quote::quote!( #(#attrs) * #vis #sig { #export_fn #block } ) .into() } #[proc_macro_attribute] pub fn export( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { export::export(attr, item) } #[proc_macro_attribute] pub fn class( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { class::class(attr, item) } ================================================ FILE: crates/neon-macros/src/name.rs ================================================ // Convert identifiers to camel case with the following rules: // * All leading and trailing underscores are preserved // * All other underscores are removed // * Characters immediately following a non-leading underscore are uppercased // * Bail (no conversion) if an unexpected condition is encountered: // - Uppercase character // - More than one adjacent interior underscore pub(crate) fn to_camel_case(name: &str) -> String { let mut out = String::with_capacity(name.len()); let mut it = name.chars(); let mut next = it.next(); let mut count = 0usize; // Keep leading underscores while matches!(next, Some('_')) { out.push('_'); next = it.next(); } // Convert to camel case while let Some(c) = next { match c { // Keep a count for maintaining trailing underscores '_' => count += 1, // Bail if there is an unexpected uppercase character or extra underscore _ if c.is_uppercase() || count >= 2 => { return name.to_string(); } // Don't uppercase the middle of a word _ if count == 0 => { out.push(c); count = 0; } // Uppercase characters following an underscore _ => { out.extend(c.to_uppercase()); count = 0; } } next = it.next(); } // We don't know underscores are a suffix until iteration has completed; // add them back. for _ in 0..count { out.push('_'); } out } // Validate JavaScript identifier names pub(crate) fn is_valid_js_identifier(name: &str) -> bool { if name.is_empty() { return false; } // Check first character (must be letter, $, or _) let first_char = name.chars().next().unwrap(); if !first_char.is_ascii_alphabetic() && first_char != '$' && first_char != '_' { return false; } // Check remaining characters (must be alphanumeric, $, or _) for ch in name.chars().skip(1) { if !ch.is_ascii_alphanumeric() && ch != '$' && ch != '_' { return false; } } // Check against JavaScript reserved words !matches!( name, "await" | "break" | "case" | "catch" | "class" | "const" | "continue" | "debugger" | "default" | "delete" | "do" | "else" | "enum" | "export" | "extends" | "false" | "finally" | "for" | "function" | "if" | "import" | "in" | "instanceof" | "new" | "null" | "return" | "super" | "switch" | "this" | "throw" | "true" | "try" | "typeof" | "var" | "void" | "while" | "with" | "yield" | "let" | "static" | "implements" | "interface" | "package" | "private" | "protected" | "public" ) } #[cfg(test)] mod test { #[test] fn to_camel_case() { use super::to_camel_case; assert_eq!(to_camel_case(""), ""); assert_eq!(to_camel_case("one"), "one"); assert_eq!(to_camel_case("two_words"), "twoWords"); assert_eq!(to_camel_case("three_word_name"), "threeWordName"); assert_eq!(to_camel_case("extra__underscore"), "extra__underscore"); assert_eq!(to_camel_case("PreserveCase"), "PreserveCase"); assert_eq!(to_camel_case("PreServe_case"), "PreServe_case"); assert_eq!(to_camel_case("_preserve_leading"), "_preserveLeading"); assert_eq!(to_camel_case("__preserve_leading"), "__preserveLeading"); assert_eq!(to_camel_case("preserve_trailing_"), "preserveTrailing_"); assert_eq!(to_camel_case("preserve_trailing__"), "preserveTrailing__"); assert_eq!(to_camel_case("_preserve_both_"), "_preserveBoth_"); assert_eq!(to_camel_case("__preserve_both__"), "__preserveBoth__"); assert_eq!(to_camel_case("_"), "_"); assert_eq!(to_camel_case("__"), "__"); assert_eq!(to_camel_case("___"), "___"); } } ================================================ FILE: doc/MIGRATION_GUIDE_0.10.md ================================================ # Neon 0.10 Migration Guide With the API improvements of Neon 0.10, a few backwards-incompatible changes have been introduced. This migration guide provides all the information you should need to make the (small!) code changes required for upgrading to Neon 0.10. We have made an effort to minimize these changes, and believe they should typically lead to simpler, clearer code. **If you run into any trouble migrating your code to 0.10, please file an issue or reach out for help on the [community Slack](https://rust-bindings.slack.com/)!** # Major changes ## Object property access is generic To improve the ergonomics of the common case of downcasting the result of property access to a specific type, the signature of `Object::get()` has been changed to have a generic return type. This means that code that follows `Object::get()` with `.downcast_or_throw()` or `.downcast::().or_throw()` no longer needs to do so. **Before:** ```rust obj.get(&mut cx, "name")?.downcast::(&mut cx).or_throw(&mut cx)? ``` **After (option 1):** ```rust obj.get::(&mut cx, "name")? ``` **After (option 2):** ```rust let v: Handle = obj.get(&mut cx, "name")? ``` Since `Object::get()` throws an exception when types don't match, use the new `Object::get_value()` or `Object::get_opt()` methods for cases that accept a wider range of allowable types. **Before:** ```rust let field: Option> = obj .get(&mut cx, "optionalField")? .downcast(&mut cx) .ok(); ``` **After:** ```rust let field: Option> = obj.get_opt(&mut cx, "optionalField")?; ``` **Before:** ```rust let length = obj.get(&mut cx, "length")?; let length: Option> = if length.is_a::(&mut cx) { None } else { Some(length.downcast_or_throw(&mut cx)?) }; ``` **After:** ```rust let length = obj.get_value(&mut cx, "length")?; let length: Option> = if length.is_a::(&mut cx) { None } else { Some(length.downcast_or_throw(&mut cx)?) }; ``` ## Layered APIs for function calls The API for calling (or constructing, i.e. the Neon equivalent of the JavaScript `new` operator) a JS function has been split into two layered alternatives. The existing `.call()` and `.construct()` functions are now a lower-level primitive, which no longer offers automatic downcasting of arguments or result. But Neon 0.10 now offers a more convenient API for calling functions with an options object and method chaining, with the introduction of the `.call_with()` and `.construct_with()` methods. ### Example: Calling a function **Before:** ```rust let s: Handle = ...; let n: Handle = ...; let args: Vec> = vec![s.upcast(), n.upcast()]; let this = cx.global(); f.call(&mut cx, this, args) ``` **After (low-level API):** ```rust let s: Handle = cx.string("hello"); let n: Handle = cx.number(42); let this = cx.global(); f.call(&mut cx, this, [s.upcast(), n.upcast()]) ``` **After (high-level API):** ```rust f.call_with(&cx) .args((cx.string("hello"), cx.number(42))) .apply(&mut cx) ``` ### Example: Constructing with a function **Before:** ```rust let s: Handle = ...; let n: Handle = ...; let args: Vec> = vec![s.upcast(), n.upcast()]; f.construct(&mut cx, args) ``` **After (low-level API):** ```rust let s: Handle = ...; let n: Handle = ...; f.construct(&mut cx, [s.upcast(), n.upcast()]) ``` **After (high-level API):** ```rust let s: Handle = ...; let n: Handle = ...; f.construct_with(&cx) .args((s, n)) .apply(&mut cx) ``` ## Idiomatic typed arrays Neon 0.10 replaces the previous typed array API with a more idiomatic API. JavaScript typed arrays (e.g. [`Uint32Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array)) can be represented with corresponding Rust types (e.g. [`JsTypedArray`](https://docs.rs/neon/0.10.0-alpha.3/neon/types/struct.JsTypedArray.html)), allowing easy access to their internals as Rust slices. ### Example: Reading a buffer **Before:** ```rust let b: Handle = ...; { let guard = cx.lock(); let data = b.borrow(&guard); let slice = data.as_slice::(); ... } ``` **After:** ```rust let b: Handle> = ...; let slice = b.as_slice(&cx); ``` ### Example: Reading and writing buffers **Before:** ```rust let b1: Handle = ...; let b2: Handle = ...; { let guard = cx.lock(); let data1 = b1.borrow(&guard); let data2 = b2.borrow(&guard); let slice1 = data1.as_slice::(); let slice2 = data2.as_slice::(); ... } ``` **After:** ```rust let src_buf: Handle> = ...; let dst_buf: Handle> = ...; { let lock = cx.lock(); let src = src_buf.as_slice(&lock).unwrap(); let dst = dst_buf.as_mut_slice(&lock).unwrap(); ... } ``` ### Example: Casting buffer types Previous versions of Neon came with a special datatype for casting the data of an ArrayBuffer, but this had incomplete handling of unaligned data and is deprecated in Neon 0.10. Crates like [bytemuck](https://crates.io/crates/bytemuck) can be used for casting buffer slices. **Before:** ```rust let b: Handle = ...; { let guard = cx.lock(); let data = b.borrow(&guard); let u8_slice = data.as_slice::(); let f32_slice = data.as_slice::(); ... } ``` **After:** ```rust use bytemuck::cast_slice; let b: Handle = ...; let u8_slice = b.as_slice(&cx); let f32_slice: &[f32] = cast_slice(u8_slice); ``` # Minor changes ## Uncaught errors in tasks Starting with 0.10, uncaught errors (whether Rust panics or JavaScript exceptions) in a task are now captured and reported to Node as an [`unhandledRejection ` event](https://nodejs.org/api/process.html#event-unhandledrejection). Previously, an uncaught JavaScript exception would be ignored. To handle uncaught exceptions, either wrap the body of a task with [`try_catch`](https://docs.rs/neon/0.10.0-alpha.3/neon/context/trait.Context.html#method.try_catch) or, alternatively, capture all uncaught rejections in Node with `process.on("unhandledRejection, (err) => {})`. **Before:** ```rust cx.task(|| "hello".to_string()) .and_then(|mut cx, _| { cx.throw_error("ignore me") }) ``` **After:** ```rust cx.task(|| "hello".to_string()) .and_then(|mut cx, _| { let _ = cx.try_catch(() { cx.throw_error("ignore me") }); Ok(()) }) ``` ## `Channel::send` returns `JoinHandle` The [`Channel::send()`](https://docs.rs/neon/0.10.0-alpha.3/neon/event/struct.Channel.html#method.send) method now returns a [`JoinHandle`](https://docs.rs/neon/0.10.0-alpha.3/neon/event/struct.JoinHandle.html) type instead of `()`, allowing code to optionally and conveniently block on the result with [`JoinHandle::join()`](https://docs.rs/neon/0.10.0-alpha.3/neon/event/struct.JoinHandle.html#method.join). Non-blocking code should usually work with little to no change; sometimes a semicolon may be needed to explicitly ignore the `JoinHandle`. **Before:** ```rust fn helper<'a, C: Context<'a>>(cx: &mut C, ch: Channel) { ch.send(...) } ``` **After:** ```rust fn helper<'a, C: Context<'a>>(cx: &mut C, ch: Channel) { ch.send(...); } ``` ## Value types aren't cloneable Previous versions of Neon had a safety bug in allowing types that implement the [`Value`](https://docs.rs/neon/0.10.0-alpha.3/neon/types/trait.Value.html) trait to be cloned. This has been fixed in Neon 0.10. It should be rare for code to ever need to clone a value. In most cases where this may be occurring, a [`Handle`](https://docs.rs/neon/0.10.0-alpha.3/neon/handle/struct.Handle.html) or reference (`&`) should work. For longer-lived use cases such as storing a value in a Rust static variable, use a [`Root`](https://docs.rs/neon/0.10.0-alpha.3/neon/handle/struct.Root.html). **Before:** ```rust fn helper<'a, C: Context<'a>>(cx: &mut C, s: JsString) -> ... { ... } ``` **After:** ```rust fn helper<'a, C: Context<'a>>(cx: &mut C, s: Handle) -> ... { ... } ``` ## Context methods now all take `&mut self` Context methods such as [`execute_scoped`](https://docs.rs/neon/0.10.0-alpha.3/neon/context/trait.Context.html#method.execute_scoped), [`compute_scoped`](https://docs.rs/neon/0.10.0-alpha.3/neon/context/trait.Context.html#method.compute_scoped), and [`lock`](https://docs.rs/neon/0.10.0-alpha.3/neon/context/trait.Context.html#method.lock) all take `&mut self` instead of the previous `&self`. This was necessary for safety and is more consistent with other `Context` methods. In normal usage, this should not require code changes. ## `Throw` is unconstructable The [`Throw`](https://docs.rs/neon/0.10.0-alpha.3/neon/result/struct.Throw.html) type can no longer be explicitly constructed, and cannot be shared across threads. This makes it harder to accidentally mis-report a [`NeonResult`](https://docs.rs/neon/0.10.0-alpha.3/neon/result/type.NeonResult.html) value by reusing a stale `Throw` value. Existing code that uses `Throw` for application-specific use cases should use a custom struct or enum instead. ================================================ FILE: doc/MIGRATION_GUIDE_1.0.0.md ================================================ # Neon 1.0.0 Migration Guide > **Note:** This migration guide assumes a project is using Neon 0.10 without Node-API backend. If using an older version or the legacy backend, see the [previous migration guide](MIGRATION_GUIDE_0.10.md). The Neon 1.0 has stabilized and brings a more consistent and ergonomic API. There are a few (minor) breaking changes and this guide will help walk through them! ## Removed Traits A few traits have been removed because they were either redundant or only used for features that no longer exist. ### `Managed` The `Managed` trait marked values that were _managed_ by the JavaScript VM. It was redundant with the `Value` trait. Trait bounds referencing `Managed` may be either removed or replaced with `Value`. #### Before ```rust fn example(h: Handle) where V: Managed, { } ``` #### After ```rust fn example(h: Handle) where V: Value, { } ``` ### `CallContext`, `This`, and `T` in `JsFunction` The `This` trait marked values for `cx.this()` in `JsFunction`. However, it was not type checked and could result in a panic at runtime. Instead, `cx.this()` always returns a `JsValue`. Since, `JsFunction`'s `T` parameter had a default, in many cases no changes are necessary. In some cases, the `T` parameter will need to be removed and a `downcast` added. #### Before ```rust // `CallContext` is equivalent to `FunctionContext` fn example(mut cx: CallContext) -> JsResult { let a = cx.this().get::(&mut cx, "a")?; Ok(cx.undefined()) } ``` #### After ```rust fn example(mut cx: FunctionContext) -> JsResult { let a = cx.this::()?.get::(&mut cx, "a")?; Ok(cx.undefined()) } ``` ### `JsResultExt` The `JsResultExt` trait provided a `.or_throw(&mut cx)` to allow converting a Rust error into a JavaScript exception. However, it required `T: Value`. It has been replaced with a more generic `ResultExt` trait. Most usages only require replacing `JsResultExt` with `ResultExt`. In some cases, an additional `T: Value` bound will need to be added or removed. #### Before ```rust use neon::result::JsResultExt; ``` #### After ```rust use neon::result::ResultExt; ``` ## `usize` indexes and lengths Neon inconsistently used `u32`, `usize`, and sometimes even `i32` for indexes and lengths. For consistency with Rust, `usize` is used everywhere. Update explicit types to use `usize` and remove type casting. Implicit types do not need to be updated. #### Before ```rust fn example(mut cx: FunctionContext) -> JsResult { let arr = cx.empty_array(); let msg = cx.string("Hello!"); arr.set(&mut cx, 0u32, msg)?; Ok(cx.undefined()) } ``` #### After ```rust fn example(mut cx: FunctionContext) -> JsResult { let arr = cx.empty_array(); let msg = cx.string("Hello!"); arr.set(&mut cx, 0usize, msg)?; Ok(cx.undefined()) } ``` ## Feature Flags Neon `0.10` made extensive use of feature flags for features that had not been stabilized (e.g., `try-catch-api`, `channel-api`). All features have been stabilized and the feature flags removed. Resolve by removing these features from the project's `Cargo.toml`. Two feature flags are still exist: `napi-N` for the Node-API version and `futures` for compatibility between Rust `Future` and JavaScript `Promise`. ================================================ FILE: doc/MIGRATION_GUIDE_NAPI.md ================================================ # N-API Migration Guide ## What is this about? Since v10, Node.js supports an improved API for building native modules, known as [N-API](https://nodejs.org/api/n-api.html). N-API offers a clearer, more complete, and more stable API layer for writing Node.js plugins than previous Node versions did. The Neon community has been [hard at work](https://github.com/neon-bindings/neon/issues/444) porting the library to a new backend based on N-API. ### Why N-API? Some key benefits of the new backend include: - Compiled Neon modules will work in all versions of Node _without needing to be recompiled_, guaranteed! - You can precompile Neon-based libraries to be completely transparent to downstream consumers. - The build process is streamlined, making Neon apps more reliable and easier to debug. - The stability guarantees of N-API allow us to avoid risk of incompatible changes to future releases of Neon. ### What does this mean for me? Porting Neon to N-API has been mostly transparent, but it has required a few backwards-incompatible changes. This guide provides instructions on how to migrate existing apps to the new N-API backend. Fortunately, the guaranteed stability of N-API means that once Neon users do this migration, we have increased confidence in the stability of Neon. We expect this to be the **last major breaking change before reaching Neon 1.0.** If you have any trouble porting, **please reach out to us** with a Neon issue or on the community Slack! We want to help everyone upgrade as smoothly and seamlessly as possible. ## Getting started ### Supported Node versions The N-API backend of Neon requires a minimum Node version of 10.0. ### Enabling the N-API backend To enable the N-API backend, you need to: 1. Remove `build.rs` from the project directory and `build = "build.rs"` from the `Cargo.toml`. The N-API backend does not require a Cargo build script. 2. Disable the default features (for now, the default features select the legacy backend) by setting `default-features = false`; and 3. Enable the appropriate feature flag in your `Cargo.toml` to select the N-API version you need support for (each N-API version N uses the feature flag `"napi-N"`, for example `"napi-4"` for N-API version 4). As a rule, you should choose the **oldest version of N-API that has the APIs you need.** (We will be adding N-API version requirements to the Neon API docs to make this clearer in the future.) You can consult the [official N-API feature matrix](https://nodejs.org/api/n-api.html#n_api_node_api_version_matrix) to see which N-API versions come with various versions of Node. ```toml [dependencies.neon] version = "0.9.1" default-features = false features = ["napi-4"] ``` ## Minor API changes ### Context Many methods that previously did not require context (e.g., `JsString::size`) now require a context. In many cases, this means adding an additional argument or using a convenience method on the `Context` trait. #### Affected methods ##### Handle * `Handle` - `is_a` - `downcast` `Handle::downcast` also requires a second type argument for the context type. This can usually be inferred, so you can typically use `_`. **Before:** ```rust value.downcast::() ``` **After:** ```rust value.downcast::(&mut cx) ``` ##### Primitive types * `JsBoolean` - `value` * `JsNull` - `new` * `JsString` - `size` - `value` * `JsNumber` - `value` * `JsUndefined` - `new` ##### Object APIs * `PropertyKey` - `get_from` - `set_from` ### Handle equality Handles no longer implement `Eq` or `PartialEq`, which had underspecified behavior. Use `Value::strict_equals` instead to invoke the behavior of JavaScript's `===` operator. ## Major API changes The N-API backend introduces two categories of significant change: 1. Embedding Rust data, which is no longer done through the awkward and complex `declare_types!` (i.e. classes) macro, but through a simpler primitive: the `JsBox` API. 2. Concurrency, which is offered through the Event Queue API instead of the Task API or Event Handlers, both of which are deprecated and removed in the N-API backend. ### Embedding Rust data The `declare_types!` macro is deprecated and replaced by the `JsBox` type. _Rationale:_ The `declare_types!` macro provides a syntax for defining classes, but requires substantial boilerplate and is unergonomic for simple cases and tends to interact poorly with IDEs. It's also not flexible enough to express the full range of JavaScript classes syntax and semantics. With the `JsBox` type, it's easy to embed Rust data in JavaScript objects, which can then be nested inside of more feature-rich classes defined in pure JavaScript (or TypeScript). **Before:** ```rust struct User { first_name: String, last_name: String, } impl User { fn full_name(&self) -> String { format!("{} {}", self.first_name, self.last_name) } } declare_types! { class JsUser for User { init(mut cx) { let first_name = cx.argument::(0)?; let last_name = cx.argument::(1)?; Ok(User { first_name, last_name }) } method full_name(mut cx) { let this = cx.this(); let guard = cx.lock(); let user = this.borrow(&guard); let full_name = user.full_name(); Ok(cx.string(full_name).upcast()) } } } ``` **After:** On the Rust side, the wrapped type must implement the `Finalize` trait, but this comes with a default implementation so it can be implemented with an empty `impl` block: ```rust struct User { first_name: String, last_name: String, } impl Finalize for User { } ``` The type can then be exposed to JavaScript with simple functions that wrap `User` in a `JsBox`: ```rust fn create_user(mut cx: FunctionContext) -> JsResult> { let first_name = cx.argument::(0)?.value(&mut cx); let last_name = cx.argument::(1)?.value(&mut cx); Ok(cx.boxed(User { first_name, last_name })) } fn user_full_name(mut cx: FunctionContext) -> JsResult { let user = cx.argument::>(0)?; let full_name = user.full_name(); Ok(cx.string(full_name)) } ``` Finally, you can provide an idiomatic JavaScript interface to the type by wrapping the boxed type in a class: ```js class User { constructor(firstName, lastName) { this.boxed = addon.createUser(firstName, lastName); } fullName() { return addon.userFullName(this.boxed); } } ``` ### Concurrency The supported mechanism for concurrency is the Channel API (`neon::event::Channel`). This feature has not yet stabilized, so to use this API, you'll also need to enable the `"channel-api"` feature flag as well: ```toml [dependencies.neon] version = "0.9.1" default-features = false features = ["napi-6", "channel-api"] ``` #### Deprecated: Task API The Task API (`neon::task`) is deprecated, and should in most cases be translated to using the Event Queue API. _Rationale:_ The Task API was built on top of the low-level libuv thread pool, which manages the concurrency of the Node.js system internals and should rarely be exposed to user-level programs. For most use cases, Neon users took advantage of this API as the only way to implement background, asynchronous computations. The Event Queue API is a more general-purpose, convenient, and safe way of achieving that purpose. That said, **if you believe you need access to the libuv thread pool, please [file an issue in the Neon repository](https://github.com/neon-bindings/neon/issues) with a description of your use case to let us know about it.** We don't believe this is commonly needed, but we don't want to leave you stuck! **Before:** With the `Task` API it was possible to define background computations off the main JavaScript thread, but these could only be run within the libuv thread pool--which runs all the system logic for the internals of Node.js. This gave Neon programmers a real power but forced them to contend with Node.js system tasks. ```rust impl Task for MyTask { type Output = i32; type Error = String; type JsEvent = JsNumber; fn perform(&self) -> Result { // compute the result... } fn complete(self, mut cx: TaskContext, result: Result) -> JsResult { match result { Ok(n) => { Ok(cx.number(n)) } Err(s) => { cx.throw_error(s) } } } } pub fn start_task(mut cx: FunctionContext) -> JsResult { let callback = cx.argument::(0)?; MyTask.schedule(callback); Ok(cx.undefined()) } ``` **After:** With the N-API backend, Neon programmers can use their own native threads and avoid competing with the Node.js system internals. This also brings some convenience since it doesn't require defining any custom trait implementations. ```rust pub fn start_task(mut cx: FunctionContext) -> JsResult { let callback = cx.argument::(0)?.root(&mut cx); let queue = cx.queue(); std::thread::spawn(move || { let result = // compute the result... queue.send(move |mut cx| { let callback = callback.into_inner(&mut cx); let this = cx.undefined(); let args = match result { Ok(n) => vec![ cx.null().upcast::(), cx.number(result).upcast() ], Err(msg) => vec![ cx.error(msg).upcast() ] }; callback.call(&mut cx, this, args)?; Ok(()) }); }); Ok(cx.undefined()) } ``` #### Deprecated: Event Handler API The Event Handler API (`neon::event::EventHandler`) is deprecated and should be replaced by the Event Queue API. _Rationale_: The Event Handler API had multiple issues with safety, memory leaks, and ergonomics ([1](https://github.com/neon-bindings/neon/issues/551), [2](https://github.com/neon-bindings/rfcs/issues/31)). **Before:** ```rust pub fn start_task(mut cx: FunctionContext) -> JsResult { let callback = cx.argument::(0)?; let handler = EventHandler::new(callback); thread::spawn(move || { let result = // compute the result... handler.schedule(move |cx| { vec![cx.number(result).upcast()] }); }); Ok(cx.undefined()) } ``` **After:** ```rust pub fn start_task(mut cx: FunctionContext) -> JsResult { let callback = cx.argument::(0)?.root(&mut cx); let queue = cx.queue(); std::thread::spawn(move || { let result = // compute the result... queue.send(move |mut cx| { let callback = callback.into_inner(&mut cx); let this = cx.undefined(); let args = vec![ cx.null().upcast::(), cx.number(result).upcast() ]; callback.call(&mut cx, this, args)?; Ok(()) }); }); Ok(cx.undefined()) } ``` ================================================ FILE: package.json ================================================ { "name": "neon-workspace", "private": true, "scripts": { "prettier": "prettier -w \"**/*.{js,json,ts,yml}\"", "prettier:check": "prettier -c \"**/*.{js,json,ts,yml}\"", "test": "npm run test:rust && npm run test:js", "test:rust": "cargo neon-test", "test:js": "npm test --workspaces --if-present" }, "workspaces": [ "pkgs/*", "test/*", "bench" ], "devDependencies": { "prettier": "^2.7.1" } } ================================================ FILE: pkgs/cargo-cp-artifact/LICENSE ================================================ MIT License Copyright (c) 2021 The Neon Project 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. ================================================ FILE: pkgs/cargo-cp-artifact/README.md ================================================ # cargo-cp-artifact `cargo-cp-artifact` is a small command line utility for parsing cargo metadata output and copying a compiler artifact to a desired location. ## Installation ```sh npm install -g cargo-cp-artifact ``` ## Usage ``` cargo-cp-artifact --artifact artifact-kind crate-name output-file -- wrapped-command ``` `cargo-cp-artifact` accepts a list of crate name and artifact kind to output file mappings and a command to wrap.`cargo-cp-artifact` will read `stdout` of the wrapped command and parse it as [cargo metadata](https://doc.rust-lang.org/cargo/reference/external-tools.html#json-messages). Compiler artifacts that match arguments provided will be copied to the target destination. When wrapping a `cargo` command, it is necessary to include a `json` format to `--message-format`. ### Arguments Multiple arguments are allowed to copy multiple build artifacts. #### `--artifact` _Alias: `-a`_ Followed by *three* arguments: `artifact-kind crate-name output-file` #### `--npm` _Alias: `-n`_ Followed by *two* arguments: `artifact-kind output-file` The crate name will be read from the `npm_package_name` environment variable. If the package name includes a namespace (`@namespace/package`), the namespace will be removed when matching the crate name (`package`). ### Artifact Kind Valid artifact kinds are `bin`, `cdylib`, and `dylib`. They may be abbreviated as `b`, `c`, and `d` respectively. For example, `-ac` is the equivalent of `--artifact cdylib`. ## Examples ### Wrapping cargo ```sh cargo-cp-artifact -a cdylib my-crate lib/index.node -- cargo build --message-format=json-render-diagnostics ``` ### Parsing a file ```sh cargo-cp-artifact -a cdylib my-crate lib/index.node -- cat build-output.txt ``` ### `npm` script `package.json` ```json { "name": "my-crate", "scripts": { "build": "cargo-cp-artifact -nc lib/index.node -- cargo build --message-format=json-render-diagnostics" } } ``` ```sh npm run build # Additional arguments may be passed npm run build -- --feature=serde ``` ## Why does this exist? At the time of writing, `cargo` does not include a configuration for outputting a library or binary to a specified location. An `--out-dir` option [exists on nightly](https://github.com/rust-lang/cargo/issues/6790), but does not allow specifying the name of the file. It's surprisingly difficult to reliably find the location of a cargo compiler artifact. It is impacted by many parameters, including: * Build profile * Target, default or specified * Crate name and name transforms However, `cargo` can emit metadata on `stdout` while continuing to provide human readable diagnostics on `stderr`. The metadata may be parsed to more easily and reliably find the location of compiler artifacts. `cargo-cp-artifact` chooses to wrap a command as a child process instead of reading `stdin` for two reasons: 1. Removes the need for `-o pipefile` when integrating with build tooling which may need to be platform agnostic. 2. Allows additional arguments to be provided when used in an [`npm` script](https://docs.npmjs.com/cli/v6/using-npm/scripts). ================================================ FILE: pkgs/cargo-cp-artifact/bin/cargo-cp-artifact.js ================================================ #!/usr/bin/env node "use strict"; const run = require(".."); run(process.argv.slice(2), process.env); ================================================ FILE: pkgs/cargo-cp-artifact/package.json ================================================ { "name": "cargo-cp-artifact", "version": "0.1.9", "description": "Copies compiler artifacts emitted by rustc by parsing Cargo metadata", "main": "src/index.js", "files": [ "bin", "src" ], "bin": { "cargo-cp-artifact": "bin/cargo-cp-artifact.js" }, "scripts": { "test": "mocha test" }, "repository": { "type": "git", "url": "git+https://github.com/neon-bindings/neon.git" }, "keywords": [ "cargo", "rust", "neon" ], "author": "K.J. Valencik", "license": "MIT", "bugs": { "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon/tree/main/pkgs/cargo-cp-artifact", "devDependencies": { "mocha": "^10.2.0" } } ================================================ FILE: pkgs/cargo-cp-artifact/src/args.js ================================================ "use strict"; class ParseError extends Error {} const NPM_ENV = "npm_package_name"; const EXPECTED_COMMAND = [ "Missing command to execute.", [ "cargo-cp-artifact -a cdylib my-crate index.node", "--", "cargo build --message-format=json-render-diagnostics", ].join(" "), ].join("\n"); function validateArtifactType(artifactType) { switch (artifactType) { case "b": case "bin": return "bin"; case "c": case "cdylib": return "cdylib"; case "d": case "dylib": return "dylib"; default: } throw new ParseError(`Unexpected artifact type: ${artifactType}`); } function getArtifactName({ artifactType, crateName }) { return `${artifactType}:${crateName}`; } function getCrateNameFromEnv(env) { if (!env.hasOwnProperty(NPM_ENV)) { throw new ParseError( [ `Could not find the \`${NPM_ENV}\` environment variable.`, "Expected to be executed from an `npm` command.", ].join(" ") ); } const name = env[NPM_ENV]; const firstSlash = name.indexOf("/"); // This is a namespaced package; assume the crate is the un-namespaced version if (name[0] === "@" && firstSlash > 0) { return name.slice(firstSlash + 1); } return name; } function parse(argv, env) { const artifacts = {}; let tokens = argv; function getNext() { if (!tokens.length) { throw new ParseError(EXPECTED_COMMAND); } const next = tokens[0]; tokens = tokens.slice(1); return next; } function getArtifactType(token) { if (token[1] !== "-" && token.length === 3) { return validateArtifactType(token[2]); } return validateArtifactType(getNext()); } function pushArtifact(artifact) { const name = getArtifactName(artifact); artifacts[name] = artifacts[name] || []; artifacts[name].push(artifact.outputFile); } while (tokens.length) { const token = getNext(); // End of CLI arguments if (token === "--") { break; } if ( token === "--artifact" || (token.length <= 3 && token.startsWith("-a")) ) { const artifactType = getArtifactType(token); const crateName = getNext(); const outputFile = getNext(); pushArtifact({ artifactType, crateName, outputFile }); continue; } if (token === "--npm" || (token.length <= 3 && token.startsWith("-n"))) { const artifactType = getArtifactType(token); const crateName = getCrateNameFromEnv(env); const outputFile = getNext(); pushArtifact({ artifactType, crateName, outputFile }); continue; } throw new ParseError(`Unexpected option: ${token}`); } if (!tokens.length) { throw new ParseError(EXPECTED_COMMAND); } const cmd = getNext(); return { artifacts, cmd, args: tokens, }; } module.exports = { ParseError, getArtifactName, parse }; ================================================ FILE: pkgs/cargo-cp-artifact/src/index.js ================================================ "use strict"; const { spawn } = require("child_process"); const { promises: { copyFile, mkdir, stat, unlink }, } = require("fs"); const { dirname, extname } = require("path"); const readline = require("readline"); const { ParseError, getArtifactName, parse } = require("./args"); function run(argv, env) { const options = parseArgs(argv, env); const copied = {}; const cp = spawn(options.cmd, options.args, { stdio: ["inherit", "pipe", "inherit"], shell: process.platform === "win32", }); const rl = readline.createInterface({ input: cp.stdout }); cp.on("error", (err) => { if (options.cmd === "cargo" && err.code === "ENOENT") { console.error(`Error: could not find the \`cargo\` executable. You can find instructions for installing Rust and Cargo at: https://www.rust-lang.org/tools/install `); } else { console.error(err); } process.exitCode = 1; }); cp.on("exit", (code) => { if (!process.exitCode) { process.exitCode = code; } }); rl.on("line", (line) => { try { processCargoBuildLine(options, copied, line); } catch (err) { console.error(err); process.exitCode = 1; } }); process.on("exit", () => { Object.keys(options.artifacts).forEach((name) => { if (!copied[name]) { console.error(`Did not copy "${name}"`); if (!process.exitCode) { process.exitCode = 1; } } }); }); } function processCargoBuildLine(options, copied, line) { const data = JSON.parse(line); const { filenames, reason, target } = data; if (!data || reason !== "compiler-artifact" || !target) { return; } const { kind: kinds, name } = data.target; if (!Array.isArray(kinds) || !Array.isArray(filenames)) { return; } // `kind` and `filenames` zip up as key/value pairs kinds.forEach((kind, i) => { const filename = filenames[i]; const { key, outputFiles } = getOutputFiles(kind, name, options.artifacts) || {}; if (!outputFiles || !filename) { return; } Promise.all( outputFiles.map((outputFile) => copyArtifact(filename, outputFile)) ) .then(() => { copied[key] = true; }) .catch((err) => { process.exitCode = 1; console.error(err); }); }); } function getOutputFiles(kind, name, artifacts) { const key = getArtifactName({ artifactType: kind, crateName: name }); const outputFiles = artifacts[key]; if (outputFiles) { return { key, outputFiles }; } // Cargo started replacing `-` with `_` in artifact names. Reverse the process // and check again. https://github.com/rust-lang/cargo/issues/13867 const altKey = key.replace(/_/g, "-"); return { key: altKey, outputFiles: artifacts[altKey], }; } async function isNewer(filename, outputFile) { try { const prevStats = await stat(outputFile); const nextStats = await stat(filename); return nextStats.mtime > prevStats.mtime; } catch (_err) {} return true; } async function copyArtifact(filename, outputFile) { if (!(await isNewer(filename, outputFile))) { return; } const outputDir = dirname(outputFile); // Don't try to create the current directory if (outputDir && outputDir !== ".") { await mkdir(outputDir, { recursive: true }); } // Apple Silicon (M1, etc.) requires shared libraries to be signed. However, // the macOS code signing cache isn't cleared when overwriting a file. // Deleting the file before copying works around the issue. // // Unfortunately, this workaround is incomplete because the file must be // deleted from the location it is loaded. If further steps in the user's // build process copy or move the file in place, the code signing cache // will not be cleared. // // https://github.com/neon-bindings/neon/issues/911 if (extname(outputFile) === ".node") { try { await unlink(outputFile); } catch (_e) { // Ignore errors; the file might not exist } } await copyFile(filename, outputFile); } function parseArgs(argv, env) { try { return parse(argv, env); } catch (err) { if (err instanceof ParseError) { quitError(err.message); } else { throw err; } } } function quitError(msg) { console.error(msg); process.exit(1); } module.exports = run; ================================================ FILE: pkgs/cargo-cp-artifact/test/args.js ================================================ "use strict"; const assert = require("assert"); const { parse } = require("../src/args"); describe("Argument Parsing", () => { it("throws on invalid artifact type", () => { assert.throws(() => parse(["-an", "a", "b", "--"]), /artifact type/); }); it("npm must have an environment variable", () => { assert.throws(() => parse(["-nc", "a", "b", "--"], {}), /environment/); }); it("must provide a command", () => { assert.throws(() => parse(["-ac", "a", "b"]), /Missing command/); assert.throws(() => parse(["-ac", "a", "b", "--"]), /Missing command/); }); it("cannot provide invalid option", () => { assert.throws(() => parse(["-q"], {}), /Unexpected option/); }); it("should be able to use --artifact", () => { const args = "bin my-crate my-bin -- a b c".split(" "); const expected = { artifacts: { "bin:my-crate": ["my-bin"], }, cmd: "a", args: ["b", "c"], }; assert.deepStrictEqual(parse(["--artifact", ...args]), expected); assert.deepStrictEqual(parse(["-a", ...args]), expected); }); it("should be able to use --npm", () => { const args = "bin my-bin -- a b c".split(" "); const env = { npm_package_name: "my-crate", }; const expected = { artifacts: { "bin:my-crate": ["my-bin"], }, cmd: "a", args: ["b", "c"], }; assert.deepStrictEqual(parse(["--npm", ...args], env), expected); assert.deepStrictEqual(parse(["-n", ...args], env), expected); }); it("should be able to use short-hand for crate type with -a", () => { const args = "-ab my-crate my-bin -- a b c".split(" "); const expected = { artifacts: { "bin:my-crate": ["my-bin"], }, cmd: "a", args: ["b", "c"], }; assert.deepStrictEqual(parse(args), expected); }); it("should be able to use short-hand for crate type with -n", () => { const args = "-nb my-bin -- a b c".split(" "); const env = { npm_package_name: "my-crate", }; const expected = { artifacts: { "bin:my-crate": ["my-bin"], }, cmd: "a", args: ["b", "c"], }; assert.deepStrictEqual(parse(args, env), expected); }); it("should remove namespace from package name", () => { const args = "-nc index.node -- a b c".split(" "); const env = { npm_package_name: "@my-namespace/my-crate", }; const expected = { artifacts: { "cdylib:my-crate": ["index.node"], }, cmd: "a", args: ["b", "c"], }; assert.deepStrictEqual(parse(args, env), expected); }); it("should be able to provide multiple artifacts", () => { const args = ` -nb my-bin --artifact d a b -ac my-crate index.node --npm bin other-copy -- a b c ` .trim() .split("\n") .map((line) => line.trim()) .join(" ") .split(" "); const env = { npm_package_name: "my-crate", }; assert.deepStrictEqual(parse(args, env), { artifacts: { "bin:my-crate": ["my-bin", "other-copy"], "dylib:a": ["b"], "cdylib:my-crate": ["index.node"], }, cmd: "a", args: ["b", "c"], }); }); }); ================================================ FILE: pkgs/create-neon/.mocharc.json ================================================ { "extension": ["ts"], "spec": "dist/test/**/*.js", "timeout": 5000 } ================================================ FILE: pkgs/create-neon/README.md ================================================ # Create Neon The `create-neon` tool bootstraps [Neon](https://neon-bindings.com) projects, which allows developers to build binary Node modules written in [Rust](https://www.rust-lang.org). ## Usage You can conveniently use this tool with the [`npm init`](https://docs.npmjs.com/cli/v7/commands/npm-init) syntax: ### Creating a Simple Project To create a simple Neon project that consists purely of Rust code: ```sh $ npm init neon[@latest] -- [ ...] my-project ``` **Note:** The initial `--` is necessary for `npm init` to pass any command-line options to Neon. **Note:** The `@latest` ensures that npm uses the latest version of this tool. #### Global Options ```sh -y|--yes Skip interactive `npm init` questionnaire. ``` ### Creating a Portable Library Neon also makes it easy to create **portable, cross-platform libraries** by publishing pre-built binaries. This means you can implement your Node.js library in Rust and publish the binaries so that users of your library (and any downstream users of theirs!) on all major hardware and operating systems can take a dependency on your library---_without having to install Rust or run any builds_. To create a portable npm library with pre-built binaries: ```sh $ npm init neon[@latest] -- [ ...] --lib [ ...] my-project ``` **Note:** The initial `--` is necessary for `npm init` to pass any command-line options to Neon. This will generate a project that can be used by pure JavaScript or TypeScript consumers without them even being aware of the use of Rust under the hood. It achieves this by publishing pre-built binaries for common Node platform architectures that are loaded just-in-time by a JS wrapper module. This command generates the necessary npm and CI/CD configuration boilerplate to require nearly zero manual installation on typical GitHub-hosted repos. The only manual step required is to configure GitHub Actions with the necessary npm access token to enable automated publishing. This command chooses the most common setup by default, but allows customization with fine-grained configuration options. These configuration options can also be modified later with the [Neon CLI](https://www.npmjs.com/package/@neon-rs/cli). #### Library Options ```sh --ci none|github CI/CD provider to generate config for. (Default: github) --bins none|npm[:org] Cache provider to publish pre-built binaries. (Default: npm, with org inferred from package) --platform Binary platform to add support to this library for. This option can be specified multiple times. (Default: macos, linux, windows) ``` ================================================ FILE: pkgs/create-neon/data/templates/.gitignore.hbs ================================================ target index.node **/node_modules **/.DS_Store npm-debug.log*{{#eq options.library.lang compare="ts"}} lib {{/eq}} cargo.log cross.log ================================================ FILE: pkgs/create-neon/data/templates/Cargo.toml.hbs ================================================ [package] name = {{crate.escaped.name}} version = {{crate.escaped.version}} {{#if crate.description}} description = {{crate.escaped.description}} {{/if}} {{#if crate.author}} authors = [{{crate.escaped.author}}] {{/if}} {{#if crate.license}} license = {{crate.escaped.license}} {{/if}} edition = "2024" exclude = ["index.node"] [lib] crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] neon = "{{versions.neon}}" ================================================ FILE: pkgs/create-neon/data/templates/README.md.hbs ================================================ # {{package.name}} {{#if package.description}} **{{package.name}}:** {{package.description}} {{/if}} This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon). ## Building {{package.name}} Building {{package.name}} requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support). To run the build, run: ```sh $ npm run build ``` This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli) utility to assemble the binary Node addon from the output of `cargo`. ## Exploring {{package.name}} After building {{package.name}}, you can explore its exports at the Node console: {{#if options.library}} ```sh $ npm i $ npm run build $ node > require('.').greeting('node') { message: 'hello node' } ``` {{else}} ```sh $ npm i $ npm run build $ node > require('.').hello('node') 'hello node' ``` {{/if}} ## Available Scripts In the project directory, you can run: {{#unless options.library}} #### `npm install` Installs the project, including running `npm run build`. {{/unless}} #### `npm run build` Builds the Node addon (`index.node`) from source, generating a release build with `cargo --release`. Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm run build` and similar commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html): ``` npm run build -- --feature=beetle ``` #### `npm run debug` Similar to `npm run build` but generates a debug build with `cargo`. #### `npm run cross` Similar to `npm run build` but uses [cross-rs](https://github.com/cross-rs/cross) to cross-compile for another platform. Use the [`CARGO_BUILD_TARGET`](https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) environment variable to select the build target. {{#eq options.library.ci.type compare="github"}} #### `npm run release` Initiate a full build and publication of a new patch release of this library via GitHub Actions. #### `npm run dryrun` Initiate a dry run of a patch release of this library via GitHub Actions. This performs a full build but does not publish the final result. {{/eq}} #### `npm test` Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/). ## Project Layout The directory structure of this project is: ``` {{package.name}}/ ├── Cargo.toml ├── README.md {{#if options.library}} ├── lib/ {{#eq options.library.lang compare="ts"}} ├── src/ | ├── index.mts | └── index.cts ├── crates/ | └── {{package.name}}/ | └── src/ | └── lib.rs {{/eq}} ├── platforms/ {{else}} ├── src/ | └── lib.rs ├── index.node {{/if}} ├── package.json └── target/ ``` | Entry | Purpose | |----------------|------------------------------------------------------------------------------------------------------------------------------------------| | `Cargo.toml` | The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command. | | `README.md` | This file. | {{#if options.library}} {{#eq options.library.lang compare="ts"}} | `lib/` | The directory containing the generated output from [tsc](https://typescriptlang.org). | | `src/` | The directory containing the TypeScript source files. | | `index.mts` | Entry point for when this library is loaded via [ESM `import`](https://nodejs.org/api/esm.html#modules-ecmascript-modules) syntax. | | `index.cts` | Entry point for when this library is loaded via [CJS `require`](https://nodejs.org/api/modules.html#requireid). | | `crates/` | The directory tree containing the Rust source code for the project. | | `lib.rs` | Entry point for the Rust source code. | {{else}} | `lib/` | The directory containing The directory containing the JavaScript source files. | {{/eq}} | `platforms/` | The directory containing distributions of the binary addon backend for each platform supported by this library. | {{else}} | `src/` | The directory tree containing the Rust source code for the project. | | `lib.rs` | Entry point for the Rust source code. | | `index.node` | The main module, a [Node addon](https://nodejs.org/api/addons.html) generated by the build and pointed to by `"main"` in `package.json`. | {{/if}} | `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. | | `target/` | Binary artifacts generated by the Rust build. | ## Learn More Learn more about: - [Neon](https://neon-bindings.com). - [Rust](https://www.rust-lang.org). - [Node](https://nodejs.org). ================================================ FILE: pkgs/create-neon/data/templates/Workspace.toml.hbs ================================================ [workspace] members = ["crates/{{crate.name}}"] resolver = "3" ================================================ FILE: pkgs/create-neon/data/templates/ci/github/build.yml.hbs ================================================ name: Build on: workflow_call: inputs: ref: description: 'The branch, tag, or SHA to check out' required: true type: string update-version: description: 'Update version before building?' required: false type: boolean default: false version: description: 'Version update (ignored if update-version is false)' required: false type: string default: 'patch' github-release: description: 'Publish GitHub release?' required: false type: boolean default: false tag: description: 'The release tag (ignored if github-release is false)' required: false type: string default: '' jobs: matrix: name: Matrix runs-on: ubuntu-latest outputs: matrix: {{#$}} steps.matrix.outputs.result {{/$}} steps: - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} with: ref: {{#$}} inputs.ref {{/$}} - name: Setup Neon Environment uses: ./.github/actions/setup with: use-rust: false - name: Look Up Matrix Data id: matrixData shell: bash run: echo "json=$(npx neon show ci github | jq -rc)" | tee -a $GITHUB_OUTPUT - name: Compute Matrix id: matrix uses: actions/github-script@{{versions.actions.verified.githubScript}} with: script: | const platforms = {{#$}} steps.matrixData.outputs.json {{/$}}; const macOS = platforms.macOS.map(platform => { return { os: "macos-latest", platform, script: "build" }; }); const windows = platforms.Windows.map(platform => { return { os: "windows-latest", platform, script: "build" }; }); const linux = platforms.Linux.map(platform => { return { os: "ubuntu-latest", platform, script: "cross" }; }); return [...macOS, ...windows, ...linux]; binaries: name: Binaries needs: [matrix] strategy: matrix: cfg: {{#$}} fromJSON(needs.matrix.outputs.matrix) {{/$}} runs-on: {{#$}} matrix.cfg.os {{/$}} permissions: contents: write steps: - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} with: ref: {{#$}} inputs.ref {{/$}} - name: Setup Neon Environment id: neon uses: ./.github/actions/setup with: use-cross: {{#$}} matrix.cfg.script == 'cross' {{/$}} platform: {{#$}} matrix.cfg.platform {{/$}} - name: Update Version if: {{#$}} inputs.update-version {{/$}} shell: bash run: | git config --global user.name $ACTIONS_USER git config --global user.email $ACTIONS_EMAIL npm version {{#$}} inputs.version {{/$}} -m "v%s" - name: Build shell: bash env: CARGO_BUILD_TARGET: {{#$}} steps.neon.outputs.target {{/$}} NEON_BUILD_PLATFORM: {{#$}} matrix.cfg.platform {{/$}} run: npm run {{#$}} matrix.cfg.script {{/$}} - name: Pack id: pack shell: bash run: | mkdir -p dist echo filename=$(basename $(npm pack ./platforms/{{#$}} matrix.cfg.platform {{/$}} --silent --pack-destination=./dist --json | jq -r '.[0].filename')) | tee -a $GITHUB_OUTPUT - name: Release if: {{#$}} inputs.github-release {{/$}} uses: softprops/action-gh-release@{{versions.actions.unverified.ghRelease.sha}} # {{versions.actions.unverified.ghRelease.tag}} with: files: ./dist/{{#$}} steps.pack.outputs.filename {{/$}} tag_name: {{#$}} inputs.tag {{/$}} main: name: Main needs: [matrix] runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} with: ref: {{#$}} inputs.ref {{/$}} - name: Setup Neon Environment uses: ./.github/actions/setup with: use-rust: false - name: Pack id: pack shell: bash run: | mkdir -p dist echo "filename=$(npm pack --silent --pack-destination=./dist)" | tee -a $GITHUB_OUTPUT - name: Release if: {{#$}} inputs.github-release {{/$}} uses: softprops/action-gh-release@{{versions.actions.unverified.ghRelease.sha}} # {{versions.actions.unverified.ghRelease.tag}} with: files: ./dist/{{#$}} steps.pack.outputs.filename {{/$}} tag_name: {{#$}} inputs.tag {{/$}} ================================================ FILE: pkgs/create-neon/data/templates/ci/github/release.yml.hbs ================================================ name: Release run-name: | {{#$}} (inputs.dryrun && 'Dry run') || format('Release: {0}', (inputs.version == 'custom' && inputs.custom) || inputs.version) {{/$}} on: workflow_dispatch: inputs: dryrun: description: 'Dry run (no npm publish)' required: false type: boolean default: true version: description: 'Version component to update (or "custom" to provide exact version)' required: true type: choice options: - patch - minor - major - prepatch - preminor - premajor - prerelease - custom custom: description: 'Custom version' required: false default: '' jobs: setup: name: Setup runs-on: ubuntu-latest permissions: contents: write outputs: dryrun: {{#$}} steps.dryrun.outputs.dryrun {{/$}} publish: {{#$}} steps.publish.outputs.publish {{/$}} ref: {{#$}} steps.tag.outputs.tag || github.event.repository.default_branch {{/$}} tag: {{#$}} steps.tag.outputs.tag || '' {{/$}} steps: - name: Validate Workflow Inputs if: {{#$}} inputs.version == 'custom' && inputs.custom == '' {{/$}} shell: bash run: | echo '::error::No custom version number provided' exit 1 - id: dryrun name: Validate Dry Run Event if: {{#$}} inputs.dryrun {{/$}} shell: bash run: echo dryrun=true | tee -a $GITHUB_OUTPUT - id: publish name: Validate Publish Event if: {{#$}} !inputs.dryrun {{/$}} shell: bash env: NPM_TOKEN: {{#$}} secrets.NPM_TOKEN {{/$}} run: | if [[ -z $NPM_TOKEN ]]; then echo "::error::Secret NPM_TOKEN is not defined for this GitHub repo." echo "::error::To publish to npm, this action requires:" echo "::error:: • an npm access token;" echo "::error:: • with Read-Write access to this project's npm packages;" echo "::error:: • stored as a repo secret named NPM_TOKEN." echo "::error::See https://docs.npmjs.com/about-access-tokens for info about creating npm tokens." echo "::error:: 💡 The simplest method is to create a Classic npm token of type Automation." echo "::error:: 💡 For greater security, consider using a Granual access token." echo "::error::See https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions for info about how to store GitHub repo secrets." exit 1 fi echo publish=true | tee -a $GITHUB_OUTPUT - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} - name: Setup Neon Environment uses: ./.github/actions/setup with: use-rust: false - name: Tag Release if: {{#$}} !inputs.dryrun {{/$}} id: tag shell: bash run: | git config --global user.name $ACTIONS_USER git config --global user.email $ACTIONS_EMAIL npm version -m 'v%s' '{{#$}} (inputs.version == 'custom' && inputs.custom) || inputs.version {{/$}}' git push --follow-tags echo tag=$(git describe --abbrev=0) | tee -a $GITHUB_OUTPUT build: name: Build needs: [setup] permissions: contents: write uses: ./.github/workflows/build.yml with: ref: {{#$}} needs.setup.outputs.ref {{/$}} tag: {{#$}} needs.setup.outputs.tag {{/$}} update-version: {{#$}} !!needs.setup.outputs.dryrun {{/$}} version: {{#$}} (inputs.version == 'custom' && inputs.custom) || inputs.version {{/$}} github-release: {{#$}} !!needs.setup.outputs.publish {{/$}} publish: name: Publish if: {{#$}} needs.setup.outputs.publish {{/$}} needs: [setup, build] runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} with: ref: {{#$}} needs.setup.outputs.ref {{/$}} - name: Setup Neon Environment uses: ./.github/actions/setup with: use-rust: false - name: Fetch uses: robinraju/release-downloader@{{versions.actions.unverified.releaseDownloader.sha}} # {{versions.actions.unverified.releaseDownloader.tag}} with: tag: {{#$}} needs.setup.outputs.tag {{/$}} fileName: "*.tgz" out-file-path: ./dist - name: Publish shell: bash env: NODE_AUTH_TOKEN: {{#$}} secrets.NPM_TOKEN {{/$}} run: | for p in ./dist/*.tgz ; do npm publish --access public $p done ================================================ FILE: pkgs/create-neon/data/templates/ci/github/setup.yml.hbs ================================================ name: 'Setup Neon' description: 'Setup the Neon toolchain.' inputs: platform: description: 'Platform being built for.' required: false default: '' use-rust: description: 'Install Rust?' required: false default: 'true' use-cross: description: 'Install cross-rs?' required: false default: 'false' workspace: description: 'Path to workspace being setup.' required: false default: '.' outputs: rust: description: 'Rust version installed.' value: {{#$}} steps.rust.outputs.version {{/$}} node: description: 'Node version installed.' value: {{#$}} steps.node.outputs.version {{/$}} target: description: 'Rust target architecture installed.' value: {{#$}} steps.target.outputs.target {{/$}} runs: using: "composite" steps: - name: Set Environment Variables uses: falti/dotenv-action@{{versions.actions.unverified.dotenv.sha}} # {{versions.actions.unverified.dotenv.tag}} with: path: ./.github/.env export-variables: true keys-case: bypass - name: Install Node uses: actions/setup-node@{{versions.actions.verified.setupNode}} with: node-version: {{#$}} env.NODE_VERSION {{/$}} registry-url: {{#$}} env.NPM_REGISTRY {{/$}} cache: npm - name: Install Dependencies shell: bash run: npm ci - name: Compute Rust Target if: {{#$}} inputs['use-rust'] == 'true' {{/$}} id: target shell: bash run: echo target=$(npx neon list-platforms | jq -r '.["{{#$}} inputs.platform {{/$}}"]') | tee -a $GITHUB_OUTPUT working-directory: {{#$}} inputs.workspace {{/$}} - name: Install Rust if: {{#$}} inputs['use-rust'] == 'true' {{/$}} uses: actions-rs/toolchain@{{versions.actions.verified.setupRust}} with: toolchain: {{#$}} env.RUST_VERSION {{/$}} target: {{#$}} steps.target.outputs.target {{/$}} override: true - name: Install cross-rs if: {{#$}} inputs['use-cross'] == 'true' {{/$}} uses: baptiste0928/cargo-install@{{versions.actions.verified.cargoInstall}} with: crate: cross - name: Node Version id: node shell: bash run: | echo version=$(node -e 'console.log(process.versions.node)') | tee -a $GITHUB_OUTPUT - name: Rust Version if: {{#$}} inputs['use-rust'] == 'true' {{/$}} id: rust shell: bash run: | echo version=$(cargo -Vv | fgrep release: | cut -d' ' -f2) | tee -a $GITHUB_OUTPUT ================================================ FILE: pkgs/create-neon/data/templates/ci/github/test.yml.hbs ================================================ name: Test run-name: | {{#$}} (github.event_name == 'pull_request' && format('Test (PR #{0}): {1}', github.event.number, github.event.pull_request.title)) || format('Test: {0}', github.event.head_commit.message) {{/$}} on: # Event: A maintainer has pushed commits or merged a PR to main. push: # Limiting push events to 'main' prevents duplicate runs of this workflow # when maintainers push to internal PRs. branches: - main # Event: A contributor has created or updated a PR. pull_request: types: [opened, synchronize, reopened, labeled] branches: - main jobs: pr: name: Pull Request Details runs-on: ubuntu-latest if: {{#$}} github.event_name == 'pull_request' {{/$}} outputs: branch: {{#$}} steps.pr-ref.outputs.branch || github.event.repository.default_branch {{/$}} steps: - name: PR Branch id: pr-ref shell: bash run: echo "branch=$(gh pr view $PR_NO --repo $REPO --json headRefName --jq '.headRefName')" | tee -a "$GITHUB_OUTPUT" env: REPO: {{#$}} github.repository {{/$}} PR_NO: {{#$}} github.event.number {{/$}} GH_TOKEN: {{#$}} github.token {{/$}} # Labeling a PR with a `ci:full-matrix` label does a full matrix build on # every run of this workflow for that PR, in addition to the other tests. full-matrix: name: Build if: {{#$}} github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'ci:full-matrix') {{/$}} needs: [pr] permissions: contents: write uses: ./.github/workflows/build.yml with: ref: {{#$}} needs.pr.outputs.branch {{/$}} update-version: true github-release: false unit-tests: name: Unit Tests runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@{{versions.actions.verified.checkout}} - name: Setup Neon Environment id: neon uses: ./.github/actions/setup with: platform: linux-x64-gnu - name: Build shell: bash env: CARGO_BUILD_TARGET: {{#$}} steps.neon.outputs.target {{/$}} NEON_BUILD_PLATFORM: linux-x64-gnu run: npm run debug - name: Test shell: bash run: npm test ================================================ FILE: pkgs/create-neon/data/templates/lib.rs.hbs ================================================ // Use #[neon::export] to export Rust functions as JavaScript functions. // See more at: https://docs.rs/neon/latest/neon/attr.export.html #[neon::export] fn hello(name: String) -> String { format!("hello {name}") } // Use #[neon::main] to add additional behavior at module loading time. // See more at: https://docs.rs/neon/latest/neon/attr.main.html // #[neon::main] // fn main(_cx: ModuleContext) -> NeonResult<()> { // println!("module is loaded!"); // Ok(()) // } ================================================ FILE: pkgs/create-neon/data/templates/manifest/base/default.json.hbs ================================================ { "name": "{{options.fullName}}", "version": "{{options.version}}", "main": "index.node", "scripts": {}, "devDependencies": { "@neon-rs/cli": "{{versions.neonCLI}}"{{#eq options.library.lang compare="ts"}}, "@tsconfig/node{{versions.tsconfigNode.major}}": "^{{versions.tsconfigNode.semver}}", "@types/node": "^{{versions.typesNode}}", "typescript": "^{{versions.typescript}}"{{/eq}} } } ================================================ FILE: pkgs/create-neon/data/templates/manifest/base/library.json.hbs ================================================ { "name": "{{options.fullName}}", "version": "{{options.version}}", {{#eq options.library.module compare="esm"}} "exports": { ".": { "import": { "types": "./lib/index.d.mts", "default": "./lib/index.mjs" }, "require": { "types": "./lib/index.d.cts", "default": "./lib/index.cjs" } } }, {{/eq}} "types": "./lib/index.d.cts", "main": "./lib/index.cjs", "files": [ "lib/**/*.?({c,m}){t,j}s" ], "scripts": {}, "neon": { "type": "library", {{#eq options.library.cache.type compare="npm"}} {{#if options.library.cache.org}} "org": "{{options.library.cache.org}}", {{/if}} {{#if options.library.cache.prefix}} "prefix": "{{options.library.cache.prefix}}", {{/if}} {{/eq}} "platforms": {}, "load": "./src/load.cts" }, "devDependencies": { "@neon-rs/cli": "^{{versions.neonCLI}}"{{#eq options.library.lang compare="ts"}}, "@tsconfig/node{{versions.tsconfigNode.major}}": "^{{versions.tsconfigNode.semver}}", "@types/node": "^{{versions.typesNode}}", "typescript": "^{{versions.typescript}}"{{/eq}} }, "dependencies": { "@neon-rs/load": "^{{versions.neonLoad}}" } } ================================================ FILE: pkgs/create-neon/data/templates/ts/index.cts.hbs ================================================ // This module is the CJS entry point for the library. // The Rust addon. import * as addon from './load.cjs'; // Use this declaration to assign types to the addon's exports, // which otherwise by default are `any`. declare module "./load.cjs" { function hello(name: string): string; } export type Greeting = { message: string }; export function greeting(name: string): Greeting { const message = addon.hello(name); return { message }; } ================================================ FILE: pkgs/create-neon/data/templates/ts/index.mts.hbs ================================================ // This module is the ESM entry point for the library. export * from './index.cjs'; ================================================ FILE: pkgs/create-neon/data/templates/ts/load.cts.hbs ================================================ // This module loads the platform-specific build of the addon on // the current system. The supported platforms are registered in // the `platforms` object below, whose entries can be managed by // by the Neon CLI: // // https://www.npmjs.com/package/@neon-rs/cli module.exports = require('@neon-rs/load').proxy({ platforms: {}, debug: () => require('../index.node') }); ================================================ FILE: pkgs/create-neon/data/templates/tsconfig.json.hbs ================================================ { {{#eq options.library.lang compare="ts"}} "extends": "@tsconfig/node{{versions.tsconfigNode.major}}/tsconfig.json", "compilerOptions": { "module": "node{{versions.tsconfigNode.module}}", "declaration": true, "outDir": "lib", }, "exclude": ["lib"] {{else}} "extends": "@tsconfig/node{{versions.tsconfigNode.major}}/tsconfig.json" {{/eq}} } ================================================ FILE: pkgs/create-neon/data/versions.json ================================================ { "neon": "1.1", "neonCLI": "0.1.82", "neonLoad": "0.1.82", "typescript": "5.3.3", "typesNode": "20.11.16", "tsconfigNode": { "major": "20", "semver": "20.1.4", "module": "16" }, "node": "20", "actions": { "verified": { "checkout": "v3", "githubScript": "v7", "setupNode": "v3", "setupRust": "v1", "cargoInstall": "v2", "neonBuild": "v0.9", "neonPublish": "v0.4.1" }, "unverified": { "dotenv": { "tag": "v1.1.2", "sha": "d1cd55661714e830a6e26f608f81d36e23424fed" }, "ghRelease": { "tag": "v2.0.4", "sha": "9d7c94cfd0a1f3ed45544c887983e9fa900f0564" }, "releaseDownloader": { "tag": "v1.10", "sha": "c39a3b234af58f0cf85888573d361fb6fa281534" } } } } ================================================ FILE: pkgs/create-neon/dev/expect.ts ================================================ import { ChildProcess } from "child_process"; import { Readable, Writable } from "stream"; import readStream from "stream-to-string"; import { readChunks } from "../src/shell.js"; function splitLines(s: string): string[] { return s.split(/([^\n]*\r?\n)/).filter((x) => x); } function isCompleteLine(s: string): boolean { return s.endsWith("\n"); } class LinesBuffer { // INVARIANT: (this.buffer.length > 0) && // !isCompleteLine(this.buffer[this.buffer.length - 1]) // In other words, the last line in the buffer is always incomplete. private buffer: string[]; constructor() { this.buffer = [""]; } add(lines: string[]) { if (lines.length === 0) { return; } if (isCompleteLine(lines[lines.length - 1])) { lines.push(""); } this.buffer[this.buffer.length - 1] += lines.shift(); this.buffer = this.buffer.concat(lines); } // Finds and removes lines from the buffer up to and including // the first line that satisfies the predicate p. // Returns the extracted lines, or null if no such line exists. find(p: (s: string) => boolean): string[] | null { let index = this.buffer.findIndex(p); if (index === -1) { return null; } let extracted = this.buffer.splice(0, index + 1); if (this.buffer.length === 0) { this.buffer.push(""); } return extracted; } } interface Pattern { expect(session: Session): AsyncGenerator; } type QA = { q: string; a: string }; class ExpectLine implements Pattern { optional: QA[]; required: QA; constructor(optional: QA[], required: QA) { this.optional = optional; this.required = required; } async *expect(session: Session): AsyncGenerator { // Use a wrapper stream so that early exit from the for-await loop doesn't // cancel the underlying stream. let stdout = Readable.toWeb(session.stdout).values({ preventCancel: true }); for await (let chunk of stdout) { session.buffer.add(splitLines(chunk)); let maxFound = -1; // Check for optional lines for (let i = 0; i < this.optional.length; i++) { let found = session.buffer.find((line) => line.startsWith(this.optional[i].q) ); if (found) { session.stdin.write(this.optional[i].a + "\n"); maxFound = i; yield found; } } // Remove from queue any lines that were found this.optional.splice(0, maxFound + 1); // Check for required line let found = session.buffer.find((line) => line.startsWith(this.required.q) ); if (found) { session.stdin.write(this.required.a + "\n"); yield found; return; } } } } // We don't currently have any scripts that end with an optional line, but if we did we'd need this class. class ExpectEOF implements Pattern { optional: QA[]; constructor(optional: QA[]) { this.optional = optional; throw new Error("Class not implemented."); } async *expect(session: Session): AsyncGenerator { throw new Error("Method not implemented."); } } class Script { clauses: Pattern[]; constructor(src: Record) { this.clauses = []; let keys = Object.keys(src); let i = 0; let optional: QA[] = []; while (i < keys.length) { if (keys[i].startsWith("?")) { // Collect optional lines optional.push({ q: keys[i].substring(1).trim(), a: src[keys[i]] }); } else { // Collect required line this.clauses.push( new ExpectLine(optional, { q: keys[i], a: src[keys[i]] }) ); optional = []; } i++; } if (optional.length > 0) { this.clauses.push(new ExpectEOF(optional)); } } async *run(session: Session): AsyncGenerator { for (let clause of this.clauses) { for await (let lines of clause.expect(session)) { yield lines; } } } } class Session { buffer: LinesBuffer; stdin: Writable; stdout: Readable; constructor(stdin: Writable, stdout: Readable) { this.buffer = new LinesBuffer(); this.stdin = stdin; this.stdout = readChunks(stdout); } } function exit(child: ChildProcess): Promise { let resolve: (code: number | null) => void; let result: Promise = new Promise((res) => { resolve = res; }); child.on("exit", (code) => { resolve(code); }); return result; } export default async function expect( child: ChildProcess, src: Record ): Promise { let output: string[][] = []; let script = new Script(src); let session = new Session(child.stdin!, child.stdout!); for await (let lines of script.run(session)) { output.push(lines); } let stderr = await readStream(child.stderr!); let code = await exit(child); switch (code) { case null: throw new Error("child process interrupted"); case 0: return; default: console.log("stderr: " + stderr.trim()); console.log("stdout: " + JSON.stringify(output)); throw new Error("child process exited with code " + code); } } ================================================ FILE: pkgs/create-neon/package.json ================================================ { "name": "create-neon", "version": "0.7.0", "description": "Create Neon projects with no build configuration.", "type": "module", "exports": "./dist/src/bin/create-neon.js", "author": "Dave Herman ", "license": "MIT", "bugs": { "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon#readme", "bin": { "create-neon": "dist/src/bin/create-neon.js" }, "files": [ "dist/src/**/*", "dist/data/**/*" ], "scripts": { "build": "tsc && rm -rf dist/data && cp -r data dist/data/ && ls -R dist/data", "prepublishOnly": "npm run build", "pretest": "npm run build", "test": "mocha", "manual-interactive-test": "npm run build && rm -rf create-neon-manual-test-project && node ./dist/src/bin/create-neon.js create-neon-manual-test-project", "manual-test": "npm run build && rm -rf create-neon-manual-test-project && node ./dist/src/bin/create-neon.js --lib --yes create-neon-manual-test-project" }, "repository": { "type": "git", "url": "git+https://github.com/neon-bindings/neon.git" }, "keywords": [ "neon" ], "devDependencies": { "@tsconfig/node18": "^18.2.2", "@types/chai": "^4.3.11", "@types/command-line-args": "^5.2.3", "@types/command-line-usage": "^5.0.4", "@types/handlebars-helpers": "^0.5.6", "@types/mocha": "^10.0.6", "@types/node": "^20.10.1", "chai": "^4.3.10", "execa": "^8.0.1", "mocha": "^10.2.0", "stream-to-string": "^1.2.1", "toml": "^3.0.0", "typescript": "^5.3.2" }, "dependencies": { "@neon-rs/manifest": "^0.2.1", "chalk": "^5.3.0", "command-line-args": "^5.2.1", "command-line-usage": "^7.0.1", "handlebars": "^4.7.8", "handlebars-helpers": "^0.10.0" } } ================================================ FILE: pkgs/create-neon/src/bin/create-neon.ts ================================================ #!/usr/bin/env node import commandLineArgs from "command-line-args"; import { printErrorWithUsage } from "../print.js"; import { createNeon } from "../index.js"; import { Cache } from "../cache.js"; import { NPM } from "../cache/npm.js"; import { CI } from "../ci.js"; import { GitHub } from "../ci/github.js"; import { Lang, ModuleType } from "../create/creator.js"; import { NodePlatform, PlatformPreset, isNodePlatform, isPlatformPreset, } from "@neon-rs/manifest/platform"; const OPTIONS = [ { name: "app", type: Boolean, defaultValue: false }, { name: "lib", type: Boolean, defaultValue: false }, { name: "bins", type: String, defaultValue: "none" }, { name: "platform", type: String, multiple: true, defaultValue: ["common"] }, { name: "ci", alias: "c", type: String, defaultValue: "github" }, { name: "yes", alias: "y", type: Boolean, defaultValue: false }, ]; try { const opts = commandLineArgs(OPTIONS, { stopAtFirstUnknown: true }); if (opts.app && opts.lib) { throw new Error("Cannot choose both --app and --lib"); } if (!opts._unknown || opts._unknown.length === 0) { throw new Error("No package name given"); } if (opts._unknown.length > 1) { throw new Error(`unexpected argument (${opts._unknown[1]})`); } const [pkg] = opts._unknown; const { org, basename } = /^((?@[^/]+)\/)?(?.*)/.exec(pkg) ?.groups as { org?: string; basename: string; }; const fullName = org ? pkg : basename; const platforms = parsePlatforms(opts.platform); const cache = parseCache(opts.lib, opts.bins, basename, org); const ci = parseCI(opts.ci); if (opts.yes) { process.env["npm_configure_yes"] = "true"; } createNeon({ org, basename, fullName, version: "0.1.0", library: opts.lib ? { lang: Lang.TS, module: ModuleType.ESM, cache, ci, platforms, } : null, app: opts.app ? true : opts.lib ? false : null, // Even if the user specifies this with a flag (e.g. `npm init -y neon`), // `npm init` sets this env var to 'true' before invoking create-neon. // So this is the most general way to check this configuration option. interactive: process.env["npm_configure_yes"] !== "true", }); } catch (e) { printErrorWithUsage(e); process.exit(1); } function parsePlatforms( platforms: string[] ): | NodePlatform | PlatformPreset | (NodePlatform | PlatformPreset)[] | undefined { if (platforms.length === 0) { return undefined; } else if (platforms.length === 1) { const platform = platforms[0]; if (isNodePlatform(platform) || isPlatformPreset(platform)) { return platform; } throw new TypeError(`expected platform or preset, got ${platform}`); } else { return platforms.map((platform) => { if (isNodePlatform(platform) || isPlatformPreset(platform)) { return platform; } throw new TypeError(`expected platform or preset, got ${platform}`); }); } } function parseCI(ci: string): CI | undefined { switch (ci) { case "none": return undefined; case "github": return new GitHub(); default: throw new Error( `Unrecognized CI system ${ci}, expected 'github' or 'none'` ); } } function parseCache( lib: boolean, bins: string, pkg: string, org: string | undefined ): Cache | undefined { const defaultPrefix = org ? `${pkg}-` : ""; org ??= `@${pkg}`; // CASE: npm create neon -- --app logos-r-us // CASE: npm create neon -- --app @acme/logos-r-us // - if (bins === "none" && !lib) { return undefined; } // CASE: npm create neon -- --lib logo-generator // CASE: npm create neon -- --lib --bins npm logo-generator // - lib: `logo-generator` // - bin: `@logo-generator/darwin-arm64` // CASE: npm create neon -- --lib @acme/logo-generator // CASE: npm create neon -- --lib --bins npm @acme/logo-generator // - lib: `@acme/logo-generator` // - bin: `@acme/logo-generator-darwin-arm64` if (bins === "none" || bins === "npm") { return new NPM(org, defaultPrefix); } // CASE: npm create neon -- --lib --bins=npm:acme logo-generator // lib: logo-generator // bin: @acme/logo-generator-darwin-arm64 // CASE: npm create neon -- --lib --bins=npm:acme/libs-logo-generator- logo-generator // lib: logo-generator // bin: @acme/libs-logo-generator-darwin-arm64 // CASE: npm create neon -- --lib --bins=npm:acme-libs @acme/logo-generator // lib: @acme-libs/logo-generator // bin: @acme-libs/logo-generator-darwin-arm64 if (bins.startsWith("npm:")) { const split = bins.substring(4).split("/", 2); const org = split[0].replace(/^@?/, "@"); // don't care if they include the @ or not const prefix = split.length > 1 ? split[1] : defaultPrefix; return new NPM(org, prefix); } throw new Error( `Unrecognized binaries cache ${bins}, expected 'npm[:org[/prefix]]' or 'none'` ); } ================================================ FILE: pkgs/create-neon/src/cache/npm.ts ================================================ import { Cache } from "../cache.js"; export class NPM implements Cache { readonly org: string; readonly prefix: string; readonly type: string = "npm"; constructor(org: string, prefix: string) { this.org = org; this.prefix = prefix; } } ================================================ FILE: pkgs/create-neon/src/cache.ts ================================================ export interface Cache { readonly type: string; } ================================================ FILE: pkgs/create-neon/src/ci/github.ts ================================================ import handlebars from "handlebars"; import { CI } from "../ci.js"; import path from "node:path"; const TEMPLATES: Record = { "setup.yml.hbs": path.join(".github", "actions", "setup", "action.yml"), ".env.hbs": path.join(".github", ".env"), "build.yml.hbs": path.join(".github", "workflows", "build.yml"), "release.yml.hbs": path.join(".github", "workflows", "release.yml"), "test.yml.hbs": path.join(".github", "workflows", "test.yml"), }; function githubDelegate( this: any, options: handlebars.HelperOptions ): handlebars.SafeString { return new handlebars.SafeString("${{" + options.fn(this) + "}}"); } export class GitHub implements CI { constructor() {} readonly type: string = "github"; templates(): Record { return TEMPLATES; } setup(): void { handlebars.registerHelper("$", githubDelegate); } scripts(): Record { return { release: "gh workflow run release.yml -f dryrun=false -f version=patch", dryrun: "gh workflow run publish.yml -f dryrun=true", }; } } ================================================ FILE: pkgs/create-neon/src/ci.ts ================================================ export interface CI { readonly type: string; templates(): Record; setup(): void; scripts(): Record; } ================================================ FILE: pkgs/create-neon/src/create/app.ts ================================================ import { Creator, ProjectOptions } from "./creator.js"; export class AppCreator extends Creator { constructor(options: ProjectOptions) { super(options); } scripts(): Record { return { test: "cargo test", "cargo-build": "cargo build --message-format=json-render-diagnostics > cargo.log", "cross-build": "cross build --message-format=json-render-diagnostics > cross.log", "postcargo-build": "neon dist < cargo.log", "postcross-build": "neon dist -m /target < cross.log", debug: "npm run cargo-build --", build: "npm run cargo-build -- --release", cross: "npm run cross-build -- --release", }; } } ================================================ FILE: pkgs/create-neon/src/create/creator.ts ================================================ import die from "../die.js"; import { mktemp } from "../fs.js"; import * as path from "node:path"; import * as fs from "node:fs/promises"; import { Context } from "../expand/context.js"; import { expand, expandTo } from "../expand/index.js"; import { npmInit } from "../shell.js"; import { Cache } from "../cache.js"; import { CI } from "../ci.js"; import { NodePlatform, PlatformPreset } from "@neon-rs/manifest/platform"; export enum Lang { JS = "js", DTS = "dts", TS = "ts", } export enum ModuleType { ESM = "esm", CJS = "cjs", } export type LibraryOptions = { lang: Lang; module: ModuleType; cache?: Cache; ci?: CI | undefined; platforms?: NodePlatform | PlatformPreset | (NodePlatform | PlatformPreset)[]; }; export type ProjectOptions = { org?: string | undefined; basename: string; fullName: string; version: string; library: LibraryOptions | null; app: boolean | null; cache?: Cache | undefined; ci?: CI | undefined; interactive: boolean; }; function stripNpmNamespace(pkg: string): string { return /^@[^/]+\/(?.*)/.exec(pkg)?.groups?.stripped || pkg; } export abstract class Creator { protected _options: ProjectOptions; protected _temp: string = ""; protected _tempPkg: string = ""; static async for(options: ProjectOptions): Promise { if (options.library) { const LibCreator = (await import("./lib.js")).LibCreator; return new LibCreator(options); } else { const AppCreator = (await import("./app.js")).AppCreator; return new AppCreator(options); } } constructor(options: ProjectOptions) { this._options = options; } async create(cx: Context): Promise { try { this._temp = await mktemp(); this._tempPkg = path.join(this._temp, this._options.basename); await fs.mkdir(this._tempPkg); } catch (err: any) { await die( `Could not create \`${this._options.basename}\`: ${err.message}`, this._temp ); } await this.prepare(cx); const manifest = await npmInit( cx.options.interactive, cx.options.interactive ? [] : ["--yes"], this._tempPkg, this._temp ); try { cx.package = { name: manifest.name, version: manifest.version, author: manifest.author, license: manifest.license, description: manifest.description, }; const crateName = stripNpmNamespace(manifest.name); cx.crate = { name: crateName, version: manifest.version, author: manifest.author, description: manifest.description, license: manifest.license, escaped: { name: JSON.stringify(crateName), version: JSON.stringify(manifest.version), author: manifest.author ? JSON.stringify(manifest.author) : undefined, description: manifest.description ? JSON.stringify(manifest.description) : undefined, license: manifest.license ? JSON.stringify(manifest.license) : undefined, }, }; } catch (err: any) { await die( "Could not create `package.json`: " + err.message, this._tempPkg ); } await this.createNeonBoilerplate(cx); try { await fs.rename(this._tempPkg, this._options.basename); await fs.rmdir(this._temp); } catch (err: any) { await die( `Could not create \`${this._options.basename}\`: ${err.message}`, this._tempPkg ); } } async createNeonBoilerplate(cx: Context): Promise { const templates = this.templates(cx.package!.name); for (const source of Object.keys(templates)) { const target = path.join(this._tempPkg, templates[source]); await expandTo(source, target, cx); } } // Write initial values to prevent `npm init` from asking unnecessary questions. async prepare(cx: Context): Promise { const template = `manifest/base/${this.baseTemplate()}`; const base = JSON.parse(await expand(template, cx)); base.scripts = this.scripts(); const filename = path.join(this._tempPkg, "package.json"); await fs.writeFile(filename, JSON.stringify(base)); } templates(_pkg: string): Record { return { ".gitignore.hbs": ".gitignore", "Cargo.toml.hbs": "Cargo.toml", "README.md.hbs": "README.md", "lib.rs.hbs": path.join("src", "lib.rs"), }; } scripts(): Record { return {}; } baseTemplate(): string { return "default.json.hbs"; } } ================================================ FILE: pkgs/create-neon/src/create/lib.ts ================================================ import { Creator, ProjectOptions, LibraryOptions, Lang } from "./creator.js"; import { Context } from "../expand/context.js"; import * as path from "node:path"; import { expandTo } from "../expand/index.js"; import { LibraryManifest } from "@neon-rs/manifest"; import { NodePlatform, PlatformPreset, isNodePlatform, } from "@neon-rs/manifest/platform"; const TS_TEMPLATES: Record = { "tsconfig.json.hbs": "tsconfig.json", "ts/index.cts.hbs": path.join("src", "index.cts"), "ts/index.mts.hbs": path.join("src", "index.mts"), "ts/load.cts.hbs": path.join("src", "load.cts"), }; export class LibCreator extends Creator { private _libOptions: LibraryOptions; constructor(options: ProjectOptions) { super(options); this._libOptions = options.library!; if (this._libOptions.ci) { this._libOptions.ci.setup(); } } templates(pkg: string): Record { return this._libOptions.lang === Lang.TS ? { ".gitignore.hbs": ".gitignore", "Cargo.toml.hbs": path.join("crates", pkg, "Cargo.toml"), "Workspace.toml.hbs": "Cargo.toml", "README.md.hbs": "README.md", "lib.rs.hbs": path.join("crates", pkg, "src", "lib.rs"), } : super.templates(pkg); } async createNeonBoilerplate(cx: Context): Promise { await super.createNeonBoilerplate(cx); if (this._libOptions.lang === Lang.TS) { await this.createTSBoilerplate(cx); } if (this._libOptions.ci) { await this.createCIBoilerplate(cx); } await this.addPlatforms(cx); } async createTSBoilerplate(cx: Context): Promise { for (const source of Object.keys(TS_TEMPLATES)) { const target = path.join(this._tempPkg, TS_TEMPLATES[source]); await expandTo(source, target, cx); } } async createCIBoilerplate(cx: Context): Promise { const templates = this._libOptions.ci!.templates(); for (const source of Object.keys(templates)) { const target = path.join(this._tempPkg, templates[source]); await expandTo(`ci/${this._libOptions.ci!.type}/${source}`, target, cx); } } async addPlatforms(cx: Context): Promise { const manifest = await LibraryManifest.load(this._tempPkg); const platforms: (NodePlatform | PlatformPreset)[] = Array.isArray( this._libOptions.platforms ) ? this._libOptions.platforms : !this._libOptions.platforms ? ["common"] : [this._libOptions.platforms]; for (const platform of platforms) { if (isNodePlatform(platform)) { await manifest.addNodePlatform(platform); } else { await manifest.addPlatformPreset(platform); } } await manifest.saveChanges((msg) => {}); } scripts(): Record { const tscAnd = this._libOptions.lang === Lang.TS ? "tsc &&" : ""; let scripts: Record = { test: `${tscAnd}cargo test`, "cargo-build": `${tscAnd}cargo build --message-format=json-render-diagnostics > cargo.log`, "cross-build": `${tscAnd}cross build --message-format=json-render-diagnostics > cross.log`, "postcargo-build": "neon dist < cargo.log", "postcross-build": "neon dist -m /target < cross.log", debug: "npm run cargo-build --", build: "npm run cargo-build -- --release", cross: "npm run cross-build -- --release", prepack: `${tscAnd}neon update`, version: "neon bump --binaries platforms && git add .", }; if (this._libOptions.ci) { Object.assign(scripts, this._libOptions.ci.scripts()); } return scripts; } baseTemplate(): string { return "library.json.hbs"; } } ================================================ FILE: pkgs/create-neon/src/die.ts ================================================ import { promises as fs } from "fs"; function deleteNeonDir(dir: string): Promise { return fs.rm(dir, { force: true, recursive: true }); } export default async function die( message: string, tmpFolderName?: string | undefined ): Promise { console.error(`❌ ${message}`); if (tmpFolderName) { await deleteNeonDir(tmpFolderName); } process.exit(1); } ================================================ FILE: pkgs/create-neon/src/expand/context.ts ================================================ import { ProjectOptions } from "../create/creator.js"; import { VERSIONS, Versions } from "./versions.js"; export type ManifestData = { name: string; version: string; description: string | undefined; author: string | undefined; license: string | undefined; }; type CrateData = ManifestData & { // The same manifest data but escaped as string literals // so they can be embedded in TOML. escaped: ManifestData; }; export class Context { options: ProjectOptions; package: ManifestData | undefined; crate: CrateData | undefined; versions: Versions; constructor(options: ProjectOptions) { this.options = options; this.package = undefined; this.crate = undefined; this.versions = VERSIONS; } } ================================================ FILE: pkgs/create-neon/src/expand/index.ts ================================================ import { promises as fs } from "fs"; import handlebars from "handlebars"; import helpers from "handlebars-helpers"; import * as path from "path"; import { Context } from "./context.js"; const TEMPLATES_DIR = new URL( path.join("..", "..", "data", "templates", "/"), import.meta.url ); const COMPARISON_HELPERS = helpers("comparison"); handlebars.registerHelper("eq", COMPARISON_HELPERS.eq); export async function expand(source: string, cx: Context): Promise { let template = await fs.readFile(new URL(source, TEMPLATES_DIR), "utf8"); let compiled = handlebars.compile(template, { noEscape: true }); return compiled(cx); } export async function expandTo(source: string, target: string, cx: Context) { await fs.mkdir(path.dirname(target), { recursive: true }); const expanded = await expand(source, cx); // The 'wx' flag creates the file but fails if it already exists. await fs.writeFile(target, expanded, { flag: "wx" }); } ================================================ FILE: pkgs/create-neon/src/expand/versions.ts ================================================ // This whole module is a bummer but was the best I could figure out since: // - Using @sindresorhus packages like 'chalk' and 'execa' forces a project to use Node's native ESM support. // - This means the tsconfig must generate a modern format like es2022. // - When generating ESM, TS doesn't support importing JSON files with static typing without import assertions. // - Import assertions are not yet stable in Node, and produce an instability warning. // // So for the time being, this module simply implements the static typing explicitly. // If and when TS adds back and way to infer the static types when importing a JSON file // and generates a stable format that Node doesn't complain about, we can eliminate this // boilerplate wrapper module. import { createRequire } from "module"; export type Versions = { neon: string; neonCLI: string; neonLoad: string; typescript: string; typesNode: string; tsconfigNode: { major: string; semver: string; module: string; }; node: string; actions: { checkout: string; githubScript: string; setupNode: string; setupRust: string; cargoInstall: string; neonBuild: string; neonPublish: string; dotenv: string; ghRelease: string; releaseDownloader: string; }; }; const KEYS = [ "neon", "neonCLI", "neonLoad", "typescript", "typesNode", "tsconfigNode", "node", "actions", ]; function assertIsVersions(data: unknown): asserts data is Versions { if (!data || typeof data !== "object") { throw new TypeError("expected object"); } KEYS.forEach((key) => { if (!(key in data)) { throw new TypeError(`require '${key}' property not found`); } }); } const dynamicRequire = createRequire(import.meta.url); function load(): Versions { const data = dynamicRequire("../../data/versions.json"); assertIsVersions(data); return data; } export const VERSIONS: Versions = load(); ================================================ FILE: pkgs/create-neon/src/fs.ts ================================================ import * as fs from "node:fs/promises"; import * as path from "node:path"; import { existsSync, rmSync } from "node:fs"; export async function assertCanMkdir(dir: string) { // pretty lightweight way to check both that folder doesn't exist and // that the user has write permissions. await fs.mkdir(dir); await fs.rmdir(dir); } export async function mktemp(): Promise { const tmpFolderName = await fs.mkdtemp("neon-"); const tmpFolderAbsPath = path.join(process.cwd(), tmpFolderName); function cleanupTmp() { try { if (existsSync(tmpFolderAbsPath)) { rmSync(tmpFolderAbsPath, { recursive: true }); } } catch (e) { console.error(`warning: could not delete ${tmpFolderName}: ${e}`); } } process.on("exit", cleanupTmp); process.on("SIGINT", cleanupTmp); process.on("uncaughtException", cleanupTmp); return tmpFolderName; } ================================================ FILE: pkgs/create-neon/src/index.ts ================================================ import { Context } from "./expand/context.js"; import { NodePlatform, PlatformPreset, isNodePlatform, isPlatformPreset, } from "@neon-rs/manifest/platform"; import { Dialog, oneOf } from "./shell.js"; import { NPM } from "./cache/npm.js"; import { GitHub } from "./ci/github.js"; import { Creator, ProjectOptions, Lang, ModuleType } from "./create/creator.js"; import { assertCanMkdir } from "./fs.js"; import die from "./die.js"; const CREATE_NEON_PRELUDE: string = ` This utility will walk you through creating a Neon project. It only covers the most common items, and tries to guess sensible defaults. Use \`npm install \` afterwards to install a package and save it as a dependency in the package.json file. Use \`npm run build\` to build the Neon project from source. Press ^C at any time to quit. `.trim(); async function askProjectType(options: ProjectOptions) { const dialog = new Dialog(); const ty = await dialog.ask({ prompt: "project type", parse: oneOf({ app: "app" as const, lib: "lib" as const }), default: "app" as const, error: 'type should be a valid Neon project type ("app" or "lib").', }); if (ty === "lib") { const platforms: (NodePlatform | PlatformPreset)[] = await dialog.ask({ prompt: "target platforms", parse: (v: string): (NodePlatform | PlatformPreset)[] => { const a = v.split(",").map((s) => s.trim()); if (a.some((elt) => !isNodePlatform(elt) && !isPlatformPreset(elt))) { throw new Error("parse error"); } return a as (NodePlatform | PlatformPreset)[]; }, default: ["common"], error: "platforms should be a comma-separated list of platforms or platform presets.", }); const cache = await dialog.ask({ prompt: "binary cache", parse: oneOf({ npm: "npm" as const, none: undefined }), default: "npm" as const, error: 'cache should be a supported Neon binary cache type ("npm" or "none").', }); const org = cache === "npm" ? ( await dialog.ask({ prompt: "cache org", parse: (v: string): string => v, default: options.org ?? `@${options.basename}`, }) ).replace(/^@?/, "@") // don't care if they include the @ or not : null; const prefix = cache === "npm" && org === `@${options.basename}` ? "" : cache === "npm" ? await dialog.ask({ prompt: "cache prefix", parse: (v: string): string => v, default: `${options.basename}-`, }) : null; const ci = await dialog.ask({ prompt: "ci provider", parse: oneOf({ npm: "github" as const, none: undefined }), default: "github" as const, error: 'provider should be a supported Neon CI provider ("github" or "none").', }); options.library = { lang: Lang.TS, module: ModuleType.ESM, cache: cache === "npm" ? new NPM(org!, prefix!) : undefined, ci: ci === "github" ? new GitHub() : undefined, platforms: platforms.length === 1 ? platforms[0] : platforms, }; } else { options.app = true; } dialog.end(); } export async function createNeon(options: ProjectOptions): Promise { try { await assertCanMkdir(options.basename); } catch (err: any) { await die(`Could not create \`${options.basename}\`: ${err.message}`); } const cx = new Context(options); // Print a Neon variation of the `npm init` prelude text. if (options.interactive) { console.log(CREATE_NEON_PRELUDE); } // If neither --lib nor --app was specified, find out. if (options.library === null && options.app === null) { if (options.interactive) { await askProjectType(options); } else { options.app = true; } } const creator = await Creator.for(options); await creator.create(cx); console.log( `✨ Created Neon project \`${options.fullName}\`. Happy 🦀 hacking! ✨` ); } ================================================ FILE: pkgs/create-neon/src/print.ts ================================================ import commandLineUsage from "command-line-usage"; import chalk from "chalk"; function pink(text: string): string { return chalk.bold.hex("#e75480")(text); } function green(text: string): string { return chalk.bold.greenBright(text); } function blue(text: string): string { return chalk.bold.cyanBright(text); } function yellow(text: string): string { return chalk.bold.yellowBright(text); } function bold(text: string): string { return chalk.bold(text); } function mainUsage(): string { const sections = [ { content: `✨ ${pink( "create-neon:" )} create a new Neon project with zero configuration ✨`, raw: true, }, { header: green("Examples:"), content: [ `${blue("$")} ${bold("npm init neon my-package")}`, "", "Create a Neon project `my-package`.", "", `${blue("$")} ${bold("npm init neon --lib my-lib")}`, "", "Create a Neon library `my-lib`, pre-configured to publish pre-builds for common Node target platforms as binary packages under the `@my-lib` org. The generated project includes GitHub CI/CD actions for testing and publishing.", "", `${blue("$")} ${bold( "npm init neon --lib my-library --target desktop" )}`, "", "Similar but configured to target just common Node desktop platforms.", ], }, { header: blue("Usage:"), content: `${blue( "$" )} npm init neon [--lib] [--bins ] [--ci ] [--target ]* `, }, { header: yellow("Options:"), content: [ { name: "--lib", summary: "Configure package as a library. (Implied defaults: `--bins npm` and `--ci github`)", }, { name: "--bins npm[:@]", summary: "Configure for pre-built binaries published to npm. (Default org: )", }, { name: "--bins none", summary: "Do not configure for pre-built binaries. (Default)", }, { name: "--target ", summary: "May be used to specify one or more targets for pre-built binaries. (Default: common)", }, { name: "--ci github", summary: "Generate CI/CD configuration for GitHub Actions. (Default)", }, { name: "--ci none", summary: "Do not generate CI/CD configuration." }, { name: "", summary: "Package name." }, ], }, ]; return commandLineUsage(sections).trim(); } export function printMainUsage() { console.error(mainUsage()); } export function printErrorWithUsage(e: any) { console.error(mainUsage()); console.error(); printError(e); } export function printError(e: any) { console.error( chalk.bold.red("error:") + " " + (e instanceof Error ? e.message : String(e)) ); } ================================================ FILE: pkgs/create-neon/src/shell.ts ================================================ import { ChildProcess, spawn } from "node:child_process"; import { PassThrough, Readable, Writable } from "node:stream"; import { StringDecoder } from "node:string_decoder"; import readline from "node:readline/promises"; import * as path from "node:path"; import * as fs from "node:fs/promises"; export function readChunks(input: Readable): Readable { let output = new PassThrough({ objectMode: true }); // Raise the default limit on listeners to avoid warnings. output.setMaxListeners(20); let decoder = new StringDecoder("utf8"); input.on("data", (data) => { output.write(decoder.write(data)); }); input.on("close", () => { output.write(decoder.end()); output.end(); }); return output; } type NpmInitExit = { code: number | null; signal: string | null; }; // A child process representing a modified `npm init` invocation: // - If interactive, the initial prelude of stdout text is suppressed // so we can present a modified prelude for create-neon. // - The process is being run in a temp subdirectory, so any output that // includes the temp directory in a path is transformed to remove it. class NpmInitProcess { private _regexp: RegExp; private _child: ChildProcess; constructor(interactive: boolean, args: string[], cwd: string, tmp: string) { this._regexp = new RegExp(tmp + "."); this._child = spawn("npm", ["init", ...args], { stdio: ["inherit", "pipe", "inherit"], shell: true, cwd, }); this.filterStdout({ interactive }).then(() => {}); } exit(): Promise { let resolve: (exit: NpmInitExit) => void; const result: Promise = new Promise((res) => { resolve = res; }); this._child.on("exit", (code, signal) => { resolve({ code, signal }); }); return result; } async filterStdout(opts: { interactive: boolean }) { // We'll suppress the `npm init` interactive prelude text, // in favor of printing our own create-neon version of the text. let inPrelude = opts.interactive; for await (const chunk of readChunks(this._child.stdout!)) { const lines = (chunk as string).split(/\r?\n/); if (opts.interactive && inPrelude) { // If there's a prompt, it'll be at the end of the data chunk // since npm init will have flushed stdout to block on stdin. if (!lines[lines.length - 1].match(/^[a-z ]+:/)) { // We're still in the prelude so suppress all the lines and // wait for the next chunk of stdout data. continue; } // Suppress the prelude lines up to the prompt. lines.splice(0, lines.length - 1); inPrelude = false; } // Print out all the lines. lines.forEach((line, i) => { // Remove the temp dir from any paths printed out by `npm init`. process.stdout.write(line.replace(this._regexp, "")); if (i < lines.length - 1) { process.stdout.write("\n"); } }); } } } // Standard order of package.json keys generated by `npm init`. const NPM_INIT_KEYS = [ "name", "version", "description", "main", "scripts", "author", "license", ]; function sort(json: any): any { // First copy the keys in the order specified in NPM_INIT_KEYS. let next = NPM_INIT_KEYS.filter((key) => json.hasOwnProperty(key)) .map((key) => [key, json[key]]) .reduce((acc, [key, val]) => Object.assign(acc, { [key]: val }), {}); // Then copy any remaining keys in the original order. return Object.assign(next, json); } export async function npmInit( interactive: boolean, args: string[], cwd: string, tmp: string ): Promise { const child = new NpmInitProcess(interactive, args, cwd, tmp); const { code, signal } = await child.exit(); if (code === null) { process.kill(process.pid, signal!); } else if (code !== 0) { process.exit(code); } const filename = path.join(cwd, "package.json"); const sorted = sort(JSON.parse(await fs.readFile(filename, "utf8"))); await fs.writeFile(filename, JSON.stringify(sorted, undefined, 2) + "\n"); return sorted; } export type Parser = (v: string) => T; export function oneOf(opts: T): Parser { return (v: string) => { for (const key in opts) { if (v === key) { return opts[key]; } } throw new Error("parse error"); }; } export interface Question { prompt: string; parse: Parser; default: T; error?: string; } export class Dialog { private _rl: readline.Interface | undefined; constructor() { this._rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); } private rl(): readline.Interface { if (!this._rl) { throw new Error("dialog already ended"); } return this._rl; } end() { this.rl().close(); this._rl = undefined; } async ask(opts: Question): Promise { while (true) { try { const answer = ( await this.rl().question( `neon ${opts.prompt}: (${String(opts.default)}) ` ) ).trim(); return answer === "" ? opts.default : opts.parse(answer); } catch (_ignored) { if (opts.error) { console.log(`Sorry, ${opts.error}`); } } } } } ================================================ FILE: pkgs/create-neon/test/create-neon.ts ================================================ import { assert } from "chai"; import { spawn } from "child_process"; import * as path from "path"; import { promises as fs } from "fs"; import * as TOML from "toml"; import expect from "../dev/expect.js"; import { execa } from "execa"; import { fileURLToPath } from "url"; const NODE: string = process.execPath; const CREATE_NEON = fileURLToPath( new URL(path.join("..", "src", "bin", "create-neon.js"), import.meta.url) ); describe("Command-line argument validation", () => { it("requires an argument", async () => { try { await execa(NODE, [CREATE_NEON]); assert.fail("should fail when no argument is supplied"); } catch (expected) { assert.isTrue(true); } }); it("fails if the directory already exists", async () => { try { await execa(NODE, [CREATE_NEON, "src"]); assert.fail("should fail when directory exists"); } catch (expected) { assert.isTrue(true); } }); }); const PROJECT = "create-neon-test-project"; const NAMESPACED_PROJECT = "@dherman/create-neon-test-project"; describe("Project creation", () => { afterEach(async () => { await fs.rm(PROJECT, { recursive: true, maxRetries: 3 }); }); it("succeeds with --yes", async () => { try { await execa(NODE, [CREATE_NEON, "--yes", PROJECT]); } catch (error: any) { assert.fail("create-neon unexpectedly failed: " + error.message); } }); it("succeeds with all default answers", async () => { try { await expect(spawn(NODE, [CREATE_NEON, "--app", PROJECT]), { "package name:": "", "version:": "", "description:": "", "git repository:": "", "keywords:": "", "author:": "", "license:": "", "?type": "", "Is this OK?": "", }); } catch (error: any) { assert.fail("create-neon unexpectedly failed: " + error.message); } let json = JSON.parse( await fs.readFile(path.join(PROJECT, "package.json"), { encoding: "utf8", }) ); assert.strictEqual(json.name, PROJECT); assert.strictEqual(json.main, "index.node"); assert.strictEqual(json.version, "0.1.0"); assert.strictEqual(json.scripts.test, "cargo test"); assert.isString(json.license); assert.strictEqual(json.description, ""); assert.strictEqual(json.author, ""); let toml = TOML.parse( await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) ); assert.strictEqual(toml.package.name, PROJECT); assert.strictEqual(toml.package.version, "0.1.0"); assert.strictEqual(toml.package.license, json.license); assert.deepEqual(toml.lib["crate-type"], ["cdylib"]); }); it("handles quotation marks in author and description", async () => { try { await expect(spawn(NODE, [CREATE_NEON, "--app", PROJECT]), { "package name:": "", "version:": "", "description:": 'the "hello world" of examples', "git repository:": "", "keywords:": "", "author:": '"Dave Herman" ', "license:": "", "?type": "", "Is this OK?": "", }); } catch (error) { assert.fail("create-neon unexpectedly failed"); } let json = JSON.parse( await fs.readFile(path.join(PROJECT, "package.json"), { encoding: "utf8", }) ); assert.strictEqual(json.description, 'the "hello world" of examples'); assert.strictEqual(json.author, '"Dave Herman" '); let toml = TOML.parse( await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) ); assert.strictEqual( toml.package.description, 'the "hello world" of examples' ); assert.deepEqual(toml.package.authors, [ '"Dave Herman" ', ]); }); it("asks Neon project type if not specified", async () => { try { await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { "neon project type": "", "package name:": "", "version:": "", "description:": "", "git repository:": "", "keywords:": "", "author:": "", "license:": "", "?type": "", "Is this OK?": "", }); } catch (error: any) { assert.fail("create-neon unexpectedly failed: " + error.message); } JSON.parse( await fs.readFile(path.join(PROJECT, "package.json"), { encoding: "utf8", }) ); TOML.parse( await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) ); }); it("asks Neon lib questions interactively", async () => { try { await expect(spawn(NODE, [CREATE_NEON, PROJECT]), { "neon project type": "lib", "neon target platforms": "", "neon binary cache": "", "neon cache org": "", "neon ci provider": "", "package name:": "", "version:": "", "description:": "", "git repository:": "", "keywords:": "", "author:": "", "license:": "", "?type": "", "Is this OK?": "", }); } catch (error: any) { assert.fail("create-neon unexpectedly failed: " + error.message); } let json = JSON.parse( await fs.readFile(path.join(PROJECT, "package.json"), { encoding: "utf8", }) ); assert.strictEqual(json.neon.type, "library"); assert.strictEqual(json.neon.org, "@create-neon-test-project"); assert.notProperty(json.neon, "prefix"); assert.strictEqual(json.neon.platforms, "common"); assert.strictEqual(json.neon.load, "./src/load.cts"); TOML.parse( await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) ); }); it("asks for a prefix when Neon lib is namespaced", async () => { try { await expect(spawn(NODE, [CREATE_NEON, NAMESPACED_PROJECT]), { "neon project type": "lib", "neon target platforms": "", "neon binary cache": "", "neon cache org": "", "neon cache prefix": "", "neon ci provider": "", "package name:": "", "version:": "", "description:": "", "git repository:": "", "keywords:": "", "author:": "", "license:": "", "?type": "", "Is this OK?": "", }); } catch (error: any) { assert.fail("create-neon unexpectedly failed: " + error.message); } let json = JSON.parse( await fs.readFile(path.join(PROJECT, "package.json"), { encoding: "utf8", }) ); assert.strictEqual(json.neon.type, "library"); assert.strictEqual(json.neon.org, "@dherman"); assert.strictEqual(json.neon.prefix, "create-neon-test-project-"); assert.strictEqual(json.neon.platforms, "common"); assert.strictEqual(json.neon.load, "./src/load.cts"); TOML.parse( await fs.readFile(path.join(PROJECT, "Cargo.toml"), { encoding: "utf8" }) ); }); }); ================================================ FILE: pkgs/create-neon/tsconfig.json ================================================ { "extends": "@tsconfig/node18/tsconfig.json", "compilerOptions": { "module": "es2022", "moduleResolution": "bundler", "outDir": "dist", "noImplicitAny": true, "resolveJsonModule": true }, "include": ["src/**/*", "dev/**/*", "test/**/*"], "exclude": ["./node_modules/", "./dist/"] } ================================================ FILE: test/electron/Cargo.toml ================================================ [package] name = "electron-tests" version = "0.1.0" edition = "2021" authors = ["The Neon Community"] license = "MIT/Apache-2.0" [lib] crate-type = ["cdylib"] [dependencies.neon] path = "../../crates/neon" ================================================ FILE: test/electron/README.md ================================================ # neon-electron-quick-start This is a minimal Electron application based on the [Quick Start Guide](https://electronjs.org/docs/tutorial/quick-start) within the Electron documentation combined with [Neon](https://neon-bindings.com). ================================================ FILE: test/electron/index.html ================================================ Neon Electron Test

================================================ FILE: test/electron/main.js ================================================ // Modules to control application life and create native browser window const { app, BrowserWindow } = require("electron"); const path = require("path"); function createWindow() { // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { contextIsolation: true, preload: path.join(__dirname, "preload.js"), nodeIntegration: true, }, }); // and load the index.html of the app. mainWindow.loadFile("index.html"); // Open the DevTools. // mainWindow.webContents.openDevTools() } // This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.whenReady().then(() => { createWindow(); app.on("activate", function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); // Quit when all windows are closed app.on("window-all-closed", function () { app.quit(); }); ================================================ FILE: test/electron/main.test.js ================================================ "use strict"; const assert = require("assert"); const path = require("path"); const { _electron: electron } = require("playwright"); const { test } = require("@playwright/test"); test("greeting", async () => { const app = await electron.launch({ args: [path.join(__dirname, "main.js")], }); const page = await app.firstWindow(); const header = page.locator("#greeting"); const text = await header.textContent(); assert.strictEqual(text, "Hello, World!"); }); ================================================ FILE: test/electron/package.json ================================================ { "name": "electron-tests", "version": "0.1.0", "description": "Electron acceptance test suite for Neon", "main": "main.js", "author": "The Neon Community", "license": "MIT", "scripts": { "install": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics", "start": "electron .", "test": "playwright test" }, "repository": "https://github.com/electron/electron-quick-start", "devDependencies": { "@playwright/test": "^1.40.1", "cargo-cp-artifact": "^0.1.9", "electron": "^27.1.3", "playwright": "^1.40.1" } } ================================================ FILE: test/electron/preload.js ================================================ const native = require("./index.node"); window.addEventListener("DOMContentLoaded", () => { document.getElementById("greeting").innerText = native.hello(); }); ================================================ FILE: test/electron/renderer.js ================================================ // This file is required by the index.html file and will // be executed in the renderer process for that window. // No Node.js APIs are available in this process because // `nodeIntegration` is turned off. Use `preload.js` to // selectively enable features needed in the rendering // process. ================================================ FILE: test/electron/src/lib.rs ================================================ use neon::prelude::*; fn hello(mut cx: FunctionContext) -> JsResult { Ok(cx.string("Hello, World!")) } #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("hello", hello)?; Ok(()) } ================================================ FILE: test/napi/.gitignore ================================================ native/target native/index.node native/artifacts.json **/*~ **/node_modules **/.DS_Store ================================================ FILE: test/napi/Cargo.toml ================================================ [package] name = "napi-tests" version = "0.1.0" authors = ["The Neon Community "] license = "MIT" exclude = ["artifacts.json", "index.node"] edition = "2021" [lib] crate-type = ["cdylib"] [dependencies] either = "1.13.0" num-bigint-dig = "0.9.1" once_cell = "1.18.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.34.0", features = ["rt-multi-thread"] } [dependencies.neon] path = "../../crates/neon" features = ["futures", "napi-experimental", "external-buffers", "serde", "tokio"] ================================================ FILE: test/napi/README.md ================================================ # napi Acceptance test suite for Neon with N-API backend ================================================ FILE: test/napi/lib/arrays.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("JsArray", function () { it("return a JsArray built in Rust", function () { assert.deepEqual([], addon.return_js_array()); }); it("return a JsArray with a number at index 0", function () { assert.deepEqual([9000], addon.return_js_array_with_number()); }); it("return a JsArray with an string at index 0", function () { assert.deepEqual(["hello node"], addon.return_js_array_with_string()); }); it("can read from a JsArray", function () { assert.strictEqual(addon.read_js_array([1234]), 1234); }); it("returns undefined when accessing outside JsArray bounds", function () { assert.strictEqual(addon.read_js_array([]), undefined); }); }); ================================================ FILE: test/napi/lib/bigint.js ================================================ const addon = require(".."); describe("JsBigInt", () => { const suite = addon.bigint_suite(); for (const [k, v] of Object.entries(suite)) { it(k, v); } }); ================================================ FILE: test/napi/lib/boxed.js ================================================ const addon = require(".."); const { expect } = require("chai"); const assert = require("chai").assert; class Person { constructor(name) { this._person = addon.person_new(name); } greet() { return addon.person_greet(this._person); } } class RefPerson { constructor(name) { this._person = addon.ref_person_new(name); } greet() { return addon.ref_person_greet(this._person); } setName(name) { addon.ref_person_set_name(this._person, name); return this; } fail() { addon.ref_person_fail(this._person); } } describe("boxed", function () { it("can call methods", function () { const person = new Person("World"); const greeting = person.greet(); assert.strictEqual(greeting, "Hello, World!"); }); it("can call methods wrapped in a RefCell", function () { const person = new RefPerson("World"); const greeting = person.greet(); assert.strictEqual(greeting, "Hello, World!"); }); it("can mutate with ref cell", function () { const person = new RefPerson("World").setName("Universe"); const greeting = person.greet(); assert.strictEqual(greeting, "Hello, Universe!"); }); it("should dynamically check borrowing rules", function () { assert.throws(() => new RefPerson("World").fail(), /already borrowed/); }); it("should type check externals", function () { // `any::type_name` does not guarantee exact format // failed downcast to neon::types::boxed::JsBox assert.throws( () => addon.person_greet({}), /failed to downcast.*JsBox.*Person/ ); }); it("should type check dynamic type", function () { const unit = addon.external_unit(); assert.throws(() => addon.person_greet(unit), /failed to downcast/); }); }); ================================================ FILE: test/napi/lib/class.js ================================================ const addon = require(".."); const assert = require("chai").assert; describe("wrapping", function () { it("should be able to wrap a Rust value in an object", () => { const msg = "Hello, World!"; const o = {}; addon.wrapString(o, msg); assert.strictEqual(addon.unwrapString(o), msg); }); it("should not be able to wrap an object twice", () => { const o = {}; addon.wrapString(o, "Hello, World!"); assert.throws( () => addon.wrapString(o, "nope"), /non-class instance expected/ ); }); it("should not be able to unwrap an object that was not wrapped", () => { const o = {}; assert.throws(() => addon.unwrapString(o), /class instance expected/); }); }); describe("classes", function () { it("can create a Message class", function () { const Message = addon.Message; const message = new Message("Hello, Neon, this is your test speaking."); assert.instanceOf(message, Message); assert.strictEqual( message.read(), "Hello, Neon, this is your test speaking." ); const message2 = message.concat(new Message(" <>")); assert.strictEqual( message2.read(), "Hello, Neon, this is your test speaking. <>" ); }); it("class methods have normal-looking function names", function () { const util = require("util"); const Message = addon.Message; const message = new Message("test"); const StringBuffer = addon.StringBuffer; const buffer = new StringBuffer(); assert.strictEqual(message.read.name, "read"); assert.strictEqual(message.append.name, "append"); assert.strictEqual(message.concat.name, "concat"); assert.strictEqual(buffer.includes.name, "includes"); assert.strictEqual(buffer.trimStart.name, "trimStart"); assert.strictEqual(buffer.trimEnd.name, "trimEnd"); assert.strictEqual(util.inspect(message.read), "[Function: read]"); assert.strictEqual(util.inspect(message.append), "[Function: append]"); assert.strictEqual(util.inspect(message.concat), "[Function: concat]"); assert.strictEqual(util.inspect(buffer.includes), "[Function: includes]"); assert.strictEqual(util.inspect(buffer.trimStart), "[Function: trimStart]"); assert.strictEqual(util.inspect(buffer.trimEnd), "[Function: trimEnd]"); assert.strictEqual( message.read.toString(), "function read() { [native code] }" ); assert.strictEqual( message.append.toString(), "function append() { [native code] }" ); assert.strictEqual( message.concat.toString(), "function concat() { [native code] }" ); assert.strictEqual( "[object Function]", Object.prototype.toString.call(message.read) ); assert.strictEqual( "[object Function]", Object.prototype.toString.call(message.append) ); assert.strictEqual( "[object Function]", Object.prototype.toString.call(message.concat) ); assert.strictEqual( buffer.includes.toString(), "function includes() { [native code] }" ); assert.strictEqual( buffer.trimStart.toString(), "function trimStart() { [native code] }" ); assert.strictEqual( buffer.trimEnd.toString(), "function trimEnd() { [native code] }" ); }); it("can mutate a Message with &mut self", function () { const Message = addon.Message; const message = new Message("Hello"); assert.strictEqual(message.read(), "Hello"); message.append(", World"); assert.strictEqual(message.read(), "Hello, World"); message.append("!"); assert.strictEqual(message.read(), "Hello, World!"); }); it("can subclass a Neon class", function () { const Message = addon.Message; class LoudMessage extends Message { shout() { return this.read().toUpperCase(); } } const message = new LoudMessage("Hello, Neon, this is your test speaking."); assert.instanceOf(message, Message); assert.instanceOf(message, LoudMessage); assert.strictEqual( message.read(), "Hello, Neon, this is your test speaking." ); assert.strictEqual( message.shout(), "HELLO, NEON, THIS IS YOUR TEST SPEAKING." ); const message2 = message.concat(new Message(" <>")); assert.strictEqual( message2.read(), "Hello, Neon, this is your test speaking. <>" ); assert.throws(() => message2.shout()); }); it("can create a Point class", function () { const Point = addon.Point; const point = new Point(1, 2); assert.instanceOf(point, Point); assert.strictEqual(point.x(), 1); assert.strictEqual(point.y(), 2); const point2 = new Point(3, 4); assert.instanceOf(point2, Point); assert.strictEqual(point2.x(), 3); assert.strictEqual(point2.y(), 4); assert.strictEqual(point.distance(point2), Math.sqrt(8)); }); it("fails with a TypeError when passing a non-object as &Point argument", function () { const Point = addon.Point; const point = new Point(1, 2); assert.instanceOf(point, Point); assert.strictEqual(point.x(), 1); assert.strictEqual(point.y(), 2); assert.throws(() => { point.distance(42); }, TypeError); }); it("can mutate a Point with &mut self", function () { const Point = addon.Point; const point = new Point(10, 20); assert.strictEqual(point.x(), 10); assert.strictEqual(point.y(), 20); point.moveBy(5, 3); assert.strictEqual(point.x(), 15); assert.strictEqual(point.y(), 23); point.setX(100); assert.strictEqual(point.x(), 100); assert.strictEqual(point.y(), 23); point.setY(200); assert.strictEqual(point.x(), 100); assert.strictEqual(point.y(), 200); }); it("can swap coordinates between two Points using &mut references", function () { const Point = addon.Point; const p1 = new Point(10, 20); const p2 = new Point(30, 40); assert.strictEqual(p1.x(), 10); assert.strictEqual(p1.y(), 20); assert.strictEqual(p2.x(), 30); assert.strictEqual(p2.y(), 40); p1.swapCoords(p2); assert.strictEqual(p1.x(), 30); assert.strictEqual(p1.y(), 40); assert.strictEqual(p2.x(), 10); assert.strictEqual(p2.y(), 20); }); it("fails with a TypeError when passing a non-object as &mut Point argument", function () { const Point = addon.Point; const point = new Point(1, 2); assert.instanceOf(point, Point); assert.strictEqual(point.x(), 1); assert.strictEqual(point.y(), 2); assert.throws(() => { point.swapCoords(42); }, TypeError); }); it("fails with TypeError when passing wrong type to Message.concat", function () { const Message = addon.Message; const Point = addon.Point; const message = new Message("Hello"); const point = new Point(1, 2); // Test with various wrong types assert.throws( () => message.concat(null), TypeError, /expected object/, "should reject null" ); assert.throws( () => message.concat(undefined), TypeError, /expected object/, "should reject undefined" ); assert.throws( () => message.concat("string"), TypeError, /expected object/, "should reject string" ); assert.throws( () => message.concat(42), TypeError, /expected object/, "should reject number" ); assert.throws( () => message.concat({ value: "test" }), TypeError, /class instance expected/, "should reject plain object" ); assert.throws( () => message.concat([]), TypeError, /class instance expected/, "should reject array" ); assert.throws( () => message.concat(point), TypeError, /expected instance of.*Message/, "should reject instance of different class" ); }); it("fails with TypeError when passing wrong type to Point.midpoint", function () { const Point = addon.Point; const point = new Point(5, 10); // Test with various wrong types assert.throws( () => point.midpoint(null), TypeError, /expected object/, "should reject null" ); assert.throws( () => point.midpoint(undefined), TypeError, /expected object/, "should reject undefined" ); assert.throws( () => point.midpoint("string"), TypeError, /expected object/, "should reject string" ); assert.throws( () => point.midpoint(123), TypeError, /expected object/, "should reject number" ); assert.throws( () => point.midpoint({ x: 1, y: 2 }), TypeError, /class instance expected/, "should reject plain object" ); }); it("fails with TypeError when mixing different class types", function () { const Point = addon.Point; const Message = addon.Message; const point = new Point(1, 2); const message = new Message("test"); // Try to pass a Message where a Point is expected try { point.distance(message); assert.fail("should have thrown an error"); } catch (e) { assert.instanceOf(e, TypeError); assert.match(e.message, /expected instance of.*Point/); // Uncomment to see the actual error message: // console.log("Point error:", e.message); } // Try to pass a Point where a Message is expected try { message.concat(point); assert.fail("should have thrown an error"); } catch (e) { assert.instanceOf(e, TypeError); assert.match(e.message, /expected instance of.*Message/); // Uncomment to see the actual error message: // console.log("Message error:", e.message); } }); it("Point class has const properties", function () { const Point = addon.Point; // Test basic const properties assert.strictEqual(Point.ORIGIN_X, 0); assert.strictEqual(Point.ORIGIN_Y, 0); // Test const property with custom name assert.strictEqual(Point.maxCoordinate, 1000); // Test const property with JSON serialization assert.deepEqual(Point.DEFAULT_MESSAGE, ["hello", "point"]); }); it("Point const properties are immutable", function () { const Point = addon.Point; // Store original values const originalX = Point.ORIGIN_X; const originalMaxCoord = Point.maxCoordinate; // Attempt to modify properties (should silently fail in non-strict mode) Point.ORIGIN_X = 999; Point.maxCoordinate = 5000; // Values should be unchanged assert.strictEqual(Point.ORIGIN_X, originalX); assert.strictEqual(Point.maxCoordinate, originalMaxCoord); // Check property descriptors const descX = Object.getOwnPropertyDescriptor(Point, "ORIGIN_X"); assert.strictEqual(descX.writable, false); assert.strictEqual(descX.configurable, false); assert.strictEqual(descX.enumerable, true); const descMaxCoord = Object.getOwnPropertyDescriptor( Point, "maxCoordinate" ); assert.strictEqual(descMaxCoord.writable, false); assert.strictEqual(descMaxCoord.configurable, false); assert.strictEqual(descMaxCoord.enumerable, true); }); it("Point supports complex const expressions", function () { const Point = addon.Point; // Test computed const expressions assert.strictEqual(Point.COMPUTED_VALUE, 42); // 10 + 20 + 12 assert.strictEqual(Point.SIZE_OF_F64, 8); // std::mem::size_of::() assert.strictEqual(Point.STRING_LENGTH, 7); // "complex".len() // Verify they're immutable const original = Point.COMPUTED_VALUE; Point.COMPUTED_VALUE = 999; assert.strictEqual(Point.COMPUTED_VALUE, original); }); it("Point supports edge case const expressions", function () { const Point = addon.Point; // Test boolean const assert.strictEqual(Point.IS_2D, true); assert.strictEqual(typeof Point.IS_2D, "boolean"); // Test conditional const expression assert.strictEqual(Point.MAX_DIMENSION, 2147483647); // Test match expression const assert.strictEqual(Point.COORDINATE_BYTES, 4); // Test const with arithmetic assert.strictEqual(Point.DOUBLE_100_SQUARED, 20000); // 100^2 * 2 // Test string with special characters (renamed property) assert.strictEqual(Point.specialString, 'Hello\nWorld\t"quoted"\r\n'); // Test negative number assert.strictEqual(Point.NEGATIVE_OFFSET, -42); // Test large integer (approximate) assert.strictEqual(Point.MAX_SAFE_INTEGER_APPROX, 2147483647); // Test const starting with underscore assert.strictEqual(Point._PRIVATE_CONST, 999); // Verify all edge case properties are immutable const props = [ "IS_2D", "MAX_DIMENSION", "COORDINATE_BYTES", "DOUBLE_100_SQUARED", "specialString", "NEGATIVE_OFFSET", "MAX_SAFE_INTEGER_APPROX", "_PRIVATE_CONST", ]; props.forEach((prop) => { const descriptor = Object.getOwnPropertyDescriptor(Point, prop); assert.strictEqual( descriptor.writable, false, `${prop} should not be writable` ); assert.strictEqual( descriptor.configurable, false, `${prop} should not be configurable` ); assert.strictEqual( descriptor.enumerable, true, `${prop} should be enumerable` ); }); }); it("can create a StringBuffer class with Default constructor", function () { const StringBuffer = addon.StringBuffer; const buffer = new StringBuffer(); buffer.push("Hello"); buffer.push(" "); buffer.push("World"); assert.strictEqual(buffer.toString(), "Hello World"); }); it("can use a renamed method in StringBuffer", function () { const StringBuffer = addon.StringBuffer; const buffer = new StringBuffer(); buffer.push(" Hello "); assert.strictEqual(buffer.trimStart(), "Hello "); assert.strictEqual(buffer.trimEnd(), " Hello"); assert.isTrue(buffer.includes("Hello")); assert.isFalse(buffer.includes("World")); }); // async tests it("AsyncClass should create instance", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("hello"); assert.ok(instance instanceof AsyncClass); }); it("AsyncClass should have sync method", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("hello"); assert.strictEqual(instance.syncMethod(), "hello"); }); it("AsyncClass should have async method", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("hello"); const result = await instance.asyncMethod(" world"); assert.strictEqual(result, "hello world"); }); it("AsyncClass should have task method for CPU-intensive work", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = await instance.heavyComputation(); // Sum of 0..99 = (99 * 100) / 2 = 4950 assert.strictEqual(result, 4950); }); it("AsyncClass should have JSON method", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const input = ["item1", "item2", "item3"]; const result = instance.jsonMethod(input); assert.strictEqual(typeof result, "object"); assert.strictEqual(result.class_value, "test"); assert.strictEqual(result.input_count, "3"); assert.strictEqual(result.first_item, "item1"); }); it("AsyncClass should have explicit async method (Meta::Async)", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = await instance.explicitAsyncMethod(3); assert.strictEqual(result, "Processing: test * 3"); }); it("AsyncClass should have explicit async method with clone", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = await instance.explicitAsyncClone(" cloned"); assert.strictEqual(result, "test cloned"); }); it("AsyncClass should have method with context parameter", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("hello"); const result = instance.methodWithContext(3); // "hello".length = 5, so 5 * 3 = 15 assert.strictEqual(result, 15); }); it("AsyncClass should have method with explicit context attribute", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = instance.methodWithExplicitContext(" suffix"); assert.strictEqual(result, "test: suffix"); }); it("AsyncClass should have task method with channel parameter", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = await instance.taskWithChannel(5); assert.strictEqual(result, "Task with channel: test * 5"); }); it("AsyncClass should have async fn method with channel parameter", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = await instance.asyncFnWithChannel(" async"); assert.strictEqual(result, "AsyncFn with channel: test async"); }); it("AsyncClass should have method with this parameter", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = instance.methodWithThis(" data"); assert.strictEqual( result, "Instance: test, JS object available, data: data" ); }); it("AsyncClass should have method with explicit this attribute", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const result = instance.methodWithExplicitThis(" suffix"); assert.strictEqual(result, "Explicit this: test suffix"); }); it("AsyncClass should have method with context and this", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("hello"); const result = instance.methodWithContextAndThis(4); // "hello".length = 5, so 5 * 4 = 20 assert.strictEqual(result, 20); }); it("AsyncClass should have reasonable performance for simple methods", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const start = process.hrtime.bigint(); // Run 1000 simple method calls for (let i = 0; i < 1000; i++) { instance.simpleMethod(i); } const end = process.hrtime.bigint(); const durationMs = Number(end - start) / 1000000; // Convert to milliseconds // Should complete 1000 calls in reasonable time (less than 100ms is very good) assert( durationMs < 1000, `Performance test took ${durationMs}ms for 1000 calls` ); }); it("AsyncClass should handle JSON methods efficiently", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const testData = [1, 2, 3, 4, 5]; const start = process.hrtime.bigint(); // Run 100 JSON method calls for (let i = 0; i < 100; i++) { const result = instance.jsonMethodPerf(testData); assert.deepStrictEqual(result, [2, 4, 6, 8, 10]); } const end = process.hrtime.bigint(); const durationMs = Number(end - start) / 1000000; // JSON serialization has overhead but should still be reasonable assert( durationMs < 1000, `JSON performance test took ${durationMs}ms for 100 calls` ); }); it("AsyncClass should handle context methods efficiently", function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const start = process.hrtime.bigint(); // Run 1000 context method calls for (let i = 0; i < 1000; i++) { const result = instance.contextMethodPerf(i); assert.strictEqual(result, i * 3); } const end = process.hrtime.bigint(); const durationMs = Number(end - start) / 1000000; // Context methods should have minimal overhead assert( durationMs < 1000, `Context performance test took ${durationMs}ms for 1000 calls` ); }); it("AsyncClass async method consumes self", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); await instance.asyncMethod(" once"); // This should fail because the instance has been consumed // In practice, we might want to document this limitation // or find a better approach for async methods }); it("AsyncClass has const properties", function () { const AsyncClass = addon.AsyncClass; // Test basic const property assert.strictEqual(AsyncClass.DEFAULT_TIMEOUT, 5000); // Test const property with custom name and JSON serialization assert.deepEqual(AsyncClass.version, [1, 0, 0]); }); it("AsyncClass should have explicit async JSON method", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const input = [1, 2, 3, 4, 5]; const result = await instance.explicitAsyncJsonMethod(input); assert.deepStrictEqual(result, [2, 4, 6, 8, 10]); }); it("AsyncClass should have auto-detected async JSON method", async function () { const AsyncClass = addon.AsyncClass; const instance = new AsyncClass("test"); const input = [1, 2, 3, 4, 5]; const result = await instance.asyncJsonMethod(input); // Should multiply each element by 2 assert.deepStrictEqual(result, [2, 4, 6, 8, 10]); }); it("can create Point instances from Rust code", function () { const Point = addon.Point; // Test creating instance via Rust function (Rust → JS path) const rustPoint = addon.createPointFromRust(10, 20); // Verify it's an instance of the class assert.instanceOf(rustPoint, Point); assert.strictEqual(rustPoint.x(), 10); assert.strictEqual(rustPoint.y(), 20); // Test that Rust-created and JS-created instances are compatible const jsPoint = new Point(5, 10); const distance = rustPoint.distance(jsPoint); // Distance between (10, 20) and (5, 10) = sqrt((10-5)^2 + (20-10)^2) = sqrt(25 + 100) = sqrt(125) assert.strictEqual(distance, Math.sqrt(125)); }); it("can create origin Point from Rust", function () { const Point = addon.Point; const origin = addon.createPointOrigin(); assert.instanceOf(origin, Point); assert.strictEqual(origin.x(), 0); assert.strictEqual(origin.y(), 0); }); it("can transform Points in Rust and return new instances", function () { const Point = addon.Point; // Create point in JS const original = new Point(3, 4); // Pass to Rust, transform, and get back new instance const doubled = addon.doublePointCoords(original); // Verify the returned point assert.instanceOf(doubled, Point); assert.strictEqual(doubled.x(), 6); assert.strictEqual(doubled.y(), 8); // Verify original is unchanged assert.strictEqual(original.x(), 3); assert.strictEqual(original.y(), 4); }); }); describe("constructor features", function () { const { FallibleCounter, ContextCounter, JsonConfig, ValidatedConfig, Argv, Secret, Carousel, Expando, } = addon; describe("nullary constructor", function () { it("should support nullary constructors", () => { const carousel = new Carousel(); assert.strictEqual(carousel.next(), "Welcome to the Neon Carousel!"); assert.strictEqual( carousel.next(), "Enjoy seamless Rust and JavaScript integration." ); assert.strictEqual( carousel.next(), "Experience high performance with native modules." ); assert.strictEqual( carousel.next(), "Build robust applications with ease." ); assert.strictEqual(carousel.next(), "Thank you for using Neon!"); assert.strictEqual(carousel.next(), "Welcome to the Neon Carousel!"); }); }); describe("Result return types", function () { it("should create instance when constructor succeeds", () => { const counter = new FallibleCounter(50); assert.strictEqual(counter.get(), 50); counter.increment(); assert.strictEqual(counter.get(), 51); }); it("should throw error when constructor fails", () => { assert.throws(() => new FallibleCounter(150), /Value must be <= 100/); }); it("should work with edge case: maximum valid value", () => { const counter = new FallibleCounter(100); assert.strictEqual(counter.get(), 100); }); }); describe("Context parameter support", function () { it("should create instance with context parameter", () => { const counter = new ContextCounter(42); assert.strictEqual(counter.get(), 42); }); it("should work with different values", () => { const counter1 = new ContextCounter(10); const counter2 = new ContextCounter(20); assert.strictEqual(counter1.get(), 10); assert.strictEqual(counter2.get(), 20); }); it("should provide correct cx.this() to constructor", () => { const o = new Expando(); assert.strictEqual(o.expando(), 42); }); }); describe("JSON support", function () { it("should create instance from JSON object", () => { const config = new JsonConfig({ name: "test", count: 5, enabled: true, }); assert.strictEqual(config.name(), "test"); assert.strictEqual(config.count(), 5); assert.strictEqual(config.enabled(), true); }); it("should work with different JSON values", () => { const config = new JsonConfig({ name: "another", count: 100, enabled: false, }); assert.strictEqual(config.name(), "another"); assert.strictEqual(config.count(), 100); assert.strictEqual(config.enabled(), false); }); }); describe("Combined features: context + Result", function () { it("should propagate errors with nullable JSON array", () => { assert.throws( () => new Secret(() => { throw new Error("Access denied"); }), /Access denied/ ); }); }); describe("Combined features: context + JSON + Result", function () { it("should create an instance with JSON array", () => { const argv = new Argv(["1", "2", "3", "4", "5"]); assert.strictEqual(argv.len(), 5); assert.strictEqual(argv.get(0), "1"); assert.strictEqual(argv.get(4), "5"); }); it("should create instance with empty JSON array", () => { const argv = new Argv([]); assert.strictEqual(argv.len(), 0); }); it("should create instance with nullable JSON array", () => { const argv = new Argv(null); assert.strictEqual(argv.len(), process.argv.length); for (let i = 0; i < process.argv.length; i++) { assert.strictEqual(argv.get(i), process.argv[i]); } }); it("should create instance when validation passes", () => { const config = new ValidatedConfig({ name: "valid", count: 500, }); assert.strictEqual(config.name(), "valid"); assert.strictEqual(config.count(), 500); }); it("should throw error when name is empty", () => { assert.throws( () => new ValidatedConfig({ name: "", count: 10 }), /Name cannot be empty/ ); }); it("should throw error when count is too large", () => { assert.throws( () => new ValidatedConfig({ name: "test", count: 2000 }), /Count must be <= 1000/ ); }); it("should work with edge case: maximum valid count", () => { const config = new ValidatedConfig({ name: "edge", count: 1000, }); assert.strictEqual(config.count(), 1000); }); }); }); ================================================ FILE: test/napi/lib/coercions.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("coercions", function () { it("can stringify", function () { assert.strictEqual(addon.to_string([1, 2, 3]), "1,2,3"); assert.strictEqual(addon.to_string(new Map()), "[object Map]"); assert.strictEqual(addon.to_string({ a: "b" }), "[object Object]"); }); }); ================================================ FILE: test/napi/lib/container.js ================================================ const addon = require(".."); const { expect } = require("chai"); const assert = require("chai").assert; describe("Container type extractors", function () { it("can produce and consume a RefCell", function () { const cell = addon.createStringRefCell("my sekret mesij"); const s = addon.readStringRefCell(cell); assert.strictEqual(s, "my sekret mesij"); }); it("can produce and modify a RefCell", function () { const cell = addon.createStringRefCell("new"); addon.writeStringRefCell(cell, "modified"); assert.strictEqual(addon.readStringRefCell(cell), "modified"); }); it("can concatenate a RefCell with a String", function () { const cell = addon.createStringRefCell("hello"); const s = addon.stringRefCellConcat(cell, " world"); assert.strictEqual(s, "hello world"); }); it("fail with a type error when not given a RefCell", function () { try { addon.stringRefCellConcat("hello", " world"); assert.fail("should have thrown"); } catch (err) { assert.instanceOf(err, TypeError); assert.strictEqual( err.message, "expected neon::types_impl::boxed::JsBox>" ); } }); it("dynamically fail when borrowing a mutably borrowed RefCell", function () { const cell = addon.createStringRefCell("hello"); try { addon.borrowMutAndThen(cell, () => { addon.stringRefConcat(cell, " world"); }); assert.fail("should have thrown"); } catch (err) { assert.instanceOf(err, Error); assert.include(err.message, "already mutably borrowed"); } }); it("dynamically fail when modifying a borrowed RefCell", function () { const cell = addon.createStringRefCell("hello"); try { addon.borrowAndThen(cell, () => { addon.writeStringRef(cell, "world"); }); assert.fail("should have thrown"); } catch (err) { assert.instanceOf(err, Error); assert.include(err.message, "already borrowed"); } }); it("can produce and consume an Rc", function () { const cell = addon.createStringRc("my sekret mesij"); const s = addon.readStringRc(cell); assert.strictEqual(s, "my sekret mesij"); }); it("can produce and consume an Arc", function () { const cell = addon.createStringArc("my sekret mesij"); const s = addon.readStringArc(cell); assert.strictEqual(s, "my sekret mesij"); }); }); ================================================ FILE: test/napi/lib/date.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("JsDate", function () { it("should create a date", function () { const date = addon.create_date(); assert.instanceOf(date, Date); }); it("should create date from time", function () { const date = addon.create_date_from_value(31415); assert.instanceOf(date, Date); assert.equal(date.toUTCString(), new Date(31415).toUTCString()); }); it("should check if date is valid", function () { const dateIsValid = addon.check_date_is_valid(31415); assert.isTrue(dateIsValid); }); it("should try new date", function () { assert.isUndefined(addon.try_new_date()); }); it("should try new lossy date", function () { assert.isUndefined(addon.try_new_lossy_date()); }); it("should handle nan dates", function () { assert.isUndefined(addon.nan_dates()); }); it("should check if date is invalid", function () { const date = addon.create_and_get_invalid_date(); assert.isNaN(date); }); it("should get date value", function () { const dateValue = addon.get_date_value(); assert.equal(dateValue, 31415); }); }); ================================================ FILE: test/napi/lib/errors.js ================================================ const addon = require(".."); const assert = require("chai").assert; describe("errors", function () { it("should be able to create an error", function () { const msg = "Oh, no!"; const err = addon.new_error(msg); assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); }); it("should be able to create a type error", function () { const msg = "Type error? From Rust?!"; const err = addon.new_type_error(msg); assert.instanceOf(err, TypeError); assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); }); it("should be able to create a range error", function () { const msg = "Out of Bounds"; const err = addon.new_range_error(msg); assert.instanceOf(err, RangeError); assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); }); it("should be able to throw an error", function () { const msg = "Out of Bounds"; assert.throws(() => addon.throw_error(msg), msg); }); it("should be able to stringify a downcast error", function () { let msg = addon.downcast_error(); assert.strictEqual(msg, "failed to downcast string to number"); }); }); ================================================ FILE: test/napi/lib/export.js ================================================ const assert = require("assert"); const addon = require(".."); describe("neon::export macro", () => { describe("globals", globals); describe("functions", functions); describe("classes", classes); }); function globals() { it("values", () => { assert.strictEqual(addon.NUMBER, 42); assert.strictEqual(addon.STRING, "Hello, World!"); assert.strictEqual(addon.renamedString, "Hello, World!"); }); it("json", () => { assert.deepStrictEqual(addon.MESSAGES, ["hello", "neon"]); assert.deepStrictEqual(addon.renamedMessages, ["hello", "neon"]); }); it("LazyLock", () => { assert.strictEqual(addon.LAZY_LOCK_HELLO, "Hello, Neon!"); }); } function functions() { it("void function", () => { assert.strictEqual(addon.noArgsOrReturn(), undefined); }); it("add - sync", () => { assert.strictEqual(addon.simpleAdd(1, 2), 3); assert.strictEqual(addon.renamedAdd(1, 2), 3); }); it("add - task", async () => { const p1 = addon.addTask(1, 2); const p2 = addon.renamedAddTask(1, 2); assert.ok(p1 instanceof Promise); assert.ok(p2 instanceof Promise); assert.strictEqual(await p1, 3); assert.strictEqual(await p2, 3); }); it("json sort", () => { const arr = ["b", "c", "a"]; const expected = [...arr].sort(); assert.deepStrictEqual(addon.jsonSort(arr), expected); assert.deepStrictEqual(addon.renamedJsonSort(arr), expected); }); it("json sort - task", async () => { const arr = ["b", "c", "a"]; const expected = [...arr].sort(); const p1 = addon.jsonSortTask(arr); const p2 = addon.renamedJsonSortTask(arr); assert.ok(p1 instanceof Promise); assert.ok(p2 instanceof Promise); assert.deepStrictEqual(await p1, expected); assert.deepStrictEqual(await p2, expected); }); it("can use context and handles", () => { const actual = addon.concatWithCxAndHandle("Hello,", " World!"); const expected = "Hello, World!"; assert.strictEqual(actual, expected); }); it("error conversion", () => { const msg = "Oh, no!"; const expected = new Error(msg); assert.throws(() => addon.failWithThrow(msg), expected); }); it("tasks are concurrent", async () => { const time = 500; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const start = process.hrtime.bigint(); await Promise.all([addon.sleepTask(time), sleep(time)]); const end = process.hrtime.bigint(); const duration = end - start; // If `addon.sleepTask` blocks the thread, the tasks will run sequentially // and take a minimum of 2x `time`. Since they are run concurrently, we // expect the time to be closer to 1x `time`. const maxExpected = 2000000n * BigInt(time); assert.ok(duration < maxExpected); }); it("can use generic Cx in exported functions", () => { assert.strictEqual(addon.numberWithCx(42), 42); }); it("i32 parameters", () => { assert.strictEqual(addon.addI32(5, 3), 8); assert.strictEqual(addon.addI32(-10, 20), 10); assert.strictEqual(addon.addI32(-5, -3), -8); assert.strictEqual(addon.toI32(Infinity), 0); assert.strictEqual(addon.toI32(-Infinity), 0); }); it("u32 parameters", () => { assert.strictEqual(addon.addU32(5, 3), 8); assert.strictEqual(addon.addU32(100, 200), 300); assert.strictEqual(addon.addU32(0, 42), 42); assert.strictEqual(addon.toU32(Infinity), 0); assert.strictEqual(addon.toU32(-Infinity), 0); }); } function classes() { it("can create and use exported class", () => { const ExportedPoint = addon.ExportedPoint; // Test that the class was exported assert.strictEqual(typeof ExportedPoint, "function"); // Test const properties assert.strictEqual(ExportedPoint.ORIGIN_X, 0.0); assert.strictEqual(ExportedPoint.ORIGIN_Y, 0.0); // Test class instantiation and methods const point1 = new ExportedPoint(3, 4); const point2 = new ExportedPoint(0, 0); assert.strictEqual(point1.x(), 3); assert.strictEqual(point1.y(), 4); assert.strictEqual(point2.x(), 0); assert.strictEqual(point2.y(), 0); // Test distance calculation assert.strictEqual(point1.distance(point2), 5); // 3-4-5 triangle }); it('can use custom export name - CASE 1: class, name = "..."', () => { const RenamedClass = addon.RenamedClass; // Test that the class was exported with custom name assert.strictEqual(typeof RenamedClass, "function"); // Test that the original name is not exported assert.strictEqual(addon.CustomNamedClass, undefined); // Test class functionality const instance = new RenamedClass("test value"); assert.strictEqual(instance.getValue(), "test value"); // Test that the class name itself is RenamedClass assert.strictEqual(RenamedClass.name, "RenamedClass"); }); it('CASE 2: class(name = "...") - parenthesized syntax', () => { const ParenRenamedClass = addon.ParenRenamedClass; // Test that the class was exported with the name from class(name = "...") assert.strictEqual(typeof ParenRenamedClass, "function"); // Test that the original Rust name is not exported assert.strictEqual(addon.Case2Class, undefined); // Test class functionality const instance = new ParenRenamedClass("hello"); assert.strictEqual(instance.getMessage(), "hello"); // Test that the JavaScript class name is ParenRenamedClass assert.strictEqual(ParenRenamedClass.name, "ParenRenamedClass"); }); it('CASE 3: class(name = "Internal"), name = "External" - separate names', () => { const ExternalExportName = addon.ExternalExportName; // Test that the class was exported with the outer name assert.strictEqual(typeof ExternalExportName, "function"); // Test that the Rust name is not exported assert.strictEqual(addon.Case3Class, undefined); // Test that the inner class name is not directly exported assert.strictEqual(addon.InternalClassName, undefined); // Test class functionality const instance = new ExternalExportName(42); assert.strictEqual(instance.getData(), 42); // Test that the JavaScript class name is the inner name (InternalClassName) // but the export binding is the outer name (ExternalExportName) assert.strictEqual(ExternalExportName.name, "InternalClassName"); }); it("can export async fn with JSON", async () => { const input = [1, 2, 3, 4, 5]; const result = await addon.exportAsyncJsonTest(input); // Should multiply each element by 3 assert.deepStrictEqual(result, [3, 6, 9, 12, 15]); }); } ================================================ FILE: test/napi/lib/extract.js ================================================ const assert = require("assert"); const addon = require(".."); describe("Extractors", () => { it("Single Argument", () => { assert.strictEqual(addon.extract_single_add_one(41), 42); }); it("Kitchen Sink", () => { const symbol = Symbol("Test"); const values = [ true, 42, undefined, "hello", new Date(), symbol, new ArrayBuffer(100), new Uint8Array(Buffer.from("Buffer")), Buffer.from("Uint8Array"), ]; // Pass `null` and `undefined` for `None` assert.deepStrictEqual(addon.extract_values(...values, null), [ ...values, undefined, undefined, ]); // Pass values for optional assert.deepStrictEqual(addon.extract_values(...values, 100, "exists"), [ ...values, 100, "exists", ]); }); it("Buffers", () => { const test = (TypedArray) => { const buf = new ArrayBuffer(24); const view = new TypedArray(buf); view[0] = 8; view[1] = 16; view[2] = 18; assert.strictEqual(addon.extract_buffer_sum(view), 42); }; test(Uint8Array); test(Uint16Array); test(Uint32Array); test(Int8Array); test(Int16Array); test(Int32Array); test(Float32Array); test(Float64Array); }); it("TypedArray", () => { assert.deepStrictEqual( Buffer.from(addon.bufferConcat(Buffer.from("abc"), Buffer.from("def"))), Buffer.from("abcdef") ); assert.deepStrictEqual( Buffer.from(addon.stringToBuf("Hello, World!")), Buffer.from("Hello, World!") ); }); it("JSON", () => { assert.strictEqual(addon.extract_json_sum([1, 2, 3, 4]), 10); assert.strictEqual(addon.extract_json_sum([8, 16, 18]), 42); assert.strictEqual(addon.extractJsonOption(42), 42); assert.strictEqual(addon.extractJsonOption(null), null); assert.strictEqual(addon.extractJsonOption(), null); }); it("Either", () => { assert.strictEqual(addon.extractEither("hello"), "String: hello"); assert.strictEqual(addon.extractEither(42), "Number: 42"); assert.throws( () => addon.extractEither({}), (err) => { assert.match(err.message, /expected either.*String.*f64/); assert.match(err.left.message, /expected string/); assert.match(err.right.message, /expected number/); return true; } ); }); it("With", async () => { assert.strictEqual(await addon.sleepWithJs(1.5), 1.5); assert.strictEqual(await addon.sleepWithJsSync(1.5), 1.5); assert.strictEqual(await addon.sleepWith(1.5), 1.5); assert.strictEqual(await addon.sleepWithSync(1.5), 1.5); }); it("Array", () => { assert.deepStrictEqual(addon.extractArrayVec([1, 2, 3]), [1, 2, 3]); assert.deepStrictEqual(addon.extractArrayDouble([1, 2, 3]), [2, 4, 6]); assert.deepStrictEqual( addon.extractArrayDedupe(["a", "a", "b", "c", "c"]).sort(), ["a", "b", "c"] ); }); }); ================================================ FILE: test/napi/lib/functions.js ================================================ var addon = require(".."); var assert = require("chai").assert; const STRICT = function () { "use strict"; return this; }; const SLOPPY = Function("return this;"); function isStrict(f) { try { f.caller; return false; } catch (e) { return true; } } describe("JsFunction", function () { it("return a JsFunction built in Rust", function () { assert.isFunction(addon.return_js_function()); }); it("return a JsFunction built in Rust that implements x => x + 1", function () { assert.equal(addon.return_js_function()(41), 42); }); it("call a JsFunction built in JS that implements x => x + 1", function () { assert.equal( addon.call_js_function(function (x) { return x + 1; }), 17 ); }); it("call a JsFunction built in JS with call_with", function () { assert.equal( addon.call_js_function_idiomatically(function (x) { return x + 1; }), 17 ); }); it("call a JsFunction built in JS with .bind().apply()", function () { assert.equal( addon.call_js_function_with_bind(function (a, b, c, d, e) { return a * b * c * d * e; }), 1 * 2 * 3 * 4 * 5 ); }); it("call a JsFunction build in JS with .bind and .args_with", function () { assert.equal( addon.call_js_function_with_bind_and_args_with(function (a, b, c) { return a + b + c; }), 1 + 2 + 3 ); }); it("call a JsFunction build in JS with .bind and .args and With", function () { assert.equal( addon.call_js_function_with_bind_and_args_and_with(function (a, b, c) { return a + b + c; }), 1 + 2 + 3 ); }); it("call parseInt with .bind().apply()", function () { assert.equal(addon.call_parse_int_with_bind(), 42); }); it("call a JsFunction built in JS with .bind and .exec", function () { let local = 41; addon.call_js_function_with_bind_and_exec(function (x) { local += x; }); assert.equal(local, 42); }); it("call a JsFunction built in JS as a constructor with .bind and .construct", function () { function MyClass(number, string) { this.number = number; this.string = string; } const obj = addon.call_js_constructor_with_bind(MyClass); assert.instanceOf(obj, MyClass); assert.equal(obj.number, 42); assert.equal(obj.string, "hello"); }); it("bind a JsFunction to an object", function () { const result = addon.bind_js_function_to_object(function () { return this.prop; }); assert.equal(result, 42); }); it("bind a strict JsFunction to a number", function () { assert.isTrue(isStrict(STRICT)); // strict mode functions are allowed to have a primitive this binding const result = addon.bind_js_function_to_number(STRICT); assert.strictEqual(result, 42); }); it("bind a sloppy JsFunction to a primitive", function () { assert.isFalse(isStrict(SLOPPY)); // legacy JS functions (aka "sloppy mode") replace primitive this bindings // with object wrappers, so 42 will get wrapped as new Number(42) const result = addon.bind_js_function_to_number(SLOPPY); assert.instanceOf(result, Number); assert.strictEqual(typeof result, "object"); assert.strictEqual(result.valueOf(), 42); }); it("call a JsFunction with zero args", function () { assert.equal(addon.call_js_function_with_zero_args(), -Infinity); }); it("call a JsFunction with one arg", function () { assert.equal(addon.call_js_function_with_one_arg(), 1.0); }); it("call a JsFunction with two args", function () { assert.equal(addon.call_js_function_with_two_args(), 2.0); }); it("call a JsFunction with three args", function () { assert.equal(addon.call_js_function_with_three_args(), 3.0); }); it("call a JsFunction with four args", function () { assert.equal(addon.call_js_function_with_four_args(), 4.0); }); it("call a JsFunction with a custom this", function () { assert.equal( addon.call_js_function_with_custom_this(function () { return this; }).secret, 42 ); }); it("call a JsFunction with the default this", function () { addon.call_js_function_with_implicit_this(function () { "use strict"; // ensure the undefined this isn't replaced with the global object assert.strictEqual(this, undefined); }); }); it("exec a JsFunction with the default this", function () { addon.exec_js_function_with_implicit_this(function () { "use strict"; // ensure the undefined this isn't replaced with the global object assert.strictEqual(this, undefined); }); }); it("call a JsFunction with a heterogeneously typed tuple", function () { assert.deepEqual(addon.call_js_function_with_heterogeneous_tuple(), [ 1, "hello", true, ]); }); it("new a JsFunction", function () { assert.equal(addon.construct_js_function(Date), 1970); }); it("new a JsFunction with construct_with", function () { assert.equal(addon.construct_js_function_idiomatically(Date), 1970); }); it("new a JsFunction with construct_with to create an array", function () { assert.deepEqual( addon.construct_js_function_with_overloaded_result(), [1, 2, 3] ); }); it("got two parameters, a string and a number", function () { addon.check_string_and_number("string", 42); }); it("converts a Rust panic to a throw in a function", function () { assert.throws( function () { addon.panic(); }, Error, /^internal error in Neon module: zomg$/ ); }); it("lets panic override a throw", function () { assert.throws( function () { addon.panic_after_throw(); }, Error, /^internal error in Neon module: this should override the RangeError$/ ); }); it("computes the right number of arguments", function () { assert.equal(addon.num_arguments(), 0); assert.equal(addon.num_arguments("a"), 1); assert.equal(addon.num_arguments("a", "b"), 2); assert.equal(addon.num_arguments("a", "b", "c"), 3); assert.equal(addon.num_arguments("a", "b", "c", "d"), 4); }); it("gets the right `this`-value", function () { var o = { iamobject: "i am object" }; assert.equal(addon.return_this.call(o), o); var d = new Date(); assert.equal(addon.return_this.call(d), d); var n = 19; assert.notStrictEqual(addon.return_this.call(n), n); }); it("can manipulate an object `this` binding", function () { var o = { modified: false }; addon.require_object_this.call(o); assert.equal(o.modified, true); // Doesn't throw because of implicit primitive wrapping: addon.require_object_this.call(42); }); it("implicitly gets global", function () { var global = new Function("return this")(); assert.equal(addon.return_this.call(undefined), global); }); it("exposes an argument via arguments_opt iff it is there", function () { assert.equal(addon.is_argument_zero_some(), false); assert.equal(addon.is_argument_zero_some("a"), true); assert.equal(addon.is_argument_zero_some("a", "b"), true); assert.equal(addon.is_argument_zero_some.call(null), false); assert.equal(addon.is_argument_zero_some.call(null, ["a"]), true); assert.equal(addon.is_argument_zero_some.call(null, ["a", "b"]), true); }); it("correctly casts an argument via cx.arguments", function () { assert.equal(addon.require_argument_zero_string("foobar"), "foobar"); assert.throws(function () { addon.require_argument_zero_string(new Date()); }, TypeError); assert.throws(function () { addon.require_argument_zero_string(17); }, TypeError); }); it("executes a scoped computation", function () { assert.equal(addon.execute_scoped(), 99); }); it("computes a value in a scoped computation", function () { const o = {}; assert.equal(addon.compute_scoped(), 99); assert.equal(addon.recompute_scoped(o), o); }); it("catches an exception with cx.try_catch", function () { var error = new Error("Something bad happened"); assert.equal(addon.throw_and_catch(error), error); assert.equal(addon.throw_and_catch(42), 42); assert.equal(addon.throw_and_catch("a string"), "a string"); assert.equal( addon.call_and_catch(() => { throw "shade"; }), "shade" ); assert.equal( addon.call_and_catch(() => { throw ( addon.call_and_catch(() => { throw ( addon.call_and_catch(() => { throw "once"; }) + " upon" ); }) + " a" ); }) + " time", "once upon a time" ); }); it("gets a regular value with cx.try_catch", function () { assert.equal( addon.call_and_catch(() => { return 42; }), 42 ); }); it("can return Rust type from cx.try_catch", function () { const n = Math.random(); assert.strictEqual(addon.get_number_or_default(n), n); assert.strictEqual(addon.get_number_or_default(), 0); }); it("always provides an object for the this-binding", function () { var meta1 = addon.assume_this_is_an_object.call(null); assert.strictEqual(meta1.prototype, Object.getPrototypeOf(global)); assert.strictEqual(meta1.hasOwn, false); assert.strictEqual(meta1.property, Object.getPrototypeOf(global).toString); var meta2 = addon.assume_this_is_an_object.call(42); assert.strictEqual(meta2.prototype, Number.prototype); assert.strictEqual(meta2.hasOwn, false); assert.strictEqual(meta2.property, Number.prototype.toString); var meta3 = addon.assume_this_is_an_object.call(Object.create(null)); assert.strictEqual(meta3.prototype, null); assert.strictEqual(meta3.hasOwn, false); assert.strictEqual(meta3.property, undefined); var meta4 = addon.assume_this_is_an_object.call({ toString: 17 }); assert.strictEqual(meta4.prototype, Object.prototype); assert.strictEqual(meta4.hasOwn, true); assert.strictEqual(meta4.property, 17); }); it("distinguishes calls from constructs", function () { assert.equal(addon.is_construct.call({}).wasConstructed, false); assert.equal(new addon.is_construct().wasConstructed, true); }); it("should be able to call a function from a closure", function () { assert.strictEqual(addon.count_called() + 1, addon.count_called()); }); (global.gc ? it : it.skip)( "should drop function when going out of scope", function (cb) { // Run from an `IIFE` to ensure that `f` is out of scope and eligible for garbage // collection when `global.gc()` is executed. (() => { const msg = "Hello, World!"; const f = addon.caller_with_drop_callback(() => msg, cb); assert.strictEqual(f(), msg); })(); global.gc(); } ); }); ================================================ FILE: test/napi/lib/futures.js ================================================ const assert = require("assert"); const addon = require(".."); async function assertRejects(f, ...args) { try { await f(); } catch (err) { assert.throws(() => { throw err; }, ...args); return; } assert.throws(() => {}, ...args); } describe("Futures", () => { describe("Channel", () => { it("should be able to await channel result", async () => { const sum = await addon.lazy_async_add( () => 1, () => 2 ); assert.strictEqual(sum, 3); }); it("exceptions should be handled", async () => { await assertRejects(async () => { await addon.lazy_async_add( () => 1, () => { throw new Error("Failed to get Y"); } ); }, /exception/i); }); }); describe("JsFuture", () => { it("should be able to convert a promise to a future", async () => { const nums = new Float64Array([1, 2, 3, 4]); const sum = await addon.lazy_async_sum(async () => nums); assert.strictEqual(sum, 10); }); it("should catch promise rejection", async () => { await assertRejects(async () => { await addon.lazy_async_sum(async () => { throw new Error("Oh, no!"); }); }, /exception/i); }); }); describe("Exported Async Functions", () => { it("should be able to call `async fn`", async () => { assert.strictEqual(await addon.asyncFnAdd(1, 2), 3); }); it("should be able to call fn with async block", async () => { assert.strictEqual(await addon.asyncAdd(1, 2), 3); }); it("should be able to call fallible `async fn`", async () => { assert.strictEqual(await addon.asyncFnDiv(10, 2), 5); await assertRejects(() => addon.asyncFnDiv(10, 0), /Divide by zero/); }); it("should be able to call fallible fn with async block", async () => { assert.strictEqual(await addon.asyncDiv(10, 2), 5); await assertRejects(() => addon.asyncDiv(10, 0), /Divide by zero/); }); it("should be able to code on the event loop before and after async", async () => { let startCalled = false; let endCalled = false; const eventHandler = (event) => { switch (event) { case "start": startCalled = true; break; case "end": endCalled = true; break; } }; process.on("async_with_events", eventHandler); try { let res = await addon.asyncWithEvents([ [1, 2], [3, 4], [5, 6], ]); assert.deepStrictEqual([...res], [2, 12, 30]); assert.ok(startCalled, "Did not emit start event"); assert.ok(endCalled, "Did not emit end event"); } finally { process.off("async_with_events", eventHandler); } }); }); }); ================================================ FILE: test/napi/lib/hello.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("hello", function () { it("should export a greeting", function () { assert.strictEqual(addon.greeting, "Hello, World!"); assert.strictEqual(addon.greeting, addon.greetingCopy); }); it("should export global singletons for JS primitives", function () { assert.strictEqual(addon.undefined, undefined); assert.ok(addon.hasOwnProperty("undefined")); assert.strictEqual(addon.null, null); assert.strictEqual(addon.true, true); assert.strictEqual(addon.false, false); }); it("should export numbers", function () { assert.strictEqual(addon.one, 1); assert.strictEqual(addon.two, 2.1); }); it("should be able to create JS objects in rust", function () { assert.deepEqual(addon.rustCreated, { 0: 1, a: 1, whatever: true, }); }); it("should export a Rust function", function () { assert.strictEqual(addon.add1(2), 3.0); }); }); ================================================ FILE: test/napi/lib/numbers.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("JsNumber", function () { it("return a JsNumber built in Rust", function () { assert.equal(addon.return_js_number(), 9000); }); it("return a JsNumber for a large int built in Rust", function () { assert.equal(addon.return_large_js_number(), 4294967296); }); it("return a negative JsNumber int built in Rust", function () { assert.equal(addon.return_negative_js_number(), -9000); }); it("return a JsNumber float built in Rust", function () { assert.equal(addon.return_float_js_number(), 1.4747); }); it("return a negative JsNumber float built in Rust", function () { assert.equal(addon.return_negative_float_js_number(), -1.4747); }); describe("round trips", function () { it("accept and return a number", function () { assert.equal(addon.accept_and_return_js_number(1), 1); }); it("accept and return a large number as a JsNumber", function () { assert.equal( addon.accept_and_return_large_js_number(9007199254740991), 9007199254740991 ); }); it("will be safe below Number.MAX_SAFE_INTEGER", function () { assert.notEqual( addon.accept_and_return_large_js_number(9007199254740990), 9007199254740991 ); }); it("will not be save above Number.MAX_SAFE_INTEGER", function () { assert.equal( addon.accept_and_return_large_js_number(9007199254740993), 9007199254740992 ); }); it("accept and return a float as a JsNumber", function () { assert.equal(addon.accept_and_return_float_js_number(0.23423), 0.23423); }); it("accept and return a negative number as a JsNumber", function () { assert.equal(addon.accept_and_return_negative_js_number(-55), -55); }); }); }); ================================================ FILE: test/napi/lib/objects.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("JsObject", function () { it("return the v8::Global object", function () { assert(global === addon.return_js_global_object()); }); it("return a JsObject built in Rust", function () { assert.deepEqual({}, addon.return_js_object()); }); it("return a JsObject with a number key value pair", function () { assert.deepEqual({ number: 9000 }, addon.return_js_object_with_number()); }); it("return a JsObject with an string key value pair", function () { assert.deepEqual( { string: "hello node" }, addon.return_js_object_with_string() ); }); it("return a JsObject with mixed content key value pairs", function () { assert.deepEqual( { number: 9000, string: "hello node" }, addon.return_js_object_with_mixed_content() ); }); it("freeze a JsObject", function () { const originalValue = 1; const obj = { x: originalValue }; assert.doesNotThrow(function () { addon.freeze_js_object(obj); }, "freeze_js_object should not throw"); obj.x = 2; assert.equal( obj.x, originalValue, "freeze_js_object should not allow mutation" ); const shouldNotFreeze = new Uint32Array(8); assert.throws(function () { addon.freeze_js_object(shouldNotFreeze); }); }); it("seal a JsObject", function () { const obj = { x: 1 }; assert.doesNotThrow(function () { addon.seal_js_object(obj); }, "seal_js_object should not throw"); delete obj.x; assert.isOk(obj.x, "seal_js_object should not allow property deletion"); const shouldNotSeal = new Uint32Array(8); assert.throws(function () { addon.freeze_js_object(shouldNotSeal); }); }); it("returns only own properties from get_own_property_names", function () { var superObject = { a: 1, }; var childObject = Object.create(superObject); childObject.b = 2; assert.deepEqual( addon.get_own_property_names(childObject), Object.getOwnPropertyNames(childObject) ); }); it("does not return Symbols from get_own_property_names", function () { var object = {}; object["this should be a thing"] = 0; object[Symbol("this should not be a thing")] = 1; assert.deepEqual( addon.get_own_property_names(object), Object.getOwnPropertyNames(object) ); assert.equal(addon.get_own_property_names(object).length, 1); }); it("data borrowed on the heap can be held longer than the handle", function () { const msg = "Hello, World!"; const buf = Buffer.from(msg); assert.strictEqual(addon.byte_length(msg), buf.length); assert.strictEqual(addon.byte_length(buf), buf.length); }); it("calling Object::call_with() properly calls object methods", function () { const obj = { value: 42, nullary() { return this.value; }, unary(x) { return this.value + x; }, }; assert.strictEqual(addon.call_nullary_method(obj), 42); assert.strictEqual(addon.call_unary_method(obj, 17), 59); }); it("calling Object::call_with() with a symbol method name works", function () { const sym = Symbol.for("mySymbol"); const obj = { [sym]() { return "hello"; }, }; assert.strictEqual(addon.call_symbol_method(obj, sym), "hello"); }); it("extracts an object property with .prop()", function () { const obj = { number: 3.141593 }; assert.strictEqual(addon.get_property_with_prop(obj), 3.141593); }); it("sets an object property with .prop()", function () { const obj = { number: 3.141593 }; addon.set_property_with_prop(obj); assert.strictEqual(obj.number, 42); }); it("calls a method with .prop()", function () { const obj = { name: "Diana Prince", setName(name) { this.name = name; }, toString() { return `[object ${this.name}]`; }, }; assert.strictEqual(obj.toString(), "[object Diana Prince]"); assert.strictEqual( addon.call_methods_with_prop(obj), "[object Wonder Woman]" ); assert.strictEqual(obj.toString(), "[object Wonder Woman]"); }); it("throws a TypeError when calling a non-method with .prop()", function () { const obj = { number: 42, }; assert.throws(() => { addon.call_non_method_with_prop(obj); }, /not a function/); }); }); ================================================ FILE: test/napi/lib/strings.js ================================================ var addon = require(".."); var { assert, expect } = require("chai"); describe("JsString", function () { it("should return a JsString built in Rust", function () { assert.equal(addon.return_js_string(), "hello node"); }); it("should return a raw valid UTF-16 string built in Rust", function () { const decoder = new TextDecoder("utf-16"); assert.equal(decoder.decode(addon.return_js_string_utf16()), "hello 🥹"); }); describe("encoding", function () { it("should return the UTF-8 string length", function () { assert.equal(addon.return_length_utf8("hello 🥹"), 10); }); it("should return the UTF-16 string length", function () { assert.equal(addon.return_length_utf16("hello 🥹"), 8); }); }); describe("run_as_script", function () { it("should return the evaluated value", function () { assert.equal(addon.run_string_as_script("6 * 7"), 42); }); it("should throw if the script throws", function () { expect(() => addon.run_string_as_script('throw new Error("b1-66er")') ).to.throw("b1-66er"); }); it("should throw SyntaxError if the string has invalid syntax", function () { expect(() => addon.run_string_as_script("invalid js code")).to.throw( SyntaxError ); }); }); }); ================================================ FILE: test/napi/lib/threads.js ================================================ const addon = require(".."); const assert = require("chai").assert; (function () { // These tests require GC exposed to shutdown properly; skip if it is not return typeof global.gc === "function" ? describe : describe.skip; })()("sync", function () { let unhandledRejectionListeners = []; beforeEach(() => { unhandledRejectionListeners = process.listeners("unhandledRejection"); }); afterEach(() => { // Restore listeners process.removeAllListeners("unhandledRejection"); unhandledRejectionListeners.forEach((listener) => process.on("unhandledRejection", listener) ); // Force garbage collection to shutdown `Channel` global.gc(); }); it("can create and deref a root", function () { const expected = {}; const result = addon.useless_root(expected); assert.strictEqual(expected, result); }); it("should be able to callback from another thread", function (cb) { addon.thread_callback(cb); }); it("should be able to callback from multiple threads", function (cb) { const n = 4; const set = new Set([...new Array(n)].map((_, i) => i)); addon.multi_threaded_callback(n, function (x) { if (!set.delete(x)) { cb(new Error(`Unexpected callback value: ${x}`)); } if (set.size === 0) { cb(); } }); }); it("should be able to use an async greeter", function (cb) { const greeter = addon.greeter_new("Hello, World!", function (greeting) { if (greeting === "Hello, World!") { cb(); } else { new Error("Greeting did not match"); } }); addon.greeter_greet(greeter); }); it("should run callback on drop", function (cb) { // IIFE to allow GC (function () { addon.greeter_new( "Hello, World!", function () {}, function () { // No assert needed; test will timeout cb(); } ); })(); global.gc(); }); it("should be able to unref channel", function () { // If the Channel is not unreferenced, the test runner will not cleanly exit addon.leak_channel(); }); it("should drop leaked Root from the global queue", function (cb) { addon.drop_global_queue(cb); // Asynchronously GC to give the task queue a chance to execute setTimeout(() => global.gc(), 10); }); it("should be able to join on the result of a channel", function (cb) { // `msg` is closed over by multiple functions. A function that returns the // current value is passed to the Neon function `addon.channel_join`. Additionally, // the value is modified after `10ms` in a timeout. let msg = "Uninitialized"; // The `addon.channel_join` function will wait 100ms before fetching the current // value of `msg` using the first closure. The second closure is called // after fetching and processing the message. We expect the message to already // have been changed. addon.channel_join( () => msg, (res) => { assert.strictEqual(res, "Received: Hello, World!"); cb(); } ); // Change the value of `msg` after 10ms. This should happen before `addon.channel_join` // fetches it. setTimeout(() => { msg = "Hello, World!"; }, 10); }); it("should be able to sum numbers on the libuv pool", async function () { const nums = new Float64Array( [...new Array(10000)].map(() => Math.random()) ); const expected = nums.reduce((y, x) => y + x, 0); const actual = await addon.sum(nums); assert.strictEqual(expected, actual); }); it("should be able to resolve a promise manually", async function () { const nums = new Float64Array( [...new Array(10000)].map(() => Math.random()) ); const expected = nums.reduce((y, x) => y + x, 0); const actual = await addon.sum_manual_promise(nums); assert.strictEqual(expected, actual); }); it("should be able to resolve a promise from a rust thread", async function () { const nums = new Float64Array( [...new Array(10000)].map(() => Math.random()) ); const expected = nums.reduce((y, x) => y + x, 0); const actual = await addon.sum_rust_thread(nums); assert.strictEqual(expected, actual); }); it("should reject promise if leaked", async function () { try { await addon.leak_promise(); } catch (err) { assert.instanceOf(err, Error); assert.ok(/Deferred/.test(err)); } }); it("should throw an unhandledRejection when panicking in a channel", function (cb) { const msg = "Hello, Panic!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( !/exception/i.test(err.message), "Expected error message not to indicate an exception" ); assert.strictEqual(err.cause, undefined); assert.instanceOf(err.panic, Error); assert.strictEqual(err.panic.message, msg); assert.strictEqual(err.panic.cause, undefined); cb(); } catch (err) { cb(err); } }); addon.channel_panic(msg); }); it("should throw an unhandledRejection when throwing in a channel", function (cb) { const msg = "Hello, Throw!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( !/panic/i.test(err.message), "Expected error message not to indicate a panic" ); assert.ok( /exception/i.test(err.message), "Expected error message to indicate an exception" ); assert.strictEqual(err.panic, undefined); assert.instanceOf(err.cause, Error); assert.strictEqual(err.cause.message, msg); cb(); } catch (err) { cb(err); } }); addon.channel_throw(msg); }); it("should throw an unhandledRejection when panicking and throwing in a channel", function (cb) { const msg = "Oh, no!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( /exception/i.test(err.message), "Expected error message to indicate an exception" ); assert.instanceOf(err.panic, Error); assert.instanceOf(err.cause, Error); assert.strictEqual(err.cause.message, msg); cb(); } catch (err) { cb(err); } }); addon.channel_panic_throw(msg); }); it("should be able to downcast a panic in a channel", function (cb) { const msg = "Hello, Secret Panic!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err.panic, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.strictEqual(addon.custom_panic_downcast(err.panic.cause), msg); cb(); } catch (err) { cb(err); } }); addon.channel_custom_panic(msg); }); it("should throw an unhandledRejection when panicking in a task", function (cb) { const msg = "Hello, Panic!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( !/exception/i.test(err.message), "Expected error message not to indicate an exception" ); assert.strictEqual(err.cause, undefined); assert.instanceOf(err.panic, Error); assert.strictEqual(err.panic.message, msg); assert.strictEqual(err.panic.cause, undefined); cb(); } catch (err) { cb(err); } }); addon.task_panic_execute(msg); }); it("should throw an unhandledRejection when panicking in a task complete", function (cb) { const msg = "Hello, Panic!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( !/exception/i.test(err.message), "Expected error message not to indicate an exception" ); assert.strictEqual(err.cause, undefined); assert.instanceOf(err.panic, Error); assert.strictEqual(err.panic.message, msg); assert.strictEqual(err.panic.cause, undefined); cb(); } catch (err) { cb(err); } }); addon.task_panic_complete(msg); }); it("should throw an unhandledRejection when throwing in a task complete", function (cb) { const msg = "Hello, Throw!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( !/panic/i.test(err.message), "Expected error message not to indicate a panic" ); assert.ok( /exception/i.test(err.message), "Expected error message to indicate an exception" ); assert.strictEqual(err.panic, undefined); assert.instanceOf(err.cause, Error); assert.strictEqual(err.cause.message, msg); cb(); } catch (err) { cb(err); } }); addon.task_throw(msg); }); it("should throw an unhandledRejection when panicking and throwing in a task complete", function (cb) { const msg = "Oh, no!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( /exception/i.test(err.message), "Expected error message to indicate an exception" ); assert.instanceOf(err.panic, Error); assert.instanceOf(err.cause, Error); assert.strictEqual(err.cause.message, msg); cb(); } catch (err) { cb(err); } }); addon.task_panic_throw(msg); }); it("should be able to downcast a panic in a task", function (cb) { const msg = "Hello, Secret Panic!"; process.removeAllListeners("unhandledRejection"); process.once("unhandledRejection", (err) => { try { assert.instanceOf(err.panic, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.strictEqual(addon.custom_panic_downcast(err.panic.cause), msg); cb(); } catch (err) { cb(err); } }); addon.task_custom_panic(msg); }); it("should be able to reject a promise in a task", async function () { const msg = "Rejected!"; try { await addon.task_reject_promise(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); } }); it("panic in a task should reject the promise", async function () { const msg = "Rejected!"; try { await addon.task_panic_execute_promise(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.instanceOf(err.panic, Error); assert.strictEqual(err.panic.message, msg); } }); it("panic in a task should reject the promise", async function () { const msg = "Rejected!"; try { await addon.task_panic_complete_promise(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.instanceOf(err.panic, Error); assert.strictEqual(err.panic.message, msg); } }); it("panic and exception in a task should reject the promise", async function () { const msg = "Rejected!"; try { await addon.task_panic_throw_promise(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.ok( /exception/i.test(err.message), "Expected error message to indicate an exception" ); assert.instanceOf(err.panic, Error); assert.instanceOf(err.cause, Error); assert.strictEqual(err.cause.message, msg); } }); it("should be able to reject a promise settling with a channel", async function () { const msg = "Rejected!"; try { await addon.deferred_settle_with_throw(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); } }); it("should reject a promise when panicking while settling with a channel", async function () { const msg = "Rejected!"; try { await addon.deferred_settle_with_throw(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.strictEqual(err.message, msg); } }); it("should reject a promise when panicking and throwing while settling with a channel", async function () { const msg = "Rejected!"; try { await addon.deferred_settle_with_panic(msg); throw new Error("Did not throw"); } catch (err) { assert.instanceOf(err, Error); assert.ok( /panic/i.test(err.message), "Expected error message to indicate a panic" ); assert.instanceOf(err.panic, Error); } }); it("should be able to settle a promise with a Rust value", async function () { assert.strictEqual(await addon.settleHelloWorld(), "Hello, World!"); }); }); ================================================ FILE: test/napi/lib/typedarrays.js ================================================ var addon = require(".."); var assert = require("chai").assert; const { Worker, isMainThread, parentPort } = require("worker_threads"); if (!isMainThread) { parentPort.on("message", (message) => { // transfer it back parentPort.postMessage(message, [message]); }); return; } // A background thread we can transfer buffers to as a way to force // them to be detached (see the `detach` function). const DETACH_WORKER = new Worker(__filename); // Allow the test harness to spin down the background thread and exit // when the main thread completes. DETACH_WORKER.unref(); function detach(buffer) { if (!(buffer instanceof ArrayBuffer)) { throw new TypeError(); } DETACH_WORKER.postMessage(buffer, [buffer]); return new Promise((resolve) => DETACH_WORKER.once("message", resolve)); } describe("Typed arrays", function () { it("correctly reads a TypedArray using the borrow API", function () { var b = new ArrayBuffer(32); var a = new Int32Array(b, 4, 4); a[0] = 49; a[1] = 1350; a[2] = 11; a[3] = 237; assert.equal(addon.read_typed_array_with_borrow(a, 0), 49); assert.equal(addon.read_typed_array_with_borrow(a, 1), 1350); assert.equal(addon.read_typed_array_with_borrow(a, 2), 11); assert.equal(addon.read_typed_array_with_borrow(a, 3), 237); }); it("correctly writes to a TypedArray using the borrow_mut API", function () { var b = new ArrayBuffer(32); var a = new Int32Array(b, 4, 4); addon.write_typed_array_with_borrow_mut(a, 0, 43); assert.equal(a[0], 43); addon.write_typed_array_with_borrow_mut(a, 1, 1000); assert.equal(a[1], 1000); addon.write_typed_array_with_borrow_mut(a, 2, 22); assert.equal(a[2], 22); addon.write_typed_array_with_borrow_mut(a, 3, 243); assert.equal(a[3], 243); }); it("correctly reads a Buffer as a typed array", function () { var a = Buffer.from([49, 135, 11, 237]); assert.equal(addon.read_u8_typed_array(a, 0), 49); assert.equal(addon.read_u8_typed_array(a, 1), 135); assert.equal(addon.read_u8_typed_array(a, 2), 11); assert.equal(addon.read_u8_typed_array(a, 3), 237); }); it("copies the contents of one typed array to another", function () { const a = new Uint32Array([1, 2, 3, 4]); const b = new Uint32Array(a.length); addon.copy_typed_array(a, b); assert.deepEqual([...a], [...b]); }); it("cannot borrow overlapping buffers", function () { const buf = new ArrayBuffer(20); const arr = new Uint32Array(buf); const a = new Uint32Array(buf, 4, 2); const b = new Uint32Array(buf, 8, 2); assert.throws(() => addon.copy_typed_array(a, b)); }); it("gets a 16-byte, zeroed ArrayBuffer", function () { var b = addon.return_array_buffer(); assert.equal(b.byteLength, 16); assert.equal(new Uint32Array(b)[0], 0); assert.equal(new Uint32Array(b)[1], 0); assert.equal(new Uint32Array(b)[2], 0); assert.equal(new Uint32Array(b)[3], 0); }); it("correctly reads an ArrayBuffer using the lock API", function () { var b = new ArrayBuffer(16); var a = new Uint32Array(b); a[0] = 47; a[1] = 133; a[2] = 9; a[3] = 88888888; assert.equal(addon.read_array_buffer_with_lock(a, 0), 47); assert.equal(addon.read_array_buffer_with_lock(a, 1), 133); assert.equal(addon.read_array_buffer_with_lock(a, 2), 9); assert.equal(addon.read_array_buffer_with_lock(a, 3), 88888888); }); it("correctly reads an ArrayBuffer using the borrow API", function () { var b = new ArrayBuffer(4); var a = new Uint8Array(b); a[0] = 49; a[1] = 135; a[2] = 11; a[3] = 237; assert.equal(addon.read_array_buffer_with_borrow(b, 0), 49); assert.equal(addon.read_array_buffer_with_borrow(b, 1), 135); assert.equal(addon.read_array_buffer_with_borrow(b, 2), 11); assert.equal(addon.read_array_buffer_with_borrow(b, 3), 237); }); it("correctly writes to an ArrayBuffer using the lock API", function () { var b = new ArrayBuffer(16); addon.write_array_buffer_with_lock(b, 0, 3); assert.equal(new Uint8Array(b)[0], 3); addon.write_array_buffer_with_lock(b, 1, 42); assert.equal(new Uint8Array(b)[1], 42); addon.write_array_buffer_with_lock(b, 2, 127); assert.equal(new Uint8Array(b)[2], 127); addon.write_array_buffer_with_lock(b, 3, 255); assert.equal(new Uint8Array(b)[3], 255); }); it("correctly writes to an ArrayBuffer using the borrow_mut API", function () { var b = new ArrayBuffer(4); addon.write_array_buffer_with_borrow_mut(b, 0, 43); assert.equal(new Uint8Array(b)[0], 43); addon.write_array_buffer_with_borrow_mut(b, 1, 100); assert.equal(new Uint8Array(b)[1], 100); addon.write_array_buffer_with_borrow_mut(b, 2, 22); assert.equal(new Uint8Array(b)[2], 22); addon.write_array_buffer_with_borrow_mut(b, 3, 243); assert.equal(new Uint8Array(b)[3], 243); }); it("gets a 16-byte, uninitialized Buffer", function () { var b = addon.return_uninitialized_buffer(); assert.ok(b.length === 16); }); it("gets a 16-byte, zeroed Buffer", function () { var b = addon.return_buffer(); assert.ok(b.equals(Buffer.alloc(16))); }); it("gets a 16-byte buffer initialized from a slice", function () { var b = addon.return_array_buffer_from_slice(16); var a = new Uint8Array(b); for (var i = 0; i < 16; i++) { assert.strictEqual(a[i], i); } }); it("gets an external Buffer", function () { var expected = "String to copy"; var buf = addon.return_external_buffer(expected); assert.instanceOf(buf, Buffer); assert.strictEqual(buf.toString(), expected); }); it("gets an external ArrayBuffer", function () { var expected = "String to copy"; var buf = addon.return_external_array_buffer(expected); assert.instanceOf(buf, ArrayBuffer); assert.strictEqual(Buffer.from(buf).toString(), expected); }); it("gets a typed array constructed from an ArrayBuffer", function () { var b = new ArrayBuffer(64); var i8 = addon.return_int8array_from_arraybuffer(b); assert.strictEqual(i8.byteLength, 64); assert.strictEqual(i8.length, 64); i8[0] = 0x17; i8[1] = -0x17; assert.deepEqual([...i8.slice(0, 2)], [0x17, -0x17]); var b = new ArrayBuffer(64); var i16 = addon.return_int16array_from_arraybuffer(b); assert.strictEqual(i16.byteLength, 64); assert.strictEqual(i16.length, 32); i16[0] = 0x1234; i16[1] = -1; i16[2] = -2; i16[3] = 0x5678; assert.deepEqual([...i16.slice(0, 4)], [0x1234, -1, -2, 0x5678]); var u8 = new Uint8Array(b); assert.deepEqual( [...u8.slice(0, 8)], [0x34, 0x12, 0xff, 0xff, 0xfe, 0xff, 0x78, 0x56] ); var b = new ArrayBuffer(64); var u32 = addon.return_uint32array_from_arraybuffer(b); assert.strictEqual(u32.byteLength, 64); assert.strictEqual(u32.length, 16); u32[0] = 0x12345678; var u8 = new Uint8Array(b); assert.deepEqual([...u8.slice(0, 4)], [0x78, 0x56, 0x34, 0x12]); var b = new ArrayBuffer(64); var f64 = addon.return_float64array_from_arraybuffer(b); assert.strictEqual(f64.byteLength, 64); assert.strictEqual(f64.length, 8); f64[0] = 1.0; f64[1] = 2.0; f64[2] = 3.141592653589793; assert.deepEqual([...f64.slice(0, 3)], [1.0, 2.0, 3.141592653589793]); assert.deepEqual( [...new Float64Array(b).slice(0, 3)], [1.0, 2.0, 3.141592653589793] ); var b = new ArrayBuffer(64); var u64 = addon.return_biguint64array_from_arraybuffer(b); assert.strictEqual(u64.byteLength, 64); assert.strictEqual(u64.length, 8); u64[0] = 0x1234567887654321n; u64[1] = 0xcafed00d1337c0den; var u8 = new Uint8Array(b); assert.deepEqual( [...u64.slice(0, 2)], [0x1234567887654321n, 0xcafed00d1337c0den] ); assert.deepEqual( [...u8.slice(0, 16)], [ 0x21, 0x43, 0x65, 0x87, 0x78, 0x56, 0x34, 0x12, 0xde, 0xc0, 0x37, 0x13, 0x0d, 0xd0, 0xfe, 0xca, ] ); }); it("gets a new typed array", function () { var i32 = addon.return_new_int32array(16); assert.strictEqual(i32.constructor, Int32Array); assert.strictEqual(i32.byteLength, 64); assert.strictEqual(i32.length, 16); assert.deepEqual( [...i32], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ); }); it("gets a typed array copied from a slice", function () { var i32 = addon.return_int32array_from_slice(16); for (var i = 0; i < 16; i++) { assert.strictEqual(i32[i], i); } }); it("gets correct typed array info", function () { var buf = new ArrayBuffer(128); var a = addon.return_int8array_from_arraybuffer(buf); var info = addon.get_typed_array_info(a); assert.strictEqual(buf, a.buffer); assert.strictEqual(0, a.byteOffset); assert.strictEqual(128, a.length); assert.strictEqual(128, a.byteLength); assert.strictEqual(buf, info.buffer); assert.strictEqual(0, info.byteOffset); assert.strictEqual(128, info.length); assert.strictEqual(128, info.byteLength); var a = addon.return_int16array_from_arraybuffer(buf); var info = addon.get_typed_array_info(a); assert.strictEqual(buf, a.buffer); assert.strictEqual(0, a.byteOffset); assert.strictEqual(64, a.length); assert.strictEqual(128, a.byteLength); assert.strictEqual(buf, info.buffer); assert.strictEqual(0, info.byteOffset); assert.strictEqual(64, info.length); assert.strictEqual(128, info.byteLength); var a = addon.return_uint32array_from_arraybuffer(buf); var info = addon.get_typed_array_info(a); assert.strictEqual(buf, a.buffer); assert.strictEqual(0, a.byteOffset); assert.strictEqual(32, a.length); assert.strictEqual(128, a.byteLength); assert.strictEqual(buf, info.buffer); assert.strictEqual(0, info.byteOffset); assert.strictEqual(32, info.length); assert.strictEqual(128, info.byteLength); var a = addon.return_biguint64array_from_arraybuffer(buf); var info = addon.get_typed_array_info(a); assert.strictEqual(buf, a.buffer); assert.strictEqual(0, a.byteOffset); assert.strictEqual(16, a.length); assert.strictEqual(128, a.byteLength); assert.strictEqual(buf, info.buffer); assert.strictEqual(0, info.byteOffset); assert.strictEqual(16, info.length); assert.strictEqual(128, info.byteLength); }); it("correctly constructs a view over a slice of a buffer", function () { var buf = new ArrayBuffer(128); var a = addon.return_uint32array_from_arraybuffer_region(buf, 16, 4); var info = addon.get_typed_array_info(a); assert.strictEqual(buf, a.buffer); assert.strictEqual(16, a.byteOffset); assert.strictEqual(4, a.length); assert.strictEqual(16, a.byteLength); assert.strictEqual(buf, info.buffer); assert.strictEqual(16, info.byteOffset); assert.strictEqual(4, info.length); assert.strictEqual(16, info.byteLength); a[0] = 17; a[1] = 42; a[2] = 100; a[3] = 1000; var left = buf.slice(0, 16); var middle = buf.slice(16, 32); var right = buf.slice(32); assert.deepEqual(new Uint8Array(16), new Uint8Array(left)); assert.deepEqual( new Uint8Array([17, 0, 0, 0, 42, 0, 0, 0, 100, 0, 0, 0, 232, 3, 0, 0]), new Uint8Array(middle) ); assert.deepEqual(new Uint8Array(96), new Uint8Array(right)); }); it("properly fails to construct typed arrays with invalid arguments", function () { var buf = new ArrayBuffer(32); try { addon.return_uint32array_from_arraybuffer_region(buf, 1, 4); assert.fail("should have thrown for unaligned offset"); } catch (expected) {} try { addon.return_uint32array_from_arraybuffer_region(buf, 100, 104); assert.fail("should have thrown for bounds check failure"); } catch (expected) {} try { addon.return_uint32array_from_arraybuffer_region(buf, 0, 5); assert.fail("should have thrown for invalid length"); } catch (expected) {} try { addon.return_uint32array_from_arraybuffer_region(buf, 0, 10); assert.fail("should have thrown for excessive length"); } catch (expected) {} }); it("correctly reads a Buffer using the lock API", function () { var b = Buffer.allocUnsafe(16); b.writeUInt8(147, 0); b.writeUInt8(113, 1); b.writeUInt8(109, 2); b.writeUInt8(189, 3); assert.equal(addon.read_buffer_with_lock(b, 0), 147); assert.equal(addon.read_buffer_with_lock(b, 1), 113); assert.equal(addon.read_buffer_with_lock(b, 2), 109); assert.equal(addon.read_buffer_with_lock(b, 3), 189); }); it("correctly reads a Buffer using the borrow API", function () { var b = Buffer.from([149, 224, 70, 229]); assert.equal(addon.read_buffer_with_borrow(b, 0), 149); assert.equal(addon.read_buffer_with_borrow(b, 1), 224); assert.equal(addon.read_buffer_with_borrow(b, 2), 70); assert.equal(addon.read_buffer_with_borrow(b, 3), 229); }); it("correctly writes to a Buffer using the lock API", function () { var b = Buffer.allocUnsafe(16); b.fill(0); addon.write_buffer_with_lock(b, 0, 6); assert.equal(b.readUInt8(0), 6); addon.write_buffer_with_lock(b, 1, 61); assert.equal(b.readUInt8(1), 61); addon.write_buffer_with_lock(b, 2, 45); assert.equal(b.readUInt8(2), 45); addon.write_buffer_with_lock(b, 3, 216); assert.equal(b.readUInt8(3), 216); }); it("correctly writes to a Buffer using the borrow_mut API", function () { var b = Buffer.alloc(4); addon.write_buffer_with_borrow_mut(b, 0, 16); assert.equal(b[0], 16); addon.write_buffer_with_borrow_mut(b, 1, 100); assert.equal(b[1], 100); addon.write_buffer_with_borrow_mut(b, 2, 232); assert.equal(b[2], 232); addon.write_buffer_with_borrow_mut(b, 3, 55); assert.equal(b[3], 55); }); it("copies from a source buffer to a destination with borrow API", function () { for (const f of [addon.copy_buffer, addon.copy_buffer_with_borrow]) { const a = Buffer.from([1, 2, 3]); const b = Buffer.from([0, 0, 0, 4, 5, 6]); // Full addon.copy_buffer(a, b); assert.deepEqual([...b], [1, 2, 3, 4, 5, 6]); // Empty buffers addon.copy_buffer(Buffer.alloc(0), Buffer.alloc(0)); } }); it("zeroes the byteLength when an ArrayBuffer is detached", function () { var buf = new ArrayBuffer(16); assert.strictEqual(buf.byteLength, 16); assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 16); detach(buf); assert.strictEqual(buf.byteLength, 0); assert.strictEqual(addon.get_arraybuffer_byte_length(buf), 0); }); function testDetach( arr, addonFn, byteLengthBefore, lengthBefore, byteOffsetBefore ) { let { before, after } = addonFn(arr, (arr) => detach(arr.buffer)); assert.strictEqual(before.byteLength, byteLengthBefore); assert.strictEqual(before.length, lengthBefore); assert.strictEqual(before.byteOffset, byteOffsetBefore); assert.strictEqual(after.byteLength, 0); assert.strictEqual(after.length, 0); assert.strictEqual(after.byteOffset, 0); } it("provides correct metadata when detaching a typed array's buffer", function () { var buf = new ArrayBuffer(16); var arr = new Uint32Array(buf, 4, 2); var buf = arr.buffer; assert.strictEqual(buf.byteLength, 16); assert.strictEqual(arr.byteLength, 8); assert.strictEqual(arr.length, 2); assert.strictEqual(arr.byteOffset, 4); var info = addon.get_typed_array_info(arr); assert.strictEqual(info.byteLength, 8); assert.strictEqual(info.length, 2); assert.strictEqual(info.byteOffset, 4); assert.strictEqual(info.buffer, buf); testDetach(arr, addon.detach_same_handle, 8, 2, 4); var info = addon.get_typed_array_info(arr); assert.strictEqual(buf.byteLength, 0); assert.strictEqual(arr.buffer, buf); assert.strictEqual(arr.byteLength, 0); assert.strictEqual(arr.length, 0); assert.strictEqual(arr.byteOffset, 0); assert.strictEqual(info.byteLength, 0); assert.strictEqual(info.length, 0); assert.strictEqual(info.byteOffset, 0); assert.strictEqual(info.buffer, buf); }); it("provides correct metadata when detaching an escaped typed array's buffer", function () { var buf = new ArrayBuffer(16); testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_escape, 8, 2, 4); }); it("provides correct metadata when detaching a casted typed array's buffer", function () { var buf = new ArrayBuffer(16); testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_cast, 8, 2, 4); }); it("provides correct metadata when detaching an un-rooted typed array's buffer", function () { var buf = new ArrayBuffer(16); testDetach(new Uint32Array(buf, 4, 2), addon.detach_and_unroot, 8, 2, 4); }); it("doesn't validate regions without instantiating", function () { var buf = new ArrayBuffer(64); try { addon.build_f32_region(buf, 1, 4, false); } catch (e) { assert.fail( "misaligned region shouldn't be validated without instantiating" ); } try { addon.build_f32_region(buf, 0, 20, false); } catch (e) { assert.fail( "region overrun shouldn't be validated without instantiating" ); } try { addon.build_f64_region(buf, 1, 4, false); } catch (e) { assert.fail( "misaligned region shouldn't be validated without instantiating" ); } try { addon.build_f64_region(buf, 0, 10, false); } catch (e) { assert.fail( "region overrun shouldn't be validated without instantiating" ); } }); it("validates regions when instantiating", function () { var buf = new ArrayBuffer(64); try { addon.build_f32_region(buf, 1, 4, false); assert.fail("misaligned region should be validated when instantiating"); } catch (expected) {} try { addon.build_f32_region(buf, 0, 20, false); assert.fail("region overrun should be validated when instantiating"); } catch (expected) {} try { addon.build_f64_region(buf, 1, 4, true); assert.fail("misaligned region should be validated when instantiating"); } catch (expected) {} try { addon.build_f64_region(buf, 0, 10, true); assert.fail("region overrun should be validated when instantiating"); } catch (expected) {} }); }); ================================================ FILE: test/napi/lib/types.js ================================================ var addon = require(".."); var assert = require("chai").assert; describe("type checks", function () { it("is_array", function () { assert(addon.is_array([])); assert(addon.is_array(new Array())); assert(!addon.is_array(null)); assert(!addon.is_array(1)); assert(!addon.is_array({ 0: "a", 1: "b", length: 2 })); }); it("is_array_buffer", function () { assert(addon.is_array_buffer(new ArrayBuffer(0))); assert(!addon.is_array_buffer(new DataView(new ArrayBuffer(0)))); assert(!addon.is_array_buffer(new Uint8Array(1024))); assert(!addon.is_array_buffer(Buffer.alloc(64))); assert(!addon.is_array_buffer([])); assert(!addon.is_array_buffer("hello world")); }); it("is_uint32_array", function () { assert(addon.is_uint32_array(new Uint32Array(0))); assert(!addon.is_uint32_array(new Uint16Array(0))); }); it("is_boolean", function () { assert(addon.is_boolean(true)); assert(addon.is_boolean(false)); assert(!addon.is_boolean(new Boolean(true))); assert(!addon.is_boolean(new Boolean(false))); }); it("is_buffer", function () { assert(addon.is_buffer(Buffer.alloc(64))); assert(addon.is_buffer(new Uint8Array(64))); assert(!addon.is_buffer(new ArrayBuffer(64))); }); it("is_error", function () { assert(addon.is_error(new Error())); assert(addon.is_error(new TypeError())); class SubclassError extends Error {} assert(addon.is_error(new SubclassError())); assert(!addon.is_error("something went wrong!")); }); it("is_null", function () { assert(addon.is_null(null)); assert(!addon.is_null(undefined)); assert(!addon.is_null("anything other than null")); }); it("is_number", function () { assert(addon.is_number(0)); assert(addon.is_number(1.4526456453)); assert(addon.is_number(NaN)); assert(!addon.is_number(new Number(2))); assert(!addon.is_number("42")); }); it("is_object", function () { assert(addon.is_object({})); assert(addon.is_object(new Number(1))); assert(addon.is_object(new String("1"))); // Unlike `typeof`, is_object does *not* consider `null` to be an Object. assert(!addon.is_object(null)); assert(!addon.is_object(undefined)); assert(!addon.is_object(1)); assert(!addon.is_object("1")); }); it("is_string", function () { assert(addon.is_string("1")); assert(!addon.is_string(new String("1"))); }); it("is_undefined", function () { assert(addon.is_undefined(undefined)); assert(!addon.is_undefined(null)); assert(!addon.is_undefined("anything other than undefined")); }); it("strict_equals", function () { assert(addon.strict_equals(17, 17)); assert(!addon.strict_equals(17, 18)); let o1 = {}; let o2 = {}; assert(addon.strict_equals(o1, o1)); assert(!addon.strict_equals(o1, o2)); assert(!addon.strict_equals(o1, 17)); }); }); ================================================ FILE: test/napi/lib/workers.js ================================================ const assert = require("assert"); const { Worker, isMainThread, parentPort, threadId, workerData, } = require("worker_threads"); const addon = require(".."); // Receive a message, try that method and return the error message if (!isMainThread) { // RACE: Attempt to reproduce shutdown race condition bug. This depends on timings // that may differ across systems. It should not produce spurious failures, but may // succeed even if the presence of a bug. addon.reject_after(new Error("Oh, no!"), 200).catch(() => {}); // Reproduce another shutdown bug; this one isn't timing-dependent. let boxed_channels = addon.box_channels(); addon.get_or_init_thread_id(threadId); parentPort.once("message", (message) => { try { switch (message) { case "get_and_replace": addon.get_and_replace({}); break; case "get_or_init": addon.get_or_init(() => ({})); break; case "get_or_init_clone": addon.get_or_init_clone(() => ({})); break; case "get_thread_id": { let id = addon.get_or_init_thread_id(NaN); parentPort.postMessage(id); } break; default: throw new Error(`Unexpected message: ${message}`); } throw new Error("Did not throw an exception"); } catch (err) { parentPort.postMessage(err); } }); if (workerData === "notify_when_startup_complete") { parentPort.postMessage("startup_complete"); } return; } // From here on, we're in the main thread. // Set the `THREAD_ID` Global value in the main thread cell. addon.get_or_init_thread_id(threadId); describe("Worker / Root Tagging Tests", () => { describe("Single Threaded", () => { it("should be able to stash a global with `get_and_replace`", () => { const first = {}; const second = {}; assert.strictEqual(addon.get_and_replace(first), undefined); assert.strictEqual(addon.get_and_replace(second), first); assert.strictEqual(addon.get_and_replace({}), second); }); it("should be able to lazily initialize with `get_or_init`", () => { const o = {}; assert.strictEqual( addon.get_or_init(() => o), o ); assert.strictEqual( addon.get_or_init(() => ({})), o ); assert.strictEqual(addon.get_or_init(), o); }); it("should be able to lazily initialize with `get_or_init_clone`", () => { const o = {}; assert.strictEqual( addon.get_or_init_clone(() => o), o ); assert.strictEqual( addon.get_or_init_clone(() => ({})), o ); assert.strictEqual(addon.get_or_init_clone(), o); }); }); // Note: These tests require that the previous set of tests have run or else they will fail describe("Multi-Threaded", () => { it("should fail to use `get_and_replace`", (cb) => { const worker = new Worker(__filename); after(() => worker.terminate()); worker.once("message", (message) => { assert.ok(/wrong module/.test(message)); cb(); }); worker.postMessage("get_and_replace"); }); it("should fail to use `get_or_init`", (cb) => { const worker = new Worker(__filename); after(() => worker.terminate()); worker.once("message", (message) => { assert.ok(/wrong module/.test(message)); cb(); }); worker.postMessage("get_or_init"); }); it("should fail to use `get_or_init`", (cb) => { const worker = new Worker(__filename); after(() => worker.terminate()); worker.once("message", (message) => { assert.ok(/wrong module/.test(message)); cb(); }); worker.postMessage("get_or_init_clone"); }); }); }); describe("Instance-local storage", () => { it("should be able to read an instance local from the main thread", () => { let lookedUpId = addon.get_or_init_thread_id(NaN); assert(!Number.isNaN(lookedUpId)); assert.strictEqual(lookedUpId, threadId); }); it("should be able to store rooted objects in instance locals", () => { addon.stash_global_object(); assert.strictEqual(global, addon.unstash_global_object()); }); it("should gracefully panic upon reentrant get_or_try_init", () => { // 1. Global should start out uninitialized assert.strictEqual(null, addon.get_reentrant_value()); // 2. Re-entrancy should panic let innerClosureExecuted = false; try { let result = addon.reentrant_try_init(() => { addon.reentrant_try_init(() => { innerClosureExecuted = true; }); }); assert.fail("should have panicked on re-entrancy"); } catch (expected) { assert.strictEqual( innerClosureExecuted, false, "inner closure should not have executed" ); } try { // 3. Local should still be uninitialized assert.strictEqual(null, addon.get_reentrant_value()); // 4. Successful fallible initialization let result = addon.reentrant_try_init(() => {}); assert.strictEqual(42, result); assert.strictEqual(42, addon.get_reentrant_value()); } catch (unexpected) { assert.fail("couldn't set reentrant local after initial failure"); } }); it("should allocate separate locals for each addon instance", (cb) => { let mainThreadId = addon.get_or_init_thread_id(NaN); assert(!Number.isNaN(mainThreadId)); const worker = new Worker(__filename); after(() => worker.terminate()); worker.once("message", (message) => { assert.strictEqual(typeof message, "number"); assert.notStrictEqual(message, mainThreadId); let mainThreadIdAgain = addon.get_or_init_thread_id(NaN); assert(!Number.isNaN(mainThreadIdAgain)); assert.strictEqual(mainThreadIdAgain, mainThreadId); cb(); }); worker.postMessage("get_thread_id"); }); it("should be able to exit a worker without a crash", (cb) => { const worker = new Worker(__filename, { workerData: "notify_when_startup_complete", }); worker.once("message", async () => { await worker.terminate(); setTimeout(cb, 200); }); }); }); ================================================ FILE: test/napi/package.json ================================================ { "name": "napi-tests", "version": "0.1.0", "description": "Acceptance test suite for Neon with N-API backend", "author": "The Neon Community", "license": "MIT", "scripts": { "install": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics", "mocha": "mocha", "test": "mocha --v8-expose-gc --timeout 5000 --recursive lib" }, "devDependencies": { "cargo-cp-artifact": "^0.1.9", "chai": "^4.3.10", "mocha": "^10.2.0" } } ================================================ FILE: test/napi/src/js/arrays.rs ================================================ use neon::prelude::*; pub fn return_js_array(mut cx: FunctionContext) -> JsResult { Ok(cx.empty_array()) } pub fn return_js_array_with_number(mut cx: FunctionContext) -> JsResult { let array: Handle = JsArray::new(&mut cx, 1); let n = cx.number(9000.0); array.set(&mut cx, 0, n)?; Ok(array) } pub fn return_js_array_with_string(mut cx: FunctionContext) -> JsResult { let array: Handle = JsArray::new(&mut cx, 1); let s = cx.string("hello node"); array.set(&mut cx, 0, s)?; Ok(array) } pub fn read_js_array(mut cx: FunctionContext) -> JsResult { let array: Handle = cx.argument(0)?; let first_element = array.get(&mut cx, 0)?; Ok(first_element) } ================================================ FILE: test/napi/src/js/bigint.rs ================================================ /// Tests for [`JsBigInt`]. All unit tests are prefixed with `test_` and exported by /// [`bigint_suite`]. use std::{any, cmp::PartialEq, fmt, panic, str::FromStr}; use neon::{ prelude::*, types::{ bigint::{RangeError, Sign}, JsBigInt, }, }; use num_bigint_dig::BigInt; // Helper that converts panics to exceptions to allow `.unwrap()` usage in unit tests fn panic_catch<'cx, F, C>(cx: &mut C, f: F) -> JsResult<'cx, JsFunction> where F: Fn(&mut FunctionContext) -> NeonResult<()> + 'static, C: Context<'cx>, { JsFunction::new(cx, move |mut cx| { panic::catch_unwind(panic::AssertUnwindSafe(|| f(&mut cx))).or_else(|panic| { if let Some(s) = panic.downcast_ref::<&str>() { cx.throw_error(s) } else if let Some(s) = panic.downcast_ref::() { cx.throw_error(s) } else { panic::resume_unwind(panic) } })??; Ok(cx.undefined()) }) } // Export a test that is expected not to throw fn export(cx: &mut FunctionContext, o: &JsObject, f: F) -> NeonResult<()> where F: Fn(&mut FunctionContext) -> NeonResult<()> + 'static, { let f = panic_catch(cx, f)?; o.set(cx, any::type_name::(), f)?; Ok(()) } // Export a test that is expected to return a `bigint::Error` fn export_lossy(cx: &mut FunctionContext, o: &JsObject, f: F) -> NeonResult<()> where F: Fn(&mut FunctionContext) -> NeonResult>> + 'static, { let f = panic_catch(cx, move |cx| { if f(cx)?.is_err() { return Ok(()); } cx.throw_error("Expected a lossy error") })?; o.set(cx, any::type_name::(), f)?; Ok(()) } // Small helper for `eval` of a script from a Rust string. This is used // for creating `BigInt` inline from literals (e.g., `0n`). fn eval<'cx, C>(cx: &mut C, script: &str) -> JsResult<'cx, JsValue> where C: Context<'cx>, { let script = cx.string(script); neon::reflect::eval(cx, script) } // Throws an exception if `l !== r` where operands are JavaScript values fn strict_eq<'cx, L, R, C>(l: Handle<'cx, L>, r: Handle<'cx, R>, cx: &mut C) -> NeonResult<()> where L: Value, R: Value, C: Context<'cx>, { if l.strict_equals(cx, r) { return Ok(()); } let l = l.to_string(cx)?.value(cx); let r = r.to_string(cx)?.value(cx); cx.throw_error(format!("Expected {l} to equal {r}")) } // Throws an exception if `l != r` where operands are Rust values fn assert_eq<'cx, L, R, C>(l: L, r: R, cx: &mut C) -> NeonResult<()> where L: fmt::Debug + PartialEq, R: fmt::Debug, C: Context<'cx>, { if l == r { return Ok(()); } cx.throw_error(format!("Expected {l:?} to equal {r:?}")) } // Create a `JsBigInt` from a `BigInt` fn bigint<'cx, C>(cx: &mut C, n: &str) -> JsResult<'cx, JsBigInt> where C: Context<'cx>, { let n = BigInt::from_str(n).or_else(|err| cx.throw_error(err.to_string()))?; let (sign, n) = n.to_bytes_le(); let n = n .chunks(8) .map(|c| { let mut x = [0; 8]; (x[..c.len()]).copy_from_slice(c); u64::from_le_bytes(x) }) .collect::>(); let sign = if matches!(sign, num_bigint_dig::Sign::Minus) { Sign::Negative } else { Sign::Positive }; Ok(JsBigInt::from_digits_le(cx, sign, &n)) } // Convert a `JsBigInt` to a `BigInt` fn to_bigint<'cx, V, C>(b: Handle, cx: &mut C) -> NeonResult where V: Value, C: Context<'cx>, { let (sign, digits) = b.downcast_or_throw::(cx)?.to_digits_le(cx); let sign = match sign { Sign::Positive => num_bigint_dig::Sign::Plus, Sign::Negative => num_bigint_dig::Sign::Minus, }; Ok(BigInt::from_slice_native(sign, &digits)) } fn test_from_u64(cx: &mut FunctionContext) -> NeonResult<()> { strict_eq(JsBigInt::from_u64(cx, 0), eval(cx, "0n")?, cx)?; strict_eq(JsBigInt::from_u64(cx, 42), eval(cx, "42n")?, cx)?; strict_eq( JsBigInt::from_u64(cx, u64::MAX), eval(cx, &(u64::MAX.to_string() + "n"))?, cx, )?; Ok(()) } fn test_from_i64(cx: &mut FunctionContext) -> NeonResult<()> { strict_eq(JsBigInt::from_i64(cx, 0), eval(cx, "0n")?, cx)?; strict_eq(JsBigInt::from_i64(cx, 42), eval(cx, "42n")?, cx)?; strict_eq(JsBigInt::from_i64(cx, -42), eval(cx, "-42n")?, cx)?; strict_eq( JsBigInt::from_i64(cx, i64::MAX), eval(cx, &(i64::MAX.to_string() + "n"))?, cx, )?; strict_eq( JsBigInt::from_i64(cx, i64::MIN), eval(cx, &(i64::MIN.to_string() + "n"))?, cx, )?; Ok(()) } fn test_from_u128(cx: &mut FunctionContext) -> NeonResult<()> { strict_eq(JsBigInt::from_u128(cx, 0), eval(cx, "0n")?, cx)?; strict_eq(JsBigInt::from_u128(cx, 42), eval(cx, "42n")?, cx)?; strict_eq( JsBigInt::from_u128(cx, u128::MAX), eval(cx, "2n ** 128n - 1n")?, cx, )?; strict_eq( JsBigInt::from_u128(cx, u128::MAX - 1), eval(cx, "2n ** 128n - 2n")?, cx, )?; Ok(()) } fn test_from_i128(cx: &mut FunctionContext) -> NeonResult<()> { strict_eq(JsBigInt::from_i128(cx, 0), eval(cx, "0n")?, cx)?; strict_eq(JsBigInt::from_i128(cx, 42), eval(cx, "42n")?, cx)?; strict_eq(JsBigInt::from_i128(cx, -42), eval(cx, "-42n")?, cx)?; strict_eq( JsBigInt::from_i128(cx, i128::MAX), eval(cx, "2n ** 127n - 1n")?, cx, )?; strict_eq( JsBigInt::from_i128(cx, i128::MAX - 1), eval(cx, "2n ** 127n - 2n")?, cx, )?; strict_eq( JsBigInt::from_i128(cx, i128::MIN), eval(cx, "-(2n ** 127n)")?, cx, )?; strict_eq( JsBigInt::from_i128(cx, i128::MIN + 1), eval(cx, "-(2n ** 127n - 1n)")?, cx, )?; Ok(()) } fn test_from_digits_le(cx: &mut FunctionContext) -> NeonResult<()> { strict_eq(bigint(cx, "0")?, eval(cx, "0n")?, cx)?; strict_eq(bigint(cx, "42")?, eval(cx, "42n")?, cx)?; strict_eq(bigint(cx, "-42")?, eval(cx, "-42n")?, cx)?; strict_eq( bigint(cx, "170141183460469231731687303715884105727")?, eval(cx, "170141183460469231731687303715884105727n")?, cx, )?; strict_eq( bigint(cx, "-170141183460469231731687303715884105728")?, eval(cx, "-170141183460469231731687303715884105728n")?, cx, )?; strict_eq( bigint(cx, "10000000000000000000000000000000000000000")?, eval(cx, "10000000000000000000000000000000000000000n")?, cx, )?; strict_eq( bigint(cx, "-10000000000000000000000000000000000000000")?, eval(cx, "-10000000000000000000000000000000000000000n")?, cx, )?; Ok(()) } fn test_to_u64(cx: &mut FunctionContext) -> NeonResult<()> { assert_eq(JsBigInt::from_u64(cx, 0).to_u64(cx).or_throw(cx)?, 0, cx)?; assert_eq(JsBigInt::from_u64(cx, 42).to_u64(cx).or_throw(cx)?, 42, cx)?; assert_eq( JsBigInt::from_u64(cx, u64::MAX).to_u64(cx).or_throw(cx)?, u64::MAX, cx, )?; Ok(()) } fn test_to_i64(cx: &mut FunctionContext) -> NeonResult<()> { assert_eq(JsBigInt::from_i64(cx, 0).to_i64(cx).or_throw(cx)?, 0, cx)?; assert_eq(JsBigInt::from_i64(cx, 42).to_i64(cx).or_throw(cx)?, 42, cx)?; assert_eq( JsBigInt::from_i64(cx, -42).to_i64(cx).or_throw(cx)?, -42, cx, )?; assert_eq( JsBigInt::from_i64(cx, i64::MAX).to_i64(cx).or_throw(cx)?, i64::MAX, cx, )?; assert_eq( JsBigInt::from_i64(cx, i64::MIN).to_i64(cx).or_throw(cx)?, i64::MIN, cx, )?; Ok(()) } fn test_to_u128(cx: &mut FunctionContext) -> NeonResult<()> { assert_eq(JsBigInt::from_u128(cx, 0).to_u128(cx).or_throw(cx)?, 0, cx)?; assert_eq( JsBigInt::from_u128(cx, 42).to_u128(cx).or_throw(cx)?, 42, cx, )?; assert_eq( JsBigInt::from_u128(cx, u128::MAX) .to_u128(cx) .or_throw(cx)?, u128::MAX, cx, )?; // Extra trailing zeroes assert_eq( JsBigInt::from_digits_le(cx, JsBigInt::POSITIVE, &[u64::MAX, u64::MAX, 0, 0, 0, 0]) .to_u128(cx) .or_throw(cx)?, u128::MAX, cx, )?; Ok(()) } fn test_to_i128(cx: &mut FunctionContext) -> NeonResult<()> { assert_eq(JsBigInt::from_i128(cx, 0).to_i128(cx).or_throw(cx)?, 0, cx)?; assert_eq( JsBigInt::from_i128(cx, 42).to_i128(cx).or_throw(cx)?, 42, cx, )?; assert_eq( JsBigInt::from_i128(cx, -42).to_i128(cx).or_throw(cx)?, -42, cx, )?; assert_eq( JsBigInt::from_i128(cx, i128::MAX) .to_i128(cx) .or_throw(cx)?, i128::MAX, cx, )?; assert_eq( JsBigInt::from_i128(cx, i128::MIN) .to_i128(cx) .or_throw(cx)?, i128::MIN, cx, )?; Ok(()) } fn test_to_digits_le(cx: &mut FunctionContext) -> NeonResult<()> { assert_eq( to_bigint(eval(cx, "0n")?, cx)?, BigInt::from_str("0").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "42n")?, cx)?, BigInt::from_str("42").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "-42n")?, cx)?, BigInt::from_str("-42").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "170141183460469231731687303715884105727n")?, cx)?, BigInt::from_str("170141183460469231731687303715884105727").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "-170141183460469231731687303715884105728n")?, cx)?, BigInt::from_str("-170141183460469231731687303715884105728").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "10000000000000000000000000000000000000000n")?, cx)?, BigInt::from_str("10000000000000000000000000000000000000000").unwrap(), cx, )?; assert_eq( to_bigint(eval(cx, "-10000000000000000000000000000000000000000n")?, cx)?, BigInt::from_str("-10000000000000000000000000000000000000000").unwrap(), cx, )?; Ok(()) } fn test_very_large_number(cx: &mut FunctionContext) -> NeonResult<()> { // 2048-bit prime generated with `crypto.generatePrimeSync(2048)` // Note: Unlike the rest of the tests, this number is big-endian let n = BigInt::from_bytes_be( num_bigint_dig::Sign::Plus, &[ 228, 178, 58, 23, 125, 164, 107, 153, 254, 98, 85, 252, 29, 61, 8, 237, 212, 36, 173, 205, 116, 52, 16, 155, 131, 82, 59, 211, 132, 139, 212, 101, 10, 26, 60, 44, 172, 86, 50, 42, 9, 124, 188, 236, 77, 46, 209, 64, 239, 34, 99, 8, 235, 165, 5, 41, 159, 211, 186, 197, 140, 111, 43, 15, 111, 132, 255, 148, 36, 12, 25, 221, 208, 162, 234, 45, 22, 13, 251, 157, 103, 50, 181, 2, 53, 81, 15, 137, 129, 10, 130, 212, 74, 125, 80, 188, 19, 218, 236, 189, 234, 145, 234, 232, 9, 218, 167, 111, 33, 62, 81, 96, 83, 125, 242, 217, 179, 211, 109, 16, 210, 250, 133, 130, 86, 182, 110, 213, 74, 78, 34, 210, 88, 3, 178, 73, 231, 53, 188, 187, 76, 247, 205, 154, 190, 200, 211, 75, 63, 34, 246, 160, 193, 98, 7, 85, 40, 208, 47, 157, 34, 120, 235, 136, 101, 88, 174, 149, 180, 114, 197, 230, 116, 47, 152, 253, 212, 191, 90, 151, 204, 6, 51, 179, 73, 128, 141, 192, 107, 74, 205, 130, 56, 115, 202, 96, 79, 187, 196, 49, 118, 18, 251, 34, 64, 208, 38, 25, 35, 195, 231, 195, 201, 224, 110, 205, 213, 92, 192, 23, 48, 165, 126, 145, 18, 30, 230, 83, 229, 187, 138, 177, 74, 15, 209, 151, 83, 160, 246, 77, 59, 228, 57, 112, 165, 4, 10, 11, 95, 213, 115, 187, 240, 57, 5, 117, ], ); assert_eq(to_bigint(eval(cx, &(n.to_string() + "n"))?, cx)?, n, cx)?; Ok(()) } fn test_i64_out_of_range(cx: &mut FunctionContext) -> NeonResult>> { Ok(JsBigInt::from_i128(cx, (i64::MIN as i128) - 1).to_i64(cx)) } fn test_u64_out_of_range(cx: &mut FunctionContext) -> NeonResult>> { Ok(JsBigInt::from_u128(cx, (u64::MAX as u128) + 1).to_u64(cx)) } fn test_i128_extra_digits(cx: &mut FunctionContext) -> NeonResult>> { let res = eval(cx, "2n ** 128n")? .downcast_or_throw::(cx)? .to_i128(cx); Ok(res) } fn test_i128_overflow(cx: &mut FunctionContext) -> NeonResult>> { let res = eval(cx, "2n ** 127n")? .downcast_or_throw::(cx)? .to_i128(cx); Ok(res) } fn test_i128_underflow(cx: &mut FunctionContext) -> NeonResult>> { let res = eval(cx, "-(2n ** 127n + 1n)")? .downcast_or_throw::(cx)? .to_i128(cx); Ok(res) } fn test_u128_overflow(cx: &mut FunctionContext) -> NeonResult>> { let res = eval(cx, "2n ** 127n")? .downcast_or_throw::(cx)? .to_i128(cx); Ok(res) } fn test_u128_underflow(cx: &mut FunctionContext) -> NeonResult>> { let res = eval(cx, "-1n")? .downcast_or_throw::(cx)? .to_u128(cx); Ok(res) } // Creates a map (object) of test name to functions to be executed by a JavaScript // test runner. pub fn bigint_suite(mut cx: FunctionContext) -> JsResult { let o = cx.empty_object(); // `Ok` tests export(&mut cx, &o, test_from_u64)?; export(&mut cx, &o, test_from_i64)?; export(&mut cx, &o, test_from_u128)?; export(&mut cx, &o, test_from_i128)?; export(&mut cx, &o, test_from_digits_le)?; export(&mut cx, &o, test_to_u64)?; export(&mut cx, &o, test_to_i64)?; export(&mut cx, &o, test_to_u128)?; export(&mut cx, &o, test_to_i128)?; export(&mut cx, &o, test_to_digits_le)?; export(&mut cx, &o, test_very_large_number)?; // `Err` tests export_lossy(&mut cx, &o, test_i64_out_of_range)?; export_lossy(&mut cx, &o, test_u64_out_of_range)?; export_lossy(&mut cx, &o, test_i128_extra_digits)?; export_lossy(&mut cx, &o, test_i128_overflow)?; export_lossy(&mut cx, &o, test_i128_underflow)?; export_lossy(&mut cx, &o, test_u128_overflow)?; export_lossy(&mut cx, &o, test_u128_underflow)?; Ok(o) } ================================================ FILE: test/napi/src/js/boxed.rs ================================================ use std::cell::RefCell; use neon::{prelude::*, types::extract::Boxed}; pub struct Person { name: String, } impl Finalize for Person {} impl Person { fn new(name: impl ToString) -> Self { Self { name: name.to_string(), } } fn greet(&self) -> String { format!("Hello, {}!", self.name) } fn set_name(&mut self, name: impl ToString) { self.name = name.to_string(); } } pub fn person_new(mut cx: FunctionContext) -> JsResult> { let name = cx.argument::(0)?.value(&mut cx); let person = Person::new(name); Ok(cx.boxed(person)) } pub fn person_greet(mut cx: FunctionContext) -> JsResult { let person = cx.argument::>(0)?; let greeting = cx.string(person.greet()); Ok(greeting) } pub fn ref_person_new(mut cx: FunctionContext) -> JsResult { let name = cx.argument::(0)?.value(&mut cx); let person = RefCell::new(Person::new(name)); Ok(cx.boxed(person).upcast()) } pub fn ref_person_greet(mut cx: FunctionContext) -> JsResult { let person = cx.argument::>>(0)?; let greeting = cx.string(person.borrow().greet()); Ok(greeting) } pub fn ref_person_set_name(mut cx: FunctionContext) -> JsResult { let person = cx.argument::>>(0)?; let name = cx.argument::(1)?.value(&mut cx); person.borrow_mut().set_name(name); Ok(cx.undefined()) } pub fn ref_person_fail(mut cx: FunctionContext) -> JsResult { let person = cx.argument::>>(0)?; let _borrow = person.borrow(); let _borrow_mut = person.borrow_mut(); Ok(cx.undefined()) } pub fn external_unit(mut cx: FunctionContext) -> JsResult> { Ok(cx.boxed(())) } #[neon::export] fn create_boxed_string(s: String) -> Boxed { Boxed(s) } #[neon::export] fn boxed_string_concat(Boxed(this): Boxed, rhs: String) -> String { this + &rhs } #[neon::export] // N.B.: Intentionally including unused `cx` and not using tuple struct pattern to test the macro fn boxed_string_repeat(_cx: &mut FunctionContext, this: Boxed, n: f64) -> String { this.0.repeat(n as usize) } ================================================ FILE: test/napi/src/js/class.rs ================================================ use std::{collections::HashMap, future::Future}; use neon::{event::Channel, prelude::*, types::extract::Json}; #[neon::export] fn wrap_string(cx: &mut Cx, o: Handle, s: String) -> NeonResult<()> { neon::macro_internal::object::wrap(cx, o, s)?.or_throw(cx) } #[neon::export] fn unwrap_string(cx: &mut Cx, o: Handle) -> NeonResult { neon::macro_internal::object::unwrap(cx, o)? .cloned() .or_throw(cx) } #[derive(Debug, Clone)] pub struct Message { value: String, } #[neon::class] impl Message { pub fn new(value: String) -> Self { Self { value } } pub fn read(&self) -> &str { &self.value } pub fn concat(&self, other: &Self) -> Self { Self { value: format!("{}{}", self.value, other.value), } } pub fn append(&mut self, suffix: String) { self.value.push_str(&suffix); } pub fn finalize<'a, C: Context<'a>>(self, _cx: &mut C) { println!("Finalizing Message with value: {}", self.value); } } #[derive(Debug, Clone)] pub struct Point { x: u32, y: u32, } #[neon::class] impl Point { // Basic const properties const ORIGIN_X: u32 = 0; const ORIGIN_Y: u32 = 0; // Const property with custom name #[neon(name = "maxCoordinate")] const MAX_COORD: u32 = 1000; // Const property with simple JSON (string slice) #[neon(json)] const DEFAULT_MESSAGE: &'static [&'static str] = &["hello", "point"]; // Test complex const expressions const COMPUTED_VALUE: u32 = 10 + 20 + 12; const SIZE_OF_F64: u32 = std::mem::size_of::() as u32; const STRING_LENGTH: u32 = "complex".len() as u32; // Test const expressions that use type information const SELF_SIZE: u32 = std::mem::size_of::() as u32; const POINT_ALIGNMENT: u32 = std::mem::align_of::() as u32; // Edge case: boolean const const IS_2D: bool = true; // Edge case: const expression with conditionals const MAX_DIMENSION: u32 = if std::mem::size_of::() == 4 { 2147483647 } else { 65535 }; // Edge case: const expression with match const COORDINATE_BYTES: u32 = match std::mem::size_of::() { 4 => 4, 8 => 8, _ => 0, }; // Edge case: const expression with arithmetic (can't use sqrt in const) const DOUBLE_100_SQUARED: u32 = 100_u32.pow(2) * 2; // Edge case: string with special characters #[neon(name = "specialString")] const SPECIAL_CHARS: &'static str = "Hello\nWorld\t\"quoted\"\r\n"; // Edge case: negative number const NEGATIVE_OFFSET: i32 = -42; // Edge case: const with underscores (use u32 instead of u64 for now) const MAX_SAFE_INTEGER_APPROX: u32 = 2147483647; // Edge case: const starting with underscore (valid in JS) const _PRIVATE_CONST: u32 = 999; pub fn new(x: u32, y: u32) -> Self { Self { x, y } } pub fn x(&self) -> u32 { self.x } pub fn y(&self) -> u32 { self.y } pub fn distance(&self, other: &Self) -> f64 { let dx = (self.x as i32 - other.x as i32).pow(2); let dy = (self.y as i32 - other.y as i32).pow(2); ((dx + dy) as f64).sqrt() } pub fn midpoint(&self, other: &Self) -> Self { Self { x: (self.x + other.x) / 2, y: (self.y + other.y) / 2, } } pub fn swap_coords(&mut self, other: &mut Self) { std::mem::swap(&mut self.x, &mut other.x); std::mem::swap(&mut self.y, &mut other.y); } pub fn move_by(&mut self, dx: u32, dy: u32) { self.x += dx; self.y += dy; } pub fn set_x(&mut self, x: u32) { self.x = x; } pub fn set_y(&mut self, y: u32) { self.y = y; } } #[derive(Debug, Default)] pub struct StringBuffer { buffer: String, } #[neon::class] impl StringBuffer { pub fn push(&mut self, s: String) { self.buffer.push_str(&s); } #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { self.buffer.clone() } #[neon(name = "includes")] pub fn contains(&self, s: String) -> bool { self.buffer.contains(&s) } #[neon(name = "trimStart")] pub fn trim_start(&self) -> String { self.buffer.trim_start().to_string() } pub fn trim_end(&self) -> String { self.buffer.trim_end().to_string() } } // Test class with async methods #[derive(Debug, Clone)] pub struct AsyncClass { value: String, } #[neon::class] impl AsyncClass { // Simple const property const DEFAULT_TIMEOUT: u32 = 5000; // Const property with custom name and simple JSON #[neon(name = "version", json)] const VERSION_NUMBERS: &'static [u32] = &[1, 0, 0]; pub fn new(value: String) -> Self { Self { value } } // This would fail to compile if we tried to use &self: // pub async fn async_method(&self, suffix: String) -> String { // format!("{}{}", self.value, suffix) // } // Async method that takes ownership (required for 'static Future) pub async fn async_method(self, suffix: String) -> String { // Simulate async work format!("{}{}", self.value, suffix) } // Task method for CPU-intensive work #[neon(task)] pub fn heavy_computation(self) -> u32 { // Simulate CPU-intensive work let mut result = 0; for i in 0..100 { result += i; } result } // Normal synchronous method for comparison pub fn sync_method(&self) -> String { self.value.clone() } // JSON method for testing serde serialization #[neon(json)] pub fn json_method(&self, data: Vec) -> HashMap { let mut result = HashMap::new(); result.insert("class_value".to_string(), self.value.clone()); result.insert("input_count".to_string(), data.len().to_string()); result.insert( "first_item".to_string(), data.first().unwrap_or(&"none".to_string()).clone(), ); result } // Explicit async method - developer controls cloning #[neon(async)] pub fn explicit_async_method(&self, multiplier: i32) -> impl Future + 'static { // Can do sync work here on main thread let base_value = format!("Processing: {}", self.value); // Must return 'static Future, so can't borrow &self async move { // Simulate async work format!("{} * {}", base_value, multiplier) } } // Explicit async method that clones by choice #[neon(async)] pub fn explicit_async_clone(&self, suffix: String) -> impl Future + 'static { // Developer explicitly chooses to clone for 'static Future let value_clone = self.value.clone(); async move { format!("{}{}", value_clone, suffix) } } // Method with context parameter (sync) pub fn method_with_context<'a>( &self, cx: &mut FunctionContext<'a>, multiplier: i32, ) -> JsResult<'a, JsNumber> { let result = self.value.len() as f64 * multiplier as f64; Ok(cx.number(result)) } // Method with explicit context attribute #[neon(context)] pub fn method_with_explicit_context(&self, _ctx: &mut Cx, suffix: String) -> String { format!("{}:{}", self.value, suffix) } // Task method with Channel parameter #[neon(task)] pub fn task_with_channel(self, _ch: Channel, multiplier: i32) -> String { // Channel is available for background tasks format!("Task with channel: {} * {}", self.value, multiplier) } // AsyncFn method with Channel parameter pub async fn async_fn_with_channel(self, _ch: Channel, suffix: String) -> String { // Channel is available for async functions format!("AsyncFn with channel: {}{}", self.value, suffix) } #[allow(unused_variables)] // Method with this parameter (should auto-detect) pub fn method_with_this(&self, this: Handle, data: String) -> String { // Access to both Rust instance and JavaScript object format!( "Instance: {}, JS object available, data: {}", self.value, data ) } // Method with explicit this attribute #[neon(this)] pub fn method_with_explicit_this(&self, _js_obj: Handle, suffix: String) -> String { format!("Explicit this: {}{}", self.value, suffix) } // Method with context and this #[neon(this)] pub fn method_with_context_and_this<'a>( &self, cx: &mut FunctionContext<'a>, _this: Handle, multiplier: i32, ) -> JsResult<'a, JsNumber> { let result = self.value.len() as f64 * multiplier as f64; Ok(cx.number(result)) } // Performance test methods pub fn simple_method(&self, x: i32) -> i32 { x * 2 } #[neon(json)] pub fn json_method_perf(&self, data: Vec) -> Vec { data.into_iter().map(|x| x * 2).collect() } pub fn context_method_perf(&self, _cx: &mut FunctionContext, x: i32) -> i32 { x * 3 } // Test explicit async + JSON combination #[neon(async, json)] pub fn explicit_async_json_method( &self, data: Vec, ) -> impl Future> + 'static { let data_clone = data; async move { // Simulate async work with JSON serialization data_clone.into_iter().map(|x| x * 2).collect() } } #[neon(json)] pub async fn async_json_method(self, data: Vec) -> Vec { data.into_iter().map(|x| x * 2).collect() } } // Test Rust → JS path: Create class instance in Rust and return to JS #[neon::export] pub fn create_point_from_rust(x: u32, y: u32) -> Point { Point::new(x, y) } // Test Rust → JS with transformation #[neon::export] pub fn create_point_origin() -> Point { Point::new(Point::ORIGIN_X, Point::ORIGIN_Y) } // Test Rust → JS: Accept a point, transform it, and return a new point #[neon::export] pub fn double_point_coords(point: Point) -> Point { Point::new(point.x() * 2, point.y() * 2) } // Test class with Result return type in constructor #[derive(Debug, Clone)] pub struct FallibleCounter { value: u32, } #[neon::class] impl FallibleCounter { pub fn new(value: u32) -> Result { if value > 100 { Err("Value must be <= 100".to_string()) } else { Ok(Self { value }) } } pub fn get(&self) -> u32 { self.value } pub fn increment(&mut self) { self.value += 1; } } // Test class with context parameter in constructor (auto-inferred, no attribute needed) #[derive(Debug, Clone)] pub struct ContextCounter { value: u32, } type AlsoCx<'cx> = Cx<'cx>; #[neon::class] impl ContextCounter { #[neon(context)] pub fn new(_cx: &mut AlsoCx, value: u32) -> Self { // Could use context to access JavaScript values, call functions, etc. // Context is auto-detected because first param is &mut Cx Self { value } } pub fn get(&self) -> u32 { self.value } } // Test class with JSON in constructor #[derive(Debug, Clone, serde::Deserialize)] pub struct JsonConfig { name: String, count: u32, enabled: bool, } #[neon::class] impl JsonConfig { #[neon(json)] pub fn new(config: JsonConfig) -> Self { config } pub fn name(&self) -> String { self.name.clone() } pub fn count(&self) -> u32 { self.count } pub fn enabled(&self) -> bool { self.enabled } } // Test class combining all features: context (auto-inferred), JSON, and Result #[derive(Debug, Clone, serde::Deserialize)] pub struct ValidatedConfig { name: String, count: u32, } #[neon::class] impl ValidatedConfig { #[neon(json)] pub fn new(_cx: &mut Cx, config: ValidatedConfig) -> Result { // Validate the configuration if config.name.is_empty() { return Err("Name cannot be empty".to_string()); } if config.count > 1000 { return Err("Count must be <= 1000".to_string()); } Ok(config) } pub fn name(&self) -> String { self.name.clone() } pub fn count(&self) -> u32 { self.count } } pub struct Secret { pub value: String, } #[neon::class] impl Secret { pub fn new(cx: &mut Cx, init: Handle) -> NeonResult { if let Ok(js_str) = init.downcast::(cx) { let secret_str: String = js_str.value(cx); return Ok(Self { value: secret_str }); } if let Ok(js_thunk) = init.downcast::(cx) { let this = cx.undefined(); let js_result: Handle = js_thunk.call(cx, this, vec![])?; let secret_str: String = js_result.to_string(cx)?.value(cx); return Ok(Self { value: secret_str }); } Ok(Self { value: "default_secret".to_string(), }) } pub fn reveal(&self) -> String { self.value.clone() } } pub struct Argv { pub args: Vec, } #[neon::class] impl Argv { #[neon(json)] pub fn new(cx: &mut Cx, args: Option>) -> NeonResult { let args = if let Some(args) = args { args } else { // Use global_object() instead of global() to avoid swallowing exceptions // from property getters. The global() method internally calls get() which // catches PendingException and converts it to a generic error. let global = cx.global_object(); let process: Handle = global.prop(cx, "process").get()?; let Json(args): Json> = process.prop(cx, "argv").get()?; args }; Ok(Self { args }) } pub fn len(&self) -> u32 { self.args.len() as u32 } pub fn get(&self, index: u32) -> Option { self.args.get(index as usize).cloned() } } const CAROUSEL_MESSAGES: [&str; 5] = [ "Welcome to the Neon Carousel!", "Enjoy seamless Rust and JavaScript integration.", "Experience high performance with native modules.", "Build robust applications with ease.", "Thank you for using Neon!", ]; pub struct Carousel { state: u32, } #[neon::class] impl Carousel { pub fn new() -> Self { Self { state: 0 } } pub fn next(&mut self) -> String { let message = CAROUSEL_MESSAGES[self.state as usize]; self.state = (self.state + 1) % CAROUSEL_MESSAGES.len() as u32; message.to_string() } } #[derive(Debug, Clone, Copy)] pub struct Expando; #[neon::class] impl Expando { pub fn new(cx: &mut FunctionContext) -> NeonResult { let this: Handle = cx.this()?; this.prop(cx, "__weirdNeonExpandoKey__").set(42)?; Ok(Self) } pub fn expando(self, cx: &mut FunctionContext) -> NeonResult { let this: Handle = cx.this()?; let value: Handle = this.prop(cx, "__weirdNeonExpandoKey__").get()?; Ok(value.value(cx) as i32) } } ================================================ FILE: test/napi/src/js/coercions.rs ================================================ use neon::prelude::*; pub fn to_string(mut cx: FunctionContext) -> JsResult { let arg: Handle = cx.argument(0)?; arg.to_string(&mut cx) } ================================================ FILE: test/napi/src/js/container.rs ================================================ use neon::prelude::*; use std::{ cell::{Ref, RefCell, RefMut}, rc::Rc, sync::Arc, }; #[neon::export] fn create_string_ref_cell(s: String) -> RefCell { RefCell::new(s) } #[neon::export] fn read_string_ref_cell(s: &RefCell) -> String { s.borrow().clone() } #[neon::export] fn write_string_ref_cell(s: &RefCell, value: String) { *s.borrow_mut() = value; } #[neon::export] fn string_ref_cell_concat(lhs: &RefCell, rhs: String) -> String { lhs.borrow().clone() + &rhs } #[neon::export] fn string_ref_concat(lhs: Ref, rhs: String) -> String { lhs.clone() + &rhs } #[neon::export] fn write_string_ref(mut s: RefMut, value: String) { *s = value; } #[neon::export] fn borrow_and_then<'cx>( cx: &mut Cx<'cx>, cell: &RefCell, f: Handle, ) -> JsResult<'cx, JsString> { let s = cell.borrow(); f.bind(cx).exec()?; Ok(cx.string(s.clone())) } #[neon::export] fn borrow_mut_and_then<'cx>( cx: &mut Cx<'cx>, cell: &RefCell, f: Handle, ) -> JsResult<'cx, JsString> { let mut s = cell.borrow_mut(); f.bind(cx).exec()?; *s = "overwritten".to_string(); Ok(cx.string(s.clone())) } #[neon::export] fn create_string_rc(s: String) -> Rc { Rc::new(s) } #[neon::export] fn read_string_rc(s: Rc) -> String { (*s).clone() } #[neon::export] fn create_string_arc(s: String) -> Arc { Arc::new(s) } #[neon::export] fn read_string_arc(s: Arc) -> String { (*s).clone() } ================================================ FILE: test/napi/src/js/date.rs ================================================ use neon::{prelude::*, types::JsDate}; pub fn create_date(mut cx: FunctionContext) -> JsResult { let date = JsDate::new_lossy(&mut cx, 31415); Ok(date) } pub fn create_date_from_value(mut cx: FunctionContext) -> JsResult { let time = cx.argument::(0)?.value(&mut cx); let date = JsDate::new_lossy(&mut cx, time); Ok(date) } pub fn check_date_is_valid(mut cx: FunctionContext) -> JsResult { let time = cx.argument::(0)?.value(&mut cx); let date = JsDate::new_lossy(&mut cx, time); let is_valid = date.is_valid(&mut cx); Ok(cx.boolean(is_valid)) } pub fn try_new_date(mut cx: FunctionContext) -> JsResult { let _date_overflow = JsDate::new(&mut cx, JsDate::MAX_VALUE + 1.0); let _date_underflow = JsDate::new(&mut cx, JsDate::MIN_VALUE - 1.0); let nan_date = JsDate::new(&mut cx, f64::NAN); assert!(nan_date.unwrap().value(&mut cx).is_nan()); Ok(cx.undefined()) } pub fn try_new_lossy_date(mut cx: FunctionContext) -> JsResult { let date_overflow = JsDate::new(&mut cx, JsDate::MAX_VALUE + 1.0); let date_underflow = JsDate::new(&mut cx, JsDate::MIN_VALUE - 1.0); assert_eq!( date_overflow.unwrap_err().kind(), neon::types::DateErrorKind::Overflow ); assert_eq!( date_underflow.unwrap_err().kind(), neon::types::DateErrorKind::Underflow ); Ok(cx.undefined()) } pub fn nan_dates(mut cx: FunctionContext) -> JsResult { let date_nan = JsDate::new(&mut cx, f64::NAN).unwrap(); assert!(!date_nan.is_valid(&mut cx)); assert!(date_nan.value(&mut cx).is_nan()); let date_nan_lossy = JsDate::new_lossy(&mut cx, f64::NAN); assert!(!date_nan_lossy.is_valid(&mut cx)); assert!(date_nan_lossy.value(&mut cx).is_nan()); Ok(cx.undefined()) } pub fn check_date_is_invalid(mut cx: FunctionContext) -> JsResult { let time = JsDate::MIN_VALUE - 1.0; let date = JsDate::new_lossy(&mut cx, time); let is_valid = date.is_valid(&mut cx); let _val = date.value(&mut cx); Ok(cx.boolean(is_valid)) } pub fn create_and_get_invalid_date(mut cx: FunctionContext) -> JsResult { let time = JsDate::MAX_VALUE + 1.0; assert!(!JsDate::new_lossy(&mut cx, time).is_valid(&mut cx)); assert!(JsDate::new_lossy(&mut cx, time).value(&mut cx).is_nan()); let time = JsDate::MIN_VALUE - 1.0; assert!(!JsDate::new_lossy(&mut cx, time).is_valid(&mut cx)); assert!(JsDate::new_lossy(&mut cx, time).value(&mut cx).is_nan()); let time = JsDate::MAX_VALUE + 2.0; assert!(!JsDate::new_lossy(&mut cx, time).is_valid(&mut cx)); assert!(JsDate::new_lossy(&mut cx, time).value(&mut cx).is_nan()); let time = JsDate::MAX_VALUE + 3.0; assert!(!JsDate::new_lossy(&mut cx, time).is_valid(&mut cx)); assert!(JsDate::new_lossy(&mut cx, time).value(&mut cx).is_nan()); let time = JsDate::MAX_VALUE + 1_000.0; let date = JsDate::new_lossy(&mut cx, time); assert!(!date.is_valid(&mut cx)); assert!(JsDate::new_lossy(&mut cx, time).value(&mut cx).is_nan()); let date_val = date.value(&mut cx); Ok(cx.number(date_val)) } pub fn get_date_value(mut cx: FunctionContext) -> JsResult { let date = JsDate::new_lossy(&mut cx, 31415); let value = date.value(&mut cx); Ok(cx.number(value)) } ================================================ FILE: test/napi/src/js/errors.rs ================================================ use neon::prelude::*; pub fn new_error(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.error(msg) } pub fn new_type_error(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.type_error(msg) } pub fn new_range_error(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.range_error(msg) } pub fn throw_error(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.throw_error(msg) } pub fn downcast_error(mut cx: FunctionContext) -> JsResult { let s = cx.string("hi"); if let Err(e) = s.downcast::(&mut cx) { Ok(cx.string(format!("{e}"))) } else { panic!() } } ================================================ FILE: test/napi/src/js/export.rs ================================================ use std::sync::LazyLock; use neon::{ prelude::*, types::extract::{Boxed, Error}, }; #[neon::export] const NUMBER: u8 = 42; #[neon::export] static STRING: &str = "Hello, World!"; #[neon::export] static LAZY_LOCK_HELLO: LazyLock = LazyLock::new(|| String::from("Hello, Neon!")); #[neon::export(name = "renamedString")] static RENAMED_STRING: &str = STRING; #[neon::export(json)] static MESSAGES: &[&str] = &["hello", "neon"]; #[neon::export(name = "renamedMessages", json)] static RENAMED_MESSAGES: &[&str] = MESSAGES; #[neon::export] fn no_args_or_return() {} #[neon::export] fn simple_add(a: f64, b: f64) -> f64 { a + b } #[neon::export(name = "renamedAdd")] fn rs_renamed_add(a: f64, b: f64) -> f64 { simple_add(a, b) } #[neon::export(task)] fn add_task(a: f64, b: f64) -> f64 { simple_add(a, b) } #[neon::export(task, name = "renamedAddTask")] fn rs_renamed_add_task(a: f64, b: f64) -> f64 { add_task(a, b) } #[neon::export(json)] fn json_sort(mut items: Vec) -> Vec { items.sort(); items } #[neon::export(json, name = "renamedJsonSort")] fn rs_renamed_json_sort(items: Vec) -> Vec { json_sort(items) } #[neon::export(json, task)] fn json_sort_task(items: Vec) -> Vec { json_sort(items) } #[neon::export(json, name = "renamedJsonSortTask", task)] fn rs_renamed_json_sort_task(items: Vec) -> Vec { json_sort(items) } #[neon::export] fn concat_with_cx_and_handle<'cx>( cx: &mut FunctionContext<'cx>, a: String, b: Handle<'cx, JsString>, ) -> Handle<'cx, JsString> { let b = b.value(cx); cx.string(a + &b) } #[neon::export] fn fail_with_throw(msg: String) -> Result<(), Error> { fn always_fails(msg: String) -> Result<(), String> { Err(msg) } // `?` converts `String` into `Error` always_fails(msg)?; Ok(()) } #[neon::export(task)] fn sleep_task(ms: f64) { use std::{thread, time::Duration}; thread::sleep(Duration::from_millis(ms as u64)); } #[neon::export] fn number_with_cx<'cx>(cx: &mut Cx<'cx>, n: f64) -> Handle<'cx, JsNumber> { cx.number(n) } #[neon::export] fn simple_self(this: Handle) -> Handle { this } #[neon::export] fn boxed_self(Boxed(this): Boxed) -> String { this } #[neon::export] fn boxed_string(s: String) -> Boxed { Boxed(s) } #[neon::export] fn add_i32(a: i32, b: i32) -> i32 { a + b } #[neon::export] fn add_u32(a: u32, b: u32) -> u32 { a + b } #[neon::export] fn to_i32(n: i32) -> i32 { n } #[neon::export] fn to_u32(n: u32) -> u32 { n } // Test the new #[neon::export(class)] shorthand syntax #[derive(Clone)] pub struct ExportedPoint { x: f64, y: f64, } #[neon::export(class)] impl ExportedPoint { const ORIGIN_X: f64 = 0.0; const ORIGIN_Y: f64 = 0.0; pub fn new(x: f64, y: f64) -> Self { Self { x, y } } pub fn x(&self) -> f64 { self.x } pub fn y(&self) -> f64 { self.y } pub fn distance(&self, other: Self) -> f64 { let dx = self.x - other.x(); let dy = self.y - other.y(); (dx * dx + dy * dy).sqrt() } } // Test the shorthand syntax with custom name (CASE 1: both names use outer name) #[derive(Clone)] pub struct CustomNamedClass { value: String, } #[neon::export(class, name = "RenamedClass")] impl CustomNamedClass { pub fn new(value: String) -> Self { Self { value } } pub fn get_value(&self) -> String { self.value.clone() } } // Test CASE 2: class(name = "...") syntax - both class and export use same name #[derive(Clone)] pub struct Case2Class { message: String, } #[neon::export(class(name = "ParenRenamedClass"))] impl Case2Class { pub fn new(message: String) -> Self { Self { message } } pub fn get_message(&self) -> String { self.message.clone() } } // Test CASE 3: separate class name and export name #[derive(Clone)] pub struct Case3Class { data: i32, } #[neon::export(class(name = "InternalClassName"), name = "ExternalExportName")] impl Case3Class { pub fn new(data: i32) -> Self { Self { data } } pub fn get_data(&self) -> i32 { self.data } } // Test async fn + JSON with export macro (compare to class limitation) #[neon::export(json)] pub async fn export_async_json_test(data: Vec) -> Vec { // Simulate async work with JSON serialization data.into_iter().map(|x| x * 3).collect() } ================================================ FILE: test/napi/src/js/extract.rs ================================================ use either::Either; use neon::{prelude::*, types::extract::*}; use std::collections::HashSet; pub fn extract_values(mut cx: FunctionContext) -> JsResult { #[allow(clippy::type_complexity)] let ( boolean, number, unit, string, Date(date), value, array_buf, buf, view, opt_number, opt_string, ): ( bool, f64, (), String, Date, Handle, ArrayBuffer>, Vec, Buffer>, Option, Option, ) = cx.args()?; let values = [ boolean.try_into_js(&mut cx)?.upcast(), number.try_into_js(&mut cx)?.upcast(), unit.try_into_js(&mut cx)?.upcast(), string.try_into_js(&mut cx)?.upcast(), Date(date).try_into_js(&mut cx)?.upcast(), value, array_buf.try_into_js(&mut cx)?.upcast(), buf.try_into_js(&mut cx)?.upcast(), view.try_into_js(&mut cx)?.upcast(), opt_number .map(|n| cx.number(n).upcast::()) .unwrap_or_else(|| cx.undefined().upcast()), opt_string .map(|n| cx.string(n).upcast::()) .unwrap_or_else(|| cx.undefined().upcast()), ]; let arr = cx.empty_array(); for (i, v) in values.into_iter().enumerate() { arr.set(&mut cx, i as u32, v)?; } Ok(arr) } pub fn extract_buffer_sum(mut cx: FunctionContext) -> JsResult { fn sum<'cx, T>( cx: &mut FunctionContext<'cx>, buf: Vec, map: impl Fn(T) -> f64, ) -> JsResult<'cx, JsNumber> { Ok(cx.number(buf.into_iter().map(map).sum::())) } // `Float32Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n.into()); } // `Float32Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n); } // `Buffer` if let Some(Buffer(buf)) = cx.arg_opt()? { return sum(&mut cx, buf, |n| n as f64); } // `ArrayBuffer` if let Some(ArrayBuffer(buf)) = cx.arg_opt()? { return sum(&mut cx, buf, |n| n as f64); } // `Uint8Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Uint16Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Uint32Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Uint64Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Int8Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Int16Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Int32Array` if let Some(buf) = cx.arg_opt::>()? { return sum(&mut cx, buf, |n| n as f64); } // `Int64Array` let buf: Vec = cx.arg()?; sum(&mut cx, buf, |n| n as f64) } pub fn extract_json_sum(mut cx: FunctionContext) -> JsResult { let Json::>(nums) = cx.arg()?; Ok(cx.number(nums.into_iter().sum::())) } pub fn extract_single_add_one(mut cx: FunctionContext) -> JsResult { let n: f64 = cx.arg()?; Ok(cx.number(n + 1.0)) } #[neon::export(json)] pub fn extract_json_option(maybe_x: Option) -> Option { maybe_x } #[neon::export] pub fn extract_either(either: Either) -> String { match either { Either::Left(s) => format!("String: {s}"), Either::Right(n) => format!("Number: {n}"), } } #[neon::export] // TypedArrays can be extracted and returned pub fn buffer_concat(mut a: Vec, Uint8Array(b): Uint8Array>) -> ArrayBuffer> { a.extend(b); ArrayBuffer(a) } #[neon::export] // Extractors work with anything that can be used as slice of the correct type pub fn string_to_buf(s: String) -> Uint8Array { Uint8Array(s) } #[neon::export(task)] // Ensure that `with` produces a closure that can be moved across thread boundaries // and can return a JavaScript value. fn sleep_with_js(n: f64) -> impl for<'cx> TryIntoJs<'cx> { use std::{thread, time::Duration}; thread::sleep(Duration::from_millis(n as u64)); with(move |cx| Ok(cx.number(n))) } #[neon::export] // Ensure that `with` can be used synchronously fn sleep_with_js_sync(n: f64) -> impl for<'cx> TryIntoJs<'cx> { sleep_with_js(n) } #[neon::export(task)] // Ensure that `With` can be used Rust data fn sleep_with(n: f64) -> impl for<'cx> TryIntoJs<'cx> { use std::{thread, time::Duration}; thread::sleep(Duration::from_millis(n as u64)); with(move |cx| n.try_into_js(cx)) } #[neon::export] // Ensure that `With` can be used Rust data synchronously fn sleep_with_sync(n: f64) -> impl for<'cx> TryIntoJs<'cx> { sleep_with(n) } #[neon::export] fn extract_array_vec(Array(arr): Array>) -> Array> { Array(arr) } #[neon::export] fn extract_array_double(Array(arr): Array>) -> Array> { Array(arr.into_iter().map(|x| x * 2.0)) } #[neon::export] fn extract_array_dedupe(set: Array>) -> Array> { set } ================================================ FILE: test/napi/src/js/functions.rs ================================================ use neon::{prelude::*, types::extract}; fn add1(mut cx: FunctionContext) -> JsResult { let x = cx.argument::(0)?.value(&mut cx); Ok(cx.number(x + 1.0)) } pub fn return_js_function(mut cx: FunctionContext) -> JsResult { JsFunction::new(&mut cx, add1) } pub fn call_js_function(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let args = [cx.number(16.0).upcast()]; let null = cx.null(); f.call(&mut cx, null, args)? .downcast::(&mut cx) .or_throw(&mut cx) } pub fn call_js_function_idiomatically(mut cx: FunctionContext) -> JsResult { cx.argument::(0)? .call_with(&cx) .this(cx.null()) .arg(cx.number(16.0)) .apply(&mut cx) } pub fn call_js_function_with_bind(mut cx: FunctionContext) -> JsResult { let n: f64 = cx .argument::(0)? .bind(&mut cx) .args((1, 2, 3))? .arg(4)? .arg_with(|cx| cx.number(5))? .call()?; Ok(cx.number(n)) } pub fn call_js_function_with_bind_and_args_with(mut cx: FunctionContext) -> JsResult { let n: f64 = cx .argument::(0)? .bind(&mut cx) .args_with(|_| (1, 2, 3))? .call()?; Ok(cx.number(n)) } pub fn call_js_function_with_bind_and_args_and_with(mut cx: FunctionContext) -> JsResult { let n: f64 = cx .argument::(0)? .bind(&mut cx) .arg(extract::with(|cx| Ok(cx.number(1))))? .arg(2)? .arg(3)? .call()?; Ok(cx.number(n)) } pub fn call_parse_int_with_bind(mut cx: FunctionContext) -> JsResult { let parse_int: Handle = cx.global("parseInt")?; let x: f64 = parse_int.bind(&mut cx).arg("41")?.call()?; Ok(cx.number(x + 1.0)) } pub fn call_js_function_with_bind_and_exec(mut cx: FunctionContext) -> JsResult { cx.argument::(0)?.bind(&mut cx).arg(1)?.exec()?; Ok(cx.undefined()) } pub fn call_js_constructor_with_bind(mut cx: FunctionContext) -> JsResult { cx.argument::(0)? .bind(&mut cx) .args((42, "hello"))? .construct() } pub fn bind_js_function_to_object(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let obj = cx.empty_object(); obj.prop(&mut cx, "prop").set(42)?; f.bind(&mut cx).this(obj)?.call() } pub fn bind_js_function_to_number(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; f.bind(&mut cx).this(42)?.call() } fn get_math_max<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsFunction> { let math: Handle = cx.global("Math")?; let max: Handle = math.get(cx, "max")?; Ok(max) } pub fn call_js_function_with_zero_args(mut cx: FunctionContext) -> JsResult { get_math_max(&mut cx)?.call_with(&cx).apply(&mut cx) } pub fn call_js_function_with_one_arg(mut cx: FunctionContext) -> JsResult { get_math_max(&mut cx)? .call_with(&cx) .arg(cx.number(1.0)) .apply(&mut cx) } pub fn call_js_function_with_two_args(mut cx: FunctionContext) -> JsResult { get_math_max(&mut cx)? .call_with(&cx) .arg(cx.number(1.0)) .arg(cx.number(2.0)) .apply(&mut cx) } pub fn call_js_function_with_three_args(mut cx: FunctionContext) -> JsResult { get_math_max(&mut cx)? .call_with(&cx) .arg(cx.number(1.0)) .arg(cx.number(2.0)) .arg(cx.number(3.0)) .apply(&mut cx) } pub fn call_js_function_with_four_args(mut cx: FunctionContext) -> JsResult { get_math_max(&mut cx)? .call_with(&cx) .arg(cx.number(1.0)) .arg(cx.number(2.0)) .arg(cx.number(3.0)) .arg(cx.number(4.0)) .apply(&mut cx) } pub fn call_js_function_with_custom_this(mut cx: FunctionContext) -> JsResult { let custom_this = cx.empty_object(); let secret = cx.number(42.0); custom_this.set(&mut cx, "secret", secret)?; cx.argument::(0)? .call_with(&cx) .this(custom_this) .apply(&mut cx) } pub fn call_js_function_with_implicit_this(mut cx: FunctionContext) -> JsResult { cx.argument::(0)? .call_with(&cx) .arg(cx.number(42)) .apply(&mut cx) } pub fn exec_js_function_with_implicit_this(mut cx: FunctionContext) -> JsResult { cx.argument::(0)? .call_with(&cx) .arg(cx.number(42)) .exec(&mut cx)?; Ok(cx.undefined()) } pub fn call_js_function_with_heterogeneous_tuple(mut cx: FunctionContext) -> JsResult { cx.global::("Array")? .call_with(&cx) .args((cx.number(1.0), cx.string("hello"), cx.boolean(true))) .apply(&mut cx) } pub fn construct_js_function(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let zero = cx.number(0.0); let o = f.construct(&mut cx, [zero.upcast()])?; let get_utc_full_year_method: Handle = o.get(&mut cx, "getUTCFullYear")?; let args: Vec> = vec![]; get_utc_full_year_method .call(&mut cx, o.upcast::(), args)? .downcast::(&mut cx) .or_throw(&mut cx) } pub fn construct_js_function_idiomatically(mut cx: FunctionContext) -> JsResult { let o: Handle = cx .argument::(0)? .construct_with(&cx) .arg(cx.number(0.0)) .apply(&mut cx)?; let get_utc_full_year_method: Handle = o.get(&mut cx, "getUTCFullYear")?; get_utc_full_year_method .call_with(&cx) .this(o) .apply(&mut cx) } pub fn construct_js_function_with_overloaded_result(mut cx: FunctionContext) -> JsResult { let f: Handle = cx.global("Array")?; f.construct_with(&cx) .arg(cx.number(1)) .arg(cx.number(2)) .arg(cx.number(3)) .apply(&mut cx) } pub fn check_string_and_number(mut cx: FunctionContext) -> JsResult { cx.argument::(0)?; cx.argument::(1)?; Ok(cx.undefined()) } pub fn panic(_: FunctionContext) -> JsResult { panic!("zomg") } pub fn panic_after_throw(mut cx: FunctionContext) -> JsResult { cx.throw_range_error::<_, ()>("entering throw state with a RangeError") .unwrap_err(); panic!("this should override the RangeError") } pub fn num_arguments(mut cx: FunctionContext) -> JsResult { let n = cx.len(); Ok(cx.number(n as i32)) } pub fn return_this(mut cx: FunctionContext) -> JsResult { cx.this() } pub fn require_object_this(mut cx: FunctionContext) -> JsResult { let this = cx.this::()?; let t = cx.boolean(true); this.set(&mut cx, "modified", t)?; Ok(cx.undefined()) } pub fn is_argument_zero_some(mut cx: FunctionContext) -> JsResult { let b = cx.argument_opt(0).is_some(); Ok(cx.boolean(b)) } pub fn require_argument_zero_string(mut cx: FunctionContext) -> JsResult { let s = cx.argument(0)?; Ok(s) } pub fn execute_scoped(mut cx: FunctionContext) -> JsResult { let mut i = 0; for _ in 1..100 { cx.execute_scoped(|mut cx| { let n = cx.number(1); i += n.value(&mut cx) as i32; }); } Ok(cx.number(i)) } pub fn compute_scoped(mut cx: FunctionContext) -> JsResult { let mut i = cx.number(0); for _ in 1..100 { i = cx.compute_scoped(|mut cx| { let n = cx.number(1); let left = i.value(&mut cx) as i32; let right = n.value(&mut cx) as i32; Ok(cx.number(left + right)) })?; } Ok(i) } // Simple identity function to verify that a handle can be moved to `compute_scoped` // closure and re-escaped. pub fn recompute_scoped(mut cx: FunctionContext) -> JsResult { let value = cx.argument::(0)?; cx.compute_scoped(move |_| Ok(value)) } pub fn throw_and_catch(mut cx: FunctionContext) -> JsResult { let v = cx .argument_opt(0) .unwrap_or_else(|| cx.undefined().upcast()); cx.try_catch(|cx| cx.throw(v)) .map(|_: ()| Ok(cx.string("unreachable").upcast())) .unwrap_or_else(Ok) } pub fn call_and_catch(mut cx: FunctionContext) -> JsResult { let f: Handle = cx.argument(0)?; Ok(cx .try_catch(|cx| f.call_with(cx).this(cx.global_object()).apply(cx)) .unwrap_or_else(|err| err)) } pub fn get_number_or_default(mut cx: FunctionContext) -> JsResult { let n = cx .try_catch(|cx| Ok(cx.argument::(0)?.value(cx))) .unwrap_or(0.0); Ok(cx.number(n)) } pub fn assume_this_is_an_object(mut cx: FunctionContext) -> JsResult { let this: Handle = cx.this()?; let result: Handle = cx.empty_object(); let object_class: Handle = cx.global("Object")?; let get_prototype_of: Handle = object_class.get(&mut cx, "getPrototypeOf")?; let object_prototype: Handle = object_class.get(&mut cx, "prototype")?; let has_own_property: Handle = object_prototype.get(&mut cx, "hasOwnProperty")?; let proto: Result, Handle> = cx.try_catch(|cx| get_prototype_of.call_with(cx).arg(this).apply(cx)); let proto: Handle = proto.unwrap_or_else(|_| cx.undefined().upcast()); let has_own: Handle = has_own_property .call_with(&cx) .this(this) .arg(cx.string("toString")) .apply(&mut cx)?; let prop: Handle = this.get(&mut cx, "toString")?; result.set(&mut cx, "prototype", proto)?; result.set(&mut cx, "hasOwn", has_own)?; result.set(&mut cx, "property", prop)?; Ok(result) } pub fn is_construct(mut cx: FunctionContext) -> JsResult { let this = cx.this::()?; let construct = matches!(cx.kind(), CallKind::Construct); let construct = cx.boolean(construct); this.set(&mut cx, "wasConstructed", construct)?; Ok(this) } // `function caller_with_drop_callback(wrappedCallback, dropCallback)` // // `wrappedCallback` will be called each time the returned function is // called to verify we have successfully dynamically created a function // from a closure. // // `dropCallback` will be called when the closure is dropped to test that // closures are not leaking. The unit test should pass the test callback here. pub fn caller_with_drop_callback(mut cx: FunctionContext) -> JsResult { struct Callback { f: Root, drop: Option>, channel: Channel, } // Call `dropCallback` when `Callback` is dropped as a sentinel to observe // the closure isn't leaked when the function is garbage collected impl Drop for Callback { fn drop(&mut self) { let callback = self.drop.take(); self.channel.send(move |mut cx| { let this = cx.undefined(); let args: [Handle; 0] = []; // Execute the unit test callback to end the test successfully callback .unwrap() .into_inner(&mut cx) .call(&mut cx, this, args)?; Ok(()) }); } } let callback = Callback { f: cx.argument::(0)?.root(&mut cx), drop: Some(cx.argument::(1)?.root(&mut cx)), channel: cx.channel(), }; JsFunction::new(&mut cx, move |mut cx| { let this = cx.undefined(); let args: [Handle; 0] = []; callback.f.to_inner(&mut cx).call(&mut cx, this, args) }) } ================================================ FILE: test/napi/src/js/futures.rs ================================================ use std::future::Future; use neon::{ prelude::*, types::{ buffer::TypedArray, extract::{self, Error, Json, TryIntoJs}, }, }; use crate::runtime; // Accepts two functions that take no parameters and return numbers. // Resolves with the sum of the two numbers. // Purpose: Test the `Future` implementation on `JoinHandle` pub fn lazy_async_add(mut cx: FunctionContext) -> JsResult { let get_x = cx.argument::(0)?.root(&mut cx); let get_y = cx.argument::(1)?.root(&mut cx); let channel = cx.channel(); let runtime = runtime(&mut cx)?; let (deferred, promise) = cx.promise(); runtime.spawn(async move { let result = channel .send(move |mut cx| { let get_x = get_x.into_inner(&mut cx); let get_y = get_y.into_inner(&mut cx); let x: Handle = get_x.call_with(&cx).apply(&mut cx)?; let y: Handle = get_y.call_with(&cx).apply(&mut cx)?; Ok((x.value(&mut cx), y.value(&mut cx))) }) .await .map(|(x, y)| x + y); deferred.settle_with(&channel, move |mut cx| { let result = result.or_throw(&mut cx)?; Ok(cx.number(result)) }); }); Ok(promise) } // Accepts a function that returns a `Promise`. // Resolves with the sum of all numbers. // Purpose: Test `JsPromise::to_future`. pub fn lazy_async_sum(mut cx: FunctionContext) -> JsResult { let nums = cx .argument::(0)? .call_with(&cx) .apply::(&mut cx)? .to_future(&mut cx, |mut cx, nums| { let nums = nums .or_throw(&mut cx)? .downcast_or_throw::, _>(&mut cx)? .as_slice(&cx) .to_vec(); Ok(nums) })?; let (deferred, promise) = cx.promise(); let channel = cx.channel(); let runtime = runtime(&mut cx)?; runtime.spawn(async move { let result = nums.await.map(|nums| nums.into_iter().sum::()); deferred.settle_with(&channel, move |mut cx| { let result = result.or_throw(&mut cx)?; Ok(cx.number(result)) }); }); Ok(promise) } #[neon::export] async fn async_fn_add(a: f64, b: f64) -> f64 { a + b } #[neon::export(async)] fn async_add(a: f64, b: f64) -> impl Future { async move { a + b } } #[neon::export] async fn async_fn_div(a: f64, b: f64) -> Result { if b == 0.0 { return Err(Error::from("Divide by zero")); } Ok(a / b) } #[neon::export(async)] fn async_div(cx: &mut FunctionContext) -> NeonResult>> { let (a, b): (f64, f64) = cx.args()?; Ok(async move { if b == 0.0 { return Err(Error::from("Divide by zero")); } Ok(a / b) }) } #[neon::export(async)] fn async_with_events( cx: &mut FunctionContext, Json(data): Json>, ) -> NeonResult TryIntoJs<'cx>>> { fn emit(cx: &mut Cx, state: &str) -> NeonResult<()> { cx.global::("process")? .call_method_with(cx, "emit")? .arg(cx.string("async_with_events")) .arg(cx.string(state)) .exec(cx) } emit(cx, "start")?; Ok(async move { let res = data.into_iter().map(|(a, b)| a * b).collect::>(); extract::with(move |cx| -> NeonResult<_> { emit(cx, "end")?; res.try_into_js(cx) }) }) } #[neon::export] async fn await_callback(ch: Channel, cb: Root) -> Result, Error> { let res = ch .send(move |mut cx| { let this = cx.undefined(); cb.into_inner(&mut cx) .call(&mut cx, this, []) .and_then(|v| v.downcast_or_throw::(&mut cx)) .map(|v| v.root(&mut cx)) }) .await?; Ok(res) } ================================================ FILE: test/napi/src/js/numbers.rs ================================================ use neon::prelude::*; pub fn return_js_number(mut cx: FunctionContext) -> JsResult { Ok(cx.number(9000_f64)) } pub fn return_large_js_number(mut cx: FunctionContext) -> JsResult { Ok(cx.number(4294967296_f64)) } pub fn return_negative_js_number(mut cx: FunctionContext) -> JsResult { Ok(cx.number(-9000_f64)) } pub fn return_float_js_number(mut cx: FunctionContext) -> JsResult { Ok(cx.number(1.4747_f64)) } pub fn return_negative_float_js_number(mut cx: FunctionContext) -> JsResult { Ok(cx.number(-1.4747_f64)) } pub fn accept_and_return_js_number(mut cx: FunctionContext) -> JsResult { let number: Handle = cx.argument(0)?; Ok(number) } pub fn accept_and_return_large_js_number(mut cx: FunctionContext) -> JsResult { let number: Handle = cx.argument(0)?; Ok(number) } pub fn accept_and_return_float_js_number(mut cx: FunctionContext) -> JsResult { let number: Handle = cx.argument(0)?; Ok(number) } pub fn accept_and_return_negative_js_number(mut cx: FunctionContext) -> JsResult { let number: Handle = cx.argument(0)?; Ok(number) } ================================================ FILE: test/napi/src/js/objects.rs ================================================ use std::borrow::Cow; use neon::{prelude::*, types::buffer::TypedArray}; pub fn return_js_global_object(mut cx: FunctionContext) -> JsResult { Ok(cx.global_object()) } pub fn return_js_object(mut cx: FunctionContext) -> JsResult { Ok(cx.empty_object()) } pub fn return_js_object_with_mixed_content(mut cx: FunctionContext) -> JsResult { let js_object: Handle = cx.empty_object(); let n = cx.number(9000.0); js_object.set(&mut cx, "number", n)?; let s = cx.string("hello node"); js_object.set(&mut cx, "string", s)?; Ok(js_object) } pub fn return_js_object_with_number(mut cx: FunctionContext) -> JsResult { let js_object: Handle = cx.empty_object(); let n = cx.number(9000.0); js_object.set(&mut cx, "number", n)?; Ok(js_object) } pub fn return_js_object_with_string(mut cx: FunctionContext) -> JsResult { let js_object: Handle = cx.empty_object(); let s = cx.string("hello node"); js_object.set(&mut cx, "string", s)?; Ok(js_object) } pub fn freeze_js_object(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; match obj.freeze(&mut cx) { Ok(_) => Ok(cx.undefined()), Err(e) => cx.throw_error(e.to_string()), } } pub fn seal_js_object(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; match obj.seal(&mut cx) { Ok(_) => Ok(cx.undefined()), Err(e) => cx.throw_error(e.to_string()), } } // Accepts either a `JsString` or `JsBuffer` and returns the contents as // as bytes; avoids copying. fn get_bytes<'cx, 'a, C>(cx: &'a mut C, v: Handle) -> NeonResult> where C: Context<'cx>, { if let Ok(v) = v.downcast::(cx) { return Ok(Cow::Owned(v.value(cx).into_bytes())); } if let Ok(v) = v.downcast::(cx) { return Ok(Cow::Borrowed(v.as_slice(cx))); } cx.throw_type_error("Value must be a string or Buffer") } pub fn byte_length(mut cx: FunctionContext) -> JsResult { let v = cx.argument::(0)?; let bytes = get_bytes(&mut cx, v)?; // `v` is dropped here, but `bytes` is still valid since the data is on the V8 heap let len = bytes.len(); Ok(cx.number(len as f64)) } pub fn call_nullary_method(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; obj.call_method_with(&mut cx, "nullary")?.apply(&mut cx) } pub fn call_unary_method(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; let x: Handle = cx.argument::(1)?; obj.call_method_with(&mut cx, "unary")? .arg(x) .apply(&mut cx) } pub fn call_symbol_method(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; let sym: Handle = cx.argument::(1)?; obj.call_method_with(&mut cx, sym)?.apply(&mut cx) } pub fn get_property_with_prop(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; let n: f64 = obj.prop(&mut cx, "number").get()?; Ok(cx.number(n)) } pub fn set_property_with_prop(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; obj.prop(&mut cx, "number").set(42)?; Ok(cx.undefined()) } pub fn call_methods_with_prop(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; obj.prop(&mut cx, "setName") .bind()? .arg("Wonder Woman")? .exec()?; obj.prop(&mut cx, "toString").bind()?.call() } pub fn call_non_method_with_prop(mut cx: FunctionContext) -> JsResult { let obj: Handle = cx.argument::(0)?; obj.prop(&mut cx, "number").bind()?.exec()?; Ok(cx.undefined()) } ================================================ FILE: test/napi/src/js/strings.rs ================================================ use neon::{prelude::*, reflect::eval}; pub fn return_js_string(mut cx: FunctionContext) -> JsResult { Ok(cx.string("hello node")) } pub fn return_js_string_utf16(mut cx: FunctionContext) -> JsResult> { let raw = "hello 🥹".encode_utf16().collect::>(); JsTypedArray::from_slice(&mut cx, &raw) } pub fn return_length_utf8(mut cx: FunctionContext) -> JsResult { let value = cx.argument::(0)?.value(&mut cx); Ok(cx.number(value.len() as f64)) } pub fn return_length_utf16(mut cx: FunctionContext) -> JsResult { let value = cx.argument::(0)?.to_utf16(&mut cx); Ok(cx.number(value.len() as f64)) } pub fn run_string_as_script(mut cx: FunctionContext) -> JsResult { let string_script = cx.argument::(0)?; eval(&mut cx, string_script) } ================================================ FILE: test/napi/src/js/threads.rs ================================================ use std::{cell::RefCell, sync::Arc, time::Duration}; use neon::{ prelude::*, types::{buffer::TypedArray, extract::Error}, }; pub fn useless_root(mut cx: FunctionContext) -> JsResult { let object = cx.argument::(0)?; let root = object.root(&mut cx); let object = root.into_inner(&mut cx); Ok(object) } pub fn thread_callback(mut cx: FunctionContext) -> JsResult { let callback = cx.argument::(0)?.root(&mut cx); let channel = cx.channel(); std::thread::spawn(move || { channel.send(move |mut cx| callback.into_inner(&mut cx).call_with(&cx).exec(&mut cx)) }); Ok(cx.undefined()) } pub fn multi_threaded_callback(mut cx: FunctionContext) -> JsResult { let n = cx.argument::(0)?.value(&mut cx); let callback = cx.argument::(1)?.root(&mut cx); let channel = Arc::new(cx.channel()); for i in 0..(n as usize) { let callback = callback.clone(&mut cx); let channel = Arc::clone(&channel); std::thread::spawn(move || { channel.send(move |mut cx| { callback .into_inner(&mut cx) .call_with(&cx) .arg(cx.number(i as f64)) .exec(&mut cx) }) }); } callback.drop(&mut cx); Ok(cx.undefined()) } type BoxedGreeter = JsBox>; pub struct AsyncGreeter { greeting: String, callback: Root, shutdown: Option>, channel: Arc, } impl AsyncGreeter { fn greet<'a, C: Context<'a>>(&self, mut cx: C) -> JsResult<'a, JsUndefined> { let greeting = self.greeting.clone(); let callback = self.callback.clone(&mut cx); let channel = Arc::clone(&self.channel); std::thread::spawn(move || { channel.send(|mut cx| { callback .into_inner(&mut cx) .call_with(&cx) .arg(cx.string(greeting)) .exec(&mut cx) }) }); Ok(cx.undefined()) } } impl Finalize for AsyncGreeter { fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { let Self { callback, shutdown, .. } = self; if let Some(shutdown) = shutdown { let _ = shutdown.into_inner(cx).call_with(cx).exec(cx); } callback.drop(cx); } } pub fn greeter_new(mut cx: FunctionContext) -> JsResult { let greeting = cx.argument::(0)?.value(&mut cx); let callback = cx.argument::(1)?.root(&mut cx); let shutdown = cx.argument_opt(2); let channel = cx.channel(); let shutdown = shutdown .map(|v| v.downcast_or_throw::(&mut cx)) .transpose()? .map(|v| v.root(&mut cx)); let greeter = cx.boxed(RefCell::new(AsyncGreeter { greeting, callback, shutdown, channel: Arc::new(channel), })); Ok(greeter) } pub fn greeter_greet(mut cx: FunctionContext) -> JsResult { let greeter = cx.argument::(0)?; let greeter = greeter.borrow(); greeter.greet(cx) } pub fn leak_channel(mut cx: FunctionContext) -> JsResult { let channel = Box::new({ let mut channel = cx.channel(); channel.unref(&mut cx); channel }); Box::leak(channel); Ok(cx.undefined()) } pub fn drop_global_queue(mut cx: FunctionContext) -> JsResult { struct Wrapper { callback: Option>, channel: Channel, } impl Finalize for Wrapper {} // To verify that the type is dropped on the global drop queue, the callback // is called from the `Drop` impl on `Wrapper` impl Drop for Wrapper { fn drop(&mut self) { if let Some(callback) = self.callback.take() { self.channel.send(|mut cx| { callback .into_inner(&mut cx) .call_with(&cx) .arg(cx.undefined()) .exec(&mut cx) }); } } } let callback = cx.argument::(0)?.root(&mut cx); let channel = cx.channel(); let wrapper = cx.boxed(Wrapper { callback: Some(callback), channel, }); // Put the `Wrapper` instance in a `Root` and drop it // Without the global drop queue, this will panic let _ = wrapper.root(&mut cx); Ok(cx.undefined()) } pub fn channel_join(mut cx: FunctionContext) -> JsResult { // Function to fetch a message for processing let get_message = cx.argument::(0)?.root(&mut cx); // Callback into JavaScript with completion let callback = cx.argument::(1)?.root(&mut cx); let channel = cx.channel(); // Spawn a Rust thread to stop blocking the event loop std::thread::spawn(move || { // Give a chance for the data to change std::thread::sleep(Duration::from_millis(100)); // Get the current message let message = channel .send(move |mut cx| { let this = cx.undefined(); get_message .into_inner(&mut cx) .call(&mut cx, this, [])? .downcast_or_throw::(&mut cx) .map(|v| v.value(&mut cx)) }) .join() .unwrap(); // Process the message let response = format!("Received: {message}"); // Call back to JavaScript with the response channel.send(move |mut cx| { let this = cx.undefined(); let args = [cx.string(response).upcast()]; callback.into_inner(&mut cx).call(&mut cx, this, args)?; Ok(()) }); }); Ok(cx.undefined()) } pub fn sum(mut cx: FunctionContext) -> JsResult { let nums = cx.argument::>(0)?.as_slice(&cx).to_vec(); let promise = cx .task(move || nums.into_iter().sum()) .promise(|mut cx, n: f64| Ok(cx.number(n))); Ok(promise) } pub fn sum_manual_promise(mut cx: FunctionContext) -> JsResult { let nums = cx.argument::>(0)?.as_slice(&cx).to_vec(); let (deferred, promise) = cx.promise(); cx.task(move || nums.into_iter().sum()) .and_then(move |mut cx, n: f64| { let n = cx.number(n); deferred.resolve(&mut cx, n); Ok(()) }); Ok(promise) } pub fn sum_rust_thread(mut cx: FunctionContext) -> JsResult { let nums = cx.argument::>(0)?.as_slice(&cx).to_vec(); let channel = cx.channel(); let (deferred, promise) = cx.promise(); std::thread::spawn(move || { let n: f64 = nums.into_iter().sum(); deferred.settle_with(&channel, move |mut cx| Ok(cx.number(n))); }); Ok(promise) } pub fn leak_promise(mut cx: FunctionContext) -> JsResult { let (_, promise) = cx.promise(); Ok(promise) } pub fn channel_panic(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let channel = cx.channel(); std::thread::spawn(move || { channel.send(move |_| -> NeonResult<()> { panic!("{}", msg); }) }); Ok(cx.undefined()) } pub fn channel_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let channel = cx.channel(); std::thread::spawn(move || { channel.send(move |mut cx| { cx.throw_error(msg)?; Ok(()) }) }); Ok(cx.undefined()) } pub fn channel_panic_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let channel = cx.channel(); std::thread::spawn(move || { channel.send(move |mut cx| { // Throw an exception, but ignore the `Err(Throw)` let _ = cx.throw_error::<_, ()>(msg); // Attempting to throw another error while already throwing should `panic` cx.throw_error("Unreachable")?; Ok(()) }) }); Ok(cx.undefined()) } struct CustomPanic(String); pub fn channel_custom_panic(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let channel = cx.channel(); std::thread::spawn(move || { channel.send(move |_| -> NeonResult<()> { std::panic::panic_any(CustomPanic(msg)); }) }); Ok(cx.undefined()) } pub fn custom_panic_downcast(mut cx: FunctionContext) -> JsResult { let panic = cx.argument::>(0)?; Ok(cx.string(&panic.0)) } pub fn task_panic_execute(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.task(move || panic!("{}", msg)).and_then(|_, _| Ok(())); Ok(cx.undefined()) } pub fn task_panic_complete(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.task(|| {}).and_then(move |_, _| panic!("{}", msg)); Ok(cx.undefined()) } pub fn task_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.task(|| {}).and_then(move |mut cx, _| { cx.throw_error(msg)?; Ok(()) }); Ok(cx.undefined()) } pub fn task_panic_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.task(|| {}).and_then(move |mut cx, _| { // Throw an exception, but ignore the `Err(Throw)` let _ = cx.throw_error::<_, ()>(msg); // Attempting to throw another error while already throwing should `panic` cx.throw_error("Unreachable")?; Ok(()) }); Ok(cx.undefined()) } pub fn task_custom_panic(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); cx.task(move || std::panic::panic_any(CustomPanic(msg))) .and_then(|_, _| Ok(())); Ok(cx.undefined()) } pub fn task_reject_promise(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let promise = cx .task(move || {}) .promise::(move |mut cx, _| cx.throw_error(msg)); Ok(promise) } pub fn task_panic_execute_promise(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let promise = cx .task(move || panic!("{}", msg)) .promise(|mut cx, _| Ok(cx.undefined())); Ok(promise) } pub fn task_panic_complete_promise(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let promise = cx .task(|| ()) .promise::(move |_, _| panic!("{}", msg)); Ok(promise) } pub fn task_panic_throw_promise(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let promise = cx.task(|| ()).promise(move |mut cx, _| { // Throw an exception, but ignore the `Err(Throw)` let _ = cx.throw_error::<_, ()>(msg); // Attempting to throw another error while already throwing should `panic` cx.throw_error("Unreachable")?; Ok(cx.undefined()) }); Ok(promise) } pub fn deferred_settle_with_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let (deferred, promise) = cx.promise(); let channel = cx.channel(); std::thread::spawn(move || { deferred.try_settle_with(&channel, move |mut cx| { cx.throw_error(msg)?; Ok(cx.undefined()) }) }); Ok(promise) } pub fn deferred_settle_with_panic(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let (deferred, promise) = cx.promise(); let channel = cx.channel(); std::thread::spawn(move || { deferred.try_settle_with::(&channel, move |_| { panic!("{}", msg); }) }); Ok(promise) } pub fn deferred_settle_with_panic_throw(mut cx: FunctionContext) -> JsResult { let msg = cx.argument::(0)?.value(&mut cx); let (deferred, promise) = cx.promise(); let channel = cx.channel(); std::thread::spawn(move || { deferred.try_settle_with(&channel, move |mut cx| { // Throw an exception, but ignore the `Err(Throw)` let _ = cx.throw_error::<_, ()>(msg); // Attempting to throw another error while already throwing should `panic` cx.throw_error("Unreachable")?; Ok(cx.undefined()) }) }); Ok(promise) } #[neon::export(task)] fn block_task_callback(ch: Channel, cb: Root) -> Result, Error> { let res = ch .send(move |mut cx| { let this = cx.undefined(); cb.into_inner(&mut cx) .call(&mut cx, this, []) .and_then(|v| v.downcast_or_throw::(&mut cx)) .map(|v| v.root(&mut cx)) }) .join()?; Ok(res) } #[neon::export] fn settle_hello_world<'cx>(cx: &mut Cx<'cx>) -> JsResult<'cx, JsPromise> { let (d, promise) = cx.promise(); let ch = cx.channel(); std::thread::spawn(move || { d.settle(&ch, "Hello, World!"); }); Ok(promise) } ================================================ FILE: test/napi/src/js/typedarrays.rs ================================================ use neon::{ prelude::*, types::buffer::{Binary, BorrowError, TypedArray}, }; pub fn return_array_buffer(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.array_buffer(16)?; Ok(b) } pub fn return_array_buffer_from_slice(mut cx: FunctionContext) -> JsResult { let len = cx.argument::(0)?.value(&mut cx) as usize; let v = (0..len).map(|i| i as u8).collect::>(); JsArrayBuffer::from_slice(&mut cx, &v) } pub fn read_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::>(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let lock = cx.lock(); let n = buf.try_borrow(&lock).map(|buf| buf[i]).or_throw(&mut cx)?; Ok(cx.number(n)) } pub fn read_array_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = buf.as_slice(&cx)[i]; Ok(cx.number(n as f64)) } pub fn write_array_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let mut b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as u32 as usize; let x = cx.argument::(2)?.value(&mut cx) as u8; let lock = cx.lock(); b.try_borrow_mut(&lock) .map(|mut slice| { slice[i] = x; }) .or_throw(&mut cx)?; Ok(cx.undefined()) } pub fn write_array_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { let mut buf = cx.argument::(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = cx.argument::(2)?.value(&mut cx) as u8; buf.as_mut_slice(&mut cx)[i] = n; Ok(cx.undefined()) } pub fn read_typed_array_with_borrow(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::>(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = buf.as_slice(&cx)[i]; Ok(cx.number(n as f64)) } pub fn write_typed_array_with_borrow_mut(mut cx: FunctionContext) -> JsResult { let mut buf = cx.argument::>(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = cx.argument::(2)?.value(&mut cx) as i32; buf.as_mut_slice(&mut cx)[i] = n; Ok(cx.undefined()) } pub fn read_u8_typed_array(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::>(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = buf.as_slice(&cx)[i]; Ok(cx.number(n as f64)) } pub fn copy_typed_array(mut cx: FunctionContext) -> JsResult { let source = cx.argument::>(0)?; let mut dest = cx.argument::>(1)?; let mut run = || -> Result<_, BorrowError> { let lock = cx.lock(); let source = source.try_borrow(&lock)?; let mut dest = dest.try_borrow_mut(&lock)?; dest.copy_from_slice(&source); Ok(()) }; run().or_throw(&mut cx)?; Ok(cx.undefined()) } pub fn return_uninitialized_buffer(mut cx: FunctionContext) -> JsResult { let b: Handle = unsafe { JsBuffer::uninitialized(&mut cx, 16)? }; Ok(b) } pub fn return_buffer(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.buffer(16)?; Ok(b) } pub fn return_external_buffer(mut cx: FunctionContext) -> JsResult { let data = cx.argument::(0)?.value(&mut cx); let buf = JsBuffer::external(&mut cx, data.into_bytes()); Ok(buf) } pub fn return_external_array_buffer(mut cx: FunctionContext) -> JsResult { let data = cx.argument::(0)?.value(&mut cx); let buf = JsArrayBuffer::external(&mut cx, data.into_bytes()); Ok(buf) } pub fn return_int8array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; JsInt8Array::from_buffer(&mut cx, buf) } pub fn return_int16array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; JsInt16Array::from_buffer(&mut cx, buf) } pub fn return_uint32array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; JsUint32Array::from_buffer(&mut cx, buf) } pub fn return_float64array_from_arraybuffer(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; JsFloat64Array::from_buffer(&mut cx, buf) } pub fn return_biguint64array_from_arraybuffer( mut cx: FunctionContext, ) -> JsResult { let buf = cx.argument::(0)?; JsBigUint64Array::from_buffer(&mut cx, buf) } pub fn return_new_int32array(mut cx: FunctionContext) -> JsResult { let len = cx.argument::(0)?.value(&mut cx) as usize; JsInt32Array::new(&mut cx, len) } pub fn return_int32array_from_slice(mut cx: FunctionContext) -> JsResult { let len: Handle = cx.argument(0)?; let len: f64 = len.value(&mut cx); let len: u32 = len as u32; let mut v: Vec = Vec::new(); for i in 0..len { v.push(i as i32); } let a: Handle = JsInt32Array::from_slice(&mut cx, &v)?; Ok(a) } pub fn return_uint32array_from_arraybuffer_region( mut cx: FunctionContext, ) -> JsResult { let buf = cx.argument::(0)?; let offset = cx.argument::(1)?; let offset = offset.value(&mut cx); let len = cx.argument::(2)?; let len = len.value(&mut cx); JsUint32Array::from_region(&mut cx, &buf.region(offset as usize, len as usize)) } pub fn get_arraybuffer_byte_length(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; let size = buf.size(&mut cx); let n = cx.number(size as u32); Ok(n) } fn typed_array_info<'cx, C, T: Binary>( cx: &mut C, a: Handle<'cx, JsTypedArray>, ) -> JsResult<'cx, JsObject> where C: Context<'cx>, JsTypedArray: Value, { let offset = a.offset(cx); let offset = cx.number(offset as u32); let len = a.len(cx); let len = cx.number(len as u32); let size = a.size(cx); let size = cx.number(size as u32); let buffer = a.buffer(cx); let obj = cx.empty_object(); obj.set(cx, "byteOffset", offset)?; obj.set(cx, "length", len)?; obj.set(cx, "byteLength", size)?; obj.set(cx, "buffer", buffer)?; Ok(obj) } fn detach_and_then<'cx, F>(mut cx: FunctionContext<'cx>, f: F) -> JsResult<'cx, JsObject> where F: FnOnce( &mut FunctionContext<'cx>, Handle<'cx, JsUint32Array>, ) -> NeonResult>>, { let mut a = cx.argument::(0)?; let detach = cx.argument::(1)?; let before = typed_array_info(&mut cx, a)?; detach.call_with(&cx).arg(a).exec(&mut cx)?; if let Some(new_array) = f(&mut cx, a)? { a = new_array; } let after = typed_array_info(&mut cx, a)?; let result = cx.empty_object(); result.set(&mut cx, "before", before)?; result.set(&mut cx, "after", after)?; Ok(result) } pub fn detach_same_handle(cx: FunctionContext) -> JsResult { detach_and_then(cx, |_, _| Ok(None)) } pub fn detach_and_escape(cx: FunctionContext) -> JsResult { detach_and_then(cx, |cx, a| { let a = cx.compute_scoped(|_| Ok(a))?; Ok(Some(a)) }) } pub fn detach_and_cast(cx: FunctionContext) -> JsResult { detach_and_then(cx, |cx, a| { let v = a.upcast::(); let a = v.downcast_or_throw::(cx)?; Ok(Some(a)) }) } pub fn detach_and_unroot(cx: FunctionContext) -> JsResult { detach_and_then(cx, |cx, a| { let root = a.root(cx); let a = root.into_inner(cx); Ok(Some(a)) }) } pub fn get_typed_array_info(mut cx: FunctionContext) -> JsResult { let x = cx.argument::(0)?; if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else if let Ok(a) = x.downcast::, _>(&mut cx) { typed_array_info(&mut cx, a) } else { cx.throw_type_error("expected a typed array") } } pub fn build_f32_region(mut cx: FunctionContext) -> JsResult { let buf: Handle = cx.argument(0)?; let offset: Handle = cx.argument(1)?; let offset: usize = offset.value(&mut cx) as u32 as usize; let len: Handle = cx.argument(2)?; let len: usize = len.value(&mut cx) as u32 as usize; let convert: Handle = cx.argument(3)?; let convert: bool = convert.value(&mut cx); let region = buf.region::(offset, len); if convert { let arr = region.to_typed_array(&mut cx)?; Ok(arr.upcast()) } else { Ok(cx.undefined().upcast()) } } pub fn build_f64_region(mut cx: FunctionContext) -> JsResult { let buf: Handle = cx.argument(0)?; let offset: Handle = cx.argument(1)?; let offset: usize = offset.value(&mut cx) as u32 as usize; let len: Handle = cx.argument(2)?; let len: usize = len.value(&mut cx) as u32 as usize; let convert: Handle = cx.argument(3)?; let convert: bool = convert.value(&mut cx); let region = buf.region::(offset, len); if convert { let arr = region.to_typed_array(&mut cx)?; Ok(arr.upcast()) } else { Ok(cx.undefined().upcast()) } } pub fn read_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let lock = cx.lock(); let x = b .try_borrow(&lock) .map(|slice| slice[i]) .or_throw(&mut cx)?; Ok(cx.number(x)) } pub fn read_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { let buf = cx.argument::(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = buf.as_slice(&cx)[i]; Ok(cx.number(n as f64)) } pub fn write_buffer_with_lock(mut cx: FunctionContext) -> JsResult { let mut b: Handle = cx.argument(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let x = cx.argument::(2)?.value(&mut cx) as u8; let lock = cx.lock(); b.try_borrow_mut(&lock) .map(|mut slice| slice[i] = x) .or_throw(&mut cx)?; Ok(cx.undefined()) } pub fn write_buffer_with_borrow_mut(mut cx: FunctionContext) -> JsResult { let mut buf = cx.argument::(0)?; let i = cx.argument::(1)?.value(&mut cx) as usize; let n = cx.argument::(2)?.value(&mut cx) as u8; buf.as_mut_slice(&mut cx)[i] = n; Ok(cx.undefined()) } pub fn copy_buffer(mut cx: FunctionContext) -> JsResult { let a = cx.argument::>(0)?; let mut b = cx.argument::>(1)?; let a_len = a.as_slice(&cx).len(); let b_len = b.as_slice(&cx).len(); let n = a_len.min(b_len); let a = a.as_slice(&cx)[..n].to_vec(); b.as_mut_slice(&mut cx)[..n].copy_from_slice(&a); Ok(cx.undefined()) } pub fn copy_buffer_with_borrow(mut cx: FunctionContext) -> JsResult { let a = cx.argument::>(0)?; let mut b = cx.argument::>(1)?; let lock = cx.lock(); let (Ok(a), Ok(mut b)) = (a.try_borrow(&lock), b.try_borrow_mut(&lock)) else { return cx.throw_error("Borrow Error"); }; let n = a.len().min(b.len()); b[..n].copy_from_slice(&a); drop((a, b)); Ok(cx.undefined()) } ================================================ FILE: test/napi/src/js/types.rs ================================================ use neon::prelude::*; pub fn is_string(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_array(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_array_buffer(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_uint32_array(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::, _>(&mut cx); Ok(cx.boolean(result)) } pub fn is_boolean(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_buffer(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_error(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_null(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_number(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_object(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let result = val.is_a::(&mut cx); Ok(cx.boolean(result)) } pub fn is_undefined(mut cx: FunctionContext) -> JsResult { let val: Handle = cx.argument(0)?; let is_string = val.is_a::(&mut cx); Ok(cx.boolean(is_string)) } pub fn strict_equals(mut cx: FunctionContext) -> JsResult { let v1: Handle = cx.argument(0)?; let v2: Handle = cx.argument(1)?; let eq = v1.strict_equals(&mut cx, v2); Ok(cx.boolean(eq)) } ================================================ FILE: test/napi/src/js/workers.rs ================================================ use std::{convert::TryFrom, sync::Mutex, thread, time::Duration}; use once_cell::sync::{Lazy, OnceCell}; use neon::prelude::*; use neon::thread::LocalKey; pub fn get_and_replace(mut cx: FunctionContext) -> JsResult { static OBJECT: Lazy>>> = Lazy::new(Default::default); let mut global = OBJECT.lock().unwrap_or_else(|err| err.into_inner()); let next = cx.argument::(0)?.root(&mut cx); let previous = global.replace(next); Ok(match previous { None => cx.undefined().upcast(), Some(previous) => previous.into_inner(&mut cx).upcast(), }) } pub fn get_or_init(mut cx: FunctionContext) -> JsResult { static OBJECT: OnceCell> = OnceCell::new(); let o = OBJECT.get_or_try_init(|| { cx.argument::(0)? .call_with(&cx) .apply::(&mut cx) .map(|v| v.root(&mut cx)) })?; Ok(o.to_inner(&mut cx)) } pub fn get_or_init_clone(mut cx: FunctionContext) -> JsResult { static OBJECT: OnceCell> = OnceCell::new(); let o = OBJECT.get_or_try_init(|| { cx.argument::(0)? .call_with(&cx) .apply::(&mut cx) .map(|v| v.root(&mut cx)) })?; // Note: This intentionally uses `clone` instead of `to_inner` in order to // test the `clone` method. Ok(o.clone(&mut cx).into_inner(&mut cx)) } static THREAD_ID: LocalKey = LocalKey::new(); pub fn get_or_init_thread_id(mut cx: FunctionContext) -> JsResult { let id = cx.argument::(0)?.value(&mut cx) as u32; let id: &u32 = THREAD_ID.get_or_init(&mut cx, || id); Ok(cx.number(*id)) } static REENTRANT_LOCAL: LocalKey = LocalKey::new(); pub fn reentrant_try_init(mut cx: FunctionContext) -> JsResult { let f = cx.argument::(0)?; let n = REENTRANT_LOCAL.get_or_try_init(&mut cx, |cx| { f.call_with(cx).exec(cx)?; Ok(42) })?; Ok(cx.number(*n)) } pub fn get_reentrant_value(mut cx: FunctionContext) -> JsResult { let value = REENTRANT_LOCAL.get(&mut cx).cloned(); match value { Some(n) => Ok(cx.number(n).upcast()), None => Ok(cx.null().upcast()), } } static GLOBAL_OBJECT: LocalKey> = LocalKey::new(); pub fn stash_global_object(mut cx: FunctionContext) -> JsResult { GLOBAL_OBJECT.get_or_try_init(&mut cx, |cx| { let global = cx.global_object(); let global: Root = Root::new(cx, &global); Ok(global) })?; Ok(cx.undefined()) } pub fn unstash_global_object(mut cx: FunctionContext) -> JsResult { Ok(match GLOBAL_OBJECT.get(&mut cx) { Some(root) => root.to_inner(&mut cx).upcast(), None => cx.null().upcast(), }) } pub fn reject_after(mut cx: FunctionContext) -> JsResult { let err = cx.argument::(0)?.root(&mut cx); let ms = cx.argument::(1)?.value(&mut cx) as i64; let ms = u64::try_from(ms) .or_else(|err| cx.throw_error(err.to_string())) .map(Duration::from_millis)?; let promise = cx.task(move || thread::sleep(ms)) .promise(move |mut cx, _| -> JsResult { let err = err.into_inner(&mut cx); cx.throw(err) }); Ok(promise) } pub struct Channels { _channel_1: Channel, _channel_2: Channel, } impl Finalize for Channels {} pub fn box_channels(mut cx: FunctionContext) -> JsResult> { let channels = Channels { _channel_1: cx.channel(), _channel_2: cx.channel(), }; Ok(cx.boxed(channels)) } ================================================ FILE: test/napi/src/lib.rs ================================================ use neon::{object::Class, prelude::*}; use once_cell::sync::OnceCell; use tokio::runtime::Runtime; use crate::js::{ arrays::*, boxed::*, coercions::*, date::*, errors::*, functions::*, numbers::*, objects::*, strings::*, threads::*, typedarrays::*, types::*, }; mod js { pub mod arrays; pub mod bigint; pub mod boxed; pub mod class; pub mod coercions; pub mod container; pub mod date; pub mod errors; pub mod export; pub mod extract; pub mod functions; pub mod futures; pub mod numbers; pub mod objects; pub mod strings; pub mod threads; pub mod typedarrays; pub mod types; pub mod workers; } #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { let rt = runtime(&mut cx)?; neon::set_global_executor(&mut cx, rt).or_else(|_| cx.throw_error("executor already set"))?; neon::registered().export(&mut cx)?; assert!(neon::registered().into_iter().next().is_some()); let greeting = cx.string("Hello, World!"); let greeting_copy = greeting.value(&mut cx); let greeting_copy = cx.string(greeting_copy); cx.export_value("greeting", greeting)?; cx.export_value("greetingCopy", greeting_copy)?; // Global singletons. let undefined = cx.undefined(); let null = cx.null(); let b_true = cx.boolean(true); let b_false = cx.boolean(false); assert!(b_true.value(&mut cx)); assert!(!b_false.value(&mut cx)); cx.export_value("undefined", undefined)?; cx.export_value("null", null)?; cx.export_value("true", b_true)?; cx.export_value("false", b_false)?; let one = cx.number(1); let two = cx.number(2.1); assert!((one.value(&mut cx) - 1.0).abs() < f64::EPSILON); assert!((two.value(&mut cx) - 2.1).abs() < f64::EPSILON); cx.export_value("one", one)?; cx.export_value("two", two)?; // Plain objects. let rust_created = cx.empty_object(); { let a = cx.number(1); // set at name rust_created.set(&mut cx, "a", a)?; // set at index rust_created.set(&mut cx, 0, a)?; } { let whatever = cx.boolean(true); rust_created.set(&mut cx, "whatever", whatever)?; } assert!( ({ let v: Handle = rust_created.get(&mut cx, "a")?; v.value(&mut cx) } - 1.0f64) .abs() < f64::EPSILON ); assert!( ({ let v: Handle = rust_created.get(&mut cx, 0)?; v.value(&mut cx) } - 1.0f64) .abs() < f64::EPSILON ); assert!({ let v: Handle = rust_created.get(&mut cx, "whatever")?; v.value(&mut cx) }); let property_names = rust_created .get_own_property_names(&mut cx)? .to_vec(&mut cx)? .into_iter() .map(|value| { let string: Handle = value.downcast_or_throw(&mut cx)?; Ok(string.value(&mut cx)) }) .collect::, _>>()?; assert_eq!(property_names, &["0", "a", "whatever"]); cx.export_value("rustCreated", rust_created)?; fn add1(mut cx: FunctionContext) -> JsResult { let x = cx.argument::(0)?.value(&mut cx); Ok(cx.number(x + 1.0)) } cx.export_function("add1", add1)?; cx.export_function("return_js_string", return_js_string)?; cx.export_function("return_js_string_utf16", return_js_string_utf16)?; cx.export_function("return_length_utf8", return_length_utf8)?; cx.export_function("return_length_utf16", return_length_utf16)?; cx.export_function("run_string_as_script", run_string_as_script)?; cx.export_function("return_js_number", return_js_number)?; cx.export_function("return_large_js_number", return_large_js_number)?; cx.export_function("return_negative_js_number", return_negative_js_number)?; cx.export_function("return_float_js_number", return_float_js_number)?; cx.export_function( "return_negative_float_js_number", return_negative_float_js_number, )?; cx.export_function("accept_and_return_js_number", accept_and_return_js_number)?; cx.export_function( "accept_and_return_large_js_number", accept_and_return_large_js_number, )?; cx.export_function( "accept_and_return_float_js_number", accept_and_return_float_js_number, )?; cx.export_function( "accept_and_return_negative_js_number", accept_and_return_negative_js_number, )?; cx.export_function("return_js_function", return_js_function)?; cx.export_function("call_js_function", call_js_function)?; cx.export_function( "call_js_function_idiomatically", call_js_function_idiomatically, )?; cx.export_function("call_js_function_with_bind", call_js_function_with_bind)?; cx.export_function( "call_js_function_with_bind_and_args_with", call_js_function_with_bind_and_args_with, )?; cx.export_function( "call_js_function_with_bind_and_args_and_with", call_js_function_with_bind_and_args_and_with, )?; cx.export_function("call_parse_int_with_bind", call_parse_int_with_bind)?; cx.export_function( "call_js_function_with_bind_and_exec", call_js_function_with_bind_and_exec, )?; cx.export_function( "call_js_constructor_with_bind", call_js_constructor_with_bind, )?; cx.export_function("bind_js_function_to_object", bind_js_function_to_object)?; cx.export_function("bind_js_function_to_number", bind_js_function_to_number)?; cx.export_function( "call_js_function_with_zero_args", call_js_function_with_zero_args, )?; cx.export_function( "call_js_function_with_one_arg", call_js_function_with_one_arg, )?; cx.export_function( "call_js_function_with_two_args", call_js_function_with_two_args, )?; cx.export_function( "call_js_function_with_three_args", call_js_function_with_three_args, )?; cx.export_function( "call_js_function_with_four_args", call_js_function_with_four_args, )?; cx.export_function( "call_js_function_with_custom_this", call_js_function_with_custom_this, )?; cx.export_function( "call_js_function_with_implicit_this", call_js_function_with_implicit_this, )?; cx.export_function( "exec_js_function_with_implicit_this", exec_js_function_with_implicit_this, )?; cx.export_function( "call_js_function_with_heterogeneous_tuple", call_js_function_with_heterogeneous_tuple, )?; cx.export_function("construct_js_function", construct_js_function)?; cx.export_function( "construct_js_function_idiomatically", construct_js_function_idiomatically, )?; cx.export_function( "construct_js_function_with_overloaded_result", construct_js_function_with_overloaded_result, )?; cx.export_function("num_arguments", num_arguments)?; cx.export_function("return_this", return_this)?; cx.export_function("require_object_this", require_object_this)?; cx.export_function("is_argument_zero_some", is_argument_zero_some)?; cx.export_function("require_argument_zero_string", require_argument_zero_string)?; cx.export_function("check_string_and_number", check_string_and_number)?; cx.export_function("execute_scoped", execute_scoped)?; cx.export_function("compute_scoped", compute_scoped)?; cx.export_function("recompute_scoped", recompute_scoped)?; cx.export_function("return_js_array", return_js_array)?; cx.export_function("return_js_array_with_number", return_js_array_with_number)?; cx.export_function("return_js_array_with_string", return_js_array_with_string)?; cx.export_function("read_js_array", read_js_array)?; cx.export_function("to_string", to_string)?; cx.export_function("return_js_global_object", return_js_global_object)?; cx.export_function("return_js_object", return_js_object)?; cx.export_function("return_js_object_with_number", return_js_object_with_number)?; cx.export_function("return_js_object_with_string", return_js_object_with_string)?; cx.export_function( "return_js_object_with_mixed_content", return_js_object_with_mixed_content, )?; cx.export_function("freeze_js_object", freeze_js_object)?; cx.export_function("seal_js_object", seal_js_object)?; cx.export_function("return_array_buffer", return_array_buffer)?; cx.export_function( "return_array_buffer_from_slice", return_array_buffer_from_slice, )?; cx.export_function("read_array_buffer_with_lock", read_array_buffer_with_lock)?; cx.export_function( "read_array_buffer_with_borrow", read_array_buffer_with_borrow, )?; cx.export_function("write_array_buffer_with_lock", write_array_buffer_with_lock)?; cx.export_function( "write_array_buffer_with_borrow_mut", write_array_buffer_with_borrow_mut, )?; cx.export_function("read_typed_array_with_borrow", read_typed_array_with_borrow)?; cx.export_function( "write_typed_array_with_borrow_mut", write_typed_array_with_borrow_mut, )?; cx.export_function("read_u8_typed_array", read_u8_typed_array)?; cx.export_function("copy_typed_array", copy_typed_array)?; cx.export_function("return_uninitialized_buffer", return_uninitialized_buffer)?; cx.export_function("return_buffer", return_buffer)?; cx.export_function("return_external_buffer", return_external_buffer)?; cx.export_function("return_external_array_buffer", return_external_array_buffer)?; cx.export_function( "return_int8array_from_arraybuffer", return_int8array_from_arraybuffer, )?; cx.export_function( "return_int16array_from_arraybuffer", return_int16array_from_arraybuffer, )?; cx.export_function( "return_uint32array_from_arraybuffer", return_uint32array_from_arraybuffer, )?; cx.export_function( "return_float64array_from_arraybuffer", return_float64array_from_arraybuffer, )?; cx.export_function( "return_biguint64array_from_arraybuffer", return_biguint64array_from_arraybuffer, )?; cx.export_function("return_new_int32array", return_new_int32array)?; cx.export_function("return_int32array_from_slice", return_int32array_from_slice)?; cx.export_function( "return_uint32array_from_arraybuffer_region", return_uint32array_from_arraybuffer_region, )?; cx.export_function("get_arraybuffer_byte_length", get_arraybuffer_byte_length)?; cx.export_function("detach_same_handle", detach_same_handle)?; cx.export_function("detach_and_escape", detach_and_escape)?; cx.export_function("detach_and_cast", detach_and_cast)?; cx.export_function("detach_and_unroot", detach_and_unroot)?; cx.export_function("get_typed_array_info", get_typed_array_info)?; cx.export_function("build_f32_region", build_f32_region)?; cx.export_function("build_f64_region", build_f64_region)?; cx.export_function("read_buffer_with_lock", read_buffer_with_lock)?; cx.export_function("read_buffer_with_borrow", read_buffer_with_borrow)?; cx.export_function("write_buffer_with_lock", write_buffer_with_lock)?; cx.export_function("write_buffer_with_borrow_mut", write_buffer_with_borrow_mut)?; cx.export_function("copy_buffer", copy_buffer)?; cx.export_function("copy_buffer_with_borrow", copy_buffer_with_borrow)?; cx.export_function("byte_length", byte_length)?; cx.export_function("call_nullary_method", call_nullary_method)?; cx.export_function("call_unary_method", call_unary_method)?; cx.export_function("call_symbol_method", call_symbol_method)?; cx.export_function("get_property_with_prop", get_property_with_prop)?; cx.export_function("set_property_with_prop", set_property_with_prop)?; cx.export_function("call_methods_with_prop", call_methods_with_prop)?; cx.export_function("call_non_method_with_prop", call_non_method_with_prop)?; cx.export_function("create_date", create_date)?; cx.export_function("get_date_value", get_date_value)?; cx.export_function("check_date_is_invalid", check_date_is_invalid)?; cx.export_function("check_date_is_valid", check_date_is_valid)?; cx.export_function("try_new_date", try_new_date)?; cx.export_function("try_new_lossy_date", try_new_lossy_date)?; cx.export_function("nan_dates", nan_dates)?; cx.export_function("create_date_from_value", create_date_from_value)?; cx.export_function("create_and_get_invalid_date", create_and_get_invalid_date)?; cx.export_function("is_array", is_array)?; cx.export_function("is_array_buffer", is_array_buffer)?; cx.export_function("is_uint32_array", is_uint32_array)?; cx.export_function("is_boolean", is_boolean)?; cx.export_function("is_buffer", is_buffer)?; cx.export_function("is_error", is_error)?; cx.export_function("is_null", is_null)?; cx.export_function("is_number", is_number)?; cx.export_function("is_object", is_object)?; cx.export_function("is_string", is_string)?; cx.export_function("is_undefined", is_undefined)?; cx.export_function("strict_equals", strict_equals)?; cx.export_function("new_error", new_error)?; cx.export_function("new_type_error", new_type_error)?; cx.export_function("new_range_error", new_range_error)?; cx.export_function("throw_error", throw_error)?; cx.export_function("downcast_error", downcast_error)?; cx.export_function("panic", panic)?; cx.export_function("panic_after_throw", panic_after_throw)?; cx.export_function("throw_and_catch", throw_and_catch)?; cx.export_function("call_and_catch", call_and_catch)?; cx.export_function("get_number_or_default", get_number_or_default)?; cx.export_function("assume_this_is_an_object", assume_this_is_an_object)?; cx.export_function("is_construct", is_construct)?; cx.export_function("caller_with_drop_callback", caller_with_drop_callback)?; cx.export_function("count_called", { let n = std::cell::RefCell::new(0); move |mut cx| { *n.borrow_mut() += 1; Ok(cx.number(*n.borrow())) } })?; fn call_get_own_property_names(mut cx: FunctionContext) -> JsResult { let object = cx.argument::(0)?; object.get_own_property_names(&mut cx) } cx.export_function("get_own_property_names", call_get_own_property_names)?; cx.export_function("person_new", person_new)?; cx.export_function("person_greet", person_greet)?; cx.export_function("ref_person_new", ref_person_new)?; cx.export_function("ref_person_greet", ref_person_greet)?; cx.export_function("ref_person_set_name", ref_person_set_name)?; cx.export_function("ref_person_fail", ref_person_fail)?; cx.export_function("external_unit", external_unit)?; cx.export_function("useless_root", useless_root)?; cx.export_function("thread_callback", thread_callback)?; cx.export_function("multi_threaded_callback", multi_threaded_callback)?; cx.export_function("greeter_new", greeter_new)?; cx.export_function("greeter_greet", greeter_greet)?; cx.export_function("leak_channel", leak_channel)?; cx.export_function("drop_global_queue", drop_global_queue)?; cx.export_function("channel_join", channel_join)?; cx.export_function("sum", sum)?; cx.export_function("sum_manual_promise", sum_manual_promise)?; cx.export_function("sum_rust_thread", sum_rust_thread)?; cx.export_function("leak_promise", leak_promise)?; cx.export_function("channel_panic", channel_panic)?; cx.export_function("channel_throw", channel_throw)?; cx.export_function("channel_panic_throw", channel_panic_throw)?; cx.export_function("channel_custom_panic", channel_custom_panic)?; cx.export_function("custom_panic_downcast", custom_panic_downcast)?; cx.export_function("task_panic_execute", task_panic_execute)?; cx.export_function("task_panic_complete", task_panic_complete)?; cx.export_function("task_throw", task_throw)?; cx.export_function("task_panic_throw", task_panic_throw)?; cx.export_function("task_custom_panic", task_custom_panic)?; cx.export_function("task_reject_promise", task_reject_promise)?; cx.export_function("task_panic_execute_promise", task_panic_execute_promise)?; cx.export_function("task_panic_complete_promise", task_panic_complete_promise)?; cx.export_function("task_panic_throw_promise", task_panic_throw_promise)?; cx.export_function("deferred_settle_with_throw", deferred_settle_with_throw)?; cx.export_function("deferred_settle_with_panic", deferred_settle_with_panic)?; cx.export_function( "deferred_settle_with_panic_throw", deferred_settle_with_panic_throw, )?; cx.export_function("get_and_replace", js::workers::get_and_replace)?; cx.export_function("get_or_init", js::workers::get_or_init)?; cx.export_function("get_or_init_clone", js::workers::get_or_init_clone)?; cx.export_function("get_or_init_thread_id", js::workers::get_or_init_thread_id)?; cx.export_function("reentrant_try_init", js::workers::reentrant_try_init)?; cx.export_function("get_reentrant_value", js::workers::get_reentrant_value)?; cx.export_function("stash_global_object", js::workers::stash_global_object)?; cx.export_function("unstash_global_object", js::workers::unstash_global_object)?; cx.export_function("reject_after", js::workers::reject_after)?; cx.export_function("box_channels", js::workers::box_channels)?; // Futures cx.export_function("lazy_async_add", js::futures::lazy_async_add)?; cx.export_function("lazy_async_sum", js::futures::lazy_async_sum)?; // JsBigInt test suite cx.export_function("bigint_suite", js::bigint::bigint_suite)?; // Extractors cx.export_function("extract_values", js::extract::extract_values)?; cx.export_function("extract_buffer_sum", js::extract::extract_buffer_sum)?; cx.export_function("extract_json_sum", js::extract::extract_json_sum)?; cx.export_function( "extract_single_add_one", js::extract::extract_single_add_one, )?; // Classes let class_constructor = js::class::Message::constructor(&mut cx)?; cx.export_value("Message", class_constructor)?; let point_constructor = js::class::Point::constructor(&mut cx)?; cx.export_value("Point", point_constructor)?; let string_buffer_constructor = js::class::StringBuffer::constructor(&mut cx)?; cx.export_value("StringBuffer", string_buffer_constructor)?; let async_class_constructor = js::class::AsyncClass::constructor(&mut cx)?; cx.export_value("AsyncClass", async_class_constructor)?; // Test constructor features let fallible_counter_constructor = js::class::FallibleCounter::constructor(&mut cx)?; cx.export_value("FallibleCounter", fallible_counter_constructor)?; let context_counter_constructor = js::class::ContextCounter::constructor(&mut cx)?; cx.export_value("ContextCounter", context_counter_constructor)?; let json_config_constructor = js::class::JsonConfig::constructor(&mut cx)?; cx.export_value("JsonConfig", json_config_constructor)?; let validated_config_constructor = js::class::ValidatedConfig::constructor(&mut cx)?; cx.export_value("ValidatedConfig", validated_config_constructor)?; let argv_constructor = js::class::Argv::constructor(&mut cx)?; cx.export_value("Argv", argv_constructor)?; let secret_constructor = js::class::Secret::constructor(&mut cx)?; cx.export_value("Secret", secret_constructor)?; let carousel_constructor = js::class::Carousel::constructor(&mut cx)?; cx.export_value("Carousel", carousel_constructor)?; let expando_constructor = js::class::Expando::constructor(&mut cx)?; cx.export_value("Expando", expando_constructor)?; Ok(()) } fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { static RUNTIME: OnceCell = OnceCell::new(); RUNTIME.get_or_try_init(|| Runtime::new().or_else(|err| cx.throw_error(err.to_string()))) } ================================================ FILE: test/rust-2024/.gitignore ================================================ target index.node **/node_modules **/.DS_Store npm-debug.log*cargo.log cross.log ================================================ FILE: test/rust-2024/Cargo.toml ================================================ [package] name = "rust-2024" version = "0.1.0" description = "Test Neon with Rust 2024 Edition" authors = ["Dave Herman "] license = "MIT" edition = "2024" exclude = ["index.node"] [lib] crate-type = ["cdylib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies.neon] path = "../../crates/neon" features = ["futures", "napi-experimental", "external-buffers", "serde", "tokio"] ================================================ FILE: test/rust-2024/README.md ================================================ # 2024 **2024:** Test Neon with Rust 2024 Edition This project was bootstrapped by [create-neon](https://www.npmjs.com/package/create-neon). ## Building 2024 Building 2024 requires a [supported version of Node and Rust](https://github.com/neon-bindings/neon#platform-support). To run the build, run: ```sh $ npm run build ``` This command uses the [@neon-rs/cli](https://www.npmjs.com/package/@neon-rs/cli) utility to assemble the binary Node addon from the output of `cargo`. ## Exploring 2024 After building 2024, you can explore its exports at the Node console: ```sh $ npm i $ npm run build $ node > require('.').hello() 'hello node' ``` ## Available Scripts In the project directory, you can run: #### `npm install` Installs the project, including running `npm run build`. #### `npm run build` Builds the Node addon (`index.node`) from source, generating a release build with `cargo --release`. Additional [`cargo build`](https://doc.rust-lang.org/cargo/commands/cargo-build.html) arguments may be passed to `npm run build` and similar commands. For example, to enable a [cargo feature](https://doc.rust-lang.org/cargo/reference/features.html): ``` npm run build -- --feature=beetle ``` #### `npm run debug` Similar to `npm run build` but generates a debug build with `cargo`. #### `npm run cross` Similar to `npm run build` but uses [cross-rs](https://github.com/cross-rs/cross) to cross-compile for another platform. Use the [`CARGO_BUILD_TARGET`](https://doc.rust-lang.org/cargo/reference/config.html#buildtarget) environment variable to select the build target. #### `npm test` Runs the unit tests by calling `cargo test`. You can learn more about [adding tests to your Rust code](https://doc.rust-lang.org/book/ch11-01-writing-tests.html) from the [Rust book](https://doc.rust-lang.org/book/). ## Project Layout The directory structure of this project is: ``` 2024/ ├── Cargo.toml ├── README.md ├── src/ | └── lib.rs ├── index.node ├── package.json └── target/ ``` | Entry | Purpose | |----------------|------------------------------------------------------------------------------------------------------------------------------------------| | `Cargo.toml` | The Cargo [manifest file](https://doc.rust-lang.org/cargo/reference/manifest.html), which informs the `cargo` command. | | `README.md` | This file. | | `src/` | The directory tree containing the Rust source code for the project. | | `lib.rs` | Entry point for the Rust source code. | | `index.node` | The main module, a [Node addon](https://nodejs.org/api/addons.html) generated by the build and pointed to by `"main"` in `package.json`. | | `package.json` | The npm [manifest file](https://docs.npmjs.com/cli/v7/configuring-npm/package-json), which informs the `npm` command. | | `target/` | Binary artifacts generated by the Rust build. | ## Learn More Learn more about: - [Neon](https://neon-bindings.com). - [Rust](https://www.rust-lang.org). - [Node](https://nodejs.org). ================================================ FILE: test/rust-2024/package.json ================================================ { "name": "rust-2024", "version": "0.1.0", "description": "Test Neon with Rust 2024 Edition", "main": "index.node", "scripts": { "test": "cargo test", "cargo-build": "cargo build --message-format=json-render-diagnostics > cargo.log", "cross-build": "cross build --message-format=json-render-diagnostics > cross.log", "postcargo-build": "neon dist < cargo.log", "postcross-build": "neon dist -m /target < cross.log", "debug": "npm run cargo-build --", "build": "npm run cargo-build -- --release", "cross": "npm run cross-build -- --release" }, "author": "Dave Herman ", "license": "MIT", "devDependencies": { "@neon-rs/cli": "0.1.82" }, "repository": { "type": "git", "url": "git+https://github.com/neon-bindings/neon.git" }, "bugs": { "url": "https://github.com/neon-bindings/neon/issues" }, "homepage": "https://github.com/neon-bindings/neon#readme" } ================================================ FILE: test/rust-2024/src/lib.rs ================================================ #[neon::export] fn hello() -> String { "hello node".to_string() } ================================================ FILE: test/ui/Cargo.toml ================================================ [package] name = "ui-test" version = "0.1.0" edition = "2021" authors = ["The Neon Community"] license = "MIT/Apache-2.0" [lib] crate-type = ["cdylib"] [dependencies.neon] path = "../../crates/neon" features = ["futures", "napi-experimental", "serde"] [dependencies] rustversion = "1.0.17" [dev-dependencies] trybuild = "1" ================================================ FILE: test/ui/src/lib.rs ================================================ #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { let t = trybuild::TestCases::new(); t.compile_fail("tests/fail/*.rs"); t.pass("tests/pass/*.rs"); } ================================================ FILE: test/ui/tests/fail/class-async-borrowed-channel.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { #[neon(async)] fn method(&self, _ch: &Channel) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-async-borrowed-channel.stderr ================================================ error: Expected `&mut Cx` instead of a `Channel` reference. --> tests/fail/class-async-borrowed-channel.rs:8:28 | 8 | fn method(&self, _ch: &Channel) {} | ^^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-async-borrowed-channel.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-async-context-ref.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { #[neon(async)] fn method(&self, _cx: &mut FunctionContext) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-async-context-ref.stderr ================================================ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-async-context-ref.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | error[E0277]: `()` is not a future --> tests/fail/class-async-context-ref.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ `()` is not a future | = help: the trait `Future` is not implemented for `()` note: required by a bound in `neon::macro_internal::spawn` --> $WORKSPACE/crates/neon/src/macro_internal/futures.rs | 9 | pub fn spawn<'cx, F, S>(cx: &mut Cx<'cx>, fut: F, settle: S) -> JsResult<'cx, JsValue> | ----- required by a bound in this function | where | F: Future + Send + 'static, | ^^^^^^ required by this bound in `spawn` = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0282]: type annotations needed --> tests/fail/class-async-context-ref.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ cannot infer type | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: test/ui/tests/fail/class-async-fn-borrowed-self.rs ================================================ #[derive(Clone)] struct Example; #[neon::class] impl Example { async fn method(&self) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-async-fn-borrowed-self.stderr ================================================ error: Async functions in classes must take `self` by value, not `&self` or `&mut self`. This is required because async functions capture `self` in the Future, which must be `'static` for spawning. --> tests/fail/class-async-fn-borrowed-self.rs:6:21 | 6 | async fn method(&self) {} | ^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-async-fn-borrowed-self.rs:4:1 | 4 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 2 + #[derive(Default)] 3 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-async-owned-context.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { #[neon(async)] fn method(&self, _cx: FunctionContext) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-async-owned-context.stderr ================================================ error: Context parameters must be a `&mut` reference. Try `&mut FunctionContext` or `&mut Cx`. --> tests/fail/class-async-owned-context.rs:8:27 | 8 | fn method(&self, _cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-async-owned-context.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-borrowed-channel-sync.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { fn method(&self, _ch: &Channel) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-borrowed-channel-sync.stderr ================================================ error: Expected `&mut Cx` instead of a `Channel` reference. --> tests/fail/class-borrowed-channel-sync.rs:7:28 | 7 | fn method(&self, _ch: &Channel) {} | ^^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-borrowed-channel-sync.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-channel-in-sync.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { fn method(&self, _ch: Channel) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-channel-in-sync.stderr ================================================ error: Unexpected `Channel` in sync method. Use `&mut FunctionContext` for sync methods, or `Channel` in async/task methods. --> tests/fail/class-channel-in-sync.rs:7:27 | 7 | fn method(&self, _ch: Channel) {} | ^^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-channel-in-sync.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-constructor-self-receiver.rs ================================================ struct Example; #[neon::class] impl Example { fn new(&self) -> Self { Example } } fn main() {} ================================================ FILE: test/ui/tests/fail/class-constructor-self-receiver.stderr ================================================ error: Constructor methods cannot have a `self` receiver --> tests/fail/class-constructor-self-receiver.rs:5:12 | 5 | fn new(&self) -> Self { | ^^^^^ ================================================ FILE: test/ui/tests/fail/class-constructor-with-self.rs ================================================ struct Example; #[neon::class] impl Example { fn new(self) -> Self { self } } fn main() {} ================================================ FILE: test/ui/tests/fail/class-constructor-with-self.stderr ================================================ error: Constructor methods cannot have a `self` receiver --> tests/fail/class-constructor-with-self.rs:5:12 | 5 | fn new(self) -> Self { | ^^^^ ================================================ FILE: test/ui/tests/fail/class-duplicate-property-names.rs ================================================ struct Example; #[neon::class] impl Example { #[neon(name = "value")] const VALUE1: i32 = 42; #[neon(name = "value")] const VALUE2: i32 = 43; } fn main() {} ================================================ FILE: test/ui/tests/fail/class-duplicate-property-names.stderr ================================================ error: duplicate property name 'value' - const property names must be unique in JavaScript --> tests/fail/class-duplicate-property-names.rs:8:5 | 8 | / #[neon(name = "value")] 9 | | const VALUE2: i32 = 43; | |___________________________^ ================================================ FILE: test/ui/tests/fail/class-immutable-context.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { fn method(&self, _cx: &FunctionContext) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-immutable-context.stderr ================================================ error: Must be a `&mut` reference. --> tests/fail/class-immutable-context.rs:7:27 | 7 | fn method(&self, _cx: &FunctionContext) {} | ^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-immutable-context.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-invalid-item-type.rs ================================================ struct Example; #[neon::class] impl Example { type AssocType = i32; } fn main() {} ================================================ FILE: test/ui/tests/fail/class-invalid-item-type.stderr ================================================ error: `neon::class` can only contain `const` and `fn` items. --> tests/fail/class-invalid-item-type.rs:5:5 | 5 | type AssocType = i32; | ^^^^ ================================================ FILE: test/ui/tests/fail/class-invalid-property-name.rs ================================================ struct Example; #[neon::class] impl Example { #[neon(name = "123invalid")] const VALUE: i32 = 42; } fn main() {} ================================================ FILE: test/ui/tests/fail/class-invalid-property-name.stderr ================================================ error: '123invalid' is not a valid JavaScript identifier --> tests/fail/class-invalid-property-name.rs:5:19 | 5 | #[neon(name = "123invalid")] | ^^^^^^^^^^^^ ================================================ FILE: test/ui/tests/fail/class-method-missing-self.rs ================================================ struct Example; #[neon::class] impl Example { fn method() {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-method-missing-self.stderr ================================================ error: Class methods must have a `self` receiver (`&self` or `&mut self`) as their first parameter --> tests/fail/class-method-missing-self.rs:5:8 | 5 | fn method() {} | ^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-method-missing-self.rs:3:1 | 3 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 1 + #[derive(Default)] 2 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-missing-forced-context.rs ================================================ struct Example; #[neon::class] impl Example { #[neon(context)] fn method(&self) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-missing-forced-context.stderr ================================================ error: Expected a context argument after `&self` when using `#[neon(context)]`. Add a parameter like `cx: &mut FunctionContext` or remove the `context` attribute. --> tests/fail/class-missing-forced-context.rs:6:15 | 6 | fn method(&self) {} | ^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-missing-forced-context.rs:3:1 | 3 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 1 + #[derive(Default)] 2 | struct Example; | ================================================ FILE: test/ui/tests/fail/class-multiple-constructors.rs ================================================ struct Example; #[neon::class] impl Example { fn new() -> Self { Example } fn new() -> Self { Example } } fn main() {} ================================================ FILE: test/ui/tests/fail/class-multiple-constructors.stderr ================================================ error: Only one `new` constructor is allowed in a class. --> tests/fail/class-multiple-constructors.rs:9:5 | 9 | fn new() -> Self { | ^^ ================================================ FILE: test/ui/tests/fail/class-multiple-finalizers.rs ================================================ struct Example; #[neon::class] impl Example { fn finalize<'a, C: neon::context::Context<'a>>(self, _cx: &mut C) {} fn finalize<'a, C: neon::context::Context<'a>>(self, _cx: &mut C) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-multiple-finalizers.stderr ================================================ error: Only one `finalize` method is allowed in a class. --> tests/fail/class-multiple-finalizers.rs:6:5 | 6 | fn finalize<'a, C: neon::context::Context<'a>>(self, _cx: &mut C) {} | ^^ ================================================ FILE: test/ui/tests/fail/class-multiple-neon-attrs-const.rs ================================================ struct Example; #[neon::class] impl Example { #[neon(name = "foo")] #[neon(json)] const VALUE: i32 = 42; } fn main() {} ================================================ FILE: test/ui/tests/fail/class-multiple-neon-attrs-const.stderr ================================================ error: multiple #[neon(...)] attributes on const property are not allowed --> tests/fail/class-multiple-neon-attrs-const.rs:6:5 | 6 | #[neon(json)] | ^^^^^^^^^^^^^ ================================================ FILE: test/ui/tests/fail/class-multiple-neon-attrs-method.rs ================================================ struct Example; #[neon::class] impl Example { #[neon(async)] #[neon(task)] fn method(&self) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-multiple-neon-attrs-method.stderr ================================================ error: multiple #[neon(...)] attributes on class method are not allowed --> tests/fail/class-multiple-neon-attrs-method.rs:6:7 | 6 | #[neon(task)] | ^^^^^^^^^^ ================================================ FILE: test/ui/tests/fail/class-owned-context.rs ================================================ use neon::prelude::*; struct Example; #[neon::class] impl Example { fn method(&self, _cx: FunctionContext) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/class-owned-context.stderr ================================================ error: Context parameters must be a `&mut` reference. Try `&mut FunctionContext` or `&mut Cx`. --> tests/fail/class-owned-context.rs:7:27 | 7 | fn method(&self, _cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Example: Default` is not satisfied --> tests/fail/class-owned-context.rs:5:1 | 5 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Example` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Example` with `#[derive(Default)]` | 3 + #[derive(Default)] 4 | struct Example; | ================================================ FILE: test/ui/tests/fail/missing-class-clone.rs ================================================ struct Point { x: i32, y: i32, } #[neon::class] impl Point { fn new(x: i32, y: i32) -> Self { Point { x, y } } fn equals(&self, other: Point) -> bool { self.x == other.x && self.y == other.y } } fn main() {} ================================================ FILE: test/ui/tests/fail/missing-class-clone.stderr ================================================ error[E0277]: the trait bound `Point: Clone` is not satisfied --> tests/fail/missing-class-clone.rs:6:1 | 6 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `Point` | = help: the following other types implement trait `FromArgs<'cx>`: () (T1, T2) (T1, T2, T3) (T1, T2, T3, T4) (T1, T2, T3, T4, T5) (T1, T2, T3, T4, T5, T6) (T1, T2, T3, T4, T5, T6, T7) (T1, T2, T3, T4, T5, T6, T7, T8) and $N others note: required for `Point` to implement `TryFromJs<'_>` --> tests/fail/missing-class-clone.rs:6:1 | 6 | #[neon::class] | ^^^^^^^^^^^^^^ unsatisfied trait bound introduced here 7 | impl Point { | ^^^^^ = note: required for `(Point,)` to implement `FromArgs<'_>` note: required by a bound in `FunctionContext::<'cx>::args` --> $WORKSPACE/crates/neon/src/context/mod.rs | | pub fn args(&mut self) -> NeonResult | ---- required by a bound in this associated function | where | T: FromArgs<'cx>, | ^^^^^^^^^^^^^ required by this bound in `FunctionContext::<'cx>::args` = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Point` with `#[derive(Clone)]` | 1 + #[derive(Clone)] 2 | struct Point { | ================================================ FILE: test/ui/tests/fail/missing-class-default.rs ================================================ struct Point { x: i32, y: i32, } #[neon::class] impl Point { } fn main() {} ================================================ FILE: test/ui/tests/fail/missing-class-default.stderr ================================================ error[E0277]: the trait bound `Point: Default` is not satisfied --> tests/fail/missing-class-default.rs:6:1 | 6 | #[neon::class] | ^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Point` | = note: this error originates in the attribute macro `neon::class` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider annotating `Point` with `#[derive(Default)]` | 1 + #[derive(Default)] 2 | struct Point { | ================================================ FILE: test/ui/tests/fail/missing-context.rs ================================================ #[neon::export(context)] fn missing_context() {} fn main() {} ================================================ FILE: test/ui/tests/fail/missing-context.stderr ================================================ error: Expected a context argument. Try removing the `context` attribute. --> tests/fail/missing-context.rs:1:1 | 1 | #[neon::export(context)] | ^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the attribute macro `neon::export` (in Nightly builds, run with -Z macro-backtrace for more info) ================================================ FILE: test/ui/tests/fail/need-borrowed-context.rs ================================================ #[neon::export] fn owned_cx(_cx: Cx) {} #[neon::export] fn owned_function_cx(_cx: FunctionContext) {} #[neon::export] fn ref_cx(_cx: &Cx) {} #[neon::export] fn ref_function_cx(_cx: &FunctionContext) {} #[neon::export(context)] fn forced_cx(_cx: String) {} #[neon::export(context)] fn forced_ref_cx(_cx: &String) {} fn main() {} ================================================ FILE: test/ui/tests/fail/need-borrowed-context.stderr ================================================ error: Context must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:2:18 | 2 | fn owned_cx(_cx: Cx) {} | ^^ error: Context must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:5:27 | 5 | fn owned_function_cx(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ error: Must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:8:16 | 8 | fn ref_cx(_cx: &Cx) {} | ^ error: Must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:11:25 | 11 | fn ref_function_cx(_cx: &FunctionContext) {} | ^ error: Context must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:14:19 | 14 | fn forced_cx(_cx: String) {} | ^^^^^^ error: Must be a `&mut` reference. --> tests/fail/need-borrowed-context.rs:17:23 | 17 | fn forced_ref_cx(_cx: &String) {} | ^ ================================================ FILE: test/ui/tests/fail/unexpected-self.rs ================================================ struct Example; impl Example { #[neon::export] fn borrow(&self) {} #[neon::export] fn borrow_mut(&mut self) {} #[neon::export] fn owned(self) {} } fn main() {} ================================================ FILE: test/ui/tests/fail/unexpected-self.stderr ================================================ error: Exported functions cannot receive `self`. --> tests/fail/unexpected-self.rs:5:15 | 5 | fn borrow(&self) {} | ^ error: Exported functions cannot receive `self`. --> tests/fail/unexpected-self.rs:8:19 | 8 | fn borrow_mut(&mut self) {} | ^ error: Exported functions cannot receive `self`. --> tests/fail/unexpected-self.rs:11:14 | 11 | fn owned(self) {} | ^^^^ ================================================ FILE: test/ui/tests/fail/unnecessary-attribute.rs ================================================ #[neon::export(async)] async fn async_with_async() {} fn main() {} ================================================ FILE: test/ui/tests/fail/unnecessary-attribute.stderr ================================================ error: `async` attribute should not be used with an `async fn` --> tests/fail/unnecessary-attribute.rs:1:16 | 1 | #[neon::export(async)] | ^^^^^ ================================================ FILE: test/ui/tests/fail/unsupported-property.rs ================================================ #[neon::export(foo)] static STRING: &str = ""; #[neon::export(foo)] fn unsupported() {} fn main() {} ================================================ FILE: test/ui/tests/fail/unsupported-property.stderr ================================================ error: unsupported property --> tests/fail/unsupported-property.rs:1:16 | 1 | #[neon::export(foo)] | ^^^ error: unsupported property --> tests/fail/unsupported-property.rs:4:16 | 4 | #[neon::export(foo)] | ^^^ ================================================ FILE: test/ui/tests/fail/wrong-context.rs ================================================ #[neon::export] fn sync_channel(_ch: Channel) {} #[neon::export] fn sync_borrow_channel(_ch: &mut Channel) {} #[neon::export(async)] fn async_channel(_ch: Channel) {} #[neon::export(async)] fn async_borrow_channel(_ch: &mut Channel) {} #[neon::export] async fn async_cx(_cx: Cx) {} #[neon::export] async fn async_function_context(_cx: FunctionContext) {} #[neon::export] async fn async_cx_ref(_cx: &Cx) {} #[neon::export] async fn async_borrow_channel(_cx: &Channel) {} #[neon::export(context)] async fn async_borrow_forced_channel(_cx: &String) {} #[neon::export] async fn async_function_context_ref(_cx: &FunctionContext) {} #[neon::export(task)] fn task_function_context(_cx: FunctionContext) {} #[neon::export(task)] fn task_cx_ref(_cx: &Cx) {} #[neon::export(task)] fn task_function_context_ref(_cx: &FunctionContext) {} fn main() {} ================================================ FILE: test/ui/tests/fail/wrong-context.stderr ================================================ error: Expected `&mut Cx` instead of `Channel`. --> tests/fail/wrong-context.rs:2:22 | 2 | fn sync_channel(_ch: Channel) {} | ^^^^^^^ error: Expected `&mut Cx` instead of a `Channel` reference. --> tests/fail/wrong-context.rs:5:34 | 5 | fn sync_borrow_channel(_ch: &mut Channel) {} | ^^^^^^^ error: Expected `&mut Cx` instead of `Channel`. --> tests/fail/wrong-context.rs:8:23 | 8 | fn async_channel(_ch: Channel) {} | ^^^^^^^ error: Expected `&mut Cx` instead of a `Channel` reference. --> tests/fail/wrong-context.rs:11:35 | 11 | fn async_borrow_channel(_ch: &mut Channel) {} | ^^^^^^^ error: Context is not available in async functions. Try a `Channel` instead. --> tests/fail/wrong-context.rs:14:24 | 14 | async fn async_cx(_cx: Cx) {} | ^^ error: Context is not available in async functions. Try a `Channel` instead. --> tests/fail/wrong-context.rs:17:38 | 17 | async fn async_function_context(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ error: Expected an owned `Channel` instead of a context reference. --> tests/fail/wrong-context.rs:20:29 | 20 | async fn async_cx_ref(_cx: &Cx) {} | ^^ error: Expected an owned `Channel` instead of a reference. --> tests/fail/wrong-context.rs:23:36 | 23 | async fn async_borrow_channel(_cx: &Channel) {} | ^ error: Expected an owned `Channel` instead of a reference. --> tests/fail/wrong-context.rs:26:43 | 26 | async fn async_borrow_forced_channel(_cx: &String) {} | ^ error: Expected an owned `Channel` instead of a context reference. --> tests/fail/wrong-context.rs:29:43 | 29 | async fn async_function_context_ref(_cx: &FunctionContext) {} | ^^^^^^^^^^^^^^^ error: Context is not available in async functions. Try a `Channel` instead. --> tests/fail/wrong-context.rs:32:31 | 32 | fn task_function_context(_cx: FunctionContext) {} | ^^^^^^^^^^^^^^^ error: Expected an owned `Channel` instead of a context reference. --> tests/fail/wrong-context.rs:35:22 | 35 | fn task_cx_ref(_cx: &Cx) {} | ^^ error: Expected an owned `Channel` instead of a context reference. --> tests/fail/wrong-context.rs:38:36 | 38 | fn task_function_context_ref(_cx: &FunctionContext) {} | ^^^^^^^^^^^^^^^ ================================================ FILE: test/ui/tests/pass/context-and-this.rs ================================================ use std::future::Future; use neon::{ context::{Context, Cx, FunctionContext}, event::Channel, handle::Handle, types::{extract::Boxed, JsString}, }; type Ch = Channel; type FnCx<'cx> = FunctionContext<'cx>; #[neon::export] fn sync_nothing() {} #[neon::export] fn sync_function_context(_cx: &mut FunctionContext) {} #[neon::export] fn sync_cx(_cx: &mut Cx) {} #[neon::export(context)] fn sync_cx_forced(_cx: &mut FnCx) {} #[neon::export] fn sync_cx_lifetimes<'cx>(cx: &mut Cx<'cx>) -> Handle<'cx, JsString> { cx.string("Hello, World!") } #[neon::export] fn sync_this(this: Vec) { let _ = this; } #[neon::export(this)] fn sync_this_forced(_this: Vec) {} #[neon::export] fn sync_cx_and_this(_cx: &mut Cx, this: Vec) { let _ = this; } #[neon::export] fn sync_cx_and_this_and_args(_cx: &mut Cx, this: Vec, _a: String) { let _ = this; } #[neon::export] fn boxed_this(Boxed(this): Boxed) { let _ = this; } #[neon::export] async fn async_nothing() {} #[neon::export] async fn async_channel(_ch: Channel) {} #[neon::export(context)] async fn async_channel_forced(_ch: Ch) {} #[neon::export] async fn async_channel_and_arg(_ch: Channel, _a: String) {} #[neon::export] async fn async_no_channel(_a: String) {} #[neon::export] async fn async_this(this: Vec) { let _ = this; } #[neon::export(this)] async fn async_this_forced(_this: Vec) {} #[neon::export] async fn async_this_args(this: Vec, _a: String) { let _ = this; } #[neon::export] async fn async_this_and_channel(_ch: Channel, this: Vec) { let _ = this; } #[neon::export] async fn async_this_and_channel_args(_ch: Channel, this: Vec, _a: String, _b: String) { let _ = this; } #[neon::export(task)] fn task_nothing() {} #[neon::export(task)] fn task_channel(_ch: Channel) {} #[neon::export(context, task)] fn task_channel_forced(_ch: Ch) {} #[neon::export(task)] fn task_channel_and_arg(_ch: Channel, _a: String) {} #[neon::export(task)] fn task_no_channel(_a: String) {} #[neon::export(task)] fn task_this(this: Vec) { let _ = this; } #[neon::export(task, this)] fn task_this_forced(_this: Vec) {} #[neon::export(task)] fn task_this_args(this: Vec, _a: String) { let _ = this; } #[neon::export(task)] fn task_this_and_channel(_ch: Channel, this: Vec) { let _ = this; } #[neon::export(task)] fn task_this_and_channel_args(_ch: Channel, this: Vec, _a: String, _b: String) { let _ = this; } #[neon::export(async)] fn impl_async_context(_cx: &mut Cx) -> impl Future { async {} } fn main() {} ================================================ FILE: test/ui/tests/pass/globals.rs ================================================ #[neon::export] static STATIC_STRING: &str = ""; #[neon::export] const CONST_NUMBER: f64 = 42.0; #[neon::export] static STATIC_ARR: &[f64] = &[42.0]; #[neon::export(json)] static ARR_OF_ARR: &[&[f64]] = &[&[42.0]]; fn main() {} ================================================ FILE: test/ui/tests/pass/json.rs ================================================ #[neon::export(json)] fn wrap_with_json(v: Vec) -> Vec { v } fn main() {}