Skip to content

feat: port the addon from NAN to N-API - #381

Open
yjdyamv wants to merge 4 commits into
mmomtchev:mainfrom
yjdyamv:napi-port
Open

yjdyamv wants to merge 4 commits into
mmomtchev:mainfrom
yjdyamv:napi-port

Conversation

@yjdyamv

@yjdyamv yjdyamv commented Oct 20, 2026

Copy link
Copy Markdown

What changed

  • src/: NAN_METHOD/NAN_GETTER/NAN_SETTER/Nan::AsyncWorker/Nan::ObjectWrap
    → Napi::Function/Napi::ObjectWrap/Napi::AsyncWorker/Napi::Reference,
    v8::Local → Napi::Value, errors thrown with Napi::Error,
    Nan::Callback → Napi::FunctionReference
  • binding.gyp: nan → node-addon-api; win_delay_load_hook: false
  • include/, lib/*.js, the public API and the TypeScript declarations are
    unchanged (JSDoc blocks moved to the new constructors only)

Commits

  1. feat: port the addon from NAN to N-API - the port itself
  2. fix: initialize the memfile's Node reference - the Napi::Reference of an
    anonymous /vsimem/ file was never initialized, so the finalizer
    dereferenced garbage (crash on exit on Windows)
  3. fix: the node-addon-api include path in binding.gyp - .include is the
    JSON-quoted form ("\"<path>\""), .include_dir is the plain one
  4. docs: keep the constructor doc blocks of the wrapped classes - without them
    yatag generates constructor() and tsc --noEmit (part of npm test)
    fails with 137 TS2554

Testing

npm test (eslint, tsc --noEmit, mocha, test/stress) on:

  • Linux x64(my wsl), Node.js 24.21.0, GDAL 3.12.2 (shared, node-gyp --shared_gdal=true):
    1446 passing, 0 failing, stress 20s ok
  • Windows x64, Node.js 24.22.0, MSVC 19.5x, GDAL 3.13.0, built with
    binding.gyp: 1446 passing, 0 failing

The single test not run is open_vsicurl.test.js, which downloads a file from
an S3 bucket and fails in an offline environment.

Not in this PR

Kept for separate PRs so this one stays reviewable:

  • worker_threads support (needs the environment-aware init and per-thread
    state handling)
  • the build system migration (CMake/Conan, napi-v8 in binary.module_path)
  • the documentation refresh the newer API made possible

package.json still names the binary <node_abi>-<platform>-<arch>; that is
part of the packaging change and not needed for the port.

yjdyamv added 4 commits October 19, 2026 16:22
The addon is now built on Node-API (NAPI_VERSION 8) instead of NAN, so it needs
nothing but the Node.js headers and it can support worker_threads later on.

* gdal_common.hpp: the N-API macro layer, the GDALObject<T> base class (with
  the "Cannot call constructor" message the tests assert) and a PrivateKey
  registry replacing Nan::SetPrivate/GetPrivate
* async.{hpp,cpp}: the workers rebuilt on Napi::AsyncProgressQueueWorker, with
  Napi::Promise::Deferred for the promise/callback plumbing
* utils/ptr_manager.*: an ObjectStore of Napi::Reference<Napi::Object> instead
  of the persistent handle table, treating a collected wrapper as a missing
  entry
* node_gdal.cpp: NODE_API_MODULE + Init, exporting node_gdal::napi_env
* all of the class Initialize()s rewritten as DefineClass descriptor lists,
  with explicit prototype chaining where NAN used lcons->Inherit()
* every .cpp converted: object access, typed arrays that accept any
  ArrayBufferView, the exception model (ThrowOverPending) and boolean results
* the nan dependency, nan-wrapper.h and the last <node.h> includes are gone;
  the ENABLE_LOGGING build loses its V8 collector tracing (NAN_GC_CALLBACK),
  which stable N-API has no equivalent for, while startLogging() and the LOG()
  object-lifecycle tracing stay

Test parity with the NAN baseline: 1453 passing, 42 pending.  The only failure
is open_vsicurl's "vsigzip/vsicurl chaining w/Net", which opens an S3 URL from
a suite-level before hook and needs network access.
An anonymous /vsimem/ file never creates the Napi::Reference that keeps its
buffer alive, but both the destructor and the napi_finalize callback
unconditionally delete it. The port from NAN left the pointer uninitialized,
so releasing an anonymous memfile read a garbage pointer - an access
violation whenever the allocator does not hand back zeroed memory (which is
what happened on exit on Windows).
`require('node-addon-api').include` is the JSON-quoted form of the include
directory (`"\"<path>\""`), so using it with `<!()` put the literal quotes into
the compiler command line and every translation unit failed with

    ../src/napi-wrapper.h:9:10: fatal error: napi.h: No such file or directory

`include_dir` is the same directory without the quotes - and relative to the
module root, which is where gyp runs the command. The 8.x releases document it
as the replacement for `include`.
The JSDoc block of a class lives above its constructor, so moving the
constructors of Feature, FeatureDefn and FieldDefn out of `NAN_METHOD(...)`
left their `@class`/`@constructor`/`@param` blocks behind - and yatag, which
generates lib/index.d.ts from these blocks, fell back to `constructor()` with
no arguments. `tsc --noEmit`, which is part of `npm test`, then rejected every
`new gdal.Feature(layer)` and `new gdal.FieldDefn(name, type)` in the suite
with 137 TS2554 errors, and the documentation lost the description and the
example of a Feature.

Move the blocks to the new constructors, verbatim.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant